summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.buckconfig3
-rw-r--r--.buckversion2
-rw-r--r--.gitignore1
-rw-r--r--.mailmap21
-rw-r--r--.pydevproject6
-rw-r--r--.settings/org.eclipse.jdt.core.prefs27
-rw-r--r--.watchmanconfig8
-rw-r--r--Documentation/BUCK32
-rw-r--r--Documentation/access-control.txt78
-rw-r--r--Documentation/asciidoc.defs56
-rw-r--r--Documentation/cmd-close-connection.txt38
-rw-r--r--Documentation/cmd-create-project.txt10
-rw-r--r--Documentation/cmd-index.txt87
-rw-r--r--Documentation/cmd-logging-ls-level.txt43
-rw-r--r--Documentation/cmd-logging-set-level.txt51
-rw-r--r--Documentation/cmd-plugin-enable.txt4
-rw-r--r--Documentation/cmd-plugin-install.txt4
-rw-r--r--Documentation/cmd-plugin-ls.txt7
-rw-r--r--Documentation/cmd-plugin-reload.txt4
-rw-r--r--Documentation/cmd-plugin-remove.txt4
-rw-r--r--Documentation/cmd-query.txt2
-rw-r--r--Documentation/cmd-review.txt30
-rw-r--r--Documentation/cmd-set-account.txt26
-rw-r--r--Documentation/cmd-set-reviewers.txt2
-rw-r--r--Documentation/cmd-stream-events.txt149
-rw-r--r--Documentation/config-gerrit.txt356
-rw-r--r--Documentation/config-gitweb.txt2
-rw-r--r--Documentation/config-hooks.txt24
-rw-r--r--Documentation/config-labels.txt24
-rw-r--r--Documentation/config-plugins.txt565
-rw-r--r--Documentation/config-project-config.txt17
-rw-r--r--Documentation/config-reverseproxy.txt2
-rw-r--r--Documentation/config-sso.txt42
-rw-r--r--Documentation/config-validation.txt32
-rw-r--r--Documentation/config.defs1
-rw-r--r--Documentation/dev-buck.txt115
-rw-r--r--Documentation/dev-contributing.txt140
-rw-r--r--Documentation/dev-eclipse.txt69
-rw-r--r--Documentation/dev-plugins.txt96
-rw-r--r--Documentation/dev-readme.txt25
-rw-r--r--Documentation/dev-release-deploy-config.txt4
-rw-r--r--Documentation/dev-release.txt113
-rw-r--r--Documentation/dev-rest-api.txt2
-rw-r--r--Documentation/doc.css.in (renamed from Documentation/doc.css)2
-rw-r--r--Documentation/error-change-does-not-belong-to-project.txt2
-rw-r--r--Documentation/error-change-not-found.txt2
-rw-r--r--Documentation/error-has-duplicates.txt10
-rw-r--r--Documentation/error-missing-changeid.txt2
-rw-r--r--Documentation/error-prohibited-by-gerrit.txt20
-rw-r--r--Documentation/error-squash-commits-first.txt27
-rwxr-xr-xDocumentation/gen_licenses.py6
-rw-r--r--Documentation/images/inline-edit-add-file-suggestion.pngbin0 -> 53192 bytes
-rw-r--r--Documentation/images/inline-edit-confirm-unsaved-edits.pngbin0 -> 12524 bytes
-rw-r--r--Documentation/images/inline-edit-create-change-project-screen-dialog.pngbin0 -> 12520 bytes
-rw-r--r--Documentation/images/inline-edit-create-change-project-screen.pngbin0 -> 67161 bytes
-rw-r--r--Documentation/images/inline-edit-create-follow-up-change.pngbin0 -> 114660 bytes
-rw-r--r--Documentation/images/inline-edit-edit-in-diff-screen-patch-list.pngbin0 -> 12974 bytes
-rw-r--r--Documentation/images/inline-edit-edit-in-patch-list.pngbin0 -> 24488 bytes
-rw-r--r--Documentation/images/inline-edit-enter-edit-mode-from-diff.pngbin0 -> 44050 bytes
-rw-r--r--Documentation/images/inline-edit-enter-edit-mode-from-file-list.pngbin0 -> 57067 bytes
-rw-r--r--Documentation/images/inline-edit-file-list-in-edit-mode.pngbin0 -> 60018 bytes
-rw-r--r--Documentation/images/inline-edit-full-screen-editor.pngbin0 -> 37789 bytes
-rw-r--r--Documentation/images/user-review-ui-change-screen-edit-commit-message.pngbin52242 -> 0 bytes
-rw-r--r--Documentation/images/user-review-ui-change-view-preference.pngbin33595 -> 0 bytes
-rw-r--r--Documentation/images/user-review-ui-side-by-side-diff-screen-preferences-popup.pngbin143604 -> 19108 bytes
-rw-r--r--Documentation/index.txt24
-rw-r--r--Documentation/intro-project-owner.txt16
-rw-r--r--Documentation/intro-quick.txt2
-rw-r--r--Documentation/intro-user.txt664
-rw-r--r--Documentation/js-api.txt20
-rw-r--r--Documentation/json.txt6
-rw-r--r--Documentation/pgm-SwitchSecureStore.txt39
-rw-r--r--Documentation/pgm-daemon.txt4
-rw-r--r--Documentation/pgm-index.txt3
-rw-r--r--Documentation/project-configuration.txt22
-rw-r--r--Documentation/prolog-cookbook.txt645
-rwxr-xr-xDocumentation/replace_macros.py7
-rw-r--r--Documentation/rest-api-access.txt13
-rw-r--r--Documentation/rest-api-accounts.txt413
-rw-r--r--Documentation/rest-api-changes.txt1149
-rw-r--r--Documentation/rest-api-config.txt72
-rw-r--r--Documentation/rest-api-documentation.txt2
-rw-r--r--Documentation/rest-api-groups.txt103
-rw-r--r--Documentation/rest-api-plugins.txt18
-rw-r--r--Documentation/rest-api-projects.txt512
-rw-r--r--Documentation/rest-api.txt26
-rw-r--r--Documentation/user-changeid.txt6
-rw-r--r--Documentation/user-dashboards.txt3
-rw-r--r--Documentation/user-inline-edit.txt189
-rw-r--r--Documentation/user-notify.txt4
-rw-r--r--Documentation/user-review-ui.txt203
-rw-r--r--Documentation/user-search.txt4
-rw-r--r--Documentation/user-upload.txt37
-rw-r--r--ReleaseNotes/ReleaseNotes-2.0.18.txt8
-rw-r--r--ReleaseNotes/ReleaseNotes-2.0.19.txt44
-rw-r--r--ReleaseNotes/ReleaseNotes-2.0.20.txt10
-rw-r--r--ReleaseNotes/ReleaseNotes-2.0.22.txt6
-rw-r--r--ReleaseNotes/ReleaseNotes-2.0.4.txt2
-rw-r--r--ReleaseNotes/ReleaseNotes-2.1.6.txt2
-rw-r--r--ReleaseNotes/ReleaseNotes-2.10.txt6
-rw-r--r--ReleaseNotes/ReleaseNotes-2.11.1.txt177
-rw-r--r--ReleaseNotes/ReleaseNotes-2.11.txt873
-rw-r--r--ReleaseNotes/ReleaseNotes-2.9.1.txt6
-rw-r--r--ReleaseNotes/ReleaseNotes-2.9.2.txt6
-rw-r--r--ReleaseNotes/ReleaseNotes-2.9.3.txt6
-rw-r--r--ReleaseNotes/ReleaseNotes-2.9.4.txt6
-rw-r--r--ReleaseNotes/ReleaseNotes-2.9.txt15
-rw-r--r--ReleaseNotes/index.txt6
-rw-r--r--VERSION2
-rw-r--r--bucklets/gerrit_plugin.bucklet5
l---------bucklets/java_doc.bucklet1
l---------bucklets/java_sources.bucklet1
l---------bucklets/local_jar.bucklet1
l---------bucklets/maven_package.bucklet1
-rwxr-xr-xcontrib/check-valid-commit.py2
-rwxr-xr-xcontrib/git-push-review88
-rw-r--r--gerrit-acceptance-tests/BUCK9
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java155
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java4
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java13
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java2
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java21
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java64
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java2
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java2
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java125
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java5
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java28
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java16
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java4
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java8
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java31
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java261
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java90
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java45
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java387
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK10
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java746
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java132
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK7
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java26
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/ForcePushIT.java70
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java6
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java5
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java236
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java265
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java64
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java16
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java11
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java117
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java8
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java14
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java43
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java26
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java226
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java16
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java144
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java33
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java26
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java21
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java59
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java116
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java48
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java132
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java233
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java40
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java41
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java177
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java10
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java35
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java33
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java14
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java161
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java72
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java52
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java47
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java20
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java18
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java62
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java25
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java84
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK2
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java39
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java27
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java8
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java24
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java89
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java21
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java27
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java29
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK2
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java37
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java15
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java74
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java140
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java67
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java32
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java45
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java165
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java51
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java158
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java30
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java126
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java26
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java68
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java40
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java143
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java278
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java120
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java51
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java55
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java90
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK1
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java21
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java57
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java4
-rw-r--r--gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g4
-rw-r--r--gerrit-cache-h2/BUCK1
-rw-r--r--gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java8
-rw-r--r--gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java76
-rw-r--r--gerrit-common/BUCK20
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java)3
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java6
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java9
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java56
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java)7
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java126
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java268
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java6
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java38
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java27
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java1
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java25
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java4
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java8
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java79
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java5
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java8
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java5
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java58
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java117
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java3
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java29
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java4
-rw-r--r--gerrit-common/src/test/java/com/google/gerrit/common/AutoValueTest.java39
-rw-r--r--gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java3
-rw-r--r--gerrit-extension-api/BUCK19
-rw-r--r--gerrit-extension-api/pom.xml2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml4
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java20
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java138
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java34
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java34
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftApi.java41
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftInput.java20
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FileApi.java56
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java19
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java25
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RebaseInput.java19
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java75
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java13
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java5
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java13
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeStatus.java)2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Comment.java)8
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GerritTopMenu.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/GerritTopMenu.java)2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/InheritableBoolean.java)2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java)10
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectState.java)2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Side.java (renamed from gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java)5
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java (renamed from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SubmitType.java)2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java39
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java7
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java4
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AvatarInfo.java31
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java18
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeType.java36
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommentInfo.java2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffInfo.java76
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffWebLinkInfo.java43
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java24
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GroupBaseInfo.java20
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergeableInfo.java25
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java36
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java2
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java6
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestedReviewerInfo.java20
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java36
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java6
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java9
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java6
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java35
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java79
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java22
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java12
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java20
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/BranchWebLink.java38
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/DiffWebLink.java47
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/FileWebLink.java39
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java3
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java17
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java14
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java1
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java31
-rw-r--r--gerrit-gwtdebug/BUCK9
-rw-r--r--gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java524
-rw-r--r--gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritGwtDebugLauncher.java80
-rw-r--r--gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java541
-rw-r--r--gerrit-gwtexpui/BUCK26
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java3
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java24
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java3
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/AttMap.java4
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/Buffer.java1
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferDirect.java7
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferSealElement.java7
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java3
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java7
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java2
-rw-r--r--gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java3
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java3
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java4
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java8
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java4
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java4
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java4
-rw-r--r--gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java4
-rw-r--r--gerrit-gwtui-common/BUCK24
-rw-r--r--gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java107
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/addFileComment.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/addFileComment.png)bin642 -> 642 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowDown.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowDown.png)bin1680 -> 1680 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowRight.pngbin0 -> 2780 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowUp.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowUp.png)bin1687 -> 1687 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteHover.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png)bin313 -> 313 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteNormal.pngbin0 -> 334 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/diffy100.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diffy100.png)bin4822 -> 4822 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/diffy26.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diffy26.png)bin1147 -> 1147 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/downloadIcon.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png)bin659 -> 659 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/draftComments.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/draftComments.png)bin244 -> 244 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editText.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png)bin409 -> 409 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editUndo.pngbin0 -> 810 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/gear.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/gear.png)bin246 -> 246 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goNext.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-next.png)bin623 -> 623 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goPrev.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-prev.png)bin609 -> 609 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goUp.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-up.png)bin637 -> 637 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/greenCheck.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png)bin208 -> 208 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/info.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/info.png)bin863 -> 863 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/listAdd.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/listAdd.png)bin323 -> 323 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/mediaFloppy.pngbin0 -> 561 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/merge.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/merge.png)bin710 -> 710 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/queryIcon.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png)bin316 -> 316 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/readOnly.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/readOnly.png)bin440 -> 440 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/redNot.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png)bin221 -> 221 bytes
-rwxr-xr-xgerrit-gwtui-common/src/main/resources/com/google/gerrit/client/sideBySideDiff.pngbin0 -> 3097 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/starFilled.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png)bin299 -> 299 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/starOpen.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_open.png)bin278 -> 278 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/undoNormal.pngbin0 -> 504 bytes
-rwxr-xr-xgerrit-gwtui-common/src/main/resources/com/google/gerrit/client/unifiedDiff.pngbin0 -> 3099 bytes
-rw-r--r--gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/warning.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/warning.png)bin717 -> 717 bytes
-rw-r--r--gerrit-gwtui/BUCK42
-rw-r--r--gerrit-gwtui/gwt.defs2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffWebLinkInfo.java26
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java300
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java124
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java60
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java61
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java27
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java22
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java9
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties9
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java116
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java42
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java46
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java19
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java22
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java10
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java10
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java14
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties18
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java66
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java45
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java156
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java69
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/MyGroupsListScreen.java41
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java17
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java307
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java59
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java161
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java13
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/api/EditGlue.java54
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowRight.gifbin78 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java74
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml45
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java74
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java109
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.ui.xml (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml)20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java10
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties12
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java)472
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml)145
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java104
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml44
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java71
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.java113
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.ui.xml43
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java52
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java57
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageAction.java80
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java108
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java248
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java47
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java271
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.ui.xml83
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/IncludedInAction.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java40
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java15
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml19
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java105
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml78
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java17
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PathSuggestOracle.java80
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java40
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java45
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java22
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java71
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.java107
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.ui.xml47
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java52
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java16
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java)28
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java83
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml9
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitFailureDialog.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java)9
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java15
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateCheckTimer.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css24
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/moreLess.png (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/more_less.png)bin235 -> 235 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.pngbin334 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java18
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java394
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java108
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java62
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java48
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java261
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java111
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java80
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java223
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeResources.java26
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java764
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java432
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java542
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java103
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java730
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java272
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java523
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java10
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.pngbin430 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java59
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css)12
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java33
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java67
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java10
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java34
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java36
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java53
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml60
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java14
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/EditIterator.java8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java14
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java29
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml17
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java266
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml71
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java)55
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.ui.xml (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.ui.xml)3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java155
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java23
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java24
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java32
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css38
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java99
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java119
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java)413
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.ui.xml (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml)4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java24
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UpToChangeCommand.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UpToChangeCommand2.java)6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goNext.pngbin0 -> 623 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goPrev.pngbin0 -> 609 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goUp.pngbin0 -> 637 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.pngbin409 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.java27
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.properties7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditFileInfo.java26
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java477
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml146
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.pngbin246 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css414
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/gwt_override.css2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java33
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommitMessageBlock.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java)93
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommitMessageBlock.ui.xml (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml)0
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java13
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java27
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java37
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java)110
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchUtil.java11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/ReviewedPanels.java76
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java682
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java24
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedPatchScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java)143
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java9
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java65
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java139
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java21
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java56
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java120
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/starFilled.gifbin171 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/starOpen.gifbin179 -> 0 bytes
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java41
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java42
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java66
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java7
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java59
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java105
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ExpandAllCommand.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java25
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java50
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableOldValue.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java15
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java77
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java80
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java56
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java147
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestBox.java137
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java85
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java81
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java13
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextAreaActionDialog.java40
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties6
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml1
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java312
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java4
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java2
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/Extras.java143
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java11
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java7
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java44
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java10
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java71
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java208
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/Pos.java41
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java12
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java25
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java67
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/lib/style.css83
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java198
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInjector.java118
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java77
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/mode/gerrit/commit.js2
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map137
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/theme/ThemeLoader.java83
-rw-r--r--gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java34
-rw-r--r--gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java14
-rw-r--r--gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java29
-rw-r--r--gerrit-httpd/BUCK3
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java26
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java24
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java (renamed from gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritUiOptions.java)10
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java)28
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java37
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java3
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java23
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java73
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java22
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java27
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java63
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java17
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java12
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java27
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java8
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java9
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java21
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java7
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java25
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java (renamed from gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceKey.java)31
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java99
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java7
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java3
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java5
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/Resource.java (renamed from gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/Resource.java)27
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java19
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceWeigher.java (renamed from gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceWeigher.java)4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/SmallResource.java (renamed from gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java)19
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java131
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/Handler.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java235
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java31
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java20
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java116
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java57
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java11
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java6
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java14
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java90
-rw-r--r--gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java4
-rw-r--r--gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java3
-rw-r--r--gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java4
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java14
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/CustomMappingAnalyzer.java65
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java139
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java6
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java10
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java69
-rw-r--r--gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java4
-rw-r--r--gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java5
-rw-r--r--gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java4
-rw-r--r--gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java5
-rw-r--r--gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java4
-rw-r--r--gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java3
-rw-r--r--gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java37
-rw-r--r--gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java2
-rw-r--r--gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java3
-rw-r--r--gerrit-pgm/BUCK149
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java47
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java20
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/JythonShell.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java)2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java296
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java245
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java217
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java5
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java1
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java20
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java56
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java6
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java)130
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java1
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java116
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java7
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java5
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java9
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java5
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java7
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java20
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java6
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java8
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java5
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java17
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java18
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java9
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java9
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InvalidSecureStoreException.java27
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java3
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java4
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java4
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java4
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java4
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java4
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java27
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java44
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java24
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java)109
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsNameOnInitProvider.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java)7
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java)2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java)14
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java)2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java)56
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InstallPlugins.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java)2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java)22
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java36
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java138
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java1
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java79
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SecureStoreException.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreException.java)2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java34
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java18
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java55
-rw-r--r--gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py4
-rwxr-xr-xgerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh (renamed from gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh)28
-rw-r--r--gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config (renamed from gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config)0
-rw-r--r--gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java5
-rw-r--r--gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java43
-rw-r--r--gerrit-plugin-api/BUCK6
-rw-r--r--gerrit-plugin-api/pom.xml2
-rw-r--r--gerrit-plugin-archetype/pom.xml2
-rw-r--r--gerrit-plugin-gwt-archetype/pom.xml2
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml14
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.buckconfig14
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore4
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK23
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/VERSION5
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gerrit/BUCK20
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK32
-rw-r--r--gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md77
-rw-r--r--gerrit-plugin-gwtui/BUCK23
-rw-r--r--gerrit-plugin-gwtui/pom.xml2
-rw-r--r--gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java6
-rw-r--r--gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java1
-rw-r--r--gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java16
-rw-r--r--gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java4
-rw-r--r--gerrit-plugin-js-archetype/pom.xml2
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java4
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java4
-rw-r--r--gerrit-reviewdb/BUCK15
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java61
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java38
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java15
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java70
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java7
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java188
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java44
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java2
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java16
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java66
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java44
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java19
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java51
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountDiffPreferenceAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAudAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAuditAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountProjectWatchAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountSshKeyAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java35
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeMessageAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchLineCommentAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java5
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SchemaVersionAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/StarredChangeAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SystemConfigAccess.java1
-rw-r--r--gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql18
-rw-r--r--gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql22
-rw-r--r--gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql20
-rw-r--r--gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java82
-rw-r--r--gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java82
-rw-r--r--gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java63
-rw-r--r--gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java75
-rw-r--r--gerrit-server/BUCK17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java52
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java154
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java33
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java30
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java (renamed from gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java)6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/FooterConstants.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/Version.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java49
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/rules/ReductionLimitException.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java454
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java85
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java308
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java158
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java40
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java49
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java130
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java98
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java147
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java (renamed from gerrit-server/src/test/java/com/google/gerrit/testutil/FakeRealm.java)8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GetAccount.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/InternalAccountDirectory.java26
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java10
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java156
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java10
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java141
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java142
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java49
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java78
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/FileApiImpl.java77
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java155
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/args4j/TimestampHandler.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java34
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java103
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java82
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java557
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java114
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java564
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java58
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeTriplet.java58
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java268
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java137
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Comments.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java490
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java114
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java)49
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java56
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java46
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java10
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java68
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java50
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/DraftResource.java)8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/DraftComments.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java)30
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java102
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java144
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java186
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java72
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java224
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java)17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetHashtags.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java97
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutor.java)31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java156
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java30
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ListDraftComments.java60
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java96
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java47
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java312
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java46
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java373
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java57
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java233
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java45
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java75
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java98
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java)49
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java)75
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java173
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java116
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java187
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java122
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java288
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java174
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java77
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java72
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java138
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookup.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/schema/Current.java)7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookupProvider.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritConfig.java46
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java40
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java50
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java36
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFootersProvider.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java)27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/contact/NoContactStore.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/data/QueryStatsAttribute.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java39
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java86
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java107
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java473
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java267
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/edit/UnchangedCommitMessageException.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/Event.java30
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java49
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java28
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetEvent.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectEvent.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/RefEvent.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/RefReceivedEvent.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountInfoMapper.java)36
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java89
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java81
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCacheImplModule.java38
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java56
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionLogFile.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java)34
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionModule.java (renamed from gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java)26
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java142
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java61
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java78
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java10
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java635
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java87
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java56
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java67
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java198
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java683
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReloadSubmitQueueOp.java46
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java123
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java106
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java26
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java123
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java61
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java174
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java36
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java48
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java107
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java115
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationMessage.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java76
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java41
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationListener.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java100
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidators.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/validators/ValidationMessage.java33
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java220
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java34
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/GetMember.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupResource.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupsCollection.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java204
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java36
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java102
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java264
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndex.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java40
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java91
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java142
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java)126
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java51
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java19
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java26
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java)2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/FileTypeRegistry.java)2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtil2Module.java43
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java)21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java70
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java319
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java491
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java436
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java332
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java229
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java171
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java72
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java108
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java10
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java30
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java65
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java56
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java28
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java53
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginCleanerTask.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java86
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginResource.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java48
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java400
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PathConflictException.java)9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/CommentLinkInfo.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java43
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java178
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java28
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java104
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java36
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java57
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java150
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java82
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheWarmer.java75
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java109
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java67
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java40
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java580
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SuggestParentCandidates.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java55
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeCosts.java39
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java174
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java33
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java388
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java73
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java76
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java133
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/LimitPredicate.java47
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java404
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java57
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java583
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryResult.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java66
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java15
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java139
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java50
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java99
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_100.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_101.java (renamed from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpdatePrimaryKeys.java)104
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java68
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_104.java27
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_105.java89
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_106.java104
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java41
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java11
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_98.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_99.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java35
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java113
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreClassName.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java77
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java71
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java60
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java57
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/OneOffRequestContext.java51
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java112
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java60
-rw-r--r--gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java4
-rw-r--r--gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java11
-rw-r--r--gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java1
-rw-r--r--gerrit-server/src/main/java/gerrit/PRED_current_user_1.java10
-rw-r--r--gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java2
-rw-r--r--gerrit-server/src/main/prolog/gerrit_common.pl2
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties2
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm5
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties (renamed from gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties)14
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java45
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java19
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java4
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/account/UniversalGroupBackendTest.java148
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java331
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java449
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java104
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java4
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java4
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java57
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java35
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java48
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/git/GroupListTest.java121
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java14
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java386
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java18
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java5
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java33
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java7
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java8
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java2
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java239
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java215
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java1004
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java257
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java7
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java176
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java2
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java45
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java4
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java430
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java33
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java185
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java2
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java20
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java5
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java10
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java6
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java52
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/util/ParboiledTest.java75
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java84
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java41
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java55
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java2
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java1
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java7
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java28
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java6
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/TempFileUtil.java (renamed from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java)4
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java56
-rw-r--r--gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java111
-rw-r--r--gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java2
-rw-r--r--gerrit-sshd/BUCK2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java9
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java10
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java8
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java4
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java3
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java1
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java92
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java1
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java8
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshPluginStarterCallback.java3
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java4
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java4
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java1
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java97
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CommandUtils.java112
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java24
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java8
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java26
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java39
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListLoggingLevelCommand.java55
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java82
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java37
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java25
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java9
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java164
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java182
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java1
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java120
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetLoggingLevelCommand.java91
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java4
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java22
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java33
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java6
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java46
-rw-r--r--gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java14
-rw-r--r--gerrit-util-http/BUCK20
-rw-r--r--gerrit-util-http/src/main/java/com/google/gerrit/util/http/RequestUtil.java60
-rw-r--r--gerrit-util-http/src/test/java/com/google/gerrit/util/http/RequestUtilTest.java91
-rw-r--r--gerrit-util-ssl/src/main/java/com/google/gerrit/util/ssl/BlindSSLSocketFactory.java3
-rw-r--r--gerrit-war/BUCK4
-rw-r--r--gerrit-war/pom.xml2
-rw-r--r--gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java23
-rw-r--r--gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java37
-rw-r--r--lib/BUCK46
-rw-r--r--lib/antlr/BUCK10
-rw-r--r--lib/asciidoctor/BUCK5
-rw-r--r--lib/asciidoctor/java/AsciiDoctor.java5
-rw-r--r--lib/asciidoctor/java/DocIndexer.java5
-rw-r--r--lib/auto/BUCK13
-rw-r--r--lib/auto/auto_value.defs21
-rw-r--r--lib/codemirror/BUCK131
-rw-r--r--lib/codemirror/closure.defs34
-rw-r--r--lib/codemirror/cm.defs82
-rw-r--r--lib/codemirror/cm3.defs60
-rw-r--r--lib/commons/BUCK32
-rw-r--r--lib/gwt/BUCK16
-rw-r--r--lib/httpcomponents/BUCK31
-rw-r--r--lib/jetty/BUCK39
-rw-r--r--lib/local.defs33
-rw-r--r--lib/lucene/BUCK8
-rw-r--r--lib/maven.defs35
-rw-r--r--lib/openid/BUCK2
-rw-r--r--lib/ow2/BUCK10
-rw-r--r--lib/solr/BUCK4
m---------plugins/commit-message-length-validator0
m---------plugins/cookbook-plugin0
m---------plugins/download-commands0
m---------plugins/replication0
m---------plugins/reviewnotes0
m---------plugins/singleusergroup0
-rw-r--r--tools/BUCK12
-rw-r--r--tools/PythonTestCaller.java67
-rw-r--r--tools/checkstyle.xml103
-rw-r--r--tools/default.defs126
-rw-r--r--tools/eclipse/BUCK4
-rw-r--r--tools/eclipse/gerrit_gwt_debug.launch13
-rwxr-xr-xtools/eclipse/project.py92
-rw-r--r--tools/gerrit.importorder9
-rw-r--r--tools/gwt-constants.defs9
-rw-r--r--tools/java_doc.defs38
-rw-r--r--tools/java_sources.defs10
-rwxr-xr-xtools/pack_war.py2
-rw-r--r--tools/util.py1
-rwxr-xr-xtools/version.py12
-rw-r--r--website/releases/index.html2
1515 files changed, 49591 insertions, 27988 deletions
diff --git a/.buckconfig b/.buckconfig
index 43bfa5e8a7..e4a19f1ade 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -6,11 +6,12 @@
war_deploy = //tools/maven:war_deploy
war_install = //tools/maven:war_install
chrome = //:chrome
- docs = //Documentation:html
+ docs = //Documentation:searchfree
firefox = //:firefox
gerrit = //:gerrit
release = //:release
safari = //:safari
+ soyc = //gerrit-gwtui:ui_soyc
withdocs = //:withdocs
[buildfile]
diff --git a/.buckversion b/.buckversion
index a0c6bc2643..9c09744a03 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-0fe4569e871fd6588f7cbfb4b1d4a14baa791a9f
+79d36de9f5284f6e833cca81867d6088a25685fb
diff --git a/.gitignore b/.gitignore
index b356144211..c30cee65d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@
/.buckd
/buck-cache
/buck-out
+/extras
/local.properties
*.pyc
/gwt-unitCache
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000000..75aea08f18
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,21 @@
+Adrian Görler <adrian.goerler@sap.com> Adrian Goerler <adrian.goerler@sap.com>
+Alex Ryazantsev <alex.ryazantsev@gmail.com> alex <alex.ryazantsev@gmail.com>
+Alex Ryazantsev <alex.ryazantsev@gmail.com> alex.ryazantsev <alex.ryazantsev@gmail.com>
+Carlos Eduardo Baldacin <carloseduardo.baldacin@sonyericsson.com> carloseduardo.baldacin <carloseduardo.baldacin@sonyericsson.com>
+Deniz Türkoglu <deniz@spotify.com> Deniz Turkoglu <deniz@spotify.com>
+Deniz Türkoglu <deniz@spotify.com> Deniz Türkoglu <deniz@spotify.com>
+Edwin Kempin <edwin.kempin@sap.com> Edwin Kempin <edwin.kempin@gmail.com>
+Hugo Arès <hugo.ares@ericsson.com> Hugo Ares <hugo.ares@ericsson.com>
+Jason Huntley <jhuntley@houghtonassociates.com> jhuntley <jhuntley@houghtonassociates.com>
+Johan Björk <jbjoerk@gmail.com> Johan Bjork <phb@spotify.com>
+Lincoln Oliveira Campos Do Nascimento <lincoln.oliveiracamposdonascimento@sonyericsson.com> lincoln <lincoln.oliveiracamposdonascimento@sonyericsson.com>
+Mônica Dionísio <monica.dionisio@sonyericsson.com> monica.dionisio <monica.dionisio@sonyericsson.com>
+Peter Jönsson <peter.joensson@gmail.com> Peter Jönsson <peter.joensson@gmail.com>
+Rafael Rabelo Silva <rafael.rabelosilva@sonyericsson.com> rafael.rabelosilva <rafael.rabelosilva@sonyericsson.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>
+Shawn Pearce <sop@google.com> Shawn O. Pearce <sop@google.com>
+Tomas Westling <thomas.westling@sonyericsson.com> thomas.westling <thomas.westling@sonyericsson.com>
+Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjolin <ulrik.sjolin@gmail.com>
+Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjolin <ulrik.sjolin@sonyericsson.com>
+Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjölin <ulrik.sjolin@sonyericsson.com>
diff --git a/.pydevproject b/.pydevproject
index be43141085..40e9f40a0a 100644
--- a/.pydevproject
+++ b/.pydevproject
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?eclipse-pydev version="1.0"?>
-
-<pydev_project>
+<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6.5</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
</pydev_project>
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 2a585e4257..0fa494d319 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,4 +1,5 @@
eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
@@ -14,11 +15,11 @@ org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
@@ -32,27 +33,28 @@ org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
@@ -60,6 +62,7 @@ org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
@@ -68,9 +71,9 @@ org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
@@ -78,13 +81,15 @@ org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
diff --git a/.watchmanconfig b/.watchmanconfig
new file mode 100644
index 0000000000..b1869ba6b8
--- /dev/null
+++ b/.watchmanconfig
@@ -0,0 +1,8 @@
+{
+ "ignore_dirs": [
+ "buck-out"
+ ],
+ "ignore_vcs": [
+ ".git"
+ ]
+}
diff --git a/Documentation/BUCK b/Documentation/BUCK
index f070d7e3b5..48a6525cdc 100644
--- a/Documentation/BUCK
+++ b/Documentation/BUCK
@@ -6,30 +6,25 @@ DOC_DIR = 'Documentation'
MAIN = ['//gerrit-pgm:pgm', '//gerrit-gwtui:ui_module']
SRCS = glob(['*.txt'], excludes = ['licenses.txt'])
-genrule(
+genasciidoc(
name = 'html',
- cmd = 'cd $TMP;' +
- 'mkdir -p %s/images;' % DOC_DIR +
- 'unzip -q $(location %s) -d %s/;'
- % (':generate_html', DOC_DIR) +
- 'for s in $SRCS;do ln -s $s %s;done;' % DOC_DIR +
- 'mv %s/*.{jpg,png} %s/images;' % (DOC_DIR, DOC_DIR) +
- 'cp $(location %s) LICENSES.txt;' % ':licenses.txt' +
- 'zip -qr $OUT *',
- srcs = glob([
- 'images/*.jpg',
- 'images/*.png',
- ]) + ['doc.css'],
out = 'html.zip',
+ docdir = DOC_DIR,
+ srcs = SRCS + [':licenses.txt'],
+ attributes = documentation_attributes(git_describe()),
+ backend = 'html5',
visibility = ['PUBLIC'],
)
genasciidoc(
- name = 'generate_html',
+ name = 'searchfree',
+ out = 'searchfree.zip',
+ docdir = DOC_DIR,
srcs = SRCS + [':licenses.txt'],
attributes = documentation_attributes(git_describe()),
backend = 'html5',
- out = 'only_html.zip',
+ searchbox = False,
+ visibility = ['PUBLIC'],
)
genrule(
@@ -39,6 +34,13 @@ genrule(
out = 'licenses.txt',
)
+genrule(
+ name = 'doc.css',
+ srcs = ['doc.css.in'],
+ cmd = 'cp $SRCS $OUT',
+ out = 'doc.css',
+)
+
python_binary(
name = 'gen_licenses',
main = 'gen_licenses.py',
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 909f972ecd..acd33c0524 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -210,8 +210,8 @@ Reference-level access control is also possible.
Permissions can be set on a single reference name to match one
branch (e.g. `refs/heads/master`), or on a reference namespace
-(e.g. `refs/heads/*`) to match any branch starting with that
-prefix. So a permission with `refs/heads/*` will match
+(e.g. `+refs/heads/*+`) to match any branch starting with that
+prefix. So a permission with `+refs/heads/*+` will match
`refs/heads/master` and `refs/heads/experimental`, etc.
Reference names can also be described with a regular expression
@@ -227,7 +227,7 @@ particular regular expression flavor.
References can have the current user name automatically included,
creating dynamic access controls that change to match the currently
logged in user. For example to provide a personal sandbox space
-to all developers, `refs/heads/sandbox/${username}/*` allowing
+to all developers, `+refs/heads/sandbox/${username}/*+` allowing
the user 'joe' to use 'refs/heads/sandbox/joe/foo'.
When evaluating a reference-level access right, Gerrit will use
@@ -405,19 +405,19 @@ link:user-upload.html#push_create[Upload changes] page.
==== refs/publish/*
-`refs/publish/*` is an alternative name to `refs/for/*` when pushing new changes
+`+refs/publish/*+` is an alternative name to `+refs/for/*+` when pushing new changes
and patch sets.
==== refs/drafts/*
-Push to `refs/drafts/*` creates a change like push to `refs/for/*`, except the
+Push to `+refs/drafts/*+` creates a change like push to `+refs/for/*+`, except the
resulting change remains hidden from public review. You then have the option
of adding individual reviewers before making the change public to all. The
change page will have a 'Publish' button which allows you to convert individual
draft patch sets of a change into public patch sets for review.
-To block push permission to `refs/drafts/*` the following permission rule can
+To block push permission to `+refs/drafts/*+` the following permission rule can
be configured:
====
@@ -464,18 +464,18 @@ branch permissions, allowing the holder of both to create new branches
as well as bypass review for new commits on that branch.
To push lightweight (non-annotated) tags, grant
-`Create Reference` for reference name `refs/tags/*`, as lightweight
+`Create Reference` for reference name `+refs/tags/*+`, as lightweight
tags are implemented just like branches in Git.
For example, to grant the possibility to create new branches under the
namespace `foo`, you have to grant this permission on
-`refs/heads/foo/*` for the group that should have it.
+`+refs/heads/foo/*+` for the group that should have it.
Finally, if you plan to grant each user a personal namespace in
where they are free to create as many branches as they wish, you
should grant the create reference permission so it's possible
to create new branches. This is done by using the special
`${username}` keyword in the reference pattern, e.g.
-`refs/heads/sandbox/${username}/*`. If you do, it's also recommended
+`+refs/heads/sandbox/${username}/*+`. If you do, it's also recommended
you grant the users the push force permission to be able to clean up
stale branches.
@@ -547,7 +547,7 @@ they are a member of, just like for any other user.
Ownership over a particular branch subspace may be delegated by
entering a branch pattern. To delegate control over all branches
that begin with `qa/` to the QA group, add `Owner` category
-for reference `refs/heads/qa/*`. Members of the QA group can
+for reference `+refs/heads/qa/*+`. Members of the QA group can
further refine access, but only for references that begin with
`refs/heads/qa/`. See <<project_owners,project owners>> to find
out more about this role.
@@ -600,15 +600,15 @@ a new commit on their local system, so in practice they must also
have the `Read` access granted to upload a change.
For an open source, public Gerrit installation, it is common to
-grant `Read` and `Push` for `refs/for/refs/heads/*`
+grant `Read` and `Push` for `+refs/for/refs/heads/*+`
to `Registered Users` in the `All-Projects` ACL. For more
private installations, its common to simply grant `Read` and
-`Push` for `refs/for/refs/heads/*` to all users of a project.
+`Push` for `+refs/for/refs/heads/*+` to all users of a project.
* Force option
+
The force option has no function when granted to a branch in the
-`refs/for/refs/heads/*` namespace.
+`+refs/for/refs/heads/*+` namespace.
[[category_push_merge]]
@@ -661,11 +661,11 @@ must be also granted in addition to `Push Annotated Tag`.
To push lightweight (non annotated) tags, grant
<<category_create,`Create Reference`>> for reference name
-`refs/tags/*`, as lightweight tags are implemented just like
+`+refs/tags/*+`, as lightweight tags are implemented just like
branches in Git.
To delete or overwrite an existing tag, grant `Push` with the force
-option enabled for reference name `refs/tags/*`, as deleting a tag
+option enabled for reference name `+refs/tags/*+`, as deleting a tag
requires the same permission as deleting a branch.
@@ -768,8 +768,7 @@ defined globally or on a per-project basis.
[[category_submit]]
=== Submit
-This category permits users to push the `Submit Patch Set n` button
-on the web UI.
+This category permits users to submit changes.
Submitting a change causes it to be merged into the destination
branch as soon as possible, making it a permanent part of the
@@ -838,6 +837,17 @@ by the 'Force Edit' flag. If this flag is not set the topic can only be
edited on open changes.
+[[category_edit_hashtags]]
+=== Edit Hashtags
+
+This category permits users to add or remove hashtags on a change that
+is uploaded for review.
+
+The change owner, branch owners, project owners, and site administrators
+can always edit or remove hashtags (even without having the `Edit Hashtags`
+access right assigned).
+
+
[[example_roles]]
== Examples of typical roles in a project
@@ -897,7 +907,7 @@ Suggested access rights to grant:
* xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*'
* link:config-labels.html#label_Code-Review[`Label: Code-Review`] with range '-2' to '+2' for 'refs/heads/*'
* link:config-labels.html#label_Verified[`Label: Verified`] with range '-1' to '+1' for 'refs/heads/*'
-* xref:category_submit[`Submit`]
+* xref:category_submit[`Submit`] on 'refs/heads/*'
If the project is small or the developers are seasoned it might make
sense to give them the freedom to push commits directly to a branch.
@@ -1016,7 +1026,7 @@ Suggested access rights to grant:
== Enforcing site wide access policies
-By granting the <<category_owner,`Owner`>> access right on the `refs/*` to a
+By granting the <<category_owner,`Owner`>> access right on the `+refs/*+` to a
group, Gerrit administrators can delegate the responsibility of maintaining
access rights for that project to that group.
@@ -1152,7 +1162,8 @@ Below you find a list of capabilities available:
[[capability_accessDatabase]]
=== Access Database
-Allow users to access the database using the `gsql` command.
+Allow users to access the database using the `gsql` command, and view code
+review metadata refs in repositories.
[[capability_administrateServer]]
@@ -1164,6 +1175,20 @@ able to grant any access right to any group. They will also have all
capabilities granted to them automatically.
+[[capability_batchChangesLimit]]
+=== Batch Changes Limit
+
+Allow site administrators to configure the batch changes limit for
+users to override the system config
+link:config-gerrit.html#receive.maxBatchChanges['receive.maxBatchChanges'].
+
+Administrators can add a global block to `All-Projects` with group(s)
+that should have different limits.
+
+When applying a batch changes limit to a user the largest value
+granted by any of their groups is used. 0 means no limit.
+
+
[[capability_createAccount]]
=== Create Account
@@ -1211,13 +1236,6 @@ This capability doesn't imply permissions to the show-caches command. For that
you need the <<capability_viewCaches,view caches capability>>.
-[[capability_generateHttpPassword]]
-=== Generate HTTP Password
-
-Allow the user to generate HTTP passwords for other users. Typically this would
-be assigned to a non-interactive users group.
-
-
[[capability_kill]]
=== Kill Task
@@ -1226,6 +1244,12 @@ kill command ends tasks that currently occupy the Gerrit server, usually
a replication task or a user initiated task such as an upload-pack or
receive-pack.
+[[capability_modifyAccount]]
+=== Modify Account
+
+Allow to link:cmd-set-account.html[modify accounts over the ssh prompt].
+This capability allows the granted group members to modify any user account
+setting.
[[capability_priority]]
=== Priority
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 7adf265bee..2520c74f46 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -12,18 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-def genasciidoc(
+def genasciidoc_htmlonly(
name,
out,
srcs = [],
attributes = [],
backend = None,
+ searchbox = True,
visibility = []):
- EXPN = '.expn'
+ EXPN = '.' + name + '_expn'
asciidoc = [
'$(exe //lib/asciidoctor:asciidoc)',
'-z', '$OUT',
+ '--base-dir', '$SRCDIR',
'--tmp', '$TMP',
'--in-ext', '".txt%s"' % EXPN,
'--out-ext', '".html"',
@@ -33,7 +35,7 @@ def genasciidoc(
for attribute in attributes:
asciidoc.extend(['-a', attribute])
asciidoc.append('$SRCS')
- newsrcs = ["doc.css"]
+ newsrcs = [":doc.css"]
for src in srcs:
fn = src
# We have two cases: regular source files and generated files.
@@ -50,14 +52,16 @@ def genasciidoc(
genrule(
name = ex,
- cmd = '$(exe :replace_macros) --suffix=' + EXPN +
- ' -s ' + passed_src +
- ' -o $OUT',
+ cmd = '$(exe :replace_macros) --suffix="%s"' % EXPN +
+ ' -s ' + passed_src + ' -o $OUT' +
+ (' --searchbox' if searchbox else ' --no-searchbox'),
srcs = srcs,
out = ex,
)
- asciidoc.append('$(location :%s)' % ex)
+ # The new AsciiDoctor requires both the css file and include files are under
+ # the same directory. Luckily Buck allows us to use :target as SRCS now.
+ newsrcs.append(':%s' % ex)
genrule(
name = name,
@@ -66,3 +70,41 @@ def genasciidoc(
out = out,
visibility = visibility,
)
+
+def genasciidoc(
+ name,
+ out,
+ docdir,
+ srcs = [],
+ attributes = [],
+ backend = None,
+ searchbox = True,
+ visibility = []):
+ SUFFIX = '_htmlonly'
+
+ genasciidoc_htmlonly(
+ name = name + SUFFIX,
+ srcs = srcs,
+ attributes = attributes,
+ backend = backend,
+ searchbox = searchbox,
+ out = name + SUFFIX + '.zip',
+ )
+
+ genrule(
+ name = name,
+ cmd = 'cd $TMP;' +
+ 'mkdir -p %s/images;' % docdir +
+ 'unzip -q $(location %s) -d %s/;'
+ % (':' + name + SUFFIX, docdir) +
+ 'for s in $SRCS;do ln -s $s %s;done;' % docdir +
+ 'mv %s/*.{jpg,png} %s/images;' % (docdir, docdir) +
+ 'cp $(location %s) LICENSES.txt;' % ':licenses.txt' +
+ 'zip -qr $OUT *',
+ srcs = glob([
+ 'images/*.jpg',
+ 'images/*.png',
+ ]) + [':doc.css'],
+ out = out,
+ visibility = visibility,
+ )
diff --git a/Documentation/cmd-close-connection.txt b/Documentation/cmd-close-connection.txt
new file mode 100644
index 0000000000..331432689d
--- /dev/null
+++ b/Documentation/cmd-close-connection.txt
@@ -0,0 +1,38 @@
+= gerrit close-connection
+
+== NAME
+gerrit close-connection - Close the specified SSH connection
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit close-connection' <SESSION_ID>
+ [--wait]
+--
+
+== DESCRIPTION
+Close an SSH connection.
+
+The connection closing is done asynchronously by default. Use `--wait` option to
+wait for connection to close.
+
+An error message will be displayed if no connection with the specified session
+ID is found.
+
+== ACCESS
+Caller must be a member of the privileged 'Administrators' group.
+
+== SCRIPTING
+Intended for interactive use only.
+
+OPTIONS
+-------
+
+`--wait`
+: Wait for connection to close before exiting.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index d74785243f..31b4538075 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -15,6 +15,7 @@ gerrit create-project - Create a new hosted project
[--use-contributor-agreements | --ca]
[--use-signed-off-by | --so]
[--use-content-merge]
+ [--create-new-change-for-all-not-in-target]
[--require-change-id | --id]
[[--branch <REF> | -b <REF>] ...]
[--empty-commit]
@@ -134,6 +135,15 @@ Submit Types].
from either the author or the uploader in the commit message.
Disabled by default.
+--create-new-change-for-all-not-in-target::
+--ncfa:
+ If enabled, a new change is created for every commit that is not in
+ the target branch. If the pushed commit is a merge commit, this flag is
+ ignored for that push. To avoid accidental creation of a large number
+ of open changes, this option also does not accept merge commits in the
+ commit chain.
+ Disabled by default.
+
--require-change-id::
--id::
Require a valid link:user-changeid.html[Change-Id] footer
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index d4d6a5799f..7c664ac728 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -19,7 +19,7 @@ see link:user-upload.html#test_ssh[Testing Your SSH Connection].
=== [[client_commands]]Commands
link:cmd-cherry-pick.html[gerrit-cherry-pick]::
- Download and cherry-pick one or more changes (commits).
+ Download and cherry-pick one or more changes (commits).
=== [[client_hooks]]Hooks
@@ -28,7 +28,7 @@ the developer experience when working with a Gerrit Code Review
server.
link:cmd-hook-commit-msg.html[commit-msg]::
- Automatically generate `Change-Id: ` tags in commit messages.
+ Automatically generate `Change-Id: ` tags in commit messages.
== Server
@@ -51,6 +51,9 @@ link:cmd-apropos.html[gerrit apropos]::
link:cmd-ban-commit.html[gerrit ban-commit]::
Bans a commit from a project's repository.
+link:cmd-create-branch.html[gerrit create-branch]::
+ Create a new project branch.
+
link:cmd-ls-groups.html[gerrit ls-groups]::
List groups visible to the caller.
@@ -60,57 +63,51 @@ link:cmd-ls-members.html[gerrit ls-members]::
link:cmd-ls-projects.html[gerrit ls-projects]::
List projects visible to the caller.
-link:cmd-rename-group.html[gerrit rename-group]::
- Rename an account group.
-
-link:cmd-set-reviewers.html[gerrit set-reviewers]::
- Add or remove reviewers on a change.
-
link:cmd-query.html[gerrit query]::
Query the change database.
'gerrit receive-pack'::
'Deprecated alias for `git receive-pack`.'
+link:cmd-rename-group.html[gerrit rename-group]::
+ Rename an account group.
+
link:cmd-review.html[gerrit review]::
Verify, approve and/or submit a patch set from the command line.
+link:cmd-set-reviewers.html[gerrit set-reviewers]::
+ Add or remove reviewers on a change.
+
link:cmd-stream-events.html[gerrit stream-events]::
Monitor events occurring in real time.
link:cmd-version.html[gerrit version]::
Show the currently executing version of Gerrit.
-git upload-pack::
- Standard Git server side command for client side `git fetch`.
-
link:cmd-receive-pack.html[git receive-pack]::
Standard Git server side command for client side `git push`.
+
Also implements the magic associated with uploading commits for
review. See link:user-upload.html#push_create[Creating Changes].
-link:cmd-create-branch.html[gerrit create-branch]::
- Create a new project branch.
+git upload-pack::
+ Standard Git server side command for client side `git fetch`.
[[admin_commands]]Administrator Commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+link:cmd-close-connection.html[gerrit close-connection]::
+ Close the specified SSH connection.
+
link:cmd-create-account.html[gerrit create-account]::
Create a new user account.
-link:cmd-set-account.html[gerrit set-account]::
- Change an account's settings.
-
link:cmd-create-group.html[gerrit create-group]::
Create a new account group.
link:cmd-create-project.html[gerrit create-project]::
Create a new project and associated Git repository.
-link:cmd-set-project.html[gerrit set-project]::
- Change a project's settings.
-
link:cmd-flush-caches.html[gerrit flush-caches]::
Flush some/all server caches from memory.
@@ -120,44 +117,56 @@ link:cmd-gc.html[gerrit gc]::
link:cmd-gsql.html[gerrit gsql]::
Administrative interface to active database.
-link:cmd-set-members.html[gerrit set-members]::
- Set group members.
+link:cmd-logging-ls-level.html[gerrit logging ls-level]::
+ List loggers and their logging level.
-link:cmd-set-project-parent.html[gerrit set-project-parent]::
- Change the project permissions are inherited from.
+link:cmd-logging-set-level.html[gerrit logging set-level]::
+ Set the logging level of loggers.
link:cmd-ls-user-refs.html[gerrit ls-user-refs]::
Lists refs visible for a specified user.
-link:cmd-show-caches.html[gerrit show-caches]::
- Display current cache statistics.
-
-link:cmd-show-connections.html[gerrit show-connections]::
- Display active client SSH connections.
-
-link:cmd-show-queue.html[gerrit show-queue]::
- Display the background work queues, including replication.
-
link:cmd-plugin-install.html[gerrit plugin add]::
- Alias for 'gerrit plugin install'.
+ Alias for 'gerrit plugin install'.
link:cmd-plugin-enable.html[gerrit plugin enable]::
- Enable plugins.
+ Enable plugins.
link:cmd-plugin-install.html[gerrit plugin install]::
- Install/Add a plugin.
+ Install/Add a plugin.
link:cmd-plugin-ls.html[gerrit plugin ls]::
- List the installed plugins.
+ List the installed plugins.
link:cmd-plugin-reload.html[gerrit plugin reload]::
- Reload/Restart plugins.
+ Reload/Restart plugins.
link:cmd-plugin-remove.html[gerrit plugin remove]::
- Disable plugins.
+ Disable plugins.
link:cmd-plugin-remove.html[gerrit plugin rm]::
- Alias for 'gerrit plugin remove'.
+ Alias for 'gerrit plugin remove'.
+
+link:cmd-set-account.html[gerrit set-account]::
+ Change an account's settings.
+
+link:cmd-set-members.html[gerrit set-members]::
+ Set group members.
+
+link:cmd-set-project.html[gerrit set-project]::
+ Change a project's settings.
+
+link:cmd-set-project-parent.html[gerrit set-project-parent]::
+ Change the project permissions are inherited from.
+
+link:cmd-show-caches.html[gerrit show-caches]::
+ Display current cache statistics.
+
+link:cmd-show-connections.html[gerrit show-connections]::
+ Display active client SSH connections.
+
+link:cmd-show-queue.html[gerrit show-queue]::
+ Display the background work queues, including replication.
link:cmd-test-submit-rule.html[gerrit test-submit rule]::
Test prolog submit rules.
diff --git a/Documentation/cmd-logging-ls-level.txt b/Documentation/cmd-logging-ls-level.txt
new file mode 100644
index 0000000000..c59dc3f435
--- /dev/null
+++ b/Documentation/cmd-logging-ls-level.txt
@@ -0,0 +1,43 @@
+= gerrit logging ls-level
+
+== NAME
+gerrit logging ls-level - view the logging level
+
+gerrit logging ls - view the logging level
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit logging ls-level | ls'
+ <NAME>
+--
+
+== DESCRIPTION
+View the logging level of specified loggers.
+
+== Options
+<NAME>::
+ Display the loggers which contain the input argument in their name. If this
+ argument is not provided, all loggers will be printed.
+
+== ACCESS
+Caller must have the ADMINISTRATE_SERVER capability.
+
+== Examples
+
+View the logging level of the loggers in the package com.google:
+=====
+ $ssh -p 29418 review.example.com gerrit logging ls-level \
+ com.google.
+=====
+
+View the logging level of every logger
+=====
+ $ssh -p 29418 review.example.com gerrit logging ls-level
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-logging-set-level.txt b/Documentation/cmd-logging-set-level.txt
new file mode 100644
index 0000000000..38062cb340
--- /dev/null
+++ b/Documentation/cmd-logging-set-level.txt
@@ -0,0 +1,51 @@
+= gerrit logging set-level
+
+== NAME
+gerrit logging set-level - set the logging level
+
+gerrit logging set - set the logging level
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit logging set-level | set'
+ <LEVEL>
+ <NAME>
+--
+
+== DESCRIPTION
+Set the logging level of specified loggers.
+
+== Options
+<LEVEL>::
+ Required; logging level for which the loggers should be set.
+ 'reset' can be used to revert all loggers back to their level
+ at deployment time.
+
+<NAME>::
+ Set the level of the loggers which contain the input argument in their name.
+ If this argument is not provided, all loggers will have their level changed.
+ Note that this argument has no effect if 'reset' is passed in LEVEL.
+
+== ACCESS
+Caller must have the ADMINISTRATE_SERVER capability.
+
+== Examples
+
+Change the logging level of the loggers in the package com.google to DEBUG.
+=====
+ $ssh -p 29418 review.example.com gerrit logging set-level \
+ debug com.google.
+=====
+
+Reset the logging level of every logger to what they were at deployment time.
+=====
+ $ssh -p 29418 review.example.com gerrit logging set-level \
+ reset
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-plugin-enable.txt b/Documentation/cmd-plugin-enable.txt
index 585efd83bc..c8022ef6ae 100644
--- a/Documentation/cmd-plugin-enable.txt
+++ b/Documentation/cmd-plugin-enable.txt
@@ -15,7 +15,9 @@ the plugin jars in the site path's `plugins` directory from
`<plugin-jar-name>.disabled` to `<plugin-jar-name>`.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+* 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`.
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/cmd-plugin-install.txt b/Documentation/cmd-plugin-install.txt
index daa6472555..0ce6d7de57 100644
--- a/Documentation/cmd-plugin-install.txt
+++ b/Documentation/cmd-plugin-install.txt
@@ -17,7 +17,9 @@ Install/Add a plugin. The plugin will be copied into the site path's
`plugins` directory.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+* 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`.
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/cmd-plugin-ls.txt b/Documentation/cmd-plugin-ls.txt
index d9c997e857..234ce8758b 100644
--- a/Documentation/cmd-plugin-ls.txt
+++ b/Documentation/cmd-plugin-ls.txt
@@ -14,7 +14,12 @@ plugin ls - List the installed plugins.
List the installed plugins and show their version and status.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+* The caller must be a member of a group that is granted the
+ link:access-control.html#capability_viewPlugins[View Plugins]
+ capability or the link:access-control.html#capability_administrateServer[
+ Administrate Server] capability.
+* link:config-gerrit.html#plugins.allowRemoteAdmin[plugins.allowRemoteAdmin]
+ must be enabled in `$site_path/etc/gerrit.config`.
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/cmd-plugin-reload.txt b/Documentation/cmd-plugin-reload.txt
index 8889307888..88cb1f33e6 100644
--- a/Documentation/cmd-plugin-reload.txt
+++ b/Documentation/cmd-plugin-reload.txt
@@ -19,7 +19,9 @@ E.g. a plugin needs to be reloaded if its configuration is modified to
make the new configuration data become active.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+* 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`.
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/cmd-plugin-remove.txt b/Documentation/cmd-plugin-remove.txt
index 3197203567..770df853da 100644
--- a/Documentation/cmd-plugin-remove.txt
+++ b/Documentation/cmd-plugin-remove.txt
@@ -16,7 +16,9 @@ Disable plugins. The plugins will be disabled by renaming the plugin
jars in the site path's `plugins` directory to `<plugin-jar-name>.disabled`.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+* 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`.
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 538b6ed4fd..0ff59d4d3c 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -116,7 +116,7 @@ Find the 2 most recent open changes in the tools/gerrit project:
====
$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
{"project":"tools/gerrit", ...}
- {"project":"tools/gerrit", ..., sortKey:"000e6aee00003e26", ...}
+ {"project":"tools/gerrit", ...}
{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
====
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 4c9962dae4..70a695e738 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -13,7 +13,9 @@ gerrit review - Apply reviews to one or more patch sets
[--notify <NOTIFYHANDLING> | -n <NOTIFYHANDLING>]
[--submit | -s]
[--abandon | --restore]
+ [--rebase]
[--publish]
+ [--json | -j]
[--delete]
[--verified <N>] [--code-review <N>]
[--label Label-Name=<N>]
@@ -55,6 +57,15 @@ branch.
-m::
Optional cover letter to include as part of the message
sent to reviewers when the approval states are updated.
+ (option is mutually exclusive with --json)
+
+--json::
+-j::
+ Read review input from JSON file. See
+ link:rest-api-changes.html#review-input[ReviewInput] entity for the
+ format.
+ (option is mutually exclusive with --submit, --restore, --publish, --delete,
+ --abandon, --message and --rebase)
--notify::
-n::
@@ -75,25 +86,32 @@ branch.
--abandon::
Abandon the specified change(s).
- (option is mutually exclusive with --submit, --restore, --publish and
- --delete)
+ (option is mutually exclusive with --submit, --restore, --publish, --delete,
+ --rebase and --json)
--restore::
Restore the specified abandoned change(s).
- (option is mutually exclusive with --abandon)
+ (option is mutually exclusive with --abandon and --json)
+
+--rebase::
+ Rebase the specified change(s).
+ (option is mutually exclusive with --abandon, --submit, --delete and --json)
--submit::
-s::
Submit the specified patch set(s) for merging.
- (option is mutually exclusive with --abandon, --publish and --delete)
+ (option is mutually exclusive with --abandon, --publish --delete, --rebase
+ and --json)
--publish::
Publish the specified draft patch set(s).
- (option is mutually exclusive with --submit, --restore, --abandon, and --delete)
+ (option is mutually exclusive with --submit, --restore, --abandon, --delete
+ and --json)
--delete::
Delete the specified draft patch set(s).
- (option is mutually exclusive with --submit, --restore, --abandon, and --publish)
+ (option is mutually exclusive with --submit, --restore, --abandon, --publish,
+ --rebase and --json)
--code-review::
--verified::
diff --git a/Documentation/cmd-set-account.txt b/Documentation/cmd-set-account.txt
index cec5a8e884..8fb8e0d6ab 100644
--- a/Documentation/cmd-set-account.txt
+++ b/Documentation/cmd-set-account.txt
@@ -7,9 +7,11 @@ gerrit set-account - Change an account's settings.
--
set-account [--full-name <FULLNAME>] [--active|--inactive] \
[--add-email <EMAIL>] [--delete-email <EMAIL> | ALL] \
+ [--preferred-email <EMAIL>] \
[--add-ssh-key - | <KEY>] \
[--delete-ssh-key - | <KEY> | ALL] \
- [--http-password <PASSWORD>] <USER>
+ [--http-password <PASSWORD>] \
+ [--clear-http-password] <USER>
--
== DESCRIPTION
@@ -21,7 +23,15 @@ It also allows managing email addresses, which bypasses the
verification step we force within the UI.
== ACCESS
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted
+link:access-control.html#capability_modifyAccount[the 'Modify Account' global capability].
+For security reasons only the members of the privileged 'Administrators'
+group can add or delete SSH keys for a user.
+
+To set the HTTP password for the user account (option --http-password) or
+to clear the HTTP password (option --clear-http-password) caller must be
+a member of the privileged 'Administrators' group.
== SCRIPTING
This command is intended to be used in scripts.
@@ -55,9 +65,16 @@ This most likely requires double quoting the value, for example
Delete an email from this user's account if it exists.
If the email provided is 'ALL', all associated emails are
deleted from this account.
- Maybe supplied more than once to remove multiple emails
+ May be supplied more than once to remove multiple emails
from an account in a single command execution.
+--preferred-email::
+ Sets the preferred email address for the user's account.
+ The email address must already have been registered
+ with the user's account before it can be set.
+ May be supplied with the delete-email option as long as
+ the emails are not the same.
+
--add-ssh-key::
Content of the public SSH key to add to the account's
keyring. If `-` the key is read from stdin, rather than
@@ -77,6 +94,9 @@ This most likely requires double quoting the value, for example
--http-password::
Set the HTTP password for the user account.
+--clear-http-password::
+ Clear the HTTP password for the user account.
+
== EXAMPLES
Add an email and SSH key to `watcher`'s account:
diff --git a/Documentation/cmd-set-reviewers.txt b/Documentation/cmd-set-reviewers.txt
index 4cb7bbd22f..79f7651f53 100644
--- a/Documentation/cmd-set-reviewers.txt
+++ b/Documentation/cmd-set-reviewers.txt
@@ -27,7 +27,7 @@ SHA-1s.
--project::
-p::
Name of the project the intended change is contained within. This
- option must be supplied before Change-ID in order to take effect.
+ option must be supplied before Change-Id in order to take effect.
--add::
-a::
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 68e796a4ba..c754f35019 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -37,46 +37,50 @@ This command is intended to be used in scripts.
== SCHEMA
The JSON messages consist of nested objects referencing the *change*,
*patchSet*, *account* involved, and other attributes as appropriate.
-The currently supported message types are *patchset-created*,
-*draft-published*, *change-abandoned*, *change-restored*,
-*change-merged*, *merge-failed*, *comment-added*, *ref-updated* and
-*reviewer-added*.
Note that any field may be missing in the JSON messages, so consumers of
this JSON stream should deal with that appropriately.
[[events]]
-=== Events
-==== Patchset Created
-type:: "patchset-created"
+== EVENTS
+=== Change Abandoned
+
+Sent when a change has been abandoned.
+
+type:: "change-abandoned"
change:: link:json.html#change[change attribute]
patchSet:: link:json.html#patchSet[patchSet attribute]
-uploader:: link:json.html#account[account attribute]
+abandoner:: link:json.html#account[account attribute]
-==== Draft Published
-type:: "draft-published"
+reason:: Reason for abandoning the change.
-change:: link:json.html#change[change attribute]
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
-patchSet:: link:json.html#patchSet[patchSet attribute]
+=== Change Merged
-uploader:: link:json.html#account[account attribute]
+Sent when a change has been merged into the git repository.
-==== Change Abandoned
-type:: "change-abandoned"
+type:: "change-merged"
change:: link:json.html#change[change attribute]
patchSet:: link:json.html#patchSet[patchSet attribute]
-abandoner:: link:json.html#account[account attribute]
+submitter:: link:json.html#account[account attribute]
-reason:: Reason for abandoning the change.
+newRev:: The resulting revision of the merge.
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Change Restored
+
+Sent when an abandoned change has been restored.
-==== Change Restored
type:: "change-restored"
change:: link:json.html#change[change attribute]
@@ -87,16 +91,72 @@ restorer:: link:json.html#account[account attribute]
reason:: Reason for restoring the change.
-==== Change Merged
-type:: "change-merged"
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Comment Added
+
+Sent when a review comment has been posted on a change.
+
+type:: "comment-added"
change:: link:json.html#change[change attribute]
patchSet:: link:json.html#patchSet[patchSet attribute]
-submitter:: link:json.html#account[account attribute]
+author:: link:json.html#account[account attribute]
+
+approvals:: All link:json.html#approval[approval attributes] granted.
+
+comment:: Review comment cover message.
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Draft Published
+
+Sent when a draft change has been published.
+
+type:: "draft-published"
+
+change:: link:json.html#change[change attribute]
+
+patchSet:: link:json.html#patchSet[patchSet attribute]
+
+uploader:: link:json.html#account[account attribute]
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Dropped Output
+
+Sent to notify a client that events have been dropped.
+
+type:: "dropped-output"
+
+=== Hashtags Changed
+
+Sent when the hashtags have been added to or removed from a change.
+
+type:: "hashtags-changed"
+
+change:: link:json.html#change[change attribute]
+
+editor:: link:json.html#account[account attribute]
+
+added:: List of hashtags added to the change
+
+removed:: List of hashtags removed from the change
+
+hashtags:: List of hashtags on the change after tags were added or removed
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Merge Failed
+
+Sent when a change has failed to be merged into the git repository.
-==== Merge Failed
type:: "merge-failed"
change:: link:json.html#change[change attribute]
@@ -107,27 +167,47 @@ submitter:: link:json.html#account[account attribute]
reason:: Reason that the merge failed.
-==== Comment Added
-type:: "comment-added"
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Patchset Created
+
+Sent when a new change has been uploaded, or a new patch set has been uploaded
+to an existing change.
+
+Note that this event is also sent for changes or patch sets uploaded as draft,
+but is only visible to the change owner, any existing reviewers, and users who
+belong to a group that is granted the
+link:access-control.html#category_view_drafts[View Drafts] capability.
+
+type:: "patchset-created"
change:: link:json.html#change[change attribute]
patchSet:: link:json.html#patchSet[patchSet attribute]
-author:: link:json.html#account[account attribute]
+uploader:: link:json.html#account[account attribute]
-approvals:: All link:json.html#approval[approval attributes] granted.
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Ref Updated
-comment:: Comment text author had written
+Sent when a reference is updated in a git repository.
-==== Ref Updated
type:: "ref-updated"
submitter:: link:json.html#account[account attribute]
refUpdate:: link:json.html#refUpdate[refUpdate attribute]
-==== Reviewer Added
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Reviewer Added
+
+Sent when a reviewer is added to a change.
+
type:: "reviewer-added"
change:: link:json.html#change[change attribute]
@@ -136,7 +216,13 @@ patchSet:: link:json.html#patchSet[patchSet attribute]
reviewer:: link:json.html#account[account attribute]
-==== Topic Changed
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Topic Changed
+
+Sent when the topic of a change has been changed.
+
type:: "topic-changed"
change:: link:json.html#change[change attribute]
@@ -145,6 +231,9 @@ changer:: link:json.html#account[account attribute]
oldTopic:: Topic name before it was changed.
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
== SEE ALSO
* link:json.html[JSON Data Formats]
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 632965fd63..837469faa2 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -283,6 +283,20 @@ If set, Gerrit trusts and enforces the user's e-mail using the HTTP header
and disables the ability to manually modify or register other e-mails
from the contact information page.
+[[auth.httpExternalIdHeader]]auth.httpExternalIdHeader::
++
+HTTP header to retrieve the user's external identification token.
+Only used if `auth.type` is set to `HTTP`.
++
+If set, Gerrit adds the value contained in the HTTP header to the
+user's identity. Typical use is with a federated identity token from
+an external system (e.g. GitHub OAuth 2.0 authentication) where
+the user's auth token exchanged during authentication handshake
+needs to be used for authenticated communication to the external
+system later on.
++
+Example: `auth.httpExternalIdHeader: X-GitHub-OTP`
+
[[auth.loginUrl]]auth.loginUrl::
+
URL to redirect a browser to after the end-user has clicked on the
@@ -323,26 +337,27 @@ If not set, the redirect returns to the list of all open changes.
[[auth.registerUrl]]auth.registerUrl::
+
Target for the "Register" link in the upper right corner. Used only
-when `auth.type` is `LDAP`.
+when `auth.type` is `LDAP`, `LDAP_BIND` or `CUSTOM_EXTENSION`.
+
If not set, no "Register" link is displayed.
[[auth.registerText]]auth.registerText::
+
Text for the "Register" link in the upper right corner. Used only
-when `auth.type` is `LDAP`.
+when `auth.type` is `LDAP`, `LDAP_BIND` or `CUSTOM_EXTENSION`.
+
If not set, defaults to "Register".
[[auth.editFullNameUrl]]auth.editFullNameUrl::
+
Target for the "Edit" button when the user is allowed to edit their
-full name.
+full name. Used only when `auth.type` is `LDAP`, `LDAP_BIND` or
+`CUSTOM_EXTENSION`.
[[auth.httpPasswordUrl]]auth.httpPasswordUrl::
+
Target for the "Obtain Password" link. Used only when `auth.type` is
-`LDAP`, `LDAP_BIND` or `CUSTOM_EXTENSION`.
+`CUSTOM_EXTENSION`.
[[auth.switchAccountUrl]]auth.switchAccountUrl::
+
@@ -488,15 +503,17 @@ Values should use common unit suffixes to express their setting:
* y, year, years (`1 year` is treated as `365 days`)
+
+--
If a unit suffix is not specified, `seconds` is assumed. If 0 is
supplied, the maximum age is infinite and items are never purged
except when the cache is full.
-+
+
Default is `0`, meaning store forever with no expire, except:
-+
+
* `"adv_bases"`: default is `10 minutes`
* `"ldap_groups"`: default is `1 hour`
* `"web_sessions"`: default is `12 hours`
+--
[[cache.name.memoryLimit]]cache.<name>.memoryLimit::
+
@@ -721,9 +738,11 @@ Values should use common unit suffixes to express their setting:
* h, hr, hour, hours
+
+--
If a unit suffix is not specified, `milliseconds` is assumed.
-+
+
Default is 5 seconds.
+--
[[cache.diff_intraline.timeout]]cache.diff_intraline.timeout::
+
@@ -744,9 +763,11 @@ Values should use common unit suffixes to express their setting:
* h, hr, hour, hours
+
+--
If a unit suffix is not specified, `milliseconds` is assumed.
-+
+
Default is 5 seconds.
+--
[[cache.diff_intraline.enabled]]cache.diff_intraline.enabled::
+
@@ -775,6 +796,26 @@ link:cmd-flush-caches.html[gerrit flush-caches].
+
Default is 5 minutes.
+[[cache.projects.loadOnStartup]]cache.projects.loadOnStartup::
++
+If the project cache should be loaded during server startup.
++
+The cache is loaded concurrently. Admins should ensure that the cache
+size set under <<cache.name.memoryLimit,cache.projects.memoryLimit>>
+is not smaller than the number of repos.
++
+Default is false, disabled.
+
+[[cache.projects.loadThreads]]cache.projects.loadThreads::
++
+Only relevant if <<cache.projects.loadOnStartup,cache.projects.loadOnStartup>>
+is true.
++
+The number of threads to allocate for loading the cache at startup. These
+threads will die out after the cache is loaded.
++
+Default is the number of CPUs.
+
[[change]]
=== Section change
@@ -830,6 +871,21 @@ abbreviated commit SHA-1 (`c9c0edb`).
+
Default is "Submit patch set ${patchSet} into ${branch}".
+[[change.replyLabel]]change.replyLabel::
++
+Label name for the reply button. In the user interface an ellipsis (…)
+is appended.
++
+Default is "Reply". In the user interface it becomes "Reply…".
+
+[[change.replyTooltip]]change.replyTooltip::
++
+Tooltip for the reply button. In the user interface a note about the
+keyboard shortcut is appended.
++
+Default is "Reply and score". In the user interface it becomes "Reply
+and score (Shortcut: a)".
+
[[changeMerge]]
=== Section changeMerge
@@ -845,19 +901,22 @@ Default is 300 seconds (5 minutes).
[[changeMerge.threadPoolSize]]changeMerge.threadPoolSize::
+
-Maximum size of the thread pool in which the mergeability flag of open
-changes is updated.
+_Deprecated:_ Formerly used to control thread pool size for background
+mergeability checks. These checks were moved to the indexing threadpool,
+so this value is now used for
+link:#index.batchThreads[index.batchThreads], only if that value is not
+provided.
+
-Default is 1.
+This option may be removed in a future version.
[[changeMerge.interactiveThreadPoolSize]]changeMerge.interactiveThreadPoolSize::
+
-Maximum size of the thread pool in which the mergeability flag of open
-changes is updated, when processing interactive user requests (e.g.
-pushes to refs/for/*). Set to 0 or negative to share the pool for
-background mergeability checks.
+_Deprecated:_ Formerly used to control thread pool size for interactive
+mergeability checks. These checks were moved to the indexing threadpool,
+so this value is now used for link:#index.threads[index.threads], only
+if that value is not provided.
+
-Default is 1.
+This option may be removed in a future version.
[[commentlink]]
=== Section commentlink
@@ -997,6 +1056,15 @@ configuration:
javaOptions = -Dlog4j.configuration=file:///home/gerrit/site/etc/log4j.properties
----
+[[container.daemonOpt]]container.daemonOpt::
++
+Additional options to pass to the daemon (e.g. '--enable-httpd'). If
+multiple values are configured, they are passed in that order to the command
+line, separated by spaces.
++
+Execute `java -jar gerrit.war daemon --help` to see all possible
+options.
+
[[container.slave]]container.slave::
+
Used on Gerrit slave installations. If set to true the Gerrit JVM is
@@ -1260,12 +1328,14 @@ Values should use common unit suffixes to express their setting:
* h, hr, hour, hours
+
+--
If a unit suffix is not specified, `milliseconds` is assumed.
-+
+
Default is `30 seconds`.
-+
+
This setting only applies if
<<database.connectionPool,database.connectionPool>> is true.
+--
[[database.dataSourceInterceptorClass]]database.dataSourceInterceptorClass::
@@ -1488,10 +1558,10 @@ same host as Gerrit.
+
Optional command to install the `commit-msg` hook. Typically of the
form:
++
----
fetch-cmd some://url/to/commit-msg .git/hooks/commit-msg ; chmod +x .git/hooks/commit-msg
----
-
+
By default unset; falls back to using scp from the canonical SSH host,
or curl from the canonical HTTP URL for the server. Only necessary if a
@@ -1510,21 +1580,34 @@ same host as Gerrit.
[[gerrit.reportBugUrl]]gerrit.reportBugUrl::
+
-URL to direct users to when they need to report a bug about the
-Gerrit service. By default this links to the upstream Gerrit
-Code Review's own bug tracker but could be directed to the system
-administrator's ticket queue.
+URL to direct users to when they need to report a bug.
++
+By default unset, meaning no bug report URL will be displayed. Administrators
+should set this to the URL of their issue tracker, if necessary.
[[gerrit.reportBugText]]gerrit.reportBugText::
+
Text to be displayed in the link to the bug report URL.
+
+Only used when `gerrit.reportBugUrl` is set.
++
Defaults to "Report Bug".
-[[gerrit.changeScreen]]gerrit.changeScreen::
+[[gerrit.disableReverseDnsLookup]]gerrit.disableReverseDnsLookup::
+
-Default change screen UI to direct users to. Valid values are
-`OLD_UI` and `CHANGE_SCREEN2`. Default is `CHANGE_SCREEN2`.
+Disables reverse DNS lookup during computing ref log entry for identified user.
++
+Defaults to false.
+
+[[gerrit.secureStoreClass]]gerrit.secureStoreClass::
++
+Use the secure store implementation from a specified class.
++
+If specified, must be the fully qualified class name of a class that implements
+the `com.google.gerrit.server.securestore.SecureStore` interface, and the jar
+file containing the class must be placed in the `$site_path/lib` folder.
++
+If not specified, the default no-op implementation is used.
[[gitweb]]
=== Section gitweb
@@ -1677,40 +1760,60 @@ See also link:config-hooks.html[Hooks].
+
Optional path to hooks, if not specified then `'$site_path'/hooks` will be used.
-[[hooks.patchsetCreatedHook]]hooks.patchsetCreatedHook::
+[[hooks.syncHookTimeout]]hooks.syncHookTimeout::
+
-Optional filename for the patchset created hook, if not specified then
-`patchset-created` will be used.
+Optional timeout value in seconds for synchronous hooks, if not specified
+then 30 seconds will be used.
-[[hooks.draftPublishedHook]]hooks.draftPublishedHook::
+[[hooks.changeAbandonedHook]]hooks.changeAbandonedHook::
+
-Optional filename for the draft published hook, if not specified then
-`draft-published` will be used.
+Optional filename for the change abandoned hook, if not specified then
+`change-abandoned` will be used.
+
+[[hooks.changeMergedHook]]hooks.changeMergedHook::
++
+Optional filename for the change merged hook, if not specified then
+`change-merged` will be used.
+
+[[hooks.changeRestoredHook]]hooks.changeRestoredHook::
++
+Optional filename for the change restored hook, if not specified then
+`change-restored` will be used.
+
+[[hooks.claSignedHook]]hooks.claSignedHook::
++
+Optional filename for the CLA signed hook, if not specified then
+`cla-signed` will be used.
[[hooks.commentAddedHook]]hooks.commentAddedHook::
+
Optional filename for the comment added hook, if not specified then
`comment-added` will be used.
-[[hooks.changeMergedHook]]hooks.changeMergedHook::
+[[hooks.draftPublishedHook]]hooks.draftPublishedHook::
+
-Optional filename for the change merged hook, if not specified then
-`change-merged` will be used.
+Optional filename for the draft published hook, if not specified then
+`draft-published` will be used.
+
+[[hooks.hashtagsChangedHook]]hooks.hashtagsChangedHook::
++
+Optional filename for the hashtags changed hook, if not specified then
+`hashtags-changed` will be used.
[[hooks.mergeFailedHook]]hooks.mergeFailedHook::
+
Optional filename for the merge failed hook, if not specified then
`merge-failed` will be used.
-[[hooks.changeAbandonedHook]]hooks.changeAbandonedHook::
+[[hooks.patchsetCreatedHook]]hooks.patchsetCreatedHook::
+
-Optional filename for the change abandoned hook, if not specified then
-`change-abandoned` will be used.
+Optional filename for the patchset created hook, if not specified then
+`patchset-created` will be used.
-[[hooks.changeRestoredHook]]hooks.changeRestoredHook::
+[[hooks.refUpdateHook]]hooks.refUpdateHook::
+
-Optional filename for the change restored hook, if not specified then
-`change-restored` will be used.
+Optional filename for the ref update hook, if not specified then
+`ref-update` will be used.
[[hooks.refUpdatedHook]]hooks.refUpdatedHook::
+
@@ -1727,21 +1830,6 @@ Optional filename for the reviewer added hook, if not specified then
Optional filename for the topic changed hook, if not specified then
`topic-changed` will be used.
-[[hooks.claSignedHook]]hooks.claSignedHook::
-+
-Optional filename for the CLA signed hook, if not specified then
-`cla-signed` will be used.
-
-[[hooks.refUpdateHook]]hooks.refUpdateHook::
-+
-Optional filename for the ref update hook, if not specified then
-`ref-update` will be used.
-
-[[hooks.syncHookTimeout]]hooks.syncHookTimeout::
-+
-Optional timeout value in seconds for synchronous hooks, if not specified
-then 30 seconds will be used.
-
[[http]]
=== Section http
@@ -1763,6 +1851,24 @@ Optional password to authenticate to the HTTP proxy with.
This property is honored only if the password does not
appear in the http.proxy property above.
+[[http.addUserAsRequestAttribute]]http.addUserAsRequestAttribute::
++
+If true, 'User' attribute will be added to the request attributes so it
+can be accessed outside the request scope (will be set to username or id
+if username not configured).
++
+This attribute can be used by the servlet container to log user in the
+http access log.
++
+When running the embedded servlet container, this attribute is used to
+print user in the httpd_log.
++
+* `%{User}r`
++
+Pattern to print user in Tomcat AccessLog.
+
++
+Default value is true.
[[httpd]]
=== Section httpd
@@ -1817,10 +1923,12 @@ Behaves exactly like proxy-http, but also sets the scheme to assume
'https://' is the proper URL back to the server.
+
+--
If multiple values are supplied, the daemon will listen on all
of them.
-+
+
By default, http://*:8080.
+--
[[httpd.reuseAddress]]httpd.reuseAddress::
+
@@ -1948,11 +2056,13 @@ Values should use common unit suffixes to express their setting:
* y, year, years (`1 year` is treated as `365 days`)
+
+--
If a unit suffix is not specified, `minutes` is assumed. If 0
is supplied, the maximum age is infinite and connections will not
abort until the client disconnects.
-+
+
By default, 5 minutes.
+--
[[httpd.filterClass]]httpd.filterClass::
+
@@ -1983,7 +2093,7 @@ a trusted username in the `TRUSTED_USER` HTTP Header:
type = HTTP
httpHeader = TRUSTED_USER
-[http]
+[httpd]
filterClass = org.anyorg.MySecureFilter
----
@@ -2031,9 +2141,20 @@ By default, `LUCENE`.
[[index.threads]]index.threads::
+
-Determines the number of threads to use for indexing.
+Number of threads to use for indexing in normal interactive operations.
+
-Defaults to 1 if not set, or set to a negative value.
+Defaults to 1 if not set, or set to a negative value (unless
+link:#changeMerge.interactiveThreadPoolSize[changeMerge.interactiveThreadPoolSize]
+is iset).
+
+[[index.batchThreads]]index.batchThreads::
++
+Number of threads to use for indexing in background operations, such as
+online schema upgrades.
++
+If not set or set to a negative value, defaults to the number of logical
+CPUs as returned by the JVM (unless
+link:#changeMerge.threadPoolSize[changeMerge.threadPoolSize] is set).
==== Lucene configuration
@@ -2161,6 +2282,13 @@ perform a query.
+
By default, true, requiring the certificate to be verified.
+[[ldap.groupsVisibleToAll]]ldap.groupsVisibleToAll::
++
+If true, LDAP groups are visible to all registered users.
++
+By default, false, LDAP groups are visible only to administrators and
+group members.
+
[[ldap.username]]ldap.username::
+
_(Optional)_ Username to bind to the LDAP server with. If not set,
@@ -2461,8 +2589,8 @@ are specified in the link:#container[container section]:
If set to true, files with the MIME type `<name>` will be sent as
direct downloads to the user's browser, rather than being wrapped up
inside of zipped archives. The type name may be a complete type
-name, e.g. `image/gif`, a generic media type, e.g. `image/*`,
-or the wildcard `*/*` to match all types.
+name, e.g. `image/gif`, a generic media type, e.g. `+image/*+`,
+or the wildcard `+*/*+` to match all types.
+
By default, false for all MIME types.
@@ -2519,7 +2647,7 @@ be specified using standard time unit abbreviations ('ms', 'sec',
'min', etc.).
+
If set to 0, automatic plugin reloading is disabled. Administrators
-may force reloading with link:cmd-plugin.html[gerrit plugin reload].
+may force reloading with link:cmd-plugin-reload.html[gerrit plugin reload].
+
Default is 1 minute.
@@ -2601,6 +2729,21 @@ Default is zero.
+
Common unit suffixes of 'k', 'm', or 'g' are supported.
+[[receive.maxBatchChanges]]receive.maxBatchChanges::
++
+The maximum number of changes that Gerrit allows to be pushed
+in a batch for review. When this number is exceeded Gerrit rejects
+the push with an error message.
++
+May be overridden for certain groups by specifying a limit in the
+link:access-control.html#capability_batchChangesLimit['Batch Changes Limit']
+global capability.
++
+This setting can be used to prevent users from uploading large
+number of changes for review by mistake.
++
+Default is zero, no limit.
+
[[receive.threadPoolSize]]receive.threadPoolSize::
+
Maximum size of the thread pool in which the change data in received packs is
@@ -2674,6 +2817,31 @@ only the default internal rules will be used.
+
Default is true, to execute project specific rules.
+[[rules.reductionLimit]]rules.reductionLimit::
++
+Maximum number of Prolog reductions that can be performed when
+evaluating rules for a single change. Each function call made
+in user rule code, internal Gerrit Prolog code, or the Prolog
+interpreter counts against this limit.
++
+Sites using very complex rules that need many reductions should
+compile Prolog to Java bytecode with link:pgm-rulec.html[rulec].
+This eliminates the dynamic Prolog interpreter from charging its
+own reductions against the limit, enabling more logic to execute
+within the same bounds.
++
+A reductionLimit of 0 is nearly infinite, implemented by setting
+the internal limit to 2^31-1.
++
+Default is 100,000 reductions (about 14 ms on Intel Core i7 CPU).
+
+[[rules.compileReductionLimit]]rules.compileReductionLimit::
++
+Maximum number of Prolog reductions that can be performed when
+compiling source code to internal Prolog machine code.
++
+Default is 10x reductionLimit (1,000,000).
+
[[execution]]
=== Section execution
@@ -2842,12 +3010,6 @@ If true the server checks the site header, footer and CSS files for
updated versions. If false, a server restart is required to change
any of these resources. Default is true, allowing automatic reloads.
-[[site.enableDeprecatedQuery]]site.enableDeprecatedQuery::
-+
-If true the deprecated `/query` URL is available to return JSON
-and text results for changes. If false, the URL is disabled and
-returns 404 to clients. Default is true, enabling `/query`.
-
[[ssh-alias]]
=== Section ssh-alias
@@ -2881,15 +3043,17 @@ default of 29418.
* 'hostname':'port' (for example `review.example.com:29418`)
* 'IPv4':'port' (for example `10.0.0.1:29418`)
* ['IPv6']:'port' (for example `[ff02::1]:29418`)
-* *:'port' (for example `*:29418`)
+* +*:'port'+ (for example `+*:29418+`)
+
+--
If multiple values are supplied, the daemon will listen on all
of them.
-+
+
To disable the internal SSHD, set listenAddress to `off`.
-+
+
By default, *:29418.
+--
[[sshd.advertisedAddress]]sshd.advertisedAddress::
+
@@ -2898,16 +3062,18 @@ This may differ from sshd.listenAddress if a firewall based port
redirector is being used, making Gerrit appear to answer on port
22. The following forms may be used to specify an address. In any
form, `:'port'` may be omitted to use the default SSH port of 22.
-+
+
* 'hostname':'port' (for example `review.example.com:22`)
* 'IPv4':'port' (for example `10.0.0.1:29418`)
* ['IPv6']:'port' (for example `[ff02::1]:29418`)
+
+--
If multiple values are supplied, the daemon will advertise all
of them.
-+
+
By default, sshd.listenAddress.
+--
[[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
+
@@ -3110,6 +3276,16 @@ otherwise, this value applies to both `suggest.accounts` and
New configurations should prefer the boolean value for this field
and an enum value for `accounts.visibility`.
+[[suggest.maxSuggestedReviewers]]suggest.maxSuggestedReviewers::
++
+The maximum numbers of reviewers suggested.
++
+By default 10.
+
+[[suggest.fullTextSearch]]suggest.fullTextSearch::
++
+If 'true' the reviewer completion suggestions will be based on a full text search.
+
[[suggest.from]]suggest.from::
+
The number of characters that a user must have typed before suggestions
@@ -3117,6 +3293,19 @@ are provided. If set to 0, suggestions are always provided.
+
By default 0.
+[[suggest.fullTextSearchMaxMatches]]suggest.fullTextSearchMaxMatches::
++
+The maximum number of matches evaluated for change access when using full text search.
++
+By default 100.
+
+[[suggest.fullTextSearchRefresh]]suggest.fullTextSearchRefresh::
++
+Refresh interval for the in-memory account search index.
++
+By default 1 hour.
+
+
[[theme]]
=== Section theme
@@ -3220,7 +3409,7 @@ As example, here is the theme configuration to have the old green look:
Tagged footer lines containing references to external
tracking systems, parsed out of the commit message and
-saved in Gerrit's database.
+saved in Gerrit's secondary index.
After making changes to this section, existing changes
must be reindexed with link:pgm-reindex.html[reindex].
@@ -3231,6 +3420,7 @@ bug:<tracking id>.
----
[trackingid "jira-bug"]
footer = Bugfix:
+ footer = Bug:
match = JRA\\d{2,8}
system = JIRA
@@ -3242,11 +3432,15 @@ bug:<tracking id>.
[[trackingid.name.footer]]trackingid.<name>.footer::
+
-A prefix tag that identify the footer line to parse for tracking ids.
-Several trackingid entries can have the same footer tag. A single
-trackingid entry can have multiple footer tags. If multiple footer
-tags are specified, each tag will be parsed separately.
-(the trailing ":" is optional)
+A prefix tag that identifies the footer line to parse for tracking ids.
++
+Several trackingid entries can have the same footer tag, and a single trackingid
+entry can have multiple footer tags.
++
+If multiple footer tags are specified, each tag will be parsed separately and
+duplicates will be ignored.
++
+The trailing ":" is optional.
[[trackingid.name.match]]trackingid.<name>.match::
+
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index d1cee572a5..23111844aa 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -215,7 +215,7 @@ gitweb CGI.
The CGI's `$projectroot` should be the same directory as
gerrit.basePath, or a fairly current replica. If a replica is
-being used, ensure it uses a full mirror, so the `refs/changes/*`
+being used, ensure it uses a full mirror, so the `+refs/changes/*+`
namespace is available.
----
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 8d58d369cf..ce908b9733 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -50,8 +50,9 @@ kind:: change kind represents the kind of change uploaded, also represented in l
TRIVIAL_REBASE;; Conflict-free merge between the new parent and the prior patch set.
- NO_CODE_CHANGE;; No code changed; same tree and same parents.
+ NO_CODE_CHANGE;; No code changed; same tree and same parent tree.
+ NO_CHANGE;; No changes; same commit message, same tree and same parent tree.
=== draft-published
@@ -74,7 +75,7 @@ This is called whenever a comment is added to a change.
Called whenever a change has been merged.
====
- change-merged --change <change id> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --topic <topic> --submitter <submitter> --commit <sha1>
+ change-merged --change <change id> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --topic <topic> --submitter <submitter> --commit <sha1> --newrev <sha1>
====
=== merge-failed
@@ -125,6 +126,25 @@ Called whenever a change's topic is changed from the Web UI or via the REST API.
topic-changed --change <change id> --change-owner <change owner> --project <project name> --branch <branch> --changer <changer> --old-topic <old topic> --new-topic <new topic>
====
+=== hashtags-changed
+
+Called whenever hashtags are added to or removed from a change from the Web UI
+or via the REST API.
+
+====
+ hashtags-changed --change <change id> --change-owner <change owner> --project <project name> --branch <branch> --editor <editor> --added <hashtag> --removed <hashtag> --hashtag <hashtag>
+====
+
+The `--added` parameter may be passed multiple times, once for each
+hashtag that was added to the change.
+
+The `--removed` parameter may be passed multiple times, once for each
+hashtag that was removed from the change.
+
+The `--hashtag` parameter may be passed multiple times, once for each
+hashtag remaining on the change after the add or remove operation has
+been performed.
+
=== cla-signed
Called whenever a user signs a contributor license agreement.
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index aaeb834af8..ce49d317c6 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -222,7 +222,8 @@ determining whether a change is submittable.
=== `label.Label-Name.copyMinScore`
If true, the lowest possible negative value for the label is copied
-forward when a new patch set is uploaded.
+forward when a new patch set is uploaded. Defaults to false, except
+for All-Projects which has it true by default.
[[label_copyMaxScore]]
=== `label.Label-Name.copyMaxScore`
@@ -230,7 +231,7 @@ forward when a new patch set is uploaded.
If true, the highest possible positive value for the label is copied
forward when a new patch set is uploaded. This can be used to enable
sticky approvals, reducing turn-around for trivial cleanups prior to
-submitting a change.
+submitting a change. Defaults to false.
[[label_copyAllScoresOnTrivialRebase]]
=== `label.Label-Name.copyAllScoresOnTrivialRebase`
@@ -241,19 +242,33 @@ 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.
This can be used to enable sticky approvals, reducing turn-around for
-trivial rebases prior to submitting a change. Defaults to false.
+trivial rebases prior to submitting a change.
+It is recommended to enable this for the Code-Review label.
+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 commit as the previous 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.
+It is recommended to enable this for the Verified label if enabled.
Defaults to false.
+[[label_copyAllScoresIfNoChange]]
+=== `label.Label-Name.copyAllScoresIfNoChange`
+
+If true, all scores for the label are copied forward when a new patch
+set is uploaded that has the same parent tree, code delta, and commit
+message as the previous patch set. This means that only the patch
+set SHA1 is different. This can be used to enable sticky
+approvals, reducing turn-around for this special case.
+It is recommended to leave this enabled for both Verified and
+Code-Review labels. Defaults to true.
+
[[label_canOverride]]
=== `label.Label-Name.canOverride`
@@ -325,6 +340,7 @@ user permissions. Assume the configuration below.
====
Upon clicking the Reply button:
+
* Administrators have all scores (-3..+3) available, -3 is set as the default.
* Project Owners have limited scores (-2..+2) available, -2 is set as the default.
* Registered Users have limited scores (-1..+1) available, -1 is set as the default.
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
new file mode 100644
index 0000000000..608a3ac81a
--- /dev/null
+++ b/Documentation/config-plugins.txt
@@ -0,0 +1,565 @@
+= Plugins
+
+The Gerrit server functionality can be extended by installing plugins.
+
+[[installation]]
+== Plugin Installation
+Plugin installation is as easy as dropping the plugin jar into the
+`$site_path/plugins/` folder. It may take
+link:config-gerrit.html#plugins.checkFrequency[a few minutes] until
+the server picks up new and updated plugins.
+
+Plugins can also be installed via
+link:rest-api-plugins.html#install-plugin[REST] and
+link:cmd-plugin-install.html[SSH].
+
+[[development]]
+== Plugin Development
+
+How to develop plugins is described in the link:dev-plugins.html[
+Plugin Development Guide].
+
+If you want to share your plugin under the link:licenses.html#Apache2_0[
+Apache License 2.0] you can host your plugin development on the
+link:https://gerrit-review.googlesource.com[gerrit-review] Gerrit
+Server. You can request the creation of a new Project by email
+to the link:https://groups.google.com/forum/#!forum/repo-discuss[Gerrit
+mailing list]. You would be assigned as project owner of the new plugin
+project so that you can submit changes on your own. It is the
+responsibility of the project owner to maintain the plugin, e.g. to
+make sure that it works with new Gerrit versions and to create stable
+branches for old releases.
+
+[[core-plugins]]
+== Core Plugins
+
+Core plugins are packaged within the Gerrit war file and can easily be
+installed during the link:pgm-init.html[Gerrit initialization].
+
+The core plugins are developed and maintained by the Gerrit maintainers
+and the Gerrit community.
+
+[[commit-message-length-validator]]
+=== commit-message-length-validator
+
+This plugin checks the length of a commit’s commit message subject and
+message body, and reports warnings or errors to the git client if the
+lengths are exceeded.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/commit-message-length-validator[
+Project] |
+link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[cookbook-plugin]]
+=== cookbook-plugin
+
+Sample plugin to demonstrate features of Gerrit's plugin API.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/cookbook-plugin[
+Project] |
+link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[download-commands]]
+=== download-commands
+
+This plugin defines commands for downloading changes in different
+download schemes (for downloading via different network protocols).
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/download-commands[
+Project] |
+link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[replication]]
+=== replication
+
+This plugin can automatically push any changes Gerrit Code Review makes
+to its managed Git repositories to another system. Usually this would
+be configured to provide mirroring of changes, for warm-standby
+backups, or a load-balanced public mirror farm.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication[
+Project] |
+link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[reviewnotes]]
+=== reviewnotes
+
+Stores review information for Gerrit changes in the `refs/notes/review`
+branch.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/reviewnotes[
+Project] |
+link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[singleusergroup]]
+=== singleusergroup
+
+This plugin provides a group per user. This is useful to assign access
+rights directly to a single user, since in Gerrit access rights can
+only be assigned to groups.
+
+[[other-plugins]]
+== Other Plugins
+
+Besides core plugins there are many other Gerrit plugins available.
+These plugins are developed and maintained by different parties.
+The Gerrit Project doesn't guarantee proper functionality of any of
+these plugins.
+
+The Gerrit Project doesn't provide binaries for these plugins, but
+there are some public services, like the
+link:https://ci.gerritforge.com/[CI Server from GerritForge], that
+offer the download of ready plugin jars.
+
+The following list gives an overview about available plugins, but the
+list may not be complete. You may discover more plugins on
+link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[
+gerrit-review].
+
+[[admin-console]]
+=== admin-console
+
+Plugin to provide administrator-only functionality, intended to
+simplify common administrative tasks. Currently providing user-level
+information. Also providing access control information by project or
+project/account.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/admin-console[
+Project] |
+link:https://gerrit.googlesource.com/plugins/admin-console/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[avatars-external]]
+=== avatars/external
+
+This plugin allows to use an external url to load the avatar images
+from.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/external[
+Project] |
+link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[avatars-gravatar]]
+=== avatars/gravatar
+
+Plugin to display user icons from Gravatar.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/gravatar[
+Project]
+
+[[branch-network]]
+=== branch-network
+
+This plugin allows the rendering of Git repository branch network in a
+graphical HTML5 Canvas. It is mainly intended to be used as a
+"project link" in a GitWeb configuration or by other Gerrit GWT UI
+plugins to be plugged elsewhere in Gerrit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/branch-network[
+Project] |
+link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[changemessage]]
+=== changemessage
+
+This plugin allows to display a static info message on the change screen.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/changemessage[
+Project] |
+link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/about.md[
+Plugin Documenatation] |
+link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[codenvy]]
+=== codenvy
+
+Plugin to allow to edit code on-line on either an existing branch or an
+active change using the link:http://codenvy.com[Codenvy] cloud
+development platform.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codenvy[
+Project]
+
+[[delete-project]]
+=== delete-project
+
+Provides the ability to delete a project.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/delete-project[
+Project] |
+link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[egit]]
+=== egit
+
+This plugin provides extensions for easier usage with EGit.
+
+The plugin adds a download command for EGit that allows to copy only
+the change ref into the clipboard. The change ref is needed for
+downloading a Gerrit change from within EGit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/egit[
+Project] |
+link:https://gerrit.googlesource.com/plugins/egit/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[force-draft]]
+=== force-draft
+
+Provides an ssh command to force a change or patch set to draft status.
+This is useful for administrators to be able to easily completely
+delete a change or patch set from the server.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/force-draft[
+Project]
+
+[[gitblit]]
+=== gitblit
+
+GitBlit code-viewer plugin with SSO and Security Access Control.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitblit[
+Project]
+
+[[github]]
+=== github
+
+Plugin to integrate with GitHub: replication, pull-request to Change-Sets
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/github[
+Project]
+
+[[gitiles]]
+=== gitiles
+
+Plugin running Gitiles alongside a Gerrit server.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitiles[
+Project]
+
+[[imagare]]
+=== imagare
+
+The imagare plugin allows Gerrit users to upload and share images.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/imagare[
+Project] |
+link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[importer]]
+=== importer
+
+The importer plugin allows to import projects from one Gerrit server
+into another Gerrit server.
+
+Projects can be imported while both source and target Gerrit server
+are online. There is no downtime required.
+
+The git repository and all changes of the project, including approvals
+and review comments, are imported. Historic timestamps are preserved.
+
+Project imports can be resumed. This means a project team can continue
+to work in the source system while the import to the target system is
+done. By resuming the import the project in the target system can be
+updated with the missing delta.
+
+The importer plugin can also be used to copy a project within one Gerrit
+server, and in combination with the link:#delete-project[delete-project]
+plugin it can be used to rename a project.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/importer[
+Project] |
+link:https://gerrit.googlesource.com/plugins/importer/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[its-plugins]]
+=== Issue Tracker System Plugins
+
+Plugins to integrate with issue tracker systems (ITS), that (based
+on events in Gerrit) allows to take actions in the ITS. For example,
+they can add comments to bugs, or change status of bugs.
+
+All its-plugins have a common base implementation which is stored in
+the `its-base` project. `its-base` is not a plugin, but just a
+framework for the ITS plugins which is packaged within each ITS plugin.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-base[
+its-base Project] |
+link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/about.md[
+its-base Documentation] |
+link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/config.md[
+its-base Configuration]
+
+[[its-bugzilla]]
+==== its-bugzilla
+
+Plugin to integrate with Bugzilla.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-bugzilla[
+Project] |
+link:https://gerrit.googlesource.com/plugins/its-bugzilla/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[its-jira]]
+==== its-jira
+
+Plugin to integrate with Jira.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-jira[
+Project] |
+link:https://gerrit.googlesource.com/plugins/its-jira/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[its-rtc]]
+==== its-rtc
+
+Plugin to integrate with IBM Rational Team Concert (RTC).
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-rtc[
+Project] |
+link:https://gerrit.googlesource.com/plugins/its-rtc/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[javamelody]]
+=== javamelody
+
+This plugin allows to monitor the Gerrit server.
+
+This plugin integrates JavaMelody in Gerrit in order to retrieve live
+instrumentation data from Gerrit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/javamelody[
+Project] |
+link:https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[menuextender]]
+=== menuextender
+
+The menuextender plugin allows Gerrit administrators to configure
+additional menu entries from the WebUI.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/menuextender[
+Project] |
+link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[motd]]
+=== motd
+
+This plugin can output messages to clients when pulling/fetching/cloning
+code from Gerrit Code Review. If the client (and transport mechanism)
+can support sending the message to the client, it will be displayed to
+the user (usually prefixed by “remote: ”), but will be silently
+discarded otherwise.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/motd[
+Project] |
+link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[project-download-commands]]
+=== project-download-commands
+
+This plugin adds support for project specific download commands.
+
+Project specific download commands that are defined on a parent project
+are inherited by the child projects. Child projects can overwrite the
+inherited download command or remove it by assigning no value to it.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/project-download-commands[
+Project] |
+link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[quota]]
+=== quota
+
+This plugin allows to enforce quotas in Gerrit.
+
+To protect a Gerrit installation it makes sense to limit the resources
+that a project or group can consume. To do this a Gerrit administrator
+can use this plugin to define quotas on project namespaces.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/quota[
+Project] |
+link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[reviewers]]
+=== reviewers
+
+A plugin that allows adding default reviewers to a change.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/reviewers[
+Project] |
+link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[reviewers-by-blame]]
+=== reviewers-by-blame
+
+A plugin that allows automatically adding reviewers to a change from
+the git blame computation on the changed files. It will add the users
+that authored most of the lines touched by the change, since these
+users should be familiar with the code and can mostly review the
+change.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/reviewers-by-blame[
+Project] |
+link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[groovy-provider]]
+=== scripting/groovy-provider
+
+This plugin provides a Groovy runtime environment for Gerrit plugins in Groovy.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/groovy-provider[
+Project] |
+link:https://gerrit.googlesource.com/plugins/scripting/groovy-provider/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[scala-provider]]
+=== scripting/scala-provider
+
+This plugin provides a Scala runtime environment for Gerrit plugins in Scala.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/scala-provider[
+Project] |
+link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[server-config]]
+=== server-config
+
+This plugin enables access (download and upload) to the server config
+files. It may be used to change Gerrit config files (like
+`etc/gerrit.config`) in cases where direct access to the file system
+where Gerrit's config files are stored is difficult or impossible to
+get.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/server-config[
+Project]
+
+[[serviceuser]]
+=== serviceuser
+
+This plugin allows to create service users in Gerrit.
+
+A service user is a user that is used by another service to communicate
+with Gerrit. E.g. a service user is needed to run the Gerrit Trigger
+Plugin in Jenkins. A service user is not able to login into the Gerrit
+WebUI and it cannot push commits or tags.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/serviceuser[
+Project] |
+link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[uploadvalidator]]
+=== uploadvalidator
+
+This plugin allows to configure upload validations per project.
+
+Project owners can configure blocked file extensions, required footers
+and a maximum allowed path length. Pushes of commits that violate these
+settings are rejected by Gerrit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/uploadvalidator[
+Project] |
+link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[websession-flatfile]]
+=== websession-flatfile
+
+This plugin replaces the built-in Gerrit H2 based websession cache with
+a flatfile based implementation. This implemantation is shareable
+amongst multiple Gerrit servers, making it useful for multi-master
+Gerrit installations.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/websession-flatfile[
+Project] |
+link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[wip]]
+=== wip
+
+This plugin adds a new button that allows a change owner to set a
+change to Work In Progress, and a button to change from WIP back to a
+"Ready For Review" state.
+
+Any change in the WIP state will not show up in anyone's Review
+Requests. Pushing a new patchset will reset the change to Review In
+Progress.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/wip[
+Project] |
+link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+[[x-docs]]
+=== x-docs
+
+This plugin serves project documentation as HTML pages.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/x-docs[
+Project] |
+link:https://gerrit.googlesource.com/plugins/x-docs/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/x-docs/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 43ede06f9a..276117bc37 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -46,10 +46,17 @@ Below you will find an example of the +project.config+ file format:
description = Rights inherited by all other projects
[access "refs/*"]
read = group Administrators
+[access "refs/heads/*"]
+ label-Your-Label-Here = -1..+1 group Administrators
[capability]
administrateServer = group Administrators
[receive]
requireContributorAgreement = false
+[label "Your-Label-Here"]
+ function = MaxWithBlock
+ value = -1 Your -1 Description
+ value = 0 Your No score Description
+ value = +1 Your +1 Description
----
As you can see, there are several sections.
@@ -57,7 +64,7 @@ As you can see, there are several sections.
The link:#project-section[+project+ section] appears once per project.
The link:#access-section[+access+ section] appears once per reference pattern,
-such as `refs/*` or `refs/heads/*`. Only one access section per pattern is
+such as `+refs/*+` or `+refs/heads/*+`. Only one access section per pattern is
allowed. You will find examples of keys and values in each category section
<<access_category,below>>.
@@ -70,6 +77,9 @@ in the +All-Projects+ repository. It controls core features that are configured
on a global level. You can find examples of these
<<capability_category,below>>.
+The link:#label-section[+label+] section can appear multiple times. You can
+also redefine the text and behavior of the built in label types `Code-Review`
+and `Verified`.
[[project-section]]
=== Project section
@@ -197,6 +207,10 @@ Please refer to the
link:access-control.html#global_capabilities[Global Capabilities]
documentation for a full list of available capabilities.
+[[label-section]]
+=== Label section
+
+Please refer to link:config-labels.html#label_custom[Custom Labels] documentation.
[[branchOrder-section]]
=== branchOrder section
@@ -247,6 +261,7 @@ This is what the default groups file for +All-Projects.git+ looks like:
#
3d6da7dc4e99e6f6e5b5196e21b6f504fc530bba Administrators
global:Anonymous-Users Anonymous Users
+global:Change-Owner Change Owner
global:Project-Owners Project Owners
global:Registered-Users Registered Users
----
diff --git a/Documentation/config-reverseproxy.txt b/Documentation/config-reverseproxy.txt
index dde26612c5..7f30b14708 100644
--- a/Documentation/config-reverseproxy.txt
+++ b/Documentation/config-reverseproxy.txt
@@ -83,7 +83,7 @@ encryption algorithm is required.
If you are encountering 'Page Not Found' errors when opening the change
screen, your Apache proxy is very likely decoding the passed URL.
Make sure to either use 'AllowEncodedSlashes On' together with
-'ProxyPass .. nodecode' or alternatively a 'mod_rewrite' configuration with
+'ProxyPass .. nocanon' or alternatively a 'mod_rewrite' configuration with
'AllowEncodedSlashes NoDecode' set.
diff --git a/Documentation/config-sso.txt b/Documentation/config-sso.txt
index e4cf20e7aa..561309bfaa 100644
--- a/Documentation/config-sso.txt
+++ b/Documentation/config-sso.txt
@@ -51,12 +51,42 @@ To trust only Yahoo!:
=== Database Schema
User identities obtained from OpenID providers are stored into the
-`account_external_ids` table. Users may link more than one OpenID
-identity to the same Gerrit account (use Settings, Web Identities
-to manage this linking), making it easier for their browser to sign
-in to Gerrit if they are frequently switching between different
-unique OpenID accounts.
-
+`account_external_ids` table.
+
+=== Multiple Identities
+
+Users may link more than one OpenID identity to the same Gerrit account, making
+it easier for their browser to sign in to Gerrit if they are frequently switching
+between different unique OpenID accounts.
+
+[WARNING]
+Users wishing to link an alternative identity should *NOT* log in separately
+with that identity. Doing so will result in a new account being created, and
+subsequent attempts to link that account with the existing account will fail.
+In cases where this happens, the administrator will need to manually merge the
+accounts. See link:https://code.google.com/p/gerrit/wiki/SqlMergeUserAccounts[
+Merging Gerrit User Accounts] on the Gerrit Wiki for details.
+
+Linking another identity is also useful for users whose primary OpenID provider
+shuts down. For example Google will
+link:https://developers.google.com/+/api/auth-migration[shut down their OpenID
+service on 20th April 2015]. Users must add an alternative identity, using another
+OpenID provider, before that shutdown date. User who fail to add an alternative
+identity before that date, and end up with their account only having a disabled
+Google identity, will need to create a separate account with an alternative
+provider and then ask the administrator to merge the accounts using the previously
+mentioned method.
+
+To link another identity to an existing account:
+
+* Login with the existing account
+* Select menu Settings -> Identities
+* Click the 'Link Another Identity' button
+* Select the OpenID provider for the other identity
+* Authenticate with the other identity
+
+Login using the other identity can only be performed after the linking is
+successful.
== HTTP Basic/Digest Authentication
diff --git a/Documentation/config-validation.txt b/Documentation/config-validation.txt
index 5d23c798d7..27b39ebbda 100644
--- a/Documentation/config-validation.txt
+++ b/Documentation/config-validation.txt
@@ -1,7 +1,7 @@
-= Gerrit Code Review - Commit Validation
+= Gerrit Code Review - Plugin-based Validation
-Gerrit supports link:dev-plugins.html[plugin-based] validation of
-commits.
+Gerrit provides interfaces to allow link:dev-plugins.html[plugins] to
+perform validation on certain operations.
[[new-commit-validation]]
== New commit validation
@@ -21,6 +21,17 @@ and cherry-pick buttons.
Out of the box, Gerrit includes a plugin that checks the length of the
subject and body lines of commit messages on uploaded commits.
+[[ref-operation-validation]]
+== Ref operation validation
+
+
+Plugins implementing the `RefOperationValidationListener` interface can
+perform additional validation checks against ref creation/deletion operation
+before it is applied to the git repository.
+
+If the ref operation fails the validation, the plugin can throw an exception
+which will cause the operation to fail.
+
[[pre-merge-validation]]
== Pre-merge validation
@@ -67,6 +78,21 @@ input arguments.
E.g. a plugin could use this to enforce a certain name scheme for
group names.
+[[hashtag-validation]]
+== Hashtag validation
+
+
+Plugins implementing the `HashtagValidationListener` interface can perform
+validation of hashtags before they are added to or removed from changes.
+
+[[outgoing-email-validation]]
+== Outgoing e-mail validation
+
+
+This interface provides a low-level e-mail filtering API for plugins.
+Plugins implementing the `OutgoingEmailValidationListener` interface can perform
+filtering of outgoing e-mails just before they are sent.
+
GERRIT
------
diff --git a/Documentation/config.defs b/Documentation/config.defs
index 642b91561c..8d67173d18 100644
--- a/Documentation/config.defs
+++ b/Documentation/config.defs
@@ -16,5 +16,6 @@ def documentation_attributes(revision):
'last-update-label!',
'source-highlighter=prettify',
'stylesheet=doc.css',
+ 'linkcss=true',
'revnumber="%s"' % revision,
]
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index ce40a01c53..2eb478c38a 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -3,6 +3,8 @@
== Installation
+Note that you need to use Java 7 for building gerrit.
+
There is currently no binary distribution of Buck, so it has to be manually
built and installed. Apache Ant is required. Currently only Linux and Mac
OS are supported. Buck requires Python version 2.7 to be installed.
@@ -10,8 +12,9 @@ OS are supported. Buck requires Python version 2.7 to be installed.
Clone the git and build it:
----
- git clone https://gerrit.googlesource.com/buck
+ git clone https://github.com/facebook/buck
cd buck
+ git checkout $(cat ../gerrit/.buckversion)
ant
----
@@ -30,10 +33,11 @@ Add the `~/bin` folder to the path:
Note that the buck executable needs to be available in all shell sessions,
so also make sure it is appended to the path globally.
-Add a symbolic link in `~/bin` to the buck executable:
+Add a symbolic link in `~/bin` to the buck and buckd executables:
----
ln -s `pwd`/bin/buck ~/bin/
+ ln -s `pwd`/bin/buckd ~/bin/
----
Verify that `buck` is accessible:
@@ -43,8 +47,8 @@ Verify that `buck` is accessible:
----
To enable autocompletion of buck commands, install the autocompletion
-script from `./scripts/bash_completion` in the buck project. Refer to
-the script's header comments for installation instructions.
+script from `./scripts/buck_completion.bash` in the buck project. Refer
+to the script's header comments for installation instructions.
[[eclipse]]
@@ -196,22 +200,23 @@ next time project.py is run:
[[documentation]]
=== Documentation
-To build only the documentation:
+To build only the documentation for testing or static hosting:
----
buck build docs
----
-The generated html files will be placed in:
+The generated html files will NOT come with the search box, and will be
+placed in:
----
- buck-out/gen/Documentation/html__tmp/Documentation
+ buck-out/gen/Documentation/searchfree__tmp/Documentation
----
-The html files will also be bundled into `html.zip` in this location:
+The html files will also be bundled into `searchfree.zip` in this location:
----
- buck-out/gen/Documentation/html.zip
+ buck-out/gen/Documentation/searchfree.zip
----
To build the executable WAR with the documentation included:
@@ -226,6 +231,26 @@ The WAR file will be placed in:
buck-out/gen/withdocs.war
----
+[[soyc]]
+=== GWT Compile Report
+
+The GWT compiler can output a compile report (or "story of your compile"),
+describing the size of the JavaScript and which source classes contributed
+to the overall download size.
+
+----
+ buck build soyc
+----
+
+The report will be written as an HTML page to the extras directory, and
+can be opened and viewed in any web browser:
+
+----
+ extras/gerrit_ui/soycReport/compile-report/index.html
+----
+
+Only the "Split Point Report" is created, "Compiler Metrics" are not output.
+
[[release]]
=== Gerrit Release WAR File
@@ -275,6 +300,7 @@ To include a specific group of acceptance tests:
The following groups of tests are currently supported:
* api
+* edit
* git
* pgm
* rest
@@ -288,6 +314,13 @@ To run a specific test, e.g. the acceptance test
buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:HttpPushForReviewIT
----
+To create test coverage report:
+
+----
+ buck test --code-coverage --code-coverage-format html --no-results-cache
+----
+
+The HTML report is created in `buck-out/gen/jacoco/code-coverage/index.html`.
== Dependencies
@@ -378,6 +411,21 @@ another project's Maven target directory:
/home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT.jar
----
+After `buck clean` and `buck build lib/jgit:jgit` the symbolic link that was
+created the first time is lost due to Buck's caching mechanism. This means that
+when a new version of the local artifact is deployed (by running `mvn package`
+in the JGit project in the example above), Buck is not aware of it, because it
+still has a stale version of it in its cache.
+
+To solve this problem and re-create the symbolic link, you don't need to wipe out
+the entire Buck cache. Just rebuilding the target with the `--no-cache` option
+does the job:
+
+----
+ buck clean
+ buck build --no-cache lib/jgit:jgit
+----
+
== Building against artifacts from custom Maven repositories
To build against custom Maven repositories, two modes of operations are
@@ -437,6 +485,25 @@ The trivial case using a local directory is:
EOF
----
+[[clean-cache]]
+=== Cleaning The Buck Cache
+
+The cache for the Gerrit Code Review project is located in
+`~/.gerritcodereview/buck-cache/cache`.
+
+The Buck cache should never need to be manually deleted. If you find yourself
+deleting the Buck cache regularly, then it is likely that there is something
+wrong with your environment or your workflow.
+
+If you really do need to clean the cache manually, then:
+
+----
+ rm -rf ~/.gerritcodereview/buck-cache/cache
+----
+
+Note that the root `buck-cache` folder should not be deleted as this is where
+downloaded artifacts are stored.
+
[[buck-daemon]]
=== Using Buck daemon
@@ -456,6 +523,15 @@ covers daemon in http://facebook.github.io/buck/command/buckd.html[buckd].
To use `buckd` the additional
link:https://facebook.github.io/watchman[watchman] program must be installed.
+To disable `buckd`, the environment variable `NO_BUCKD` must be set. It's not
+recommended to put it in the shell config, as it can be forgotten about it and
+then assumed Buck was working as it should when it should be using buckd.
+Prepend the variable to Buck invocation instead:
+
+----
+ NO_BUCKD=1 buck build gerrit
+----
+
[[watchman]]
=== Installing watchman
@@ -517,13 +593,13 @@ Test execution results are cached by Buck. If a test that was already run
needs to be repeated, the unit test cache for that test must be removed first:
----
- $ rm -rf buck-out/bin/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/.AddRemoveGroupMembersIT/
+ rm -rf buck-out/bin/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/.AddRemoveGroupMembersIT/
----
After clearing the cache, the test can be run again:
----
- $ buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
+ buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
TESTING //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
PASS 14,9s 8 Passed 0 Failed com.google.gerrit.acceptance.rest.group.AddRemoveGroupMembersIT
TESTS PASSED
@@ -557,6 +633,23 @@ option:
buck test --no-results-cache
----
+== Troubleshooting Buck
+
+In some cases problems with Buck itself need to be investigated. See for example
+link:https://gerrit-review.googlesource.com/62411[this attempt to upgrade Buck]
+and link:https://github.com/facebook/buck/pull/227[the fix that was needed] to
+make the update possible.
+
+To build Gerrit with a custom version of Buck, the following steps are necessary:
+
+1. In the Buck git apply any necessary changes from pull requests
+2. Compile Buck with `ant`
+3. In the root of the Gerrit project create a `.nobuckcheck` file to prevent Buck
+from updating itself
+4. Replace the sha1 in Gerrit's `.buckversion` file with the required version from
+the custom Buck build
+5. Build Gerrit as usual
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 95a555428e..72d7ddf1ee 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -1,33 +1,36 @@
= Gerrit Code Review - Contributing
-Gerrit is developed as a self-hosting open source project and
-very much welcomes contributions from anyone with a contributor's
+== 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.
-* https://gerrit-review.googlesource.com/
-
+== 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/
+* 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 https://gerrit-review.googlesource.com/#/settings/agreements
-* Click "New Contributor Agreement" and follow the instructions
+* 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
-* https://gerrit-review.googlesource.com/static/cla_individual.html
-* https://gerrit-review.googlesource.com/static/cla_corporate.html
+* link:https://cla.developers.google.com/about/android-individual[Individual Agreement]
+* link:https://source.android.com/source/cla-corporate.pdf[Corporate Agreement]
+== 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 here:
-
-* https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit
+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
@@ -64,7 +67,7 @@ spans, we really do want your code.
[[commit-message]]
-== Commit Message
+=== Commit Message
It is essential to have a good commit message if you want your
change to be reviewed.
@@ -75,8 +78,22 @@ change to be reviewed.
* 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
- * Include a Change-Id line
+ * Include a `Bug: Issue <#>` line if fixing a Gerrit issue
+ * 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
+====
=== A sample good Gerrit commit message:
@@ -97,8 +114,8 @@ change to be reviewed.
Change-Id: Ic4a7c07eeb98cdeaf44e9d231a65a51f3fceae52
====
-The Change-Id is, as usual, created by a local git hook. To install it, simply
-copy it from the checkout and make it executable:
+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/
@@ -121,12 +138,12 @@ To set up git's remote for easy pushing, run the following:
====
The HTTPS access requires proper username and password; this can be obtained
-by clicking the "Obtain Password" link on the
+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].
-== Style
+=== Style
The basic coding style is covered by the tools/GoogleFormat.xml
doc, see the link:dev-eclipse.html#Formatting[Eclipse Setup]
@@ -159,8 +176,28 @@ examples:
* Put a blank line between external import sources, but not
between internal ones.
+When to use `final` modifier and when not (in new code):
+
+Always:
+
+ * final fields: marking fields as final forces them to be
+ initialised 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 make the code less
+ readable. When copying old code to new location, finals should
+ be removed
+ * method parameters: similar to local variables
-== Code Organization
+=== Code Organization
Do your best to organize classes and methods in a logical way.
Here are some guidelines that Gerrit uses:
@@ -179,14 +216,18 @@ Here are some guidelines that Gerrit uses:
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 baring a good reason not to.
+ 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...) +
- Example: @Assisted @Nullable final type varName
+ * Annotations should go before language keywords (`final`, `private`, etc) +
+ Example: `@Assisted @Nullable final type varName`
+ * The `@Inject`-ed constructor arguments should be listed one per line.
* Imports should be mostly alphabetical (uppercase sorts before
all lowercase, which means classes come before packages at the
same level).
+ * 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
@@ -195,7 +236,7 @@ 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:
@@ -211,7 +252,7 @@ when coding:
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
+ * `@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
@@ -227,12 +268,12 @@ when coding:
* ...and so is Guava (previously known as Google Collections).
-== Tests
+=== Tests
* Tests for new code will greatly help your change get approved.
-== Change Size/Number of Files Touched
+=== 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
@@ -265,9 +306,9 @@ especially if changing one without the other will break something!
* 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 help keep "git blame" more useful in the future
- and it also makes "git revert" more useful.
- * Use topic branches to link your separate changes together.
+ 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
@@ -283,6 +324,45 @@ Fixes that are known to be needed for a particular release should be pushed
for review on that release's stable branch. It will then be included in
the master branch when the stable branch is merged back.
+=== Updating to new version of GWT
+
+When updating to a new version of GWT, there are several things that also need
+to be updated or at least checked.
+
+* Update common and plugin dependencies in `tools/gwt-constants.defs`.
+* Update to the same GWT version in the cookbook plugin and optionally in other
+plugins that have a dependency on GWT.
+* Update the GWT version in the archetype metadata in the
+`gerrit-plugin-gwt-archetype`.
+* Update to the same GWT version in the `gwtjsonrpc` project, and release a
+new version.
+
+=== Updating to new version of CodeMirror
+
+* Clone the git from https://github.com/codemirror/CodeMirror
+* Checkout the version needed
+* If the needed version is not a tagged version, use `git describe` to determine
+the version number:
++
+----
+ git describe --tags
+----
+
+* Create the release zip file:
++
+----
+ git archive --format=zip --prefix=codemirror-4.10.0-6-gd0a2dda/ d0a2dda > codemirror-4.10.0-6-gd0a2dda.zip
+----
+
+* Determine the sha1 hash of the zip file:
++
+----
+ openssl sha1 4.10.0-6-gd0a2dda.zip
+----
+
+* Upload the zip file to the
+link:https://console.developers.google.com/project/164060093628/storage/gerrit-maven/[
+gerrit-maven] storage bucket
GERRIT
------
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 384bb74f41..b3525a12c3 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -31,15 +31,13 @@ Developer Setup guide to configure a local site for testing.
Duplicate the existing launch configuration:
-* Run -> Debug Configurations ...
+* In Eclipse select Run -> Debug Configurations ...
* Java Application -> `gerrit_daemon`
* Right click, Duplicate
-
* Modify the name to be unique.
-
* Switch to Arguments tab.
* Edit the `-d` program argument flag to match the path used during
- 'init'. The template launch configuration resolves to ../gerrit_testsite
+ 'init'. The template launch configuration resolves to `../gerrit_testsite`
since that is what the documentation recommends.
* Switch to Common tab.
@@ -47,50 +45,39 @@ Duplicate the existing launch configuration:
* Close the Debug Configurations dialog and save the changes when prompted.
-=== Running Hosted Mode
+=== Running GWT Debug Mode
-Duplicate the existing launch configuration:
+The `gerrit_gwt_debug` launch configuration uses GWT's
+link:http://www.gwtproject.org/articles/superdevmode.html[Super Dev Mode].
-* Run -> Debug Configurations ...
-* Java Application -> `buck_gwt_debug`
-* Right click, Duplicate
+* Make a local copy of the `gerrit_gwt_debug` configuration, using the
+process described for `gerrit_daemon` above.
+* Launch the local copy of `gerrit_gwt_debug` from the Eclipse debug menu.
+* If debugging GWT for the first time:
-* Modify the name to be unique.
+** Open the link:http://localhost:9876/[codeserver URL] and add the `Dev Mode On`
+and `Dev Mode Off` bookmarklet to your bookmark bar.
-* Switch to Arguments tab.
-* Edit the `-Dgerrit.site_path=` VM argument to match the path
- used during 'init'. The template launch configuration resolves
- to ../gerrit_testsite since that is what the documentation recommends.
+** Activate the source maps feature in your browser. Refer to the
+link:https://developer.chrome.com/devtools/docs/javascript-debugging#source-maps[
+Chrome] and
+link:https://developer.mozilla.org/en-US/docs/Tools/Debugger#Use_a_source_map[
+Firefox] developer documentation.
-* Switch to Common tab.
-* Change Save as to be Local file.
-* Close the Debug Configurations dialog and save the changes when prompted.
+* Load the link:http://localhost:8080[Gerrit page].
+* Open the source tab in developer tools.
+* Click the `Dev Mode On` bookmark to incrementally recompile changed files.
+* Select the `gerrit_ui` module to compile (the `Compile` button can also be used
+as a bookmarklet).
+* In the developer tools source tab, open a file and set a breakpoint.
+* Navigate to the UI and confirm that the breakpoint is hit.
+* To end the debugging session, click the `Dev Mode Off` bookmark.
+.After changing the client side code:
-[[known-problems]]
-== Known problems
-
-* OpenID authentication won't work in hosted mode, so you need to change
-the link:config-gerrit.html#auth.type[auth.type] configuration parameter
-to `DEVELOPMENT_BECOME_ANY_ACCOUNT` to disable OpenID and allow you to
-impersonate whatever account you otherwise would've used.
-
-* Error "Cannot create ReviewDb" occurs if the test site is already running.
-Stop the test site with `gerrit.sh stop` before attempting to run hosted mode
-debugging.
-
-* Gerrit site doesn't appear, only directory listing is shown. Web toolkit
-developer browser plugin is missing. If there is no warning, that browser
-plugin is missing with the suggestion to install it, you can install the
-right extension for your browser from the following locations:
-+
-https://dl.google.com/dl/gwt/plugins/chrome/gwt-dev-plugin.crx[Chrome]
-+
-link:https://dl.google.com/dl/gwt/plugins/firefox/gwt-dev-plugin.xpi[Firefox]
-+
-link:http://dl.google.com/dl/gwt/plugins/ie/1.0.7263.20091208111100/gwt-dev-plugin.msi[IE]
-+
-https://dl.google.com/dl/gwt/plugins/safari/gwt-dev-plugin.dmg[Safari]
+* Hitting `F5` in the browser only reloads the last compile output, without
+recompiling.
+* To reflect your changes in the debug session, click `Dev Mode On` then `Compile`.
GERRIT
------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index e619923c17..f9b66cb3d4 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -274,7 +274,7 @@ the @PluginName String injection containing their own plugin name.
During their initialization plugins may get access to the
`project.config` file of the `All-Projects` project and they are able
to store configuration parameters in it. For this a plugin `InitStep`
-can get `com.google.gerrit.pgm.init.AllProjectsConfig` injected:
+can get `com.google.gerrit.pgm.init.api.AllProjectsConfig` injected:
[source,java]
----
@@ -299,7 +299,7 @@ can get `com.google.gerrit.pgm.init.AllProjectsConfig` injected:
ui.message("\n");
ui.header(pluginName + " Integration");
boolean enabled = ui.yesno(true, "By default enabled for all projects");
- Config cfg = allProjectsConfig.load();
+ Config cfg = allProjectsConfig.load().getConfig();
if (enabled) {
cfg.setBoolean("plugin", pluginName, "enabled", enabled);
} else {
@@ -378,9 +378,9 @@ Certain operations in Gerrit trigger events. Plugins may receive
notifications of these events by implementing the corresponding
listeners.
-* `com.google.gerrit.common.ChangeListener`:
+* `com.google.gerrit.common.EventListener`:
+
-Allows to listen to change events. These are the same
+Allows to listen to events. 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.
@@ -412,7 +412,15 @@ Gerrit's `stream-events` ssh command will receive them.
To send an event, the plugin must invoke one of the `postEvent`
methods in the `ChangeHookRunner` class, passing an instance of
-its own custom event class derived from `ChangeEvent`.
+its own custom event class derived from
+`com.google.gerrit.server.events.Event`.
+
+Plugins which define new Events should register them via the
+`com.google.gerrit.server.events.EventTypes.registerClass()`
+method. This will make the EventType known to the system.
+Deserialzing events with the
+`com.google.gerrit.server.events.EventDeserializer` class requires
+that the event be registered in EventTypes.
[[validation]]
== Validation Listeners
@@ -655,9 +663,6 @@ String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
.getStringList("branch", "refs/heads/master", "reviewer");
----
-The plugin configuration is loaded only once and is then cached.
-Similar to changes in 'gerrit.config', changes to the plugin
-configuration file will only become effective after a Gerrit restart.
[[simple-project-specific-configuration]]
== Simple Project Specific Configuration in `project.config`
@@ -1258,6 +1263,26 @@ public class MyTopMenuExtension implements TopMenu {
}
----
+`MenuItems` that are bound for the `MenuEntry` with the name
+`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
+which is automatically replaced by the actual project name.
+
+E.g. plugins may register an link:#http[HTTP Servlet] to handle project
+specific requests and add an menu item for this:
+
+[source,java]
+---
+ new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
+---
+
+This also enables plugins to provide menu items for project aware
+screens:
+
+[source,java]
+---
+ new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
+---
+
If no Guice modules are declared in the manifest, the top menu extension may use
auto-registration by providing an `@Listen` annotation:
@@ -1667,7 +1692,7 @@ class HelloServlet extends HttpServlet {
----
The auto registration only works for standard servlet mappings like
-`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
+`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
to register the HTTP servlets and declare it explicitly in the manifest
with the `Gerrit-HttpModule` attribute:
@@ -1706,6 +1731,34 @@ MyType(@PluginData java.io.File myDir) {
}
----
+[[secure-store]]
+== SecureStore
+
+SecureStore allows to change the way Gerrit stores sensitive data like
+passwords.
+
+In order to replace the default SecureStore (no-op) implementation,
+a class that extends `com.google.gerrit.server.securestore.SecureStore`
+needs to be provided (with dependencies) in a separate jar file. Then
+link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
+switch implementations.
+
+The SecureStore implementation is instantiated using a Guice injector
+which binds the `File` annotated with the `@SitePath` annotation.
+This means that a SecureStore implementation class can get access to
+the `site_path` like in the following example:
+
+[source,java]
+----
+@Inject
+MySecureStore(@SitePath java.io.File sitePath) {
+ // your code
+}
+----
+
+No Guice bindings or modules are required. Gerrit will automatically
+discover and bind the implementation.
+
[[download-commands]]
== Download Commands
@@ -1732,34 +1785,41 @@ PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
----
import com.google.gerrit.extensions.annotations.Listen;
import com.google.gerrit.extensions.webui.PatchSetWebLink;;
+import com.google.gerrit.extensions.webui.WebLinkTarget;
@Listen
public class MyWeblinkPlugin implements PatchSetWebLink {
private String name = "MyLink";
private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
+ private String imageUrl = "http://placehold.it/16x16.gif";
@Override
- public String getLinkName() {
- return name ;
+ public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
+ return new WebLinkInfo(name,
+ imageUrl,
+ String.format(placeHolderUrlProjectCommit, project, commit),
+ WebLinkTarget.BLANK);
}
-
- @Override
- public String getPatchSetUrl(String project, String commit) {
- return String.format(placeHolderUrlProjectCommit, project, commit);
- }
-
}
----
+FileWebLinks will appear in the side-by-side diff screen on the right
+side of the patch selection on each side.
+
+DiffWebLinks will appear in the side-by-side and unified diff screen in
+the header next to the navigation icons.
+
ProjectWebLinks will appear in the project list in the
`Repository Browser` column.
+BranchWebLinks will appear in the branch list in the last column.
+
[[documentation]]
== Documentation
If a plugin does not register a filter or servlet to handle URLs
-`/Documentation/*` or `/static/*`, the core Gerrit server will
+`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
automatically export these resources over HTTP from the plugin JAR.
Static resources under the `static/` directory in the JAR will be
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index da1ca700a3..bcf4a588a5 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -188,31 +188,6 @@ match the Java sources.
http://localhost:8080/?dbg=1
----
-To use the GWT DETAILED style the package must be recompiled and
-`?dbg=1` must be omitted from the URL:
-
-----
- mvn package -Dgwt.style=DETAILED
-----
-
-
-== Release Builds
-
-To create a release build for a production server, or deployment
-through the download site:
-
-----
- ./tools/release.sh
-----
-
-If AsciiDoc isn't installed or is otherwise unavailable, the WAR
-can still be built without the embedded documentation by passing
-an additional flag:
-
-----
- ./tools/release.sh --without-documentation
-----
-
== Client-Server RPC
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index 364e61dde4..f3397a488b 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -40,8 +40,8 @@ permission by commenting on the same issue.
* Generate and publish a PGP key
+
Generate and publish a PGP key as described in
-link:https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven[
-How To Generate PGP Signatures With Maven].
+link:http://central.sonatype.org/pages/working-with-pgp-signatures.html[
+Working with PGP Signatures].
+
Please be aware that after publishing your public key it may take a
while until it is visible to the Sonatype server.
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 6cf9edfdb4..3d3104ca06 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -122,23 +122,35 @@ for the Subproject in `/lib/BUCK` to the released version.
[[update-versions]]
=== Update Versions and Create Release Tag
-Before doing the release build the `GERRIT_VERSION` in the `VERSION`
-file must be updated, e.g. change if from `2.5-SNAPSHOT` to `2.5`.
+Before doing the release build, the `GERRIT_VERSION` in the `VERSION`
+file must be updated, e.g. change it from `2.5-SNAPSHOT` to `2.5`.
In addition the version must be updated in a number of pom.xml files.
-To do this run the `./tools/version.sh` script and provide the new
+
+To do this run the `./tools/version.py` script and provide the new
version as parameter, e.g.:
----
- ./tools/version.sh 2.5
+ ./tools/version.py 2.5
----
+Also check and update the referenced `archetypeVersion` and the
+`archetypeRepository` in the `Documentation/dev-plugins.txt` file.
+If the referenced `archetypeVersion` will be available in the Maven central,
+delete the line with the `archetypeRepository`.
+
Commit the changes and create the release tag on the new commit:
----
git tag -a v2.5
----
+Tag the plugins:
+
+----
+ git submodule foreach git tag -a v2.5
+----
+
[[build-gerrit]]
=== Build Gerrit
@@ -307,37 +319,50 @@ get them merged
[[push-tag]]
==== Push the Release Tag
-* Push the new Release Tag
-+
-For an `RC`:
-+
+Push the new Release Tag:
+
----
- git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
+ git push gerrit-review tag v2.5
----
-+
-For a final `stable` release:
-+
+
+Push the new Release Tag on the plugins:
+
----
- git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
+ git submodule foreach git push gerrit-review tag v2.5
----
[[upload-documentation]]
==== Upload the Documentation
-Build the release notes:
-
+* Build the release notes:
++
----
make -C ReleaseNotes
----
-* Upload html files to the storage bucket via `https://cloud.google.com/console` (manual via web browser)
-** Documentation html files must be extracted from `buck-out/gen/Documentation/html.zip`
-* Update Google Code project links
-** Go to http://code.google.com/p/gerrit/admin
-** Update the documentation link in the `Resources` section of the
+* Build the documentation:
++
+----
+ buck build docs
+----
+
+* Extract the documentation html files from the generated zip file
+`buck-out/gen/Documentation/searchfree.zip`.
+
+* Upload the html files manually via web browser to the
+link:https://console.developers.google.com/project/164060093628/storage/gerrit-documentation/[
+gerrit-documentation] storage bucket. The `gerrit-documentation`
+storage bucket is accessible via the
+link:https://cloud.google.com/console[Google Developers Console].
+
+[[update-links]]
+==== Update Google Code project links
+
+* Go to http://code.google.com/p/gerrit/admin
+* Update the documentation link in the `Resources` section of the
Description text, and in the `Links` section.
-** Add a link to the new release notes in the `News` section of the
+* Add a link to the new release notes in the `News` section of the
Description text
[[update-issues]]
@@ -369,31 +394,6 @@ including some or all of the following in the email:
** A link to the docs
** Describe the type of release (stable, bug fix, RC)
-----
-To: Repo and Gerrit Discussion <repo-discuss@googlegroups.com>
-Subject: Announce: Gerrit 2.2.2.1 (Stable bug fix update)
-
-I am pleased to announce Gerrit Code Review 2.2.2.1.
-
-Download:
-
- http://code.google.com/p/gerrit/downloads/list
-
-
-This release is a stable bug fix release with some
-documentation updates including a new "Contributing to
-Gerrit" doc:
-
- http://gerrit-documentation.googlecode.com/svn/Documentation/2.2.2/dev-contributing.html
-
-
-To read more about the bug fixes:
-
- http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.1.html
-
--Martin
-----
-
* Add an entry to the `NEWS` section of the main Gerrit project web page
** Go to: http://code.google.com/p/gerrit/admin
** Add entry like:
@@ -404,9 +404,8 @@ To read more about the bug fixes:
* Update the new discussion group announcement to be sticky
** Go to: http://groups.google.com/group/repo-discuss/topics
** Click on the announcement thread
-** Near the top right, click on options
-** Under options, click the "Display this top first" checkbox
-** and Save
+** Near the top right, click on actions
+** Under actions, click the "Display this top first" checkbox
* Update the previous discussion group announcement to no longer be sticky
** See above (unclick checkbox)
@@ -415,11 +414,19 @@ To read more about the bug fixes:
[[increase-version]]
=== Increase Gerrit Version for Current Development
-All new development that is done in the `master` branch will be
-included in the next Gerrit release. Update the Gerrit version in the
-`VERSION` file, and plugin archetypes' `pom.xml` files. Push the change
-for review and get it merged.
+All new development that is done in the `master` branch will be included in the
+next Gerrit release. The Gerrit version should be set to the snapshot version
+for the next release.
+
+Use the `version` tool to set the version in the `VERSION` file and plugin
+archetypes' `pom.xml` files:
+
+----
+ ./tools/version.py 2.11-SNAPSHOT
+----
+Verify that the changes made by the tool are sane, then commit them, push
+the change for review on the master branch, and get it merged.
[[merge-stable]]
=== Merge `stable` into `master`
diff --git a/Documentation/dev-rest-api.txt b/Documentation/dev-rest-api.txt
index ec4b66613a..308d4bd09b 100644
--- a/Documentation/dev-rest-api.txt
+++ b/Documentation/dev-rest-api.txt
@@ -47,7 +47,7 @@ option instead:
Example to set a Gerrit project's link:rest-api-projects.html#set-project-description[description]:
----
- curl -X PUT --digest --user john:2LlAB3K9B0PF --data-binary @project-desc.txt --header "Content-Type: application/json;charset=UTF-8" http://localhost:8080/a/projects/myproject/description
+ curl -X PUT --digest --user john:2LlAB3K9B0PF --data-binary @project-desc.txt --header "Content-Type: application/json; charset=UTF-8" http://localhost:8080/a/projects/myproject/description
----
=== Authentication
diff --git a/Documentation/doc.css b/Documentation/doc.css.in
index c49b596382..6be89f6cdb 100644
--- a/Documentation/doc.css
+++ b/Documentation/doc.css.in
@@ -24,6 +24,7 @@ li p {
margin: 0.2em 0 0.2em 0;
}
+#license > .content,
.listingblock > .content {
border: 2px solid silver;
background: #ebebeb;
@@ -33,6 +34,7 @@ li p {
overflow: auto;
}
+#license > .content pre,
.listingblock > .content pre {
background: none;
border: 0 solid silver;
diff --git a/Documentation/error-change-does-not-belong-to-project.txt b/Documentation/error-change-does-not-belong-to-project.txt
index ce9c23a1e9..21596b1467 100644
--- a/Documentation/error-change-does-not-belong-to-project.txt
+++ b/Documentation/error-change-does-not-belong-to-project.txt
@@ -6,7 +6,7 @@ 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
+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].
diff --git a/Documentation/error-change-not-found.txt b/Documentation/error-change-not-found.txt
index 98969b041a..df99388122 100644
--- a/Documentation/error-change-not-found.txt
+++ b/Documentation/error-change-not-found.txt
@@ -6,7 +6,7 @@ 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].
+It is recommended to only rely on Change-Ids for link:user-upload.html#push_replace[replacing changes].
GERRIT
diff --git a/Documentation/error-has-duplicates.txt b/Documentation/error-has-duplicates.txt
index c34bf52e5b..0ec6eda8e8 100644
--- a/Documentation/error-has-duplicates.txt
+++ b/Documentation/error-has-duplicates.txt
@@ -1,20 +1,20 @@
= \... has duplicates
With this error message Gerrit rejects to push a commit if its commit
-message contains a Change-ID for which multiple changes can be found
+message contains a Change-Id for which multiple changes can be found
in the project.
This error means that there is an inconsistency in Gerrit since for
-one project there are multiple changes that have the same Change-ID.
-Every change is expected to have an unique Change-ID.
+one project there are multiple changes that have the same Change-Id.
+Every change is expected to have an unique Change-Id.
Since this error should never occur in practice, you should inform
your Gerrit administrator if you hit this problem and/or
link:http://code.google.com/p/gerrit/issues/list[open a Gerrit issue].
In any case to not be blocked with your work, you can simply create a
-new Change-ID for your commit and then push it as new change to
-Gerrit. How to exchange the Change-ID in the commit message of your
+new Change-Id for your commit and then push it as new change to
+Gerrit. How to exchange the Change-Id in the commit message of your
commit is explained link:error-push-fails-due-to-commit-message.html[here].
diff --git a/Documentation/error-missing-changeid.txt b/Documentation/error-missing-changeid.txt
index 2a937607f2..9cddd85b03 100644
--- a/Documentation/error-missing-changeid.txt
+++ b/Documentation/error-missing-changeid.txt
@@ -45,7 +45,7 @@ of a commit message, for details, see link:user-changeid.html[Change-Id Lines].
If the Change-Id is contained in the commit message but not in its
last paragraph you have to update the commit message and move the
-Change-ID into the last paragraph. How to update the commit message
+Change-Id into the last paragraph. How to update the commit message
is explained link:error-push-fails-due-to-commit-message.html[here].
diff --git a/Documentation/error-prohibited-by-gerrit.txt b/Documentation/error-prohibited-by-gerrit.txt
index 4c7bf22a29..3d9bbad2b6 100644
--- a/Documentation/error-prohibited-by-gerrit.txt
+++ b/Documentation/error-prohibited-by-gerrit.txt
@@ -9,32 +9,32 @@ In particular this error occurs:
1. if you push a commit for code review to a branch for which you
don't have upload permissions (access right
link:access-control.html#category_push_review['Push'] on
- `refs/for/refs/heads/*`)
+ `+refs/for/refs/heads/*+`)
2. if you bypass code review without
link:access-control.html#category_push_direct['Push'] access right
- on `refs/heads/*`
+ on `+refs/heads/*+`
3. if you bypass code review pushing to a non-existing branch without
link:access-control.html#category_create['Create Reference'] access
- right on `refs/heads/*`
+ right on `+refs/heads/*+`
4. if you push an annotated tag without
link:access-control.html#category_push_annotated['Push Annotated Tag']
- access right on 'refs/tags/*'
+ access right on `+refs/tags/*+`
5. if you push a signed tag without
link:access-control.html#category_push_signed['Push Signed Tag']
- access right on 'refs/tags/*'
+ access right on `+refs/tags/*+`
6. if you push a lightweight tag without the access right link:access-control.html#category_create['Create
- Reference'] for the reference name 'refs/tags/*'
+ Reference'] for the reference name `+refs/tags/*+`
7. if you push a tag with somebody else as tagger and you don't have the
link:access-control.html#category_forge_committer['Forge Committer']
- access right for the reference name 'refs/tags/*'
+ access right for the reference name `+refs/tags/*+`
8. if you push to a project that is in state 'Read Only'
For new users it often happens that they accidentally try to bypass
code review. The push then fails with the error message 'prohibited
by Gerrit' because the project didn't allow to bypass code review.
-Bypassing the code review is done by pushing directly to refs/heads/*
-(e.g. refs/heads/master) instead of pushing to refs/for/* (e.g.
-refs/for/master). Details about how to push commits for code review
+Bypassing the code review is done by pushing directly to `+refs/heads/*+`
+(e.g. `refs/heads/master`) instead of pushing to `+refs/for/*+` (e.g.
+`refs/for/master`). Details about how to push commits for code review
are explained link:user-upload.html#push_create[here].
diff --git a/Documentation/error-squash-commits-first.txt b/Documentation/error-squash-commits-first.txt
index b3b9d560d1..4069d5be67 100644
--- a/Documentation/error-squash-commits-first.txt
+++ b/Documentation/error-squash-commits-first.txt
@@ -1,7 +1,7 @@
= squash commits first
With this error message Gerrit rejects to push a commit if it
-contains the same Change-ID as a predecessor commit.
+contains the same Change-Id as a predecessor commit.
The reason for rejecting such a commit is that it would introduce, for
the corresponding change in Gerrit, a dependency upon itself. Gerrit
@@ -14,7 +14,7 @@ This error is quite common, it appears when a user tries to address
review comments and creates a new commit instead of amending the
existing commit. Another possibility for this error, although less
likely, is that the user tried to create a patch series with multiple
-changes to be reviewed and accidentally included the same Change-ID
+changes to be reviewed and accidentally included the same Change-Id
into the different commit messages.
@@ -22,8 +22,8 @@ into the different commit messages.
Here an example about how the push is failing. Please note that the
two commits 'one commit' and 'another commit' both have the same
-Change-ID (of course in real life it can happen that there are more
-than two commits that have the same Change-ID).
+Change-Id (of course in real life it can happen that there are more
+than two commits that have the same Change-Id).
----
$ git log
@@ -54,11 +54,12 @@ than two commits that have the same Change-ID).
error: failed to push some refs to 'ssh://JohnDoe@host:29418/myProject'
----
-If it was the intention to rework on a change and to push a new patch
-set the problem can be fixed by squashing the commits that contain the
-same Change-ID. The squashed commit can then be pushed to Gerrit.
-To squash the commits use git rebase to do an interactive rebase. For
-the example above where the last two commits have the same Change-ID
+If it was the intention to rework a change and push a new patch
+set, the problem can be fixed by squashing the commits that contain the
+same Change-Id. The squashed commit can then be pushed to Gerrit.
+
+To squash the commits, use `git rebase -i` to do an interactive rebase. For
+the example above where the last two commits have the same Change-Id,
this means an interactive rebase for the last two commits should be
done. For further details about the git rebase command please check
the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation for rebase].
@@ -92,11 +93,11 @@ the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git doc
If it was the intention to create a patch series with multiple
changes to be reviewed, each commit message should contain the
-Change-ID of the corresponding change in Gerrit. If a change in
-Gerrit does not exist yet, the Change-ID should be generated (either
-by using a link:cmd-hook-commit-msg.html[commit hook] or by using EGit) or the Change-ID could be
+Change-Id of the corresponding change in Gerrit. If a change in
+Gerrit does not exist yet, the Change-Id should be generated (either
+by using a link:cmd-hook-commit-msg.html[commit hook] or by using EGit) or the Change-Id could be
removed (not recommended since then amending this commit to create
-subsequent patch sets is more error prone). To change the Change-ID
+subsequent patch sets is more error prone). To change the Change-Id
of an existing commit do an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] and fix the
affected commit messages.
diff --git a/Documentation/gen_licenses.py b/Documentation/gen_licenses.py
index 61d2e24143..af7cd88a91 100755
--- a/Documentation/gen_licenses.py
+++ b/Documentation/gen_licenses.py
@@ -134,10 +134,12 @@ for n in used:
p = d[d.index(':')+1:].lower()
print('* ' + p)
print()
- print('----')
+ print('[[license]]')
+ print('[verse]')
+ print('--')
with open(n[2:].replace(':', '/')) as fd:
copyfileobj(fd, stdout)
- print('----')
+ print('--')
print("""
GERRIT
diff --git a/Documentation/images/inline-edit-add-file-suggestion.png b/Documentation/images/inline-edit-add-file-suggestion.png
new file mode 100644
index 0000000000..25a33f871f
--- /dev/null
+++ b/Documentation/images/inline-edit-add-file-suggestion.png
Binary files differ
diff --git a/Documentation/images/inline-edit-confirm-unsaved-edits.png b/Documentation/images/inline-edit-confirm-unsaved-edits.png
new file mode 100644
index 0000000000..87e4a328ed
--- /dev/null
+++ b/Documentation/images/inline-edit-confirm-unsaved-edits.png
Binary files differ
diff --git a/Documentation/images/inline-edit-create-change-project-screen-dialog.png b/Documentation/images/inline-edit-create-change-project-screen-dialog.png
new file mode 100644
index 0000000000..ea5daa97a8
--- /dev/null
+++ b/Documentation/images/inline-edit-create-change-project-screen-dialog.png
Binary files differ
diff --git a/Documentation/images/inline-edit-create-change-project-screen.png b/Documentation/images/inline-edit-create-change-project-screen.png
new file mode 100644
index 0000000000..e9c7033186
--- /dev/null
+++ b/Documentation/images/inline-edit-create-change-project-screen.png
Binary files differ
diff --git a/Documentation/images/inline-edit-create-follow-up-change.png b/Documentation/images/inline-edit-create-follow-up-change.png
new file mode 100644
index 0000000000..3e81eee8a5
--- /dev/null
+++ b/Documentation/images/inline-edit-create-follow-up-change.png
Binary files differ
diff --git a/Documentation/images/inline-edit-edit-in-diff-screen-patch-list.png b/Documentation/images/inline-edit-edit-in-diff-screen-patch-list.png
new file mode 100644
index 0000000000..bdbc59dd96
--- /dev/null
+++ b/Documentation/images/inline-edit-edit-in-diff-screen-patch-list.png
Binary files differ
diff --git a/Documentation/images/inline-edit-edit-in-patch-list.png b/Documentation/images/inline-edit-edit-in-patch-list.png
new file mode 100644
index 0000000000..9a31e0209e
--- /dev/null
+++ b/Documentation/images/inline-edit-edit-in-patch-list.png
Binary files differ
diff --git a/Documentation/images/inline-edit-enter-edit-mode-from-diff.png b/Documentation/images/inline-edit-enter-edit-mode-from-diff.png
new file mode 100644
index 0000000000..46dd0ffce2
--- /dev/null
+++ b/Documentation/images/inline-edit-enter-edit-mode-from-diff.png
Binary files differ
diff --git a/Documentation/images/inline-edit-enter-edit-mode-from-file-list.png b/Documentation/images/inline-edit-enter-edit-mode-from-file-list.png
new file mode 100644
index 0000000000..b8c52c9b0b
--- /dev/null
+++ b/Documentation/images/inline-edit-enter-edit-mode-from-file-list.png
Binary files differ
diff --git a/Documentation/images/inline-edit-file-list-in-edit-mode.png b/Documentation/images/inline-edit-file-list-in-edit-mode.png
new file mode 100644
index 0000000000..8f3553352f
--- /dev/null
+++ b/Documentation/images/inline-edit-file-list-in-edit-mode.png
Binary files differ
diff --git a/Documentation/images/inline-edit-full-screen-editor.png b/Documentation/images/inline-edit-full-screen-editor.png
new file mode 100644
index 0000000000..474fae58af
--- /dev/null
+++ b/Documentation/images/inline-edit-full-screen-editor.png
Binary files differ
diff --git a/Documentation/images/user-review-ui-change-screen-edit-commit-message.png b/Documentation/images/user-review-ui-change-screen-edit-commit-message.png
deleted file mode 100644
index 615e9a744f..0000000000
--- a/Documentation/images/user-review-ui-change-screen-edit-commit-message.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/user-review-ui-change-view-preference.png b/Documentation/images/user-review-ui-change-view-preference.png
deleted file mode 100644
index 825b7f66c8..0000000000
--- a/Documentation/images/user-review-ui-change-view-preference.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-screen-preferences-popup.png b/Documentation/images/user-review-ui-side-by-side-diff-screen-preferences-popup.png
index 35e29a3aa0..043c1ff921 100644
--- a/Documentation/images/user-review-ui-side-by-side-diff-screen-preferences-popup.png
+++ b/Documentation/images/user-review-ui-side-by-side-diff-screen-preferences-popup.png
Binary files differ
diff --git a/Documentation/index.txt b/Documentation/index.txt
index e0a823b272..6b83644168 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -3,12 +3,14 @@
== Tutorial
. Getting started
.. link:intro-quick.html[A Quick Introduction to Gerrit]
+.. link:intro-user.html[User Guide]
.. link:intro-project-owner.html[Project Owner Guide]
.. link:http://source.android.com/submit-patches/workflow[Default Android Workflow] (external)
. Web
.. Registering a new Gerrit account
.. link:user-review-ui.html[Reviewing Changes]
.. link:user-search.html[Searching Changes]
+.. link:user-inline-edit.html[Manipulating Changes in Browser]
.. link:user-notify.html[Subscribing to Email Notifications]
. SSH
.. SSH connection details
@@ -57,26 +59,26 @@
.. How to read stats from the JVM
. High availability
. Replication
-. link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[Plugins]
-. link:dev-design.html[System Design]
+. link:config-plugins.html[Plugins]
. link:config-contact.html[User Contact Information]
. link:config-reverseproxy.html[Reverse Proxy]
. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
. link:pgm-index.html[Server Side Administrative Tools]
== Developer
-. link:dev-readme.html[Developer Setup]
-. link:dev-buck.html[Building with Buck]
-. link:dev-build-plugins.html[Building Gerrit plugins]
-. link:dev-eclipse.html[Eclipse Setup]
-. link:dev-contributing.html[Contributing to Gerrit]
+. Getting Started
+.. link:dev-readme.html[Developer Setup]
+.. link:dev-eclipse.html[Eclipse Setup]
+.. link:dev-buck.html[Building with Buck]
+.. 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]
. Documentation formatting guide for contributions
. link:dev-design.html[System Design]
. link:i18n-readme.html[i18n Support]
-. Plugin development
-.. link:dev-plugins.html[Developing Plugins]
-.. link:js-api.html[JavaScript Plugin API]
-.. link:config-validation.html[Commit Validation]
== Maintainer
. link:dev-release.html[Developer Release]
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt
index c55a43cb6f..f5d5277e26 100644
--- a/Documentation/intro-project-owner.txt
+++ b/Documentation/intro-project-owner.txt
@@ -10,7 +10,7 @@ workflows for a project.
Being project owner means that you own a project in Gerrit.
Technically this is expressed by having the
link:access-control.html#category_owner[Owner] access right on
-`refs/*` on that project. As project owner you have the permission to
+`+refs/*+` on that project. As project owner you have the permission to
edit the access control list and the project settings of the project.
It also means that you should get familiar with these settings so that
you can adapt them to the needs of your project.
@@ -127,12 +127,12 @@ Access rights can be assigned on a concrete ref, e.g.
`refs/heads/master` but also on ref patterns and regular expressions
for ref names.
-A ref pattern ends with `/*` and describes a complete ref name
-namespace, e.g. access rights assigned on `refs/heads/*` apply to all
+A ref pattern ends with `+/*+` and describes a complete ref name
+namespace, e.g. access rights assigned on `+refs/heads/*+` apply to all
branches.
Regular expressions must start with `^`, e.g. access rights assigned
-on `^refs/heads/rel-.*` would apply to all `rel-*` branches.
+on `+^refs/heads/rel-.*+` would apply to all `+rel-*+` branches.
[[groups]]
=== Groups
@@ -765,6 +765,14 @@ As workaround you may
. link:#import-history[import the history of the old project]
. link:#project-deletion[delete the old project]
+Please note that a drawback of this workaround is that the whole review
+history (changes, review comments) is lost.
+
+Alternatively, you can use the
+link:https://gerrit.googlesource.com/plugins/importer/[importer] plugin
+to copy the project _including the review history_, and then
+link:#project-deletion[delete the old project].
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/intro-quick.txt b/Documentation/intro-quick.txt
index 9ff6058852..bb8013458d 100644
--- a/Documentation/intro-quick.txt
+++ b/Documentation/intro-quick.txt
@@ -251,7 +251,7 @@ As long as we set up the
link:user-changeid.html[Change-Id commit-msg hook]
before we uploaded the change, re-working it is easy. All we need
to do to upload a re-worked change is to push another commit that has
-the same Change-Id in the message. Since the hook added a Change-ID in
+the same Change-Id in the message. Since the hook added a Change-Id in
our initial commit we can simply checkout and then amend that commit.
Then push it to Gerrit in the same way as we did to create the review. E.g.
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
new file mode 100644
index 0000000000..46773076c6
--- /dev/null
+++ b/Documentation/intro-user.txt
@@ -0,0 +1,664 @@
+= User Guide
+
+This is a Gerrit guide that is dedicated to Gerrit end-users. It
+explains the standard Gerrit workflows and how a user can adapt Gerrit
+to personal preferences.
+
+It is expected that readers know about link:http://git-scm.com/[Git]
+and that they are familiar with basic git commands and workflows.
+
+[[gerrit]]
+== What is Gerrit?
+
+Gerrit is a Git server that provides link:access-control.html[access
+control] for the hosted Git repositories and a web front-end for doing
+link:#code-review[code review]. Code review is a core functionality of
+Gerrit, but still it is optional and teams can decide to
+link:#no-code-review[work without code review].
+
+[[tools]]
+== Tools
+
+Gerrit speaks the git protocol. This means in order to work with Gerrit
+you do *not* need to install any Gerrit client, but having a regular
+git client, such as the link:http://git-scm.com/[git command line] or
+link:http://eclipse.org/egit/[EGit] in Eclipse, is sufficient.
+
+Still there are some client-side tools for Gerrit, which can be used
+optionally:
+
+* link:http://eclipse.org/mylyn/[Mylyn Gerrit Connector]: Gerrit
+ integration with Mylyn
+* link:https://play.google.com/store/apps/details?id=com.jbirdvegas.mgerrit[
+ mGerrit]: Android client for Gerrit
+* link:https://github.com/stackforge/gertty[Gertty]: Console-based
+ interface for Gerrit
+
+[[clone]]
+== Clone Gerrit Project
+
+Cloning a Gerrit project is done the same way as cloning any other git
+repository by using the `git clone` command.
+
+.Clone Gerrit Project
+----
+ $ git clone ssh://gerrithost:29418/RecipeBook.git RecipeBook
+ Cloning into RecipeBook...
+----
+
+The URL for cloning the project can be found in the Gerrit web UI
+under `Projects` > `List` > <project-name> > `General`.
+
+For git operations Gerrit supports the link:user-upload.html#ssh[SSH]
+and the link:user-upload.html#http[HTTP/HTTPS] protocols.
+
+[NOTE]
+To use SSH you must link:user-upload.html#configure_ssh[generate an SSH
+key pair and upload the public SSH key to Gerrit].
+
+[[code-review]]
+== Code Review Workflow
+
+With Gerrit _Code Review_ means to link:#review-change[review] every
+commit *before* it is accepted into the code base. The author of a code
+modification link:user-upload.html#push_create[uploads a commit] as a
+change to Gerrit. In Gerrit each change is stored in a
+link:#change-ref[staging area] where it can be checked and reviewed.
+Only when it is approved and submitted it gets applied to the code
+base. If there is feedback on a change, the author can improve the code
+modification by link:#upload-patch-set[amending the commit and
+uploading the new commit as a new patch set]. This way a change is
+improved iteratively and it is applied to the code base only when is
+ready.
+
+[[upload-change]]
+== Upload a Change
+
+Uploading a change to Gerrit is done by pushing a commit to Gerrit. The
+commit must be pushed to a ref in the `refs/for/` namespace which
+defines the target branch: `refs/for/<target-branch>`.
+The magic `refs/for/` prefix allows Gerrit to differentiate commits
+that are pushed for review from commits that are pushed directly into
+the repository, bypassing code review. For the target branch it is
+sufficient to specify the short name, e.g. `master`, but you can also
+specify the fully qualified branch name, e.g. `refs/heads/master`.
+
+.Push for Code Review
+----
+ $ git commit
+ $ git push origin HEAD:refs/for/master
+
+ // this is the same as:
+ $ git commit
+ $ git push origin HEAD:refs/for/refs/heads/master
+----
+
+.Push with bypassing Code Review
+----
+ $ git commit
+ $ git push origin HEAD:master
+
+ // this is the same as:
+ $ git commit
+ $ git push origin HEAD:refs/heads/master
+----
+
+[[push-fails]]
+[NOTE]
+If pushing to Gerrit fails consult the Gerrit documentation that
+explains the link:error-messages.html[error messages].
+
+[[change-ref]]
+When a commit is pushed for review, Gerrit stores it in a staging area
+which is a branch in the special `refs/changes/` namespace. A change
+ref has the format `refs/changes/XX/YYYY/ZZ` where `YYYY` is the
+numeric change number, `ZZ` is the patch set number and `XX` is the
+last two digits of the numeric change number, e.g.
+`refs/changes/20/884120/1`. Understanding the format of this ref is not
+required for working with Gerrit.
+
+[[fetch-change]]
+Using the change ref git clients can fetch the corresponding commit,
+e.g. for local verification.
+
+.Fetch Change
+----
+ $ git fetch https://gerrithost/myProject refs/changes/74/67374/2 && git checkout FETCH_HEAD
+----
+
+[NOTE]
+The fetch command can be copied from the
+link:user-review-ui.html#download[download commands] in the change
+screen.
+
+The `refs/for/` prefix is used to map the Gerrit concept of
+"Pushing for Review" to the git protocol. For the git client it looks
+like every push goes to the same branch, e.g. `refs/for/master` but in
+fact for each commit that is pushed to this ref Gerrit creates a new
+branch under the `refs/changes/` namespace. In addition Gerrit creates
+an open change.
+
+[[change]]
+A change consists of a link:user-changeid.html[Change-Id], meta data
+(owner, project, target branch etc.), one or more patch sets, comments
+and votes. A patch set is a git commit. Each patch set in a change
+represents a new version of the change and replaces the previous patch
+set. Only the latest patch set is relevant. This means all failed
+iterations of a change will never be applied to the target branch, but
+only the last patch set that is approved is integrated.
+
+[[change-id]]
+The Change-Id is important for Gerrit to know whether a commit that is
+pushed for code review should create a new change or whether it should
+create a new patch set for an existing change.
+
+The Change-Id is a SHA-1 that is prefixed with an uppercase `I`. It is
+specified as footer in the commit message (last paragraph):
+
+----
+ Improve foo widget by attaching a bar.
+
+ We want a bar, because it improves the foo by providing more
+ wizbangery to the dowhatimeanery.
+
+ Bug: #42
+ Change-Id: Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b
+ Signed-off-by: A. U. Thor <author@example.com>
+----
+
+If a commit that has a Change-Id in its commit message is pushed for
+review, Gerrit checks if a change with this Change-Id already exists
+for this project and target branch, and if yes, Gerrit creates a new
+patch set for this change. If not, a new change with the given
+Change-Id is created.
+
+If a commit without Change-Id is pushed for review, Gerrit creates a
+new change and generates a Change-Id for it. Since in this case the
+Change-Id is not included in the commit message, it must be manually
+inserted when a new patch set should be uploaded. Most projects already
+link:project-configuration.html#require-change-id[require a Change-Id]
+when pushing the very first patch set. This reduces the risk of
+accidentally creating a new change instead of uploading a new patch
+set. Any push without Change-Id then fails with
+link:error-missing-changeid.html[missing Change-Id in commit message
+footer]. New patch sets can always be uploaded to a specific change
+(even without any Change-Id) by pushing to the change ref, e.g.
+`refs/changes/74/67374`.
+
+Amending and rebasing a commit preserves the Change-Id so that the new
+commit automatically becomes a new patch set of the existing change,
+when it is pushed for review.
+
+.Push new Patch Set
+----
+ $ git commit --amend
+ $ git push origin HEAD:refs/for/master
+----
+
+Change-Ids are unique for a branch of a project. E.g. commits that fix
+the same issue in different branches should have the same Change-Id,
+which happens automatically if a commit is cherry-picked to another
+branch. This way you can link:user-search.html[search] by the Change-Id
+in the Gerrit web UI to find a fix in all branches.
+
+Change-Ids can be created automatically by installing the `commit-msg`
+hook as described in the link:user-changeid.html#creation[Change-Id
+documentation].
+
+Instead of manually installing the `commit-msg` hook for each git
+repository, you can copy it into the
+link:http://git-scm.com/docs/git-init#_template_directory[git template
+directory]. Then it is automatically copied to every newly cloned
+repository.
+
+[[review-change]]
+== Review Change
+
+After link:#upload-change[uploading a change for review] reviewers can
+inspect it via the Gerrit web UI. Reviewers can see the code delta and
+link:user-review-ui.html#inline-comments[comment directly in the code]
+on code blocks or lines. They can also link:user-review-ui.html#reply[
+post summary comments and vote on review labels]. The
+link:user-review-ui.html[documentation of the review UI] explains the
+screens and controls for doing code reviews.
+
+There are several options to control how patch diffs should be
+rendered. Users can configure their preferences in the
+link:user-review-ui.html#diff-preferences[diff preferences].
+
+[[upload-patch-set]]
+== Upload a new Patch Set
+
+If there is feedback from code review and a change should be improved a
+new patch set with the reworked code should be uploaded.
+
+This is done by amending the commit of the last patch set. If needed
+this commit can be fetched from Gerrit by using the fetch command from
+the link:user-review-ui.html#download[download commands] in the change
+screen.
+
+It is important that the commit message contains the
+link:user-changeid.html[Change-Id] of the change that should be updated
+as a footer (last paragraph). Normally the commit message already
+contains the correct Change-Id and the Change-Id is preserved when the
+commit is amended.
+
+.Push Patch Set
+----
+ // fetch and checkout the change
+ // (checkout command copied from change screen)
+ $ git fetch https://gerrithost/myProject refs/changes/74/67374/2 && git checkout FETCH_HEAD
+
+ // rework the change
+ $ git add <path-of-reworked-file>
+ ...
+
+ // amend commit
+ $ git commit --amend
+
+ // push patch set
+ $ git push origin HEAD:refs/for/master
+----
+
+[NOTE]
+Never amend a commit that is already part of a central branch.
+
+Pushing a new patch set triggers email notification to the reviewers.
+
+[[multiple-features]]
+== Developing multiple Features in parallel
+
+Code review takes time, which can be used by the change author to
+implement other features. Each feature should be implemented in its own
+local feature branch that is based on the current HEAD of the target
+branch. This way there is no dependency to open changes and new
+features can be reviewed and applied independently. If wanted, it is
+also possible to base a new feature on an open change. This will create
+a dependency between the changes in Gerrit and each change can only be
+applied if all its predecessor are applied as well. Dependencies
+between changes can be seen from the
+link:user-review-ui.html#related-changes-tab[Related Changes] tab on
+the change screen.
+
+[[watch]]
+== Watching Projects
+
+To get to know about new changes you can link:user-notify.html#user[
+watch the projects] that you are interested in. For watched projects
+Gerrit sends you email notifications when a change is uploaded or
+modified. You can decide on which events you want to be notified and
+you can filter the notifications by using link:user-search.html[change
+search expressions]. For example '+branch:master file:^.*\.txt$+' would
+send you email notifications only for changes in the master branch that
+touch a 'txt' file.
+
+It is common that the members of a project team watch their own
+projects and then pick the changes that are interesting to them for
+review.
+
+Project owners may also configure
+link:intro-project-owner.html#notifications[notifications on
+project-level].
+
+[[adding-reviewers]]
+== Adding Reviewers
+
+In the link:user-review-ui.html#reviewers[change screen] reviewers can
+be added explicitly to a change. The added reviewer will then be
+notified by email about the review request.
+
+Mainly this functionality is used to request the review of specific
+person who is known to be an expert in the modified code or who is a
+stakeholder of the implemented feature. Normally it is not needed to
+explicitly add reviewers on every change, but you rather rely on the
+project team to watch their project and to process the incoming changes
+by importance, interest, time etc.
+
+There are also link:intro-project-owner.html#reviewers[plugins which
+can add reviewers automatically] (e.g. by configuration or based on git
+blame annotations). If this functionality is required it should be
+discussed with the project owners and the Gerrit administrators.
+
+[[dashboards]]
+== Dashboards
+
+Gerrit supports a wide range of link:user-search.html#search-operators[
+query operators] to search for changes by different criteria, e.g. by
+status, change owner, votes etc.
+
+The page that shows the results of a change query has the change query
+contained in its URL. This means you can bookmark this URL in your
+browser to save the change query. This way it can be easily re-executed
+later.
+
+Several change queries can be also combined into a dashboard. A
+dashboard is a screen in Gerrit that presents the results of several
+change queries in different sections, each section having a descriptive
+title.
+
+A default dashboard is available under `My` > `Changes`. It has
+sections to list outgoing reviews, incoming reviews and recently closed
+changes.
+
+Users can also define link:user-dashboards.html#custom-dashboards[
+custom dashboards]. Dashboards can be bookmarked in a browser so that
+they can be re-executed later.
+
+It is also possible to link:#my-menu[customize the My menu] and add
+menu entries for custom queries or dashboards to it.
+
+Dashboards are very useful to define own views on changes, e.g. you can
+have different dashboards for own contributions, for doing reviews or
+for different sets of projects.
+
+[NOTE]
+You can use the link:user-search.html#limit[limit] and
+link:user-search.html#age[age] query operators to limit the result set
+in a dashboard section. Clicking on the section title executes the
+change query without the `limit` and `age` operator so that you can
+inspect the full result set.
+
+Project owners can also define shared
+link:user-dashboards.html#project-dashboards[dashboards on
+project-level]. The project dashboards can be seen in the web UI under
+`Projects` > `List` > <project-name> > `Dashboards`.
+
+[[submit]]
+== Submit a Change
+
+Submitting a change means that the code modifications of the current
+patch set are applied to the target branch. Submit requires the
+link:access-control.html#category_submit[Submit] access right and is
+done on the change screen by clicking on the
+link:user-review-ui.html#submit[Submit] button.
+
+In order to be submittable changes must first be approved by
+link:user-review-ui.html#vote[voting on the review labels]. By default
+a change can only be submitted if it has a vote with the highest value
+on each review label and no vote with the lowest value (veto vote).
+Projects can configure link:intro-project-owner.html#labels[custom
+labels] and link:intro-project-owner.html#submit-rules[custom submit
+rules] to control when a change becomes submittable.
+
+How the code modification is applied to the target branch when a change
+is submitted is controlled by the
+link:project-configuration.html#submit_type[submit type] which can be
+link:intro-project-owner.html#submit-type[configured on project-level].
+
+Submitting a change may fail with conflicts. In this case you need to
+link:#rebase[rebase] the change locally, resolve the conflicts and
+upload the commit with the conflict resolution as new patch set.
+
+If a change cannot be merged due to path conflicts this is highlighted
+on the change screen by a bold red `Cannot Merge` label.
+
+[[rebase]]
+== Rebase a Change
+
+While a change is in review the HEAD of the target branch can evolve.
+In this case the change can be rebased onto the new HEAD of the target
+branch. When there are no conflicts the rebase can be done directly
+from the link:user-review-ui.html#rebase[change screen], otherwise it
+must be done locally.
+
+.Rebase a Change locally
+----
+ // update the remote tracking branches
+ $ git fetch
+
+ // fetch and checkout the change
+ // (checkout command copied from change screen)
+ $ git fetch https://gerrithost/myProject refs/changes/74/67374/2 && git checkout FETCH_HEAD
+
+ // do the rebase
+ $ git rebase origin/master
+
+ // resolve conflicts if needed and stage the conflict resolution
+ ...
+ $ git add <path-of-file-with-conflicts-resolved>
+
+ // continue the rebase
+ $ git rebase --continue
+
+ // push the commit with the conflict resolution as new patch set
+ $ git push origin HEAD:refs/for/master
+----
+
+Doing a manual rebase is only necessary when there are conflicts that
+cannot be resolved by Gerrit. If manual conflict resolution is needed
+also depends on the link:intro-project-owner.html#submit-type[submit
+type] that is configured for the project.
+
+Generally changes shouldn't be rebased without reason as it
+increases the number of patch sets and creates noise with
+notifications. However if a change is in review for a long time it may
+make sense to rebase it from time to time, so that reviewers can see
+the delta against the current HEAD of the target branch. It also shows
+that there is still an interest in this change.
+
+[NOTE]
+Never rebase commits that are already part of a central branch.
+
+[[abandon]]
+[[restore]]
+== Abandon/Restore a Change
+
+Sometimes during code review a change is found to be bad and it should
+be given up. In this case the change can be
+link:user-review-ui.html#abandon[abandoned] so that it doesn't appear
+in list of open changes anymore.
+
+Abandoned changes can be link:user-review-ui.html#restore[restored] if
+later they are needed again.
+
+[[topics]]
+== Using Topics
+
+Changes can be grouped by topics. This is useful because it allows you
+to easily find related changes by using the
+link:user-search.html#topic[topic search operator]. Also on the change
+screen link:user-review-ui.html#same-topic[changes with the same topic]
+are displayed so that you can easily navigate between them.
+
+Often changes that together implement a feature or a user story are
+group by a topic.
+
+Assigning a topic to a change can be done in the
+link:user-review-ui.html#project-branch-topic[change screen].
+
+It is also possible to link:user-upload.html#topic[set a topic on
+push].
+
+.Set Topic on Push
+----
+ $ git push origin HEAD:refs/for/master%topic=multi-master
+----
+
+[[drafts]]
+== Working with Drafts
+
+Changes can be uploaded as drafts. By default draft changes are only
+visible to the change owner. This gives you the possibility to have
+some staging before making your changes visible to the reviewers. Draft
+changes can also be used to backup unfinished changes.
+
+A draft change is created by pushing to the magic
+`refs/drafts/<target-branch>` ref.
+
+.Push a Draft Change
+----
+ $ git commit
+ $ git push origin HEAD:refs/drafts/master
+----
+
+Draft changes have the state link:user-review-ui.html#draft[Draft] and
+can be link:user-review-ui.html#publish[published] or
+link:user-review-ui.html#delete[deleted] from the change screen.
+
+By link:user-review-ui.html#reviewers[adding reviewers] to a draft
+change the change is made visible to these users. This way you can
+collaborate with other users in privacy.
+
+By pushing to `refs/drafts/<target-branch>` you can also upload draft
+patch sets to non-draft changes. Draft patch sets are immediately
+visible to all reviewers of the change, but other users cannot see the
+draft patch set. A draft patch set can be published and deleted in the
+same way as a draft change.
+
+[[inline-edit]]
+== Inline Edit
+
+It is possible to link:user-inline-edit.html#editing-change[edit
+changes inline] directly in the web UI. This is useful to make small
+corrections immediately and publish them as a new patch set.
+
+It is also possible to link:user-inline-edit.html#create-change[create
+new changes inline].
+
+[[project-administration]]
+== Project Administration
+
+Every project has a link:intro-project-owner.html#project-owner[project
+owner] that administrates the project. Project administration includes
+the configuration of the project
+link:intro-project-owner.html#access-rights[access rights], but project
+owners have many more possibilities to customize the workflows for a
+project which are described in the link:intro-project-owner.html[
+project owner guide].
+
+[[no-code-review]]
+== Working without Code Review
+
+Doing code reviews with Gerrit is optional and you can use Gerrit
+without code review as a pure Git server.
+
+.Push with bypassing Code Review
+----
+ $ git commit
+ $ git push origin HEAD:master
+
+ // this is the same as:
+ $ git commit
+ $ git push origin HEAD:refs/heads/master
+----
+
+[NOTE]
+Bypassing code review must be enabled in the project access rights. The
+project owner must allow it by assigning the
+link:access-control.html#category_push_direct[Push] access right on the
+target branch (`refs/heads/<branch-name>`).
+
+[NOTE]
+If you bypass code review you always need to merge/rebase manually if
+the tip of the destination branch has moved. Please keep this in mind
+if you choose to not work with code review because you think it's
+easier to avoid the additional complexity of the review workflow; it
+might actually not be easier.
+
+[NOTE]
+The project owner may enable link:user-upload.html#auto_merge[
+auto-merge on push] to benefit from the automatic merge/rebase on
+server side while pushing directly into the repository.
+
+[[preferences]]
+== Preferences
+
+There are several options to control the rendering in the Gerrit web UI.
+Users can configure their preferences under `Settings` > `Preferences`.
+
+The following preferences can be configured:
+
+- [[show-site-header]]`Show Site Header`:
++
+Whether the site header should be shown.
+
+- [[use-flash]]`Use Flash Clipboard Widget`:
++
+Whether the Flash clipboard widget should be used. If enabled Gerrit
+offers a copy-to-clipboard icon next to IDs and commands that need to
+be copied frequently, such as the Change-Ids, commit IDs and download
+commands.
+
+- [[cc-me]]`CC Me On Comments I Write`:
++
+Whether you get notified by email as CC on comments that you write
+yourself.
+
+- [[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, vetos 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
+when paging through changes, projects, branches or groups.
+
+- [[date-time-format]]`Date/Time Format`:
++
+The format that should be used to render dates and timestamps.
+
+- [[relative-dates]]`Show Relative Dates In Changes Table`:
++
+Whether timestamps in change lists and dashboards should be shown as
+relative timestamps, e.g. '12 days ago' instead of absolute timestamps
+such as 'Apr 15'.
+
+- [[change-size-bars]]`Show Change Sizes As Colored Bars`:
++
+Whether change sizes should be visualized as colored bars. If disabled
+the numbers of added and deleted lines are shown as text, e.g.
+'+297, -63'.
+
+- [[show-change-number]]`Show Change Number In Changes Table`:
++
+Whether in change lists and dashboards an `ID` column with the numeric
+change IDs should be shown.
+
+- [[mute-common-path-prefixes]]`Mute Common Path Prefixes In File List`:
++
+Whether common path prefixes in the file list on the change screen
+should be link:user-review-ui.html#repeating-path-segments[grayed out].
+
+- [[diff-view]]`Diff View`:
++
+Whether the Side-by-Side diff view or the Unified diff view should be
+shown when clicking on a file path in the change screen.
+
+[[my-menu]]
+In addition it is possible to customize the menu entries of the `My`
+menu. This can be used to make the navigation to frequently used
+screens, e.g. configured link:#dashboards[dashboards], quick.
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 883198ad99..43e43366d8 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -60,6 +60,11 @@ self.get(url, callback)
a string, otherwise the result is a JavaScript object or array,
as described in the relevant REST API documentation.
+[[self_getCurrentUser]]
+=== self.getCurrentUser()
+Returns the currently signed in user's AccountInfo data; empty account
+data if no user is currently signed in.
+
[[self_getPluginName]]
=== self.getPluginName()
Returns the name this plugin was installed as by the server
@@ -168,7 +173,7 @@ on a button associated with a server side `UiAction`.
self.onAction(type, view_name, callback);
----
-* type: `'change'`, `'revision'`, `'project'`, or `'branch'`
+* type: `'change'`, `'edit'`, `'revision'`, `'project'`, or `'branch'`
indicating which type of resource the `UiAction` was bound to
in the server.
@@ -625,6 +630,11 @@ Gerrit.get('/changes/?q=status:open', function (open) {
});
----
+[[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
@@ -838,8 +848,8 @@ on a button associated with a server side `UiAction`.
Gerrit.onAction(type, view_name, callback);
----
-* type: `'change'`, `'revision'`, `'project'` or `'branch'` indicating
- what sort of resource the `UiAction` was bound to in the server.
+* 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()`
@@ -877,6 +887,10 @@ Redisplays the current web UI view, refreshing all information.
=== 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
diff --git a/Documentation/json.txt b/Documentation/json.txt
index b45f4049a5..feef1a1f74 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -35,8 +35,6 @@ was created.
lastUpdated:: Time in seconds since the UNIX epoch when this change
was last updated.
-sortKey:: Internal key used to sort changes, based on lastUpdated.
-
open:: Boolean indicating if the change is still open for review.
status:: Current state of this change.
@@ -121,7 +119,9 @@ kind:: Kind of change uploaded.
TRIVIAL_REBASE;; Conflict-free merge between the new parent and the prior patch set.
- NO_CODE_CHANGE;; No code changed; same tree and same parents.
+ NO_CODE_CHANGE;; No code changed; same tree and same parent tree.
+
+ NO_CHANGE;; No changes; same commit message, same tree and same parent tree.
approvals:: The <<approval,approval attribute>> granted.
diff --git a/Documentation/pgm-SwitchSecureStore.txt b/Documentation/pgm-SwitchSecureStore.txt
new file mode 100644
index 0000000000..f9b2aa4dfb
--- /dev/null
+++ b/Documentation/pgm-SwitchSecureStore.txt
@@ -0,0 +1,39 @@
+= SwitchSecureStore
+
+== NAME
+SwitchSecureStore - Changes the currently used SecureStore implementation
+
+== SYNOPSIS
+--
+'java' -jar gerrit.war 'SwitchSecureStore' [<OPTIONS>]
+--
+
+== DESCRIPTION
+Changes the SecureStore implementation used by Gerrit. It migrates all data
+stored in the old implementation, removes the old implementation jar file
+from `$site_path/lib` and puts the new one there. As a final step
+the link:config-gerrit.html#gerrit.secureStoreClass[gerrit.secureStoreClass]
+property of `gerrit.config` will be updated.
+
+All dependencies not provided by Gerrit should be put the in `$site_path/lib`
+directory manually, before running the `SwitchSecureStore` program.
+
+After this operation there is no automatic way back the to standard Gerrit no-op
+secure store implementation, however there is a manual procedure:
+* stop Gerrit,
+* remove SecureStore jar file from `$site_path/lib`,
+* put plain text passwords into `$site_path/etc/secure.conf` file,
+* start Gerrit.
+
+== OPTIONS
+
+--new-secure-store-lib::
+ Path to jar file with new SecureStore implementation. Jar dependencies must be
+ put in `$site_path/lib` directory.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-daemon.txt b/Documentation/pgm-daemon.txt
index 3d7dd45441..bcf2b1b722 100644
--- a/Documentation/pgm-daemon.txt
+++ b/Documentation/pgm-daemon.txt
@@ -37,7 +37,7 @@ to different values.
--enable-httpd::
--disable-httpd::
Enable (or disable) the internal HTTP daemon, answering
- web requests. Enabled by default.
+ web requests. Enabled by default when --slave is not used.
--enable-sshd::
--disable-sshd::
@@ -51,7 +51,7 @@ to different values.
or updates existing ones) or link:cmd-review.html[review]
(sets approve marks) are disabled.
+
-This option automatically implies '--disable-httpd --enable-sshd'.
+This option automatically implies '--enable-sshd'.
--console-log::
Send log messages to the console, instead of to the standard
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
index 3bb618232d..bf6dc572d5 100644
--- a/Documentation/pgm-index.txt
+++ b/Documentation/pgm-index.txt
@@ -24,6 +24,9 @@ link:pgm-prolog-shell.html[prolog-shell]::
link:pgm-reindex.html[reindex]::
Rebuild the secondary index.
+link:pgm-SwitchSecureStore.html[SwitchSecureStore]::
+ Change used SecureStore implementation.
+
link:pgm-rulec.html[rulec]::
Compile project-specific Prolog rules to JARs.
diff --git a/Documentation/project-configuration.txt b/Documentation/project-configuration.txt
index 4b755f3723..1cecabff8e 100644
--- a/Documentation/project-configuration.txt
+++ b/Documentation/project-configuration.txt
@@ -58,7 +58,7 @@ modified by any project owner through the project console, `Projects` >
[[fast_forward_only]]
* Fast Forward Only
+
-This method produces a strictly linear history. All merges must
+With this method no merge commits are produced. All merges must
be handled on the client, prior to uploading to Gerrit for review.
+
To submit a change, the change must be a strict superset of the
@@ -150,6 +150,26 @@ The project is hidden and only visible to project owners. Other users
are not able to see the project even if they have read permissions
granted on the project.
+=== Use target branch when determining new changes to open
+
+The `create-new-change-for-all-not-in-target` option provides a
+convenience for selecting link:user-upload.html#base[the merge base]
+by setting it automatically to the target branch's tip so you can
+create new changes for all commits not in the target branch.
+
+This option is disabled if the tip of the push is a merge commit.
+
+This option also only works if there are no merge commits in the
+commit chain, in such cases it fails warning the user that such
+pushes can only be performed by manually specifying
+link:user-upload.html#base[bases]
+
+This option is useful if you want to push a change to your personal
+branch first and for review to another branch for example. Or in cases
+where a commit is already merged into a branch and you want to create
+a new open change for that commit on another branch.
+
+[[require-change-id]]
=== Require Change-Id
The `Require Change-Id in commit message` option defines whether a
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 2fedb9ec87..b15c283a9f 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -2,11 +2,11 @@
[[SubmitRule]]
== Submit Rule
-A 'Submit Rule' in Gerrit is logic that defines when a change is submittable.
+A _Submit Rule_ in Gerrit is logic that defines when a change is submittable.
By default, a change is submittable when it gets at least one
highest vote in each voting category and has no lowest vote (aka veto vote) in
-any category. Typically, this means that a change needs 'Code-Review+2',
-'Verified+1' and has neither 'Code-Review-2' nor 'Verified-1' to become
+any category. Typically, this means that a change needs `Code-Review+2`,
+`Verified+1` and has neither `Code-Review-2` nor `Verified-1` to become
submittable.
While this rule is a good default, there are projects which need more
@@ -29,7 +29,7 @@ link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.
[[SubmitType]]
== Submit Type
-A 'Submit Type' is a strategy that is used on submit to integrate the
+A _Submit Type_ is a strategy that is used on submit to integrate the
change into the destination branch. Supported submit types are:
* `Fast Forward Only`
@@ -38,7 +38,7 @@ change into the destination branch. Supported submit types are:
* `Cherry Pick`
* `Rebase If Necessary`
-'Submit Type' is a project global setting. This means that the same submit type
+_Submit Type_ is a project global setting. This means that the same submit type
is used for all changes of one project.
Projects which need more flexibility in choosing, or enforcing, a submit type
@@ -51,14 +51,15 @@ submit type is shown on the change screen for each change.
== Prolog Language
This document is not a complete Prolog tutorial.
link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a
-good starting point for learning the Prolog language. This document will only explain
-some elements of Prolog that are necessary to understand the provided examples.
+good starting point for learning the Prolog language. This document will only
+explain some elements of Prolog that are necessary to understand the provided
+examples.
== Prolog in Gerrit
Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the
original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe]
-project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at
-runtime.
+project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs
+at runtime.
== Interactive Prolog Cafe Shell
For interactive testing and playing with Prolog, Gerrit provides the
@@ -66,7 +67,8 @@ link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
Prolog interpreter shell.
NOTE: The interactive shell is just a prolog shell, it does not load
-a gerrit server environment and thus is not intended for xref:TestingSubmitRules[testing submit rules].
+a gerrit server environment and thus is not intended for
+xref:TestingSubmitRules[testing submit rules].
== SWI-Prolog
Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can
@@ -94,8 +96,8 @@ file:
[[HowToWriteSubmitRules]]
== How to write submit rules
-Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it
-will first initialize the embedded Prolog interpreter by:
+Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P`
+it will first initialize the embedded Prolog interpreter by:
* consulting a set of facts about the change `C`
* consulting the `rules.pl` from the project `P`
@@ -126,9 +128,9 @@ link:prolog-change-facts.html[Prolog Facts for Gerrit Change].
By default, Gerrit will search for a `submit_rule/1` predicate in the `rules.pl`
file, evaluate the `submit_rule(X)` and then inspect the value of `X` in order
to decide whether the change is submittable or not and also to find the set of
-needed criteria for the change to become submittable. This means that Gerrit has an
-expectation on the format and value of the result of the `submit_rule` predicate
-which is expected to be a `submit` term of the following format:
+needed criteria for the change to become submittable. This means that Gerrit has
+an expectation on the format and value of the result of the `submit_rule`
+predicate which is expected to be a `submit` term of the following format:
====
submit(label(label-name, status) [, label(label-name, status)]*)
@@ -138,7 +140,7 @@ where `label-name` is usually `'Code-Review'` or `'Verified'` but could also
be any other string (see examples below). The `status` is one of:
* `ok(user(ID))` or just `ok(_)` if user info is not important. This status is
- used to tell that this label/category has been met.
+ used to tell that this label/category has been met.
* `need(_)` is used to tell that this label/category is needed for the change to
become submittable.
* `reject(user(ID))` or just `reject(_)`. This status is used to tell that this
@@ -148,8 +150,8 @@ be any other string (see examples below). The `status` is one of:
group to apply a specific label on a change, but no users are in that group.
This is usually caused by misconfiguration of permissions.
* `may(_)` allows expression of approval categories that are optional, i.e.
- could either be set or unset without ever influencing whether the change
- could be submitted.
+ could either be set or unset without ever influencing whether the change
+ could be submitted.
NOTE: For a change to be submittable all `label` terms contained in the returned
`submit` term must have either `ok` or `may` status.
@@ -175,10 +177,11 @@ Here some examples of possible return values from the `submit_rule` predicate:
<2> label `'Verified'` is rejected. Change is not submittable.
<3> label `'Author-is-John-Doe'` is needed for the change to become submittable.
Note that this tells nothing about how this criteria will be met. It is up
- to the implementer of the `submit_rule` to return `label('Author-is-John-Doe',
- ok(_))` when this criteria is met. Most likely, it will have to match
- against `gerrit:commit_author` in order to check if this criteria is met.
- This will become clear through the examples below.
+ to the implementer of the `submit_rule` to return
+ `label('Author-is-John-Doe', ok(_))` when this criteria is met. Most
+ likely, it will have to match against `gerrit:commit_author` in order to
+ check if this criteria is met. This will become clear through the examples
+ below.
Of course, when implementing the `submit_rule` we will use the facts about the
change that are already provided by Gerrit.
@@ -189,9 +192,9 @@ screen for voting. If the return result contains label `'ABC'` and if the label
`'ABC'` is link:config-labels.html[defined for the project] then voting for the
label `'ABC'` will be displayed. Otherwise, it is not displayed. Note that the
project doesn't need a defined label for each label contained in the result of
-`submit_rule` predicate. For example, the decision whether `'Author-is-John-Doe'`
-label is met will probably not be made by explicit voting but, instead, by
-inspecting the facts about the change.
+`submit_rule` predicate. For example, the decision whether
+`'Author-is-John-Doe'` label is met will probably not be made by explicit voting
+but, instead, by inspecting the facts about the change.
[[SubmitFilter]]
== Submit Filter
@@ -201,7 +204,7 @@ in the `rules.pl` file of the current project, the `submit_filter` will be
searched for in the `rules.pl` of all parent projects of the current project,
but not in the `rules.pl` of the current project. The search will start from the
immediate parent of the current project, then in the parent project of that
-project and so on until, and including, the 'All-Projects' project.
+project and so on until, and including, the `'All-Projects'` project.
The purpose of the submit filter is, as its name says, to filter the results
of the `submit_rule`. Therefore, the `submit_filter` predicate has two
@@ -263,7 +266,7 @@ its result will be filtered as described above.
[[HowToWriteSubmitType]]
== How to write submit type
-Writing custom submit type logic in Prolog is the similar top
+Writing custom submit type logic in Prolog is similar to
xref:HowToWriteSubmitRules[writing submit rules]. The only difference is that
one has to implement a `submit_type` predicate (instead of the `submit_rule`)
and that the return result of the `submit_type` has to be an atom that
@@ -289,14 +292,15 @@ the result.
[[TestingSubmitRules]]
== Testing submit rules
-The prolog environment running the `submit_rule` is loaded with state describing the
-change that is being evaluated. The easiest way to load this state is to test your
-`submit_rule` against a real change on a running gerrit instance. The command
-link:cmd-test-submit-rule.html[test-submit rule] loads a specific change and executes
-the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing.
+The prolog environment running the `submit_rule` is loaded with state describing
+the change that is being evaluated. The easiest way to load this state is to
+test your `submit_rule` against a real change on a running gerrit instance. The
+command link:cmd-test-submit-rule.html[test-submit rule] loads a specific change
+and executes the `submit_rule`. It optionally reads the rule from from `stdin`
+to facilitate easy testing.
====
- cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
+ $ cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
====
== Prolog vs Gerrit plugin for project specific submit rules
@@ -317,9 +321,9 @@ From version 2.6 Gerrit plugins can contribute Prolog predicates. This way, we
can make use of the plugin provided predicates when writing Prolog based rules.
== Examples - Submit Rule
-The following examples should serve as a cookbook for developing own submit rules.
-Some of them are too trivial to be used in production and their only purpose is
-to provide step by step introduction and understanding.
+The following examples should serve as a cookbook for developing own submit
+rules. Some of them are too trivial to be used in production and their only
+purpose is to provide step by step introduction and understanding.
Some of the examples will implement the `submit_rule` and some will implement
the `submit_filter` just to show both possibilities. Remember that
@@ -328,49 +332,49 @@ invoked from all parent projects. This is the most important fact in deciding
whether to implement `submit_rule` or `submit_filter`.
=== Example 1: Make every change submittable
-Let's start with a most trivial example where we would make every change submittable
-regardless of the votes it has:
+Let's start with a most trivial example where we would make every change
+submittable regardless of the votes it has:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(W)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(W)) :-
W = label('Any-Label-Name', ok(_)).
-====
+----
-In this case we make no use of facts about the change. We don't need it as we are simply
-making every change submittable. Note that, in this case, the Gerrit UI will not show
-the UI for voting for the standard `'Code-Review'` and `'Verified'` categories as labels
-with these names are not part of the return result. The `'Any-Label-Name'` could really
-be any string.
+In this case we make no use of facts about the change. We don't need it as we
+are simply making every change submittable. Note that, in this case, the Gerrit
+UI will not show the UI for voting for the standard `'Code-Review'` and
+`'Verified'` categories as labels with these names are not part of the return
+result. The `'Any-Label-Name'` could really be any string.
=== Example 2: Every change submittable and voting in the standard categories possible
This is continuation of the previous example where, in addition, to making
every change submittable we want to enable voting in the standard
`'Code-Review'` and `'Verified'` categories.
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(CR, V)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(CR, V)) :-
CR = label('Code-Review', ok(_)),
V = label('Verified', ok(_)).
-====
+----
-Since for every change all label statuses are `'ok'` every change will be submittable.
-Voting in the standard labels will be shown in the UI as the standard label names are
-included in the return result.
+Since for every change all label statuses are `'ok'` every change will be
+submittable. Voting in the standard labels will be shown in the UI as the
+standard label names are included in the return result.
=== Example 3: Nothing is submittable
This example shows how to make all changes non-submittable regardless of the
votes they have.
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(R)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(R)) :-
R = label('Any-Label-Name', reject(_)).
-====
+----
Since for any change we return only one label with status `reject`, no change
will be submittable. The UI will, however, not indicate what is needed for a
@@ -380,67 +384,66 @@ change to become submittable as we return no labels with status `need`.
In this example no change is submittable but here we show how to present 'Need
<label>' information to the user in the UI.
-.rules.pl
-[caption=""]
-====
- % In the UI this will show: Need Any-Label-Name
- submit_rule(submit(N)) :-
+`rules.pl`
+[source,prolog]
+----
+% In the UI this will show: Need Any-Label-Name
+submit_rule(submit(N)) :-
N = label('Any-Label-Name', need(_)).
- % We could define more "need" labels by adding more rules
- submit_rule(submit(N)) :-
+% We could define more "need" labels by adding more rules
+submit_rule(submit(N)) :-
N = label('Another-Label-Name', need(_)).
- % or by providing more than one need label in the same rule
- submit_rule(submit(NX, NY)) :-
+% or by providing more than one need label in the same rule
+submit_rule(submit(NX, NY)) :-
NX = label('X-Label-Name', need(_)),
NY = label('Y-Label-Name', need(_)).
-====
+----
In the UI this will show:
-****
-* Need Any-Label-Name
-* Need Another-Label-Name
-* Need X-Label-Name
-* Need Y-Label-Name
-****
+
+* `Need Any-Label-Name`
+* `Need Another-Label-Name`
+* `Need X-Label-Name`
+* `Need Y-Label-Name`
From the example above we can see a few more things:
* comment in Prolog starts with the `%` character
-* there could be multiple `submit_rule` predicates. Since Prolog, by default, tries to find
- all solutions for a query, the result will be union of all solutions.
- Therefore, we see all 4 `need` labels in the UI.
+* there could be multiple `submit_rule` predicates. Since Prolog, by default,
+ tries to find all solutions for a query, the result will be union of all
+ solutions. Therefore, we see all 4 `need` labels in the UI.
=== Example 5: The 'Need ...' labels not shown when change is submittable
-This example shows that, when there is a solution for `submit_rule(X)` where all labels
-have status `ok` then Gerrit will not show any labels with the `need` status from
-any of the previous `submit_rule(X)` solutions.
-
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(N)) :-
+This example shows that, when there is a solution for `submit_rule(X)` where all
+labels have status `ok` then Gerrit will not show any labels with the `need`
+status from any of the previous `submit_rule(X)` solutions.
+
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(N)) :-
N = label('Some-Condition', need(_)).
- submit_rule(submit(OK)) :-
+submit_rule(submit(OK)) :-
OK = label('Another-Condition', ok(_)).
-====
+----
-The 'Need Some-Condition' will not be show in the UI because of the result of
+The `'Need Some-Condition'` will not be shown in the UI because of the result of
the second rule.
The same is valid if the two rules are swapped:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(OK)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(OK)) :-
OK = label('Another-Condition', ok(_)).
- submit_rule(submit(N)) :-
+submit_rule(submit(N)) :-
N = label('Some-Condition', need(_)).
-====
+----
The result of the first rule will stop search for any further solutions.
@@ -448,81 +451,80 @@ The result of the first rule will stop search for any further solutions.
This is the first example where we will use the Prolog facts about a change that
are automatically exposed by Gerrit. Our goal is to make any change submittable
when the commit author is named `'John Doe'`. In the very first
-step let's make sure Gerrit UI shows 'Need Author-is-John-Doe' in
+step let's make sure Gerrit UI shows `'Need Author-is-John-Doe'` in
the UI to clearly indicate to the user what is needed for a change to become
submittable:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Author)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)).
-====
+----
This will show:
-****
-* Need Author-is-John-Doe
-****
+
+* `Need Author-is-John-Doe`
in the UI but no change will be submittable yet. Let's add another rule:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Author)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)).
- submit_rule(submit(Author)) :-
+submit_rule(submit(Author)) :-
gerrit:commit_author(_, 'John Doe', _),
Author = label('Author-is-John-Doe', ok(_)).
-====
+----
In the second rule we return `ok` status for the `'Author-is-John-Doe'` label
if there is a `commit_author` fact where the full name is `'John Doe'`. If
author of a change is `'John Doe'` then the second rule will return a solution
where all labels have `ok` status and the change will become submittable. If
author of a change is not `'John Doe'` then only the first rule will produce a
-solution. The UI will show 'Need Author-is-John-Doe' but, as expected, the
+solution. The UI will show `'Need Author-is-John-Doe'` but, as expected, the
change will not be submittable.
Instead of checking by full name we could also check by the email address:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Author)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)).
- submit_rule(submit(Author)) :-
+submit_rule(submit(Author)) :-
gerrit:commit_author(_, _, 'john.doe@example.com'),
Author = label('Author-is-John-Doe', ok(_)).
-====
+----
-or by user id (assuming it is 1000000):
+or by user id (assuming it is `1000000`):
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Author)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)).
- submit_rule(submit(Author)) :-
+submit_rule(submit(Author)) :-
gerrit:commit_author(user(1000000), _, _),
Author = label('Author-is-John-Doe', ok(_)).
-====
+----
or by a combination of these 3 attributes:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Author)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)).
- submit_rule(submit(Author)) :-
+submit_rule(submit(Author)) :-
gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'),
Author = label('Author-is-John-Doe', ok(_)).
-====
+----
=== Example 7: Make change submittable if commit message starts with "Fix "
Besides showing how to make use of the commit message text the purpose of this
@@ -539,19 +541,19 @@ options:
Let's implement both options:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Fix)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)).
- submit_rule(submit(Fix)) :-
+submit_rule(submit(Fix)) :-
gerrit:commit_message(M), name(M, L), starts_with(L, "Fix "),
Fix = label('Commit-Message-starts-with-Fix', ok(_)).
- starts_with(L, []).
- starts_with([H|T1], [H|T2]) :- starts_with(T1, T2).
-====
+starts_with(L, []).
+starts_with([H|T1], [H|T2]) :- starts_with(T1, T2).
+----
NOTE: The `name/2` embedded predicate is used to convert a string symbol into a
list of characters. A string `abc` is converted into a list of characters `[97,
@@ -563,32 +565,33 @@ The `starts_with` predicate is self explaining.
Using the `gerrit:commit_message_matches` predicate is probably more efficient:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Fix)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)).
- submit_rule(submit(Fix)) :-
+submit_rule(submit(Fix)) :-
gerrit:commit_message_matches('^Fix '),
Fix = label('Commit-Message-starts-with-Fix', ok(_)).
-====
+----
The previous example could also be written so that it first checks if the commit
message starts with 'Fix '. If true then it sets OK for that category and stops
further backtracking by using the cut `!` operator:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(Fix)) :-
+
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(Fix)) :-
gerrit:commit_message_matches('^Fix '),
Fix = label('Commit-Message-starts-with-Fix', ok(_)),
!.
- % Message does not start with 'Fix ' so Fix is needed to submit
- submit_rule(submit(Fix)) :-
+% Message does not start with 'Fix ' so Fix is needed to submit
+submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)).
-====
+----
== The default submit policy
All examples until now concentrate on one particular aspect of change data.
@@ -605,44 +608,46 @@ done in one of the following ways:
The default submit rule with the two default categories, `Code-Review` and
`Verified`, can be implemented as:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(V, CR)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(V, CR)) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
-====
+----
Once this implementation is understood it can be customized to implement
project specific submit rules. Note, that this implementation hardcodes
the two default categories. Introducing a new category in the database would
require introducing the same category here or a `submit_filter` in a parent
project would have to care about including the new category in the result of
-this `submit_rule`. On the other side, this example is easy to read and
+this `submit_rule`. On the other side, this example is easy to read and
understand.
=== Reusing the default submit policy
To get results of Gerrit's default submit policy we use the
`gerrit:default_submit` predicate. The `gerrit:default_submit(X)` includes all
-categories from the database. This means that if we write a submit rule like:
+categories from the database. This means that if we write a submit rule like
+this:
-.rules.pl
-[caption=""]
-====
- submit_rule(X) :- gerrit:default_submit(X).
-====
-then this is equivalent to not using `rules.pl` at all. We just delegate to
+`rules.pl`
+[source,prolog]
+----
+submit_rule(X) :- gerrit:default_submit(X).
+----
+
+it is equivalent to not using `rules.pl` at all. We just delegate to
default logic. However, once we invoke the `gerrit:default_submit(X)` we can
perform further actions on the return result `X` and apply our specific
logic. The following pattern illustrates this technique:
-.rules.pl
-[caption=""]
-====
- submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S).
+`rules.pl`
+[source,prolog]
+----
+submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S).
- project_specific_policy(R, S) :- ...
-====
+project_specific_policy(R, S) :- ...
+----
In the following examples both styles will be shown.
@@ -658,26 +663,26 @@ submit policy and then add the `Non-Author-Code-Review` label to it. The
`Non-Author-Code-Review` label is added with status `ok` if such an approval
exists or with status `need` if it doesn't exist.
-.rules.pl
-[caption=""]
-====
- submit_rule(S) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(S) :-
gerrit:default_submit(X),
X =.. [submit | Ls],
add_non_author_approval(Ls, R),
S =.. [submit | R].
- add_non_author_approval(S1, S2) :-
+add_non_author_approval(S1, S2) :-
gerrit:commit_author(A),
gerrit:commit_label(label('Code-Review', 2), R),
R \= A, !,
S2 = [label('Non-Author-Code-Review', ok(R)) | S1].
- add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]).
-====
+add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]).
+----
This example uses the `univ` operator `=..` to "unpack" the result of the
default_submit, which is a structure of the form `submit(label('Code-Review',
-ok(_)), label('Verified', need(_)) ...)` into a list like `[submit,
+ok(_)), label('Verified', need(_)), ...)` into a list like `[submit,
label('Code-Review', ok(_)), label('Verified', need(_)), ...]`. Then we
process the tail of the list (the list of labels) as a Prolog list, which is
much easier than processing a structure. In the end we use the same `univ`
@@ -696,24 +701,24 @@ predicate before the `cut` fails.
Let's implement the same submit rule the other way, without reusing the
`gerrit:default_submit`:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(CR, V)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(CR, V)) :-
base(CR, V),
CR = label(_, ok(Reviewer)),
gerrit:commit_author(Author),
Author \= Reviewer,
!.
- submit_rule(submit(CR, V, N)) :-
+submit_rule(submit(CR, V, N)) :-
base(CR, V),
N = label('Non-Author-Code-Review', need(_)).
- base(CR, V) :-
+base(CR, V) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
-====
+----
The latter implementation is probably easier to understand and the code looks
cleaner. Note, however, that the latter implementation will always return the
@@ -729,53 +734,52 @@ invokes the `gerrit:default_submit` and then modifies its result.
Which of these two behaviors is desired will always depend on how a particular
Gerrit server is managed.
-Example 9: Remove the `Verified` category
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+==== Example 9: Remove the `Verified` category
A project has no build and test. It consists of only text files and needs only
code review. We want to remove the `Verified` category from this project so
that `Code-Review+2` is the only criteria for a change to become submittable.
We also want the UI to not show the `Verified` category in the table with
votes and on the voting screen.
-This is quite simple without reusing the 'gerrit:default_submit`:
+This is quite simple without reusing the `gerrit:default_submit`:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(CR)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(CR)) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR).
-====
+----
Implementing the same rule by reusing `gerrit:default_submit` is a bit more complex:
-.rules.pl
-[caption=""]
-====
- submit_rule(S) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(S) :-
gerrit:default_submit(X),
X =.. [submit | Ls],
remove_verified_category(Ls, R),
S =.. [submit | R].
- remove_verified_category([], []).
- remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !.
- remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
-====
+remove_verified_category([], []).
+remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !.
+remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
+----
=== Example 10: Combine examples 8 and 9
In this example we want to both remove the verified and have the four eyes
principle. This means we want a combination of examples 7 and 8.
-.rules.pl
-[caption=""]
-====
- submit_rule(S) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(S) :-
gerrit:default_submit(X),
X =.. [submit | Ls],
remove_verified_category(Ls, R1),
add_non_author_approval(R1, R),
S =.. [submit | R].
-====
+----
The `remove_verified_category` and `add_non_author_approval` predicates are the
same as defined in the previous two examples.
@@ -783,23 +787,23 @@ same as defined in the previous two examples.
Without reusing the `gerrit:default_submit` the same example may be implemented
as:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(CR)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(CR)) :-
base(CR),
CR = label(_, ok(Reviewer)),
gerrit:commit_author(Author),
Author \= Reviewer,
!.
- submit_rule(submit(CR, N)) :-
+submit_rule(submit(CR, N)) :-
base(CR),
N = label('Non-Author-Code-Review', need(_)).
- base(CR) :-
+base(CR) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
-====
+----
=== Example 11: Remove the `Verified` category from all projects
Example 9, implements `submit_rule` that removes the `Verified` category from
@@ -807,34 +811,34 @@ one project. In this example we do the same but we want to remove the `Verified`
category from all projects. This means we have to implement `submit_filter` and
we have to do that in the `rules.pl` of the `All-Projects` project.
-.rules.pl
-[caption=""]
-====
- submit_filter(In, Out) :-
+`rules.pl`
+[source,prolog]
+----
+submit_filter(In, Out) :-
In =.. [submit | Ls],
remove_verified_category(Ls, R),
Out =.. [submit | R].
- remove_verified_category([], []).
- remove_verified_category([label('Verified', _) | T], R) :-
- remove_verified_category(T, R), !.
- remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
-====
+remove_verified_category([], []).
+remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !.
+remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
+----
=== Example 12: On release branches require DrNo in addition to project rules
A new category 'DrNo' is added to the database and is required for release
-branches. To mark a branch as a release branch we use `drno('refs/heads/branch')`.
-
-.rules.pl
-[caption=""]
-====
- drno('refs/heads/master').
- drno('refs/heads/stable-2.3').
- drno('refs/heads/stable-2.4').
- drno('refs/heads/stable-2.5').
- drno('refs/heads/stable-2.5').
-
- submit_filter(In, Out) :-
+branches. To mark a branch as a release branch we use
+`drno('refs/heads/branch')`.
+
+`rules.pl`
+[source,prolog]
+----
+drno('refs/heads/master').
+drno('refs/heads/stable-2.3').
+drno('refs/heads/stable-2.4').
+drno('refs/heads/stable-2.5').
+drno('refs/heads/stable-2.5').
+
+submit_filter(In, Out) :-
gerrit:change_branch(Branch),
drno(Branch),
!,
@@ -842,129 +846,131 @@ branches. To mark a branch as a release branch we use `drno('refs/heads/branch')
gerrit:max_with_block(-1, 1, 'DrNo', DrNo),
Out =.. [submit, DrNo | I].
- submit_filter(In, Out) :- In = Out.
-====
+submit_filter(In, Out) :- In = Out.
+----
=== Example 13: 1+1=2 Code-Review
In this example we introduce accumulative voting to determine if a change is
-submittable or not. We modify the standard Code-Review to be accumulative, and make the
-change submittable if the total score is 2 or higher.
-
-The code in this example is very similar to Example 8, with the addition of findall/3
-and gerrit:remove_label.
-The findall/3 embedded predicate is used to form a list of all objects that satisfy a
-specified Goal. In this example it is used to get a list of all the 'Code-Review' scores.
-gerrit:remove_label is a built-in helper that is implemented similarly to the
-'remove_verified_category' as seen in the previous example.
-
-.rules.pl
-[caption=""]
-====
- sum_list([], 0).
- sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp.
+submittable or not. We modify the standard `Code-Review` to be accumulative, and
+make the change submittable if the total score is `2` or higher.
+
+The code in this example is very similar to Example 8, with the addition of
+`findall/3` and `gerrit:remove_label`.
+
+The `findall/3` embedded predicate is used to form a list of all objects that
+satisfy a specified Goal. In this example it is used to get a list of all the
+`Code-Review` scores. `gerrit:remove_label` is a built-in helper that is
+implemented similarly to the `remove_verified_category` as seen in the previous
+example.
- add_category_min_score(In, Category, Min, P) :-
+`rules.pl`
+[source,prolog]
+----
+sum_list([], 0).
+sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp.
+
+add_category_min_score(In, Category, Min, P) :-
findall(X, gerrit:commit_label(label(Category,X),R),Z),
sum_list(Z, Sum),
Sum >= Min, !,
P = [label(Category,ok(R)) | In].
- add_category_min_score(In, Category,Min,P) :-
+add_category_min_score(In, Category,Min,P) :-
P = [label(Category,need(Min)) | In].
- submit_rule(S) :-
+submit_rule(S) :-
gerrit:default_submit(X),
X =.. [submit | Ls],
gerrit:remove_label(Ls,label('Code-Review',_),NoCR),
add_category_min_score(NoCR,'Code-Review', 2, Labels),
S =.. [submit | Labels].
-====
+----
Implementing the same example without using `gerrit:default_submit`:
-.rules.pl
-[caption=""]
-====
- submit_rule(submit(CR, V)) :-
+`rules.pl`
+[source,prolog]
+----
+submit_rule(submit(CR, V)) :-
sum(2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
- % Sum the votes in a category. Uses a helper function score/2
- % to select out only the score values the given category.
- sum(VotesNeeded, Category, label(Category, ok(_))) :-
+% Sum the votes in a category. Uses a helper function score/2
+% to select out only the score values the given category.
+sum(VotesNeeded, Category, label(Category, ok(_))) :-
findall(Score, score(Category, Score), All),
sum_list(All, Sum),
Sum >= VotesNeeded,
!.
- sum(VotesNeeded, Category, label(Category, need(VotesNeeded))).
+sum(VotesNeeded, Category, label(Category, need(VotesNeeded))).
- score(Category, Score) :-
+score(Category, Score) :-
gerrit:commit_label(label(Category, Score), User).
- % Simple Prolog routine to sum a list of integers.
- sum_list(List, Sum) :- sum_list(List, 0, Sum).
- sum_list([X|T], Y, S) :- Z is X + Y, sum_list(T, Z, S).
- sum_list([], S, S).
-====
+% Simple Prolog routine to sum a list of integers.
+sum_list(List, Sum) :- sum_list(List, 0, Sum).
+sum_list([X|T], Y, S) :- Z is X + Y, sum_list(T, Z, S).
+sum_list([], S, S).
+----
=== Example 14: Master and apprentice
The master and apprentice example allow you to specify a user (the `master`)
that must approve all changes done by another user (the `apprentice`).
The code first checks if the commit author is in the apprentice database.
-If the commit is done by an apprentice, it will check if there is a +2
+If the commit is done by an `apprentice`, it will check if there is a `+2`
review by the associated `master`.
-.rules.pl
-[caption=""]
-====
- % master_apprentice(Master, Apprentice).
- % Extend this with appropriate user-id's for your master/apprentice setup.
- master_apprentice(user(1000064), user(1000000)).
+`rules.pl`
+[source,prolog]
+----
+% master_apprentice(Master, Apprentice).
+% Extend this with appropriate user-id for your master/apprentice setup.
+master_apprentice(user(1000064), user(1000000)).
- submit_rule(S) :-
+submit_rule(S) :-
gerrit:default_submit(In),
In =.. [submit | Ls],
add_apprentice_master(Ls, R),
S =.. [submit | R].
- check_master_approval(S1, S2, Master) :-
+check_master_approval(S1, S2, Master) :-
gerrit:commit_label(label('Code-Review', 2), R),
R = Master, !,
S2 = [label('Master-Approval', ok(R)) | S1].
- check_master_approval(S1, [label('Master-Approval', need(_)) | S1], _).
+check_master_approval(S1, [label('Master-Approval', need(_)) | S1], _).
- add_apprentice_master(S1, S2) :-
+add_apprentice_master(S1, S2) :-
gerrit:commit_author(Id),
master_apprentice(Master, Id),
!,
check_master_approval(S1, S2, Master).
- add_apprentice_master(S, S).
-====
+add_apprentice_master(S, S).
+----
=== Example 15: Only allow Author to submit change
-This example adds a new needed category `Patchset-Author` for any user that is
-not the author of the patch. This effectively blocks all users except the author
-from submitting the change. This could result in an impossible situation if the
-author does not have permissions for submitting the change.
-
-.rules.pl
-[caption=""]
-====
- submit_rule(S) :-
+This example adds a new needed category `Only-Author-Can-Submit` for any user
+that is not the author of the patch. This effectively blocks all users except
+the author from submitting the change. This could result in an impossible
+situation if the author does not have permissions for submitting the change.
+
+`rules.pl`
+[source,prolog]
+----
+submit_rule(S) :-
gerrit:default_submit(In),
In =.. [submit | Ls],
only_allow_author_to_submit(Ls, R),
S =.. [submit | R].
- only_allow_author_to_submit(S, S) :-
+only_allow_author_to_submit(S, S) :-
gerrit:commit_author(Id),
gerrit:current_user(Id),
!.
- only_allow_author_to_submit(S1, [label('Patchset-Author', need(_)) | S1]).
-====
+only_allow_author_to_submit(S1, [label('Only-Author-Can-Submit', need(_)) | S1]).
+----
== Examples - Submit Type
The following examples show how to implement own submit type rules.
@@ -974,48 +980,47 @@ This example sets the `Cherry Pick` submit type for all changes. It overrides
whatever is set as project default submit type.
rules.pl
-[caption=""]
-====
- submit_type(cherry_pick).
-====
-
+[source,prolog]
+----
+submit_type(cherry_pick).
+----
[[SubmitTypePerBranch]]
-=== Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches
-For all `refs/heads/stable.*` branches we would like to enforce the `Fast
+=== Example 2: `Fast Forward Only` for all `+refs/heads/stable*+` branches
+For all `+refs/heads/stable*+` branches we would like to enforce the `Fast
Forward Only` submit type. A reason for this decision may be a need to never
break the build in the stable branches. For all other branches, those not
-matching the `refs/heads/stable.*` pattern, we would like to use the project's
+matching the `+refs/heads/stable*+` pattern, we would like to use the project's
default submit type as defined on the project settings page.
-.rules.pl
-[caption=""]
-====
- submit_type(fast_forward_only) :-
+`rules.pl`
+[source,prolog]
+----
+submit_type(fast_forward_only) :-
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
!.
- submit_type(T) :- gerrit:project_default_submit_type(T)
-====
+submit_type(T) :- gerrit:project_default_submit_type(T)
+----
The first `submit_type` predicate defines the `Fast Forward Only` submit type
-for `refs/heads/stable.*` branches. The second `submit_type` predicate returns
+for `+refs/heads/stable.*+` branches. The second `submit_type` predicate returns
the project's default submit type.
=== Example 3: Don't require `Fast Forward Only` if only documentation was changed
-Like in the previous example we want the `Fast Forward Only` submit type for
-the `refs/heads/stable*` branches. However, if only documentation was changed
-(only `*.txt` files), then we allow project's default submit type for such
+Like in the previous example we want the `Fast Forward Only` submit type for the
+`+refs/heads/stable*+` branches. However, if only documentation was changed
+(only `+*.txt+` files), then we allow project's default submit type for such
changes.
-.rules.pl
-[caption=""]
-====
- submit_type(fast_forward_only) :-
+`rules.pl`
+[source,prolog]
+----
+submit_type(fast_forward_only) :-
gerrit:commit_delta('(?<!\.txt)$'),
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
!.
- submit_type(T) :- gerrit:project_default_submit_type(T)
-====
+submit_type(T) :- gerrit:project_default_submit_type(T)
+----
The `gerrit:commit_delta('(?<!\.txt)$')` succeeds if the change contains a file
whose name doesn't end with `.txt` The rest of this rule is same like in the
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index 5cdfe9125c..8deee620ff 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -193,6 +193,10 @@ opts = OptionParser()
opts.add_option('-o', '--out', help='output file')
opts.add_option('-s', '--src', help='source file')
opts.add_option('-x', '--suffix', help='suffix for included filenames')
+opts.add_option('-b', '--searchbox', action="store_true", default=True,
+ help="generate the search boxes")
+opts.add_option('--no-searchbox', action="store_false", dest='searchbox',
+ help="don't generate the search boxes")
options, _ = opts.parse_args()
try:
@@ -208,7 +212,8 @@ try:
last_line = ''
elif PAT_SEARCHBOX.match(last_line):
# Case of 'SEARCHBOX\n---------'
- out_file.write(SEARCH_BOX)
+ if options.searchbox:
+ out_file.write(SEARCH_BOX)
last_line = ''
elif PAT_INCLUDE.match(line):
# Case of 'include::<filename>'
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt
index 42214fed4f..ee3e8ce1d0 100644
--- a/Documentation/rest-api-access.txt
+++ b/Documentation/rest-api-access.txt
@@ -30,7 +30,7 @@ The entries in the map are sorted by project name.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -284,12 +284,15 @@ The entries in the map are sorted by project name.
}
----
+[[json-entities]]
+== JSON Entities
+
[[access-section-info]]
=== AccessSectionInfo
The `AccessSectionInfo` describes the access rights that are assigned
on a ref.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`permissions` ||
@@ -303,7 +306,7 @@ entities.
The `PermissionInfo` entity contains information about an assigned
permission.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`label` |optional|
@@ -321,7 +324,7 @@ link:#permission-info[PermissionRuleInfo] entities.
The `PermissionRuleInfo` entity contains information about a permission
rule that is assigned to group.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`action` ||
@@ -343,7 +346,7 @@ The max value of the permission range.
The `ProjectAccessInfo` entity contains information about the access
rights for a project.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`revision` ||
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 201b020fb3..7aec531b7a 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -7,6 +7,44 @@ link:rest-api.html[REST API].
[[account-endpoints]]
== Account Endpoints
+[[suggest-account]]
+=== Suggest Account
+--
+'GET /accounts/'
+--
+
+Suggest users for a given query `q` and result limit `n`. If result
+limit is not passed, then the default 10 is used. Returns a list of
+matching link:#account-info[AccountInfo] entities.
+
+.Request
+----
+ GET /accounts/?q=John HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "_account_id": 1000096,
+ "name": "John Doe",
+ "email": "john.doe@example.com",
+ "username": "john"
+ },
+ {
+ "_account_id": 1001439,
+ "name": "John Smith",
+ "email": "john.smith@example.com",
+ "username": "jsmith"
+ },
+ ]
+----
+
[[get-account]]
=== Get Account
--
@@ -24,7 +62,7 @@ Returns an account as an link:#account-info[AccountInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -49,7 +87,7 @@ link:#account-input[AccountInput].
.Request
----
PUT /accounts/john HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"name": "John Doe",
@@ -69,7 +107,7 @@ returned that describes the created account.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -96,7 +134,7 @@ Retrieves the full name of an account.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"John Doe"
@@ -118,7 +156,7 @@ an link:#account-name-input[AccountNameInput] entity.
.Request
----
PUT /accounts/self/name HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"name": "John F. Doe"
@@ -131,7 +169,7 @@ As response the new account name is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"John F. Doe"
@@ -177,13 +215,13 @@ Retrieves the username of an account.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"john.doe"
----
-If the account does not have a username the response is `404 Not Found`.
+If the account does not have a username the response is "`404 Not Found`".
[[get-active]]
=== Get Active
@@ -207,7 +245,7 @@ If the account is active the string `ok` is returned.
ok
----
-If the account is inactive the response is `204 No Content`.
+If the account is inactive the response is "`204 No Content`".
[[set-active]]
=== Set Active
@@ -227,7 +265,7 @@ Sets the account state to active.
HTTP/1.1 201 Created
----
-If the account was already active the response is `200 OK`.
+If the account was already active the response is "`200 OK`".
[[delete-active]]
=== Delete Active
@@ -247,7 +285,7 @@ Sets the account state to inactive.
HTTP/1.1 204 No Content
----
-If the account was already inactive the response is `404 Not Found`.
+If the account was already inactive the response is "`404 Not Found`".
[[get-http-password]]
=== Get HTTP Password
@@ -266,13 +304,13 @@ Retrieves the HTTP password of an account.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"Qmxlc21ydCB1YmVyIGFsbGVzIGluIGRlciBXZWx0IQ"
----
-If the account does not have an HTTP password the response is `404 Not Found`.
+If the account does not have an HTTP password the response is "`404 Not Found`".
[[set-http-password]]
=== Set/Generate HTTP Password
@@ -289,7 +327,7 @@ HttpPasswordInput] entity.
.Request
----
PUT /accounts/self/password.http HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"generate": true
@@ -302,7 +340,7 @@ As response the new HTTP password is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"ETxgpih8xrNs"
@@ -348,7 +386,7 @@ link:#email-info[EmailInfo] entities.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -382,7 +420,7 @@ describes the email address.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -420,7 +458,7 @@ link:#email-info[EmailInfo] entity.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -488,7 +526,7 @@ link:#ssh-key-info[SshKeyInfo] entities.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -523,7 +561,7 @@ describes the SSH key.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -561,7 +599,7 @@ describes the new SSH key.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -618,7 +656,7 @@ link:#capability-info[CapabilityInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -642,7 +680,7 @@ Administrator that has authenticated with digest authentication:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -684,7 +722,7 @@ possible alternative for the caller.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -721,7 +759,7 @@ If the user has the global capability the string `ok` is returned.
----
If the user doesn't have the global capability the response is
-`404 Not Found`.
+"`404 Not Found`".
.Check if you can create groups
****
@@ -748,7 +786,7 @@ entries is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -828,7 +866,7 @@ Retrieves the URL where the user can change the avatar image.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
https://profiles/pictures/john.doe
----
@@ -853,7 +891,7 @@ link:#preferences-info[PreferencesInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -864,7 +902,6 @@ link:#preferences-info[PreferencesInfo] entity.
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
"review_category_strategy": "ABBREV",
- "comment_visibility_strategy": "EXPAND_RECENT",
"diff_view": "SIDE_BY_SIDE",
"my": [
{
@@ -872,7 +909,7 @@ link:#preferences-info[PreferencesInfo] entity.
"name": "Changes"
},
{
- "url": "#/q/is:draft",
+ "url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
@@ -909,7 +946,7 @@ link:#preferences-input[PreferencesInput] entity.
.Request
----
PUT /a/accounts/self/preferences HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"changes_per_page": 50,
@@ -919,7 +956,6 @@ link:#preferences-input[PreferencesInput] entity.
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
"review_category_strategy": "NAME",
- "comment_visibility_strategy": "EXPAND_RECENT",
"diff_view": "SIDE_BY_SIDE",
"my": [
{
@@ -927,7 +963,7 @@ link:#preferences-input[PreferencesInput] entity.
"name": "Changes"
},
{
- "url": "#/q/is:draft",
+ "url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
@@ -957,7 +993,7 @@ link:#preferences-info[PreferencesInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -968,7 +1004,6 @@ link:#preferences-info[PreferencesInfo] entity.
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
"review_category_strategy": "NAME",
- "comment_visibility_strategy": "EXPAND_RECENT",
"diff_view": "SIDE_BY_SIDE",
"my": [
{
@@ -976,7 +1011,7 @@ link:#preferences-info[PreferencesInfo] entity.
"name": "Changes"
},
{
- "url": "#/q/is:draft",
+ "url": "#/q/owner:self+is:draft",
"name": "Drafts"
},
{
@@ -1019,11 +1054,12 @@ link:#diff-preferences-info[DiffPreferencesInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
"context": 10,
+ "theme": "DEFAULT",
"ignore_whitespace": "IGNORE_ALL_SPACE",
"intraline_difference": true,
"line_length": 100,
@@ -1048,10 +1084,11 @@ link:#diff-preferences-input[DiffPreferencesInput] entity.
.Request
----
GET /a/accounts/self/preferences.diff HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"context": 10,
+ "theme": "ECLIPSE",
"ignore_whitespace": "IGNORE_ALL_SPACE",
"intraline_difference": true,
"line_length": 100,
@@ -1070,11 +1107,12 @@ link:#diff-preferences-info[DiffPreferencesInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
"context": 10,
+ "theme": "ECLIPSE",
"ignore_whitespace": "IGNORE_ALL_SPACE",
"intraline_difference": true,
"line_length": 100,
@@ -1106,7 +1144,7 @@ link:rest-api-changes.html#change-info[ChangeInfo] entities.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1120,7 +1158,6 @@ link:rest-api-changes.html#change-info[ChangeInfo] entities.
"created": "2013-02-01 09:59:32.126000000",
"updated": "2013-02-21 11:16:36.775000000",
"mergeable": true,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -1152,7 +1189,7 @@ whenever updates are made to the change.
[[unstar-change]]
=== Unstar Change
--
-'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes#change-id[\{change-id\}]'
+'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
--
Unstar a change. Removes the starred flag, stopping notifications.
@@ -1209,7 +1246,7 @@ The sequence number of the SSH key.
=== AccountInfo
The `AccountInfo` entity contains information about an account.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`_account_id` ||The numeric ID of the account.
@@ -1230,7 +1267,7 @@ account information] is requested.
The `AccountInput` entity contains information for the creation of
a new account.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|============================
|Field Name ||Description
|`username` |optional|
@@ -1249,7 +1286,7 @@ the groups to which the user should be added.
The `AccountNameInput` entity contains information for setting a name
for an account.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`name` |optional|The new full name of the account. +
@@ -1261,7 +1298,7 @@ If not set or if set to an empty string, the account name is deleted.
The `CapabilityInfo` entity contains information about the global
capabilities of a user.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|=================================
|Field Name ||Description
|`accessDatabase` |not set if `false`|Whether the user has the
@@ -1314,151 +1351,63 @@ link:access-control.html#capability_viewPlugins[View Plugins] capability.
link:access-control.html#capability_viewQueue[View Queue] capability.
|=================================
-[[preferences-info]]
-=== PreferencesInfo
-The `PreferencesInfo` entity contains information about a user's preferences.
-
-[options="header",width="50%",cols="1,^1,5"]
-|=====================================
-|Field Name ||Description
-|`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.
-|`download_scheme` ||
-The type of download URL the user prefers to use.
-|`download_command` ||
-The type of download command the user prefers to use.
-|`copy_self_on_email` |not set if `false`|
-Whether to CC me on comments I write.
-|`date_format` ||
-The format to display the date in.
-Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`.
-|`time_format` ||
-The format to display the time in.
-Allowed values are `HHMM_12`, `HHMM_24`.
-|`reverse_patch_set_order` |not set if `false`|
-Whether to display the patch sets in reverse order.
-|`relative_date_in_change_table` |not set if `false`|
-Whether to show relative dates in the changes table.
-|`size_bar_in_change_table` |not set if `false`|
-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`.
-|`comment_visibility_strategy` ||
-The strategy used to display the comments.
-Allowed values are `COLLAPSE_ALL`, `EXPAND_MOST_RECENT`, `EXPAND_RECENT`, `EXPAND_ALL`.
-|`diff_view` ||
-The type of diff view to show.
-Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
-|`change_screen` ||
-The change screen to use.
-Allowed values are `OLD_UI`, `CHANGE_SCREEN2`.
-|=====================================
-
-[[preferences-input]]
-=== PreferencesInput
-The `PreferencesInput` entity contains information for setting the
-user preferences. Fields which are not set will not be updated.
-
-[options="header",width="50%",cols="1,^1,5"]
-|=====================================
-|Field Name ||Description
-|`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.
-|`download_scheme` |optional|
-The type of download URL the user prefers to use.
-|`download_command` |optional|
-The type of download command the user prefers to use.
-|`copy_self_on_email` |optional|
-Whether to CC me on comments I write.
-|`date_format` |optional|
-The format to display the date in.
-Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`.
-|`time_format` |optional|
-The format to display the time in.
-Allowed values are `HHMM_12`, `HHMM_24`.
-|`reverse_patch_set_order` |optional|
-Whether to display the patch sets in reverse order.
-|`relative_date_in_change_table` |optional|
-Whether to show relative dates in the changes table.
-|`size_bar_in_change_table` |optional|
-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`.
-|`comment_visibility_strategy` |optional|
-The strategy used to display the comments.
-Allowed values are `COLLAPSE_ALL`, `EXPAND_MOST_RECENT`, `EXPAND_RECENT`, `EXPAND_ALL`.
-|`diff_view` |optional|
-The type of diff view to show.
-Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
-|`change_screen` |optional|
-The change screen to use.
-Allowed values are `OLD_UI`, `CHANGE_SCREEN2`.
-|=====================================
-
[[diff-preferences-info]]
=== DiffPreferencesInfo
The `DiffPreferencesInfo` entity contains information about the diff
preferences of a user.
-[options="header",width="50%",cols="1,^1,5"]
-|=====================================
-|Field Name ||Description
-|`context` ||
+[options="header",cols="1,^1,5"]
+|===========================================
+|Field Name ||Description
+|`context` ||
The number of lines of context when viewing a patch.
-|`expand_all_comments` |not set if `false`|
+|`theme` ||
+The CodeMirror theme. Currently only a subset of light and dark
+CodeMirror themes are supported.
+|`expand_all_comments` |not set if `false`|
Whether all inline comments should be automatically expanded.
-|`ignore_whitespace` ||
+|`ignore_whitespace` ||
Whether whitespace changes should be ignored and if yes, which
whitespace changes should be ignored. +
Allowed values are `IGNORE_NONE`, `IGNORE_SPACE_AT_EOL`,
`IGNORE_SPACE_CHANGE`, `IGNORE_ALL_SPACE`.
-|`intraline_difference` |not set if `false`|
+|`intraline_difference` |not set if `false`|
Whether intraline differences should be highlighted.
-|`line_length` ||
+|`line_length` ||
Number of characters that should be displayed in one line.
-|`manual_review` |not set if `false`|
+|`manual_review` |not set if `false`|
Whether the 'Reviewed' flag should not be set automatically on a patch
when it is viewed.
-|`retain_header` |not set if `false`|
+|`retain_header` |not set if `false`|
Whether the header that is displayed above the patch (that either shows
the commit message, the diff preferences, the patch sets or the files)
should be retained on file switch.
-|`show_line_endings` |not set if `false`|
+|`show_line_endings` |not set if `false`|
Whether Windows EOL/Cr-Lf should be displayed as '\r' in a dotted-line
box.
-|`show_tabs` |not set if `false`|
+|`show_tabs` |not set if `false`|
Whether tabs should be shown.
-|`show_whitespace_errors`|not set if `false`|
+|`show_whitespace_errors` |not set if `false`|
Whether whitespace errors should be shown.
-|`skip_deleted` |not set if `false`|
+|`skip_deleted` |not set if `false`|
Whether deleted files should be skipped on file switch.
-|`skip_uncommented` |not set if `false`|
+|`skip_uncommented` |not set if `false`|
Whether uncommented files should be skipped on file switch.
-|`syntax_highlighting` |not set if `false`|
+|`syntax_highlighting` |not set if `false`|
Whether syntax highlighting should be enabled.
-|`hide_top_menu` |not set if `false`|
+|`hide_top_menu` |not set if `false`|
If true the top menu header and site header is hidden.
-|`hide_line_numbers` |not set if `false`|
+|`auto_hide_diff_table_header` |not set if `false`|
+If true the diff table header is automatically hidden when
+scrolling down more than half of a page.
+|`hide_line_numbers` |not set if `false`|
If true the line numbers are hidden.
-|`tab_size` ||
+|`tab_size` ||
Number of spaces that should be used to display one tab.
-|=====================================
+|'hide_empty_pane' |not set if `false`|
+Whether empty panes should be hidden. The left pane is empty when a
+file was added; the right pane is empty when a file was deleted.
+|===========================================
[[diff-preferences-input]]
=== DiffPreferencesInput
@@ -1466,56 +1415,59 @@ The `DiffPreferencesInput` entity contains information for setting the
diff preferences of a user. Fields which are not set will not be
updated.
-[options="header",width="50%",cols="1,^1,5"]
-|=====================================
-|Field Name ||Description
-|`context` |optional|
+[options="header",cols="1,^1,5"]
+|===========================================
+|Field Name ||Description
+|`context` |optional|
The number of lines of context when viewing a patch.
-|`expand_all_comments` |optional|
+|`expand_all_comments` |optional|
Whether all inline comments should be automatically expanded.
-|`ignore_whitespace` |optional|
+|`ignore_whitespace` |optional|
Whether whitespace changes should be ignored and if yes, which
whitespace changes should be ignored. +
Allowed values are `IGNORE_NONE`, `IGNORE_SPACE_AT_EOL`,
`IGNORE_SPACE_CHANGE`, `IGNORE_ALL_SPACE`.
-|`intraline_difference` |optional|
+|`intraline_difference` |optional|
Whether intraline differences should be highlighted.
-|`line_length` |optional|
+|`line_length` |optional|
Number of characters that should be displayed in one line.
-|`manual_review` |optional|
+|`manual_review` |optional|
Whether the 'Reviewed' flag should not be set automatically on a patch
when it is viewed.
-|`retain_header` |optional|
+|`retain_header` |optional|
Whether the header that is displayed above the patch (that either shows
the commit message, the diff preferences, the patch sets or the files)
should be retained on file switch.
-|`show_line_endings` |optional|
+|`show_line_endings` |optional|
Whether Windows EOL/Cr-Lf should be displayed as '\r' in a dotted-line
box.
-|`show_tabs` |optional|
+|`show_tabs` |optional|
Whether tabs should be shown.
-|`show_whitespace_errors`|optional|
+|`show_whitespace_errors` |optional|
Whether whitespace errors should be shown.
-|`skip_deleted` |optional|
+|`skip_deleted` |optional|
Whether deleted files should be skipped on file switch.
-|`skip_uncommented` |optional|
+|`skip_uncommented` |optional|
Whether uncommented files should be skipped on file switch.
-|`syntax_highlighting` |optional|
+|`syntax_highlighting` |optional|
Whether syntax highlighting should be enabled.
-|`hide_top_menu` |optional|
+|`hide_top_menu` |optional|
True if the top menu header and site header should be hidden.
-|`hide_line_numbers` |optional|
+|`auto_hide_diff_table_header` |optional|
+True if the diff table header is automatically hidden when
+scrolling down more than half of a page.
+|`hide_line_numbers` |optional|
True if the line numbers should be hidden.
-|`tab_size` |optional|
+|`tab_size` |optional|
Number of spaces that should be used to display one tab.
-|=====================================
+|===========================================
[[email-info]]
=== EmailInfo
The `EmailInfo` entity contains information about an email address of a
user.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|========================
|Field Name ||Description
|`email` ||The email address.
@@ -1532,7 +1484,7 @@ this address.
The `EmailInput` entity contains information for registering a new
email address.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==============================
|Field Name ||Description
|`email` ||
@@ -1554,7 +1506,7 @@ confirmation.
The `HttpPasswordInput` entity contains information for setting/generating
an HTTP password.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|============================
|Field Name ||Description
|`generate` |`false` if not set|
@@ -1566,12 +1518,97 @@ If empty or not set and `generate` is false or not set, the HTTP
password is deleted.
|============================
+[[preferences-info]]
+=== PreferencesInfo
+The `PreferencesInfo` entity contains information about a user's preferences.
+
+[options="header",cols="1,^1,5"]
+|=====================================
+|Field Name ||Description
+|`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.
+|`download_scheme` ||
+The type of download URL the user prefers to use.
+|`download_command` ||
+The type of download command the user prefers to use.
+|`copy_self_on_email` |not set if `false`|
+Whether to CC me on comments I write.
+|`date_format` ||
+The format to display the date in.
+Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`.
+|`time_format` ||
+The format to display the time in.
+Allowed values are `HHMM_12`, `HHMM_24`.
+|`relative_date_in_change_table` |not set if `false`|
+Whether to show relative dates in the changes table.
+|`size_bar_in_change_table` |not set if `false`|
+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.
+|`mute_common_path_prefixes` |not set if `false`|
+Whether to mute common path prefixes in file names in the file table.
+|`review_category_strategy` ||
+The strategy used to displayed info in the review category column.
+Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
+|`diff_view` ||
+The type of diff view to show.
+Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
+|=====================================
+
+[[preferences-input]]
+=== PreferencesInput
+The `PreferencesInput` entity contains information for setting the
+user preferences. Fields which are not set will not be updated.
+
+[options="header",cols="1,^1,5"]
+|=====================================
+|Field Name ||Description
+|`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.
+|`download_scheme` |optional|
+The type of download URL the user prefers to use.
+|`download_command` |optional|
+The type of download command the user prefers to use.
+|`copy_self_on_email` |optional|
+Whether to CC me on comments I write.
+|`date_format` |optional|
+The format to display the date in.
+Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`.
+|`time_format` |optional|
+The format to display the time in.
+Allowed values are `HHMM_12`, `HHMM_24`.
+|`relative_date_in_change_table` |optional|
+Whether to show relative dates in the changes table.
+|`size_bar_in_change_table` |optional|
+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.
+|`mute_common_path_prefixes` |optional|
+Whether to mute common path prefixes in file names in the file table.
+|`review_category_strategy` |optional|
+The strategy used to displayed info in the review category column.
+Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
+|`diff_view` |optional|
+The type of diff view to show.
+Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
+|=====================================
+
[[query-limit-info]]
=== QueryLimitInfo
The `QueryLimitInfo` entity contains information about the
link:access-control.html#capability_queryLimit[Query Limit] of a user.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|================================
|Field Name |Description
|`min` |Lower limit.
@@ -1583,7 +1620,7 @@ link:access-control.html#capability_queryLimit[Query Limit] of a user.
The `SshKeyInfo` entity contains information about an SSH key of a
user.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|=============================
|Field Name ||Description
|`seq` ||The sequence number of the SSH key.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index e5fd1b6edc..1b9a705886 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -21,7 +21,7 @@ mandatory. Valid values for status are: `DRAFT` and `NEW`.
.Request
----
POST /changes HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"project" : "myProject",
@@ -39,7 +39,7 @@ the resulting change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -55,7 +55,6 @@ the resulting change.
"mergeable": true,
"insertions": 0,
"deletions": 0,
- "_sortkey": "002cbc25000004e5",
"_number": 4711,
"owner": {
"name": "John Doe"
@@ -89,7 +88,7 @@ Query for open changes of watched projects:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -105,7 +104,6 @@ Query for open changes of watched projects:
"mergeable": true,
"insertions": 26,
"deletions": 10,
- "_sortkey": "001e7057000006dc",
"_number": 1756,
"owner": {
"name": "John Doe"
@@ -123,7 +121,6 @@ Query for open changes of watched projects:
"mergeable": true,
"insertions": 12,
"deletions": 18,
- "_sortkey": "001e7056000006dd",
"_number": 1757,
"owner": {
"name": "John Doe"
@@ -160,7 +157,7 @@ Query that retrieves changes for a user's dashboard:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -177,7 +174,6 @@ Query that retrieves changes for a user's dashboard:
"mergeable": true,
"insertions": 4,
"deletions": 7,
- "_sortkey": "001e7057000006dc",
"_number": 1756,
"owner": {
"name": "John Doe"
@@ -285,8 +281,15 @@ default. Optional fields are:
[[actions]]
--
* `CURRENT_ACTIONS`: include information on available actions
- for the change and its current revision. The caller must be
- authenticated to obtain the available actions.
+ for the change and its current revision. Ignored if the caller
+ is not authenticated.
+--
+
+[[change-actions]]
+--
+* `CHANGE_ACTIONS`: include information on available
+ change actions for the change. Ignored if the caller
+ is not authenticated.
--
[[reviewed]]
@@ -295,9 +298,16 @@ default. Optional fields are:
authenticated and has commented on the current revision.
--
-[[patch-set-links]]
+[[web-links]]
+--
+* `WEB_LINKS`: include the `web_links` field in link:#commit-info[CommitInfo],
+ therefore only valid in combination with `CURRENT_COMMIT` or
+ `ALL_COMMITS`.
+--
+
+[[check]]
--
-* `PATCHSET_LINKS`: include the `web_links` field.
+* `CHECK`: include potential problems with the change.
--
.Request
@@ -309,7 +319,7 @@ default. Optional fields are:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -325,7 +335,6 @@ default. Optional fields are:
"mergeable": true,
"insertions": 16,
"deletions": 7,
- "_sortkey": "001c9bf400000061",
"_number": 97,
"owner": {
"name": "Shawn Pearce"
@@ -334,6 +343,7 @@ default. Optional fields are:
"revisions": {
"184ebe53805e102605d11f6b143486d15c23a09c": {
"_number": 1,
+ "ref": "refs/changes/97/97/1",
"fetch": {
"git": {
"url": "git://localhost/gerrit",
@@ -446,7 +456,7 @@ describes the change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -461,7 +471,6 @@ describes the change.
"mergeable": true,
"insertions": 34,
"deletions": 101,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -499,7 +508,7 @@ REJECTED > APPROVED > DISLIKED > RECOMMENDED.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -514,7 +523,6 @@ REJECTED > APPROVED > DISLIKED > RECOMMENDED.
"mergeable": true,
"insertions": 126,
"deletions": 11,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"_account_id": 1000096,
@@ -652,7 +660,7 @@ Retrieves the topic of a change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"Documentation"
@@ -674,7 +682,7 @@ link:#topic-input[TopicInput] entity.
.Request
----
PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"topic": "Documentation"
@@ -687,7 +695,7 @@ As response the new topic is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"Documentation"
@@ -743,7 +751,7 @@ describes the abandoned change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -758,7 +766,6 @@ describes the abandoned change.
"mergeable": true,
"insertions": 3,
"deletions": 310,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -774,7 +781,7 @@ the error message is contained in the response body.
----
HTTP/1.1 409 Conflict
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
change is merged
----
@@ -802,7 +809,7 @@ describes the restored change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -817,7 +824,6 @@ describes the restored change.
"mergeable": true,
"insertions": 2,
"deletions": 13,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -833,7 +839,7 @@ the error message is contained in the response body.
----
HTTP/1.1 409 Conflict
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
change is new
----
@@ -846,9 +852,17 @@ the error message is contained in the response body.
Rebases a change.
+Optionally, the parent revision can be changed to another patch set through the
+link:#rebase-input[RebaseInput] entity.
+
.Request
----
POST /changes/myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2/rebase HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "base" : "1234",
+ }
----
As response a link:#change-info[ChangeInfo] entity is returned that
@@ -859,7 +873,7 @@ is included.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -874,7 +888,6 @@ is included.
"mergeable": false,
"insertions": 33,
"deletions": 9,
- "_sortkey": "0024cf9a000012bf",
"_number": 4799,
"owner": {
"name": "John Doe"
@@ -883,6 +896,7 @@ is included.
"revisions": {
"27cc4558b5a3d3387dd11ee2df7a117e7e581822": {
"_number": 2,
+ "ref": "refs/changes/99/4799/2",
"fetch": {
"http": {
"url": "http://gerrit:8080/myProject",
@@ -923,7 +937,7 @@ body.
----
HTTP/1.1 409 Conflict
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
The change could not be rebased due to a path conflict during merge.
----
@@ -951,7 +965,7 @@ describes the reverting change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -966,7 +980,6 @@ describes the reverting change.
"mergeable": true,
"insertions": 6,
"deletions": 4,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -982,7 +995,7 @@ the error message is contained in the response body.
----
HTTP/1.1 409 Conflict
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
change is new
----
@@ -1002,7 +1015,7 @@ complete.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/submit HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"wait_for_merge": true
@@ -1016,7 +1029,7 @@ describes the submitted/merged change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1028,7 +1041,6 @@ describes the submitted/merged change.
"status": "MERGED",
"created": "2013-02-01 09:59:32.126000000",
"updated": "2013-02-21 11:16:36.775000000",
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -1044,7 +1056,7 @@ message is contained in the response body.
----
HTTP/1.1 409 Conflict
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
blocked by Verified
----
@@ -1060,7 +1072,7 @@ Publishes a draft change.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
----
.Response
@@ -1079,7 +1091,7 @@ Deletes a draft change.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
----
.Response
@@ -1105,7 +1117,7 @@ an link:#included-in-info[IncludedInInfo] entity is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1124,10 +1136,6 @@ an link:#included-in-info[IncludedInInfo] entity is returned.
Adds or updates the change in the secondary index.
-The caller must be a member of a group that is granted the
-link:access-control.html#capability_administrateServer[
-Administrate Server] capability.
-
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/index HTTP/1.0
@@ -1138,6 +1146,465 @@ Administrate Server] capability.
HTTP/1.1 204 No Content
----
+[[check-change]]
+=== Check change
+--
+'GET /changes/link:#change-id[\{change-id\}]/check'
+--
+
+Performs consistency checks on the change, and returns a
+link:#change-info[ChangeInfo] entity with the `problems` field set to a
+list of link:#problem-info[ProblemInfo] entities.
+
+Depending on the type of problem, some fields not marked optional may be
+missing from the result. At least `id`, `project`, `branch`, and
+`_number` will be present.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/check HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ },
+ "problems": [
+ {
+ "message": "Current patch set 1 not found"
+ }
+ ]
+ }
+----
+
+[[fix-change]]
+=== Fix change
+--
+'POST /changes/link:#change-id[\{change-id\}]/check'
+--
+
+Performs consistency checks on the change as with link:#check-change[GET
+/check], and additionally fixes any problems that can be fixed
+automatically. The returned field values reflect any fixes.
+
+Only the change owner, a project owner, or an administrator may fix changes.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/check HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "MERGED",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ },
+ "problems": [
+ {
+ "message": "Current patch set 2 not found"
+ },
+ {
+ "message": "Patch set 1 (1eee2c9d8f352483781e772f35dc586a69ff5646) is merged into destination ref master (1eee2c9d8f352483781e772f35dc586a69ff5646), but change status is NEW",
+ "status": FIXED,
+ "outcome": "Marked change as merged"
+ }
+ ]
+ }
+----
+
+[[edit-endpoints]]
+== Change Edit Endpoints
+
+These endpoints are considered to be unstable and can be changed in
+backwards incompatible way any time without notice.
+
+[[get-edit-detail]]
+=== Get Change Edit Details
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit
+--
+
+Retrieves a change edit details.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+----
+
+As response an link:#edit-info[EditInfo] entity is returned that
+describes the change edit, or "`204 No Content`" when change edit doesn't
+exist for this change. Change edits are stored on special branches and there
+can be max one edit per user per change. Edits aren't tracked in the database.
+When request parameter `list` is provided the response also includes the file
+list. When `base` request parameter is provided the file list is computed
+against this base revision. When request parameter `download-commands` is
+provided fetch info map is also included.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "commit":{
+ "parents":[
+ {
+ "commit":"1eee2c9d8f352483781e772f35dc586a69ff5646",
+ }
+ ],
+ "author":{
+ "name":"Shawn O. Pearce",
+ "email":"sop@google.com",
+ "date":"2012-04-24 18:08:08.000000000",
+ "tz":-420
+ },
+ "committer":{
+ "name":"Shawn O. Pearce",
+ "email":"sop@google.com",
+ "date":"2012-04-24 18:08:08.000000000",
+ "tz":-420
+ },
+ "subject":"Use an EventBus to manage star icons",
+ "message":"Use an EventBus to manage star icons\n\nImage widgets that need to ..."
+ },
+ }
+----
+
+[[put-edit-file]]
+=== Change file content in Change Edit
+--
+'PUT /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile
+--
+
+Put content of a file to a change edit.
+
+.Request
+----
+ PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+When change edit doesn't exist for this change yet it is created. When file
+content isn't provided, it is wiped out for that file. As response
+"`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[post-edit]]
+=== Restore file content or rename files in Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/edit
+--
+
+Creates empty change edit, restores file content or renames files in change
+edit. The request body needs to include a
+link:#change-edit-input[ChangeEditInput] entity when a file within change
+edit should be restored or old and new file names to rename a file.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "restore_path": "foo"
+ }
+----
+
+or for rename:
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "old_path": "foo",
+ "new_path": "bar"
+ }
+----
+
+When change edit doesn't exist for this change yet it is created. When path
+and restore flag are provided in request body, this file is restored. When
+old and new file names are provided, the file is renamed. As response
+"`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[put-change-edit-message]]
+=== Change commit message in Change Edit
+--
+'PUT /changes/link:#change-id[\{change-id\}]/edit:message
+--
+
+Modify commit message. The request body needs to include a
+link:#change-edit-message-input[ChangeEditMessageInput]
+entity.
+
+.Request
+----
+ PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit:message HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "message": "New commit message\n\nChange-Id: I10394472cbd17dd12454f229e4f6de00b143a444"
+ }
+----
+
+If a change edit doesn't exist for this change yet, it is created. As
+response "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[delete-edit-file]]
+=== Delete file in Change Edit
+--
+'DELETE /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile'
+--
+
+Deletes a file from a change edit. This deletes the file from the repository
+completely. This is not the same as reverting or restoring a file to its
+previous contents.
+
+.Request
+----
+ DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+When change edit doesn't exist for this change yet it is created.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[get-edit-file]]
+=== Retrieve file content from Change Edit
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile
+--
+
+Retrieves content of a file from a change edit.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
+----
+
+The content of the file is returned as text encoded inside base64.
+The Content-Type header will always be `text/plain` reflecting the
+outer base64 encoding. A Gerrit-specific `X-FYI-Content-Type` header
+can be examined to find the server detected content type of the file.
+
+When the specified file was deleted in the change edit
+"`204 No Content`" is returned.
+
+If only the content type is required, callers should use HEAD to
+avoid downloading the encoded file contents.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: text/plain; charset=ISO-8859-1
+ X-FYI-Content-Encoding: base64
+ X-FYI-Content-Type: text/xml
+
+ RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
+----
+
+Alternatively, if the only value of the Accept request header is
+`application/json` the content is returned as JSON string and
+`X-FYI-Content-Encoding` is set to `json`.
+
+[[get-edit-meta-data]]
+=== Retrieve meta data of a file from Change Edit
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile/meta
+--
+
+Retrieves meta data of a file from a change edit. Currently only
+web links are returned.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo/meta HTTP/1.0
+----
+
+This REST endpoint retrieves additional information for a file in a
+change edit. As result an link:#edit-file-info[EditFileInfo] entity is
+returned.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "web_links":[
+ {
+ "show_on_side_by_side_diff_view": true,
+ "name": "side-by-side preview diff",
+ "image_url": "plugins/xdocs/static/sideBySideDiffPreview.png",
+ "url": "#/x/xdocs/c/42/1..0/README.md",
+ "target": "_self"
+ },
+ {
+ "show_on_unified_diff_view": true,
+ "name": "unified preview diff",
+ "image_url": "plugins/xdocs/static/unifiedDiffPreview.png",
+ "url": "#/x/xdocs/c/42/1..0/README.md,unified",
+ "target": "_self"
+ }
+ ]}
+----
+
+[[get-edit-message]]
+=== Retrieve commit message from Change Edit or current patch set of the change
+--
+'GET /changes/link:#change-id[\{change-id\}]/edit:message
+--
+
+Retrieves commit message from change edit.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit:message HTTP/1.0
+----
+
+The commit message is returned as base64 encoded string.
+
+.Response
+----
+ HTTP/1.1 200 OK
+
+ VGhpcyBpcyBhIGNvbW1pdCBtZXNzYWdlCgpDaGFuZ2UtSWQ6IElhYzhmZGM1MGRlZjFiYWUzYjAz
+M2JhNjcxZTk0OTBmNzUxNDU5ZGUzCg==
+----
+
+Alternatively, if the only value of the Accept request header is
+`application/json` the commit message is returned as JSON string:
+
+.Response
+----
+ HTTP/1.1 200 OK
+
+)]}'
+"Subject of the commit message\n\nThis is the body of the commit message.\n\nChange-Id: Iaf1ba916bf843c175673d675bf7f52862f452db9\n"
+----
+
+
+[[publish-edit]]
+=== Publish Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/edit:publish
+--
+
+Promotes change edit to a regular patch set.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit:publish HTTP/1.0
+----
+
+As response "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[rebase-edit]]
+=== Rebase Change Edit
+--
+'POST /changes/link:#change-id[\{change-id\}]/edit:rebase
+--
+
+Rebases change edit on top of latest patch set.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit:rebase HTTP/1.0
+----
+
+When change was rebased on top of latest patch set, response
+"`204 No Content`" is returned. When change edit is already
+based on top of the latest patch set, the response
+"`409 Conflict`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[delete-edit]]
+=== Delete Change Edit
+--
+'DELETE /changes/link:#change-id[\{change-id\}]/edit'
+--
+
+Deletes change edit.
+
+.Request
+----
+ DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+----
+
+As response "`204 No Content`" is returned.
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
[[reviewer-endpoints]]
== Reviewer Endpoints
@@ -1161,7 +1628,7 @@ As result a list of link:#reviewer-info[ReviewerInfo] entries is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1206,7 +1673,7 @@ As result a list of link:#suggested-reviewer-info[SuggestedReviewerInfo] entries
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1246,7 +1713,7 @@ describes the reviewer.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1274,7 +1741,7 @@ body as a link:#reviewer-input[ReviewerInput] entity.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"reviewer": "john.doe@example.com"
@@ -1288,7 +1755,7 @@ returned that describes the newly added reviewers.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1316,7 +1783,7 @@ required.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"reviewer": "MyProjectVerifiers"
@@ -1327,7 +1794,7 @@ required.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1342,7 +1809,7 @@ To confirm the addition of the reviewers, resend the request with the
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"reviewer": "MyProjectVerifiers",
@@ -1391,7 +1858,7 @@ describes the revision.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1418,6 +1885,48 @@ describes the revision.
}
----
+Adding query parameter `links` (for example `/changes/.../commit?links`)
+returns a link:#commit-info[CommitInfo] with the additional field `web_links`.
+
+[[get-revision-actions]]
+=== Get Revision Actions
+--
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/actions'
+--
+
+Retrieves revision link:#action-info[actions] of the revision of a change.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/actions' HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+
+{
+ "submit": {
+ "method": "POST",
+ "label": "Submit",
+ "title": "Submit patch set 1 into master",
+ "enabled": true
+ },
+ "cherrypick": {
+ "method": "POST",
+ "label": "Cherry Pick",
+ "title": "Cherry pick change to a different branch",
+ "enabled": true
+ }
+}
+----
+
+The response is a flat map of possible revision actions mapped to their
+link:#action-info[ActionInfo].
[[get-review]]
=== Get Review
@@ -1445,7 +1954,7 @@ for the current patch set.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1460,7 +1969,6 @@ for the current patch set.
"mergeable": true,
"insertions": 34,
"deletions": 45,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"_account_id": 1000096,
@@ -1543,6 +2051,7 @@ for the current patch set.
"revisions": {
"674ac754f91e64a0efb8087e59a176484bd534d1": {
"_number": 2,
+ "ref": "refs/changes/65/3965/2",
"fetch": {
"http": {
"url": "http://gerrit/myProject",
@@ -1574,7 +2083,7 @@ describing the related changes.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1640,7 +2149,7 @@ link:#review-input[ReviewInput] entity.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/review HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"message": "Some nits need to be fixed.",
@@ -1678,7 +2187,7 @@ describes the applied labels.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1688,6 +2197,9 @@ describes the applied labels.
}
----
+A review cannot be set on a change edit. Trying to post a review for a
+change edit fails with `409 Conflict`.
+
[[rebase-revision]]
=== Rebase Revision
--
@@ -1696,9 +2208,17 @@ describes the applied labels.
Rebases a revision.
+Optionally, the parent revision can be changed to another patch set through the
+link:#rebase-input[RebaseInput] entity.
+
.Request
----
POST /changes/myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/rebase HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "base" : "1234",
+ }
----
As response a link:#change-info[ChangeInfo] entity is returned that
@@ -1709,7 +2229,7 @@ is included.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1724,7 +2244,6 @@ is included.
"mergeable": false,
"insertions": 21,
"deletions": 21,
- "_sortkey": "0024cf9a000012bf",
"_number": 4799,
"owner": {
"name": "John Doe"
@@ -1733,6 +2252,7 @@ is included.
"revisions": {
"27cc4558b5a3d3387dd11ee2df7a117e7e581822": {
"_number": 2,
+ "ref": "refs/changes/99/4799/2",
"fetch": {
"http": {
"url": "http://gerrit:8080/myProject",
@@ -1773,7 +2293,7 @@ body.
----
HTTP/1.1 409 Conflict
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
The change could not be rebased due to a path conflict during merge.
----
@@ -1793,7 +2313,7 @@ complete.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/submit HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"wait_for_merge": true
@@ -1807,7 +2327,7 @@ describes the status of the submitted change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1823,7 +2343,7 @@ message is contained in the response body.
.Response
----
HTTP/1.1 409 Conflict
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
"revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
----
@@ -1839,7 +2359,7 @@ Publishes a draft revision.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/publish HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
----
.Response
@@ -1858,7 +2378,7 @@ Deletes a draft revision.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1 HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
----
.Response
@@ -1885,7 +2405,7 @@ The formatted patch is returned as text encoded inside base64:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: text/plain;charset=ISO-8859-1
+ Content-Type: text/plain; charset=ISO-8859-1
X-FYI-Content-Encoding: base64
X-FYI-Content-Type: application/mbox
@@ -1921,7 +2441,7 @@ As response a link:#mergeable-info[MergeableInfo] entity is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1933,9 +2453,6 @@ As response a link:#mergeable-info[MergeableInfo] entity is returned.
If the `other-branches` parameter is specified, the mergeability will also be
checked for all other branches.
-If the `force` parameter is specified, the mergeability against the destination
-will be rechecked, in case of prior transient failures or bugs.
-
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/mergeable?other-branches HTTP/1.0
@@ -1948,7 +2465,7 @@ could merge cleanly.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1978,7 +2495,7 @@ Gets the method the server will use to submit (merge) the change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"MERGE_IF_NECESSARY"
@@ -2000,7 +2517,7 @@ a project-specific rule.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/test.submit_type HTTP/1.0
- Content-Type: text/plain;charset-UTF-8
+ Content-Type: text/plain; charset-UTF-8
submit_type(cherry_pick).
----
@@ -2009,7 +2526,7 @@ a project-specific rule.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"CHERRY_PICK"
@@ -2031,7 +2548,7 @@ a project-specific rule.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/test.submit_rule?filters=SKIP HTTP/1.0
- Content-Type: text/plain;charset-UTF-8
+ Content-Type: text/plain; charset-UTF-8
submit_rule(submit(R)) :-
R = label('Any-Label-Name', reject(_)).
@@ -2044,7 +2561,7 @@ describing the permutations that satisfy the tested submit rule.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -2057,6 +2574,17 @@ describing the permutations that satisfy the tested submit rule.
]
----
+When testing with the `curl` command line client the
+`--data-binary @rules.pl` flag should be used to ensure
+all LFs are included in the Prolog code:
+
+----
+ curl -X POST \
+ -H 'Content-Type: text/plain; charset=UTF-8' \
+ --data-binary @rules.pl \
+ http://.../test.submit_rule
+----
+
[[list-drafts]]
=== List Drafts
--
@@ -2079,7 +2607,7 @@ sorted by file path.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2115,7 +2643,7 @@ link:#comment-input[CommentInput] entity.
.Request
----
PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/drafts HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"path": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
@@ -2131,7 +2659,7 @@ describes the draft comment.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2164,7 +2692,7 @@ describes the draft comment.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2190,7 +2718,7 @@ link:#comment-input[CommentInput] entity.
.Request
----
PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/drafts/TvcXrmjM HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"path": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
@@ -2206,7 +2734,7 @@ describes the draft comment.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2259,7 +2787,7 @@ the general change message (or comment).
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2311,7 +2839,7 @@ describes the published comment.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2349,7 +2877,7 @@ sorted by file path.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2368,6 +2896,11 @@ The request parameter `reviewed` changes the response to return a list
of the paths the caller has marked as reviewed. Clients that also
need the FileInfo should make two requests.
+The request parameter `q` changes the response to return a list
+of all files (modified or unmodified) that contain that substring
+in the path name. This is useful to implement suggestion services
+finding a file by partial name.
+
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/?reviewed HTTP/1.0
@@ -2377,7 +2910,7 @@ need the FileInfo should make two requests.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -2399,17 +2932,29 @@ Gets the content of a file from a certain revision.
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/content HTTP/1.0
----
-The content is returned as base64 encoded string.
+The content is returned as base64 encoded string. The HTTP response
+Content-Type is always `text/plain`, reflecting the base64 wrapping.
+A Gerrit-specific `X-FYI-Content-Type` header is returned describing
+the server detected content type of the file.
+
+If only the content type is required, callers should use HEAD to
+avoid downloading the encoded file contents.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=ISO-8859-1
+ X-FYI-Content-Encoding: base64
+ X-FYI-Content-Type: text/xml
Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
----
+Alternatively, if the only value of the Accept request header is
+`application/json` the content is returned as JSON string and
+`X-FYI-Content-Encoding` is set to `json`.
+
[[get-diff]]
=== Get Diff
--
@@ -2429,7 +2974,7 @@ As response a link:#diff-info[DiffInfo] entity is returned that describes the di
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]
{
@@ -2499,7 +3044,7 @@ If the `intraline` parameter is specified, intraline differences are included in
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]
{
@@ -2544,6 +3089,9 @@ If the `intraline` parameter is specified, intraline differences are included in
The `base` parameter can be specified to control the base patch set from which the diff should
be generated.
+[[weblinks-only]]
+If the `weblinks-only` parameter is specified, only the diff web links are returned.
+
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/b6b9c10649b9041884046119ab794374470a1b45/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/diff?base=2 HTTP/1.0
@@ -2553,7 +3101,7 @@ be generated.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]
{
@@ -2635,7 +3183,7 @@ link:#cherrypick-input[CherryPickInput] entity.
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/cherrypick HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"message" : "Implementing Feature X",
@@ -2650,7 +3198,7 @@ describes the resulting cherry picked change.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -2665,58 +3213,6 @@ describes the resulting cherry picked change.
"mergeable": true,
"insertions": 12,
"deletions": 11,
- "_sortkey": "0023412400000f7d",
- "_number": 3965,
- "owner": {
- "name": "John Doe"
- }
- }
-----
-
-[[message]]
-=== Edit Commit Message
---
-'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/message'
---
-
-Edit commit message.
-
-The commit message must be provided in the request body inside a
-link:#cherrypick-input[CherryPickInput] entity.
-
-.Request
-----
- POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/message HTTP/1.0
- Content-Type: application/json;charset=UTF-8
-
- {
- "message" : "Reword Implementing Feature X",
- }
-----
-
-As response a link:#change-info[ChangeInfo] entity is returned that
-describes the change.
-
-.Response
-----
- HTTP/1.1 200 OK
- Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
-
- )]}'
- {
- "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9941",
- "project": "myProject",
- "branch": "release-branch",
- "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9941",
- "subject": "Reword Implementing Feature X",
- "status": "NEW",
- "created": "2013-02-01 09:59:32.126000000",
- "updated": "2013-02-21 11:16:36.775000000",
- "mergeable": true,
- "insertions": 261,
- "deletions": 101,
- "_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
@@ -2777,7 +3273,7 @@ This can be:
=== AbandonInput
The `AbandonInput` entity contains information for abandoning a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`message` |optional|
@@ -2791,7 +3287,7 @@ The `ActionInfo` entity describes a REST API call the client can
make to manipulate a resource. These are frequently implemented by
plugins and may be discovered at runtime.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|====================================
|Field Name ||Description
|`method` |optional|
@@ -2816,7 +3312,7 @@ at the server or permissions are modified. Not present if false.
The `AddReviewerResult` entity describes the result of adding a
reviewer to a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`reviewers` |optional|
@@ -2839,7 +3335,7 @@ user for a label on a change.
link:rest-api-accounts.html#account-info[AccountInfo].
In addition `ApprovalInfo` has the following fields:
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`value` |optional|
@@ -2850,22 +3346,35 @@ permitted to vote on that label.
The time and date describing when the approval was made.
|===========================
-[[group-base-info]]
-=== GroupBaseInfo
-The `GroupBaseInfo` entity contains base information about the group.
+[[change-edit-input]]
+=== ChangeEditInput
+The `ChangeEditInput` entity contains information for restoring a
+path within change edit.
-[options="header",width="50%",cols="1,6"]
-|==========================
-|Field Name |Description
-|`id` |The id of the group.
-|`name` |The name of the group.
-|==========================
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`restore_path`|optional|Path to file to restore.
+|`old_path` |optional|Old path to file to rename.
+|`new_path` |optional|New path to file to rename.
+|===========================
+
+[[change-edit-message-input]]
+=== ChangeEditMessageInput
+The `ChangeEditMessageInput` entity contains information for changing
+the commit message within a change edit.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`message` ||New commit message.
+|===========================
[[change-info]]
=== ChangeInfo
The `ChangeInfo` entity contains information about a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`id` ||
@@ -2901,7 +3410,6 @@ Not set for merged changes, or if the change has not yet been tested.
Number of inserted lines.
|`deletions` ||
Number of deleted lines.
-|`_sortkey` ||The sortkey of the change.
|`_number` ||The legacy numeric ID of the change.
|`owner` ||
The owner of the change as an link:rest-api-accounts.html#account-info[
@@ -2939,46 +3447,21 @@ Only set if link:#current-revision[the current revision] is requested
if link:#all-revisions[all revisions] are requested.
|`_more_changes` |optional, not set if `false`|
Whether the query would deliver more results if not limited. +
-Only set on either the last or the first change that is returned.
+Only set on the last change that is returned.
+|`problems` |optional|
+A list of link:#problem-info[ProblemInfo] entities describing potential
+problems with this change. Only set if link:#check[CHECK] is set.
+|`base_change` |optional|
+A link:#change-id[\{change-id\}] that identifies the base change for a create
+change operation. Only used for the link:#create-change[CreateChange] endpoint.
|==================================
-[[related-changes-info]]
-=== RelatedChangesInfo
-The `RelatedChangesInfo` entity contains information about related
-changes.
-
-[options="header",width="50%",cols="1,6"]
-|===========================
-|Field Name |Description
-|`changes` |A list of
-link:#related-change-and-commit-info[RelatedChangeAndCommitInfo] entities
-describing the related changes. Sorted by git commit order, newest to
-oldest. Empty if there are no related changes.
-|===========================
-
-[[related-change-and-commit-info]]
-=== RelatedChangeAndCommitInfo
-
-The `RelatedChangeAndCommitInfo` entity contains information about
-a related change and commit.
-
-[options="header",width="50%",cols="1,^1,5"]
-|===========================
-|Field Name ||Description
-|`change_id` |optional|The Change-Id of the change.
-|`commit` ||The commit as a
-link:#commit-info[CommitInfo] entity.
-|`_change_number` |optional|The change number.
-|`_revision_number` |optional|The revision number.
-|`_current_revision_number`|optional|The current revision number.
-|===========================
-
[[change-message-info]]
=== ChangeMessageInfo
The `ChangeMessageInfo` entity contains information about a message
attached to a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`id` ||The ID of the message.
@@ -2997,7 +3480,7 @@ Which patchset (if any) generated this message.
=== CherryPickInput
The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|===========================
|Field Name |Description
|`message` |Commit message for the cherry-picked change
@@ -3008,7 +3491,7 @@ The `CherryPickInput` entity contains information for cherry-picking a change to
=== CommentInfo
The `CommentInfo` entity contains information about an inline comment.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`id` ||The URL encoded UUID of the comment.
@@ -3043,7 +3526,7 @@ Unset for draft comments, assumed to be the calling user.
The `CommentInput` entity contains information for creating an inline
comment.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`id` |optional|
@@ -3080,7 +3563,7 @@ comment is deleted.
=== CommentRange
The `CommentRange` entity describes the range of an inline comment.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`start_line` ||The start line number of the range.
@@ -3093,7 +3576,7 @@ The `CommentRange` entity describes the range of an inline comment.
=== CommitInfo
The `CommitInfo` entity contains information about a commit.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|==========================
|Field Name |Description
|`commit` |The commit ID.
@@ -3108,6 +3591,9 @@ link:#git-person-info[GitPersonInfo] entity.
|`subject` |
The subject of the commit (header line of the commit message).
|`message` |The commit message.
+|`web_links` |optional|
+Links to the commit in external sites as a list of
+link:#web-link-info[WebLinkInfo] entities.
|==========================
[[diff-content]]
@@ -3115,7 +3601,7 @@ The subject of the commit (header line of the commit message).
The `DiffContent` entity contains information about the content differences
in a file.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==========================
|Field Name ||Description
|`a` |optional|Content only in the file on side A (deleted in B).
@@ -3139,12 +3625,15 @@ used instead of ab.
=== DiffFileMetaInfo
The `DiffFileMetaInfo` entity contains meta information about a file diff.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,^1,5"]
|==========================
-|Field Name |Description
-|`name` |The name of the file.
-|`content_type`|The content type of the file.
-|`lines` |The total number of lines in the file.
+|Field Name ||Description
+|`name` ||The name of the file.
+|`content_type`||The content type of the file.
+|`lines` ||The total number of lines in the file.
+|`web_links` |optional|
+Links to the file in external sites as a list of
+link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
|==========================
[[diff-info]]
@@ -3152,7 +3641,10 @@ The `DiffFileMetaInfo` entity contains meta information about a file diff.
The `DiffInfo` entity contains information about the diff of a file
in a revision.
-[options="header",width="50%",cols="1,^1,5"]
+If the link:#weblinks-only[weblinks-only] parameter is specified, only
+the `web_links` field is set.
+
+[options="header",cols="1,^1,5"]
|==========================
|Field Name ||Description
|`meta_a` |not present when the file is added|
@@ -3168,6 +3660,10 @@ Intraline status (`OK`, `ERROR`, `TIMEOUT`).
|`diff_header` ||A list of strings representing the patch set diff header.
|`content` ||The content differences in the file as a list of
link:#diff-content[DiffContent] entities.
+|`web_links` |optional|
+Links to the file diff in external sites as a list of
+link:rest-api-changes.html#diff-web-link-info[DiffWebLinkInfo] entries.
+|`binary` |not set if `false`|Whether the file is binary.
|==========================
[[diff-intraline-info]]
@@ -3184,12 +3680,61 @@ 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.
+[[diff-web-link-info]]
+=== DiffWebLinkInfo
+The `DiffWebLinkInfo` entity describes a link on a diff screen to an
+external site.
+
+[options="header",cols="1,6"]
+|=======================
+|Field Name|Description
+|`name` |The link name.
+|`url` |The link URL.
+|`image_url`|URL to the icon of the link.
+|show_on_side_by_side_diff_view|
+Whether the web link should be shown on the side-by-side diff screen.
+|show_on_unified_diff_view|
+Whether the web link should be shown on the unified diff screen.
+|=======================
+
+[[edit-file-info]]
+=== EditFileInfo
+The `EditFileInfo` entity contains additional information
+of a file within a change edit.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`web_links` |optional|
+Links to the diff info in external sites as a list of
+link:#web-link-info[WebLinkInfo] entities.
+|===========================
+
+[[edit-info]]
+=== EditInfo
+The `EditInfo` entity contains information about a change edit.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`commit` ||The commit of change edit as
+link:#commit-info[CommitInfo] entity.
+|`baseRevision`||The revision of the patch set change edit is based on.
+|`fetch` ||
+Information about how to fetch this patch set. The fetch information is
+provided as a map that maps the protocol name ("`git`", "`http`",
+"`ssh`") to link:#fetch-info[FetchInfo] entities.
+|`files` |optional|
+The files of the change edit as a map that maps the file names to
+link:#file-info[FileInfo] entities.
+|===========================
+
[[fetch-info]]
=== FetchInfo
The `FetchInfo` entity contains information about how to fetch a patch
set via a certain protocol.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==========================
|Field Name ||Description
|`url` ||The URL of the project.
@@ -3204,7 +3749,7 @@ Only set if link:#download_commands[download commands] are requested.
=== FileInfo
The `FileInfo` entity contains information about a file in a patch set.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|=============================
|Field Name ||Description
|`status` |optional|
@@ -3228,7 +3773,7 @@ Not set for binary files or if no lines were deleted.
The `GitPersonInfo` entity contains information about the
author/committer of a commit.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|==========================
|Field Name |Description
|`name` |The name of the author/committer.
@@ -3239,6 +3784,31 @@ this identity was constructed.
constructed.
|==========================
+[[group-base-info]]
+=== GroupBaseInfo
+The `GroupBaseInfo` entity contains base information about the group.
+
+[options="header",cols="1,6"]
+|==========================
+|Field Name |Description
+|`id` |The id of the group.
+|`name` |The name of the group.
+|==========================
+
+[[included-in-info]]
+=== IncludedInInfo
+The `IncludedInInfo` entity contains information about the branches a
+change was merged into and tags it was tagged with.
+
+[options="header",cols="1,6"]
+|==========================
+|Field Name |Description
+|`branches` | The list of branches this change was merged into.
+Each branch is listed without the 'refs/head/' prefix.
+|`tags` | The list of tags this change was tagged with.
+Each tag is listed without the 'refs/tags/' prefix.
+|==========================
+
[[label-info]]
=== LabelInfo
The `LabelInfo` entity contains information about a label on a change, always
@@ -3252,7 +3822,7 @@ link:#labels[`LABELS`] and link:#detailed-labels[`DETAILED_LABELS`].
users and the allowed range of votes for the current user, use `DETAILED_LABELS`.
==== Common fields
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`optional` |not set if `false`|
@@ -3262,7 +3832,7 @@ set.
|===========================
==== Fields set by `LABELS`
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`approved` |optional|One user who approved this label on the change
@@ -3287,7 +3857,7 @@ This value may be outside the range specified in permitted_labels.
|===========================
==== Fields set by `DETAILED_LABELS`
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`all` |optional|List of all approvals for this label as a list
@@ -3302,7 +3872,7 @@ to the value descriptions.
The `MergeableInfo` entity contains information about the mergeability of a
change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|============================
|Field Name ||Description
|`submit_type` ||
@@ -3315,11 +3885,76 @@ Submit type used for this change, can be `MERGE_IF_NECESSARY`,
A list of other branch names where this change could merge cleanly
|============================
+[[problem-info]]
+=== ProblemInfo
+The `ProblemInfo` entity contains a description of a potential consistency problem
+with a change. These are not related to the code review process, but rather
+indicate some inconsistency in Gerrit's database or repository metadata related
+to the enclosing change.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name||Description
+|`message` ||Plaintext message describing the problem with the change.
+|`status` |optional|
+The status of fixing the problem (`FIXED`, `FIX_FAILED`). Only set if a
+fix was attempted.
+|`outcome` |optional|
+If `status` is set, an additional plaintext message describing the
+outcome of the fix.
+|===========================
+
+[[rebase-input]]
+=== RebaseInput
+The `RebaseInput` entity contains information for changing parent when rebasing.
+
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`base` |optional|
+The new parent revision. This can be a ref or a SHA1 to a concrete patchset. +
+Alternatively, a change number can be specified, in which case the current
+patch set is inferred. +
+Empty string is used for rebasing directly on top of the target branch,
+which effectively breaks dependency towards a parent change.
+|===========================
+
+[[related-change-and-commit-info]]
+=== RelatedChangeAndCommitInfo
+
+The `RelatedChangeAndCommitInfo` entity contains information about
+a related change and commit.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`change_id` |optional|The Change-Id of the change.
+|`commit` ||The commit as a
+link:#commit-info[CommitInfo] entity.
+|`_change_number` |optional|The change number.
+|`_revision_number` |optional|The revision number.
+|`_current_revision_number`|optional|The current revision number.
+|===========================
+
+[[related-changes-info]]
+=== RelatedChangesInfo
+The `RelatedChangesInfo` entity contains information about related
+changes.
+
+[options="header",cols="1,6"]
+|===========================
+|Field Name |Description
+|`changes` |A list of
+link:#related-change-and-commit-info[RelatedChangeAndCommitInfo] entities
+describing the related changes. Sorted by git commit order, newest to
+oldest. Empty if there are no related changes.
+|===========================
+
[[restore-input]]
=== RestoreInput
The `RestoreInput` entity contains information for restoring a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`message` |optional|
@@ -3331,7 +3966,7 @@ change.
=== RevertInput
The `RevertInput` entity contains information for reverting a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`message` |optional|
@@ -3343,7 +3978,7 @@ change.
=== ReviewInfo
The `ReviewInfo` entity contains information about a review.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|===========================
|Field Name |Description
|`labels` |
@@ -3356,7 +3991,7 @@ voting values.
The `ReviewInput` entity contains information for adding a review to a
revision.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|============================
|Field Name ||Description
|`message` |optional|
@@ -3401,7 +4036,7 @@ link:rest-api-accounts.html#account-info[AccountInfo] and includes
link:#detailed-accounts[detailed account information].
In addition `ReviewerInfo` has the following fields:
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|==========================
|Field Name |Description
|`approvals` |
@@ -3414,7 +4049,7 @@ approval values ("`-2`", "`-1`", "`0`", "`+1`", "`+2`").
The `ReviewerInput` entity contains information for adding a reviewer
to a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`reviewer` ||
@@ -3433,38 +4068,53 @@ confirmation] when adding a group as reviewer that has many members.
[[revision-info]]
=== RevisionInfo
The `RevisionInfo` entity contains information about a patch set.
+Not all fields are returned by default. Additional fields can
+be obtained by adding `o` parameters as described in
+link:#list-changes[Query Changes].
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`draft` |not set if `false`|Whether the patch set is a draft.
|`has_draft_comments` |not set if `false`|Whether the patch
set has one or more draft comments by the calling user. Only set if
-link:#draft_comments[draft comments] is requested.
+link:#draft_comments[DRAFT_COMMENTS] option is requested.
|`_number` ||The patch set number.
+|`created` ||
+The link:rest-api.html#timestamp[timestamp] of when the patch set was
+created.
+|`uploader` ||
+The uploader of the patch set as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`ref` ||The Git reference for the patch set.
|`fetch` ||
Information about how to fetch this patch set. The fetch information is
provided as a map that maps the protocol name ("`git`", "`http`",
-"`ssh`") to link:#fetch-info[FetchInfo] entities.
+"`ssh`") to link:#fetch-info[FetchInfo] entities. This information is
+only included if a plugin implementing the
+link:intro-project-owner.html#download-commands[download commands]
+interface is installed.
|`commit` |optional|The commit of the patch set as
link:#commit-info[CommitInfo] entity.
|`files` |optional|
The files of the patch set as a map that maps the file names to
-link:#file-info[FileInfo] entities.
+link:#file-info[FileInfo] entities. Only set if
+link:#current-files[CURRENT_FILES] or link:#all-files[ALL_FILES]
+option is requested.
|`actions` |optional|
Actions the caller might be able to perform on this revision. The
information is a map of view name to link:#action-info[ActionInfo]
entities.
-|'web_links' |optional|
-Links to the patch set in external sites as a list of
-link:#web-link-info[WebLinkInfo] 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.
|===========================
[[rule-input]]
=== RuleInput
The `RuleInput` entity contains information to test a Prolog rule.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`rule`||
@@ -3477,22 +4127,12 @@ If `SKIP` the parent filters are not called, allowing the test
to return results from the input rule.
|===========================
-[[suggested-reviewer-info]]
-=== SuggestedReviewerInfo
-The `SuggestedReviewerInfo` entity contains information about a reviewer
-that can be added to a change (an account or a group).
-
-`SuggestedReviewerInfo` has either the `account` field that contains
-the link:rest-api-accounts.html#account-info[AccountInfo] entity, or
-the `group` field that contains the
-link:rest-api-changes.html#group-base-info[GroupBaseInfo] entity.
-
[[submit-info]]
=== SubmitInfo
The `SubmitInfo` entity contains information about the change status
after submitting.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|==========================
|Field Name |Description
|`status` |
@@ -3516,7 +4156,7 @@ not with the identity of the CI account.
=== SubmitInput
The `SubmitInput` entity contains information for submitting a change.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`wait_for_merge`|`false` if not set|
@@ -3530,7 +4170,7 @@ could be merged successfully.
=== SubmitRecord
The `SubmitRecord` entity describes results from a submit_rule.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`status`||
@@ -3561,40 +4201,37 @@ When status is RULE_ERROR this message provides some text describing
the failure of the rule predicate.
|===========================
+[[suggested-reviewer-info]]
+=== SuggestedReviewerInfo
+The `SuggestedReviewerInfo` entity contains information about a reviewer
+that can be added to a change (an account or a group).
+
+`SuggestedReviewerInfo` has either the `account` field that contains
+the link:rest-api-accounts.html#account-info[AccountInfo] entity, or
+the `group` field that contains the
+link:rest-api-changes.html#group-base-info[GroupBaseInfo] entity.
+
[[topic-input]]
=== TopicInput
The `TopicInput` entity contains information for setting a topic.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`topic` |optional|The topic. +
The topic will be deleted if not set.
|===========================
-[[included-in-info]]
-=== IncludedInInfo
-The `IncludedInInfo` entity contains information about the branches a
-change was merged into and tags it was tagged with.
-
-[options="header",width="50%",cols="1,6"]
-|==========================
-|Field Name |Description
-|`branches` | The list of branches this change was merged into.
-Each branch is listed without the 'refs/head/' prefix.
-|`tags` | The list of tags this change was tagged with.
-Each tag is listed without the 'refs/tags/' prefix.
-|==========================
-
[[web-link-info]]
=== WebLinkInfo
The `WebLinkInfo` entity describes a link to an external site.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|======================
|Field Name|Description
|`name` |The link name.
|`url` |The link URL.
+|`image_url`|URL to the icon of the link.
|======================
GERRIT
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 2968ebbd35..0ee696648b 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -24,7 +24,7 @@ Returns the version of the Gerrit server.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"2.7"
@@ -55,7 +55,7 @@ The entries in the map are sorted by cache name.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -280,7 +280,7 @@ The cache names are alphabetically sorted.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -325,7 +325,7 @@ The cache names are lexicographically sorted.
.Response
----
HTTP/1.1 200 OK
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
YWNjb3VudHMKYW...ViX3Nlc3Npb25z
----
@@ -354,7 +354,7 @@ link:#cache-operation-input[CacheOperationInput] entity.
.Request
----
POST /config/server/caches/ HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"operation": "FLUSH_ALL"
@@ -372,10 +372,10 @@ link:#cache-operation-input[CacheOperationInput] entity.
.Request
----
POST /config/server/caches/ HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
- "operation": "FLUSH"
+ "operation": "FLUSH",
"caches": [
"projects",
"project_list"
@@ -411,7 +411,7 @@ As result a link:#cache-info[CacheInfo] entity is returned.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -488,7 +488,7 @@ As result a link:#summary-info[SummaryInfo] entity is returned.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -561,7 +561,7 @@ The entries in the map are sorted by capability ID.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -670,7 +670,7 @@ command.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -719,7 +719,7 @@ As result a link:#task-info[TaskInfo] entity is returned.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -783,7 +783,7 @@ link:#top-menu-entry-info[TopMenuEntryInfo] entities is returned.
.Response
----
HTTP/1.1 200 OK
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -826,7 +826,7 @@ The ID of the task (hex string).
=== CacheInfo
The `CacheInfo` entity contains information about a cache.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`name` |
@@ -847,23 +847,12 @@ Information about the hit ratio as a link:#hit-ration-info[
HitRatioInfo] entity.
|==================================
-[[capability-info]]
-=== CapabilityInfo
-The `CapabilityInfo` entity contains information about a capability.
-
-[options="header",width="50%",cols="1,6"]
-|=================================
-|Field Name |Description
-|`id` |capability ID
-|`name` |capability name
-|=================================
-
[[cache-operation-input]]
=== CacheOperationInput
The `CacheOperationInput` entity contains information about an
operation that should be executed on caches.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`operation` ||
@@ -878,12 +867,23 @@ specified operation should be executed. Whether this list must be
specified depends on the operation being executed.
|==================================
+[[capability-info]]
+=== CapabilityInfo
+The `CapabilityInfo` entity contains information about a capability.
+
+[options="header",cols="1,6"]
+|=================================
+|Field Name |Description
+|`id` |capability ID
+|`name` |capability name
+|=================================
+
[[entries-info]]
=== EntriesInfo
The `EntriesInfo` entity contains information about the entries in a
cache.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`mem` |optional|Number of cache entries that are held in memory.
@@ -901,7 +901,7 @@ with a unit abbreviation (`k`: kilobytes, `m`: megabytes,
The `HitRatioInfo` entity contains information about the hit ratio of a
cache.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==================================
|Field Name ||Description
|`mem` ||
@@ -915,7 +915,7 @@ Only set for disk caches.
=== JvmSummaryInfo
The `JvmSummaryInfo` entity contains information about the JVM.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|========================================
|Field Name ||Description
|`vm_vendor` ||The vendor of the virtual machine.
@@ -936,7 +936,7 @@ The host on which Gerrit is running.
The `MemSummaryInfo` entity contains information about the current
memory usage.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|============================
|Field Name ||Description
|`total` ||
@@ -963,7 +963,7 @@ The number of open files.
The `SummaryInfo` entity contains information about the current state
of the server.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|============================
|Field Name ||Description
|`task_summary` ||
@@ -985,7 +985,7 @@ Only set if the `jvm` option was set.
The `TaskInfo` entity contains information about a task in a background
work queue.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|====================================
|Field Name ||Description
|`id` ||The ID of the task.
@@ -1006,7 +1006,7 @@ project.
The `TaskSummaryInfo` entity contains information about the current
tasks.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|============================
|Field Name ||Description
|`total` |optional|
@@ -1024,7 +1024,7 @@ Number of currently sleeping tasks.
The `ThreadSummaryInfo` entity contains information about the current
threads.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|===========================
|Field Name |Description
|`cpus` |
@@ -1048,7 +1048,7 @@ included: `NEW`, `RUNNABLE`, `BLOCKED`, `WAITING`, `TIMED_WAITING` and
The `TopMenuEntryInfo` entity contains information about a top menu
entry.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|=================================
|Field Name |Description
|`name` |Name of the top menu entry.
@@ -1060,7 +1060,7 @@ entry.
The `TopMenuItemInfo` entity contains information about a menu item in
a top menu entry.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|========================
|Field Name ||Description
|`url` ||The URL of the menu item link.
diff --git a/Documentation/rest-api-documentation.txt b/Documentation/rest-api-documentation.txt
index 2629dcffd3..4c9db2b40f 100644
--- a/Documentation/rest-api-documentation.txt
+++ b/Documentation/rest-api-documentation.txt
@@ -130,7 +130,7 @@ get::/Documentation/?q=keyword
=== DocResult
The `DocResult` entity contains information about a document.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=========================
|Field Name ||Description
|`title` ||The title of the document.
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index afdcffde81..fbd3ba5d01 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -30,7 +30,7 @@ by group name.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -136,7 +136,7 @@ returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -192,7 +192,7 @@ describes the group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -222,7 +222,7 @@ link:#group-input[GroupInput].
.Request
----
PUT /groups/MyProject-Committers HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"description": "contains all committers for MyProject",
@@ -239,7 +239,7 @@ describes the created group.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -280,7 +280,7 @@ describes the group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -328,7 +328,7 @@ Retrieves the name of a group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"MyProject-Committers"
@@ -347,7 +347,7 @@ The new group name must be provided in the request body.
.Request
----
PUT /groups/MyProject-Committers/name HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"name": "My-Project-Committers"
@@ -360,7 +360,7 @@ As response the new group name is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"My-Project-Committers"
@@ -386,7 +386,7 @@ Retrieves the description of a group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"contains all committers for MyProject"
@@ -407,7 +407,7 @@ The new group description must be provided in the request body.
.Request
----
PUT /groups/9999c971bb4ab872aab759d8c49833ee6b9ff320/description HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"description": "The committers of MyProject."
@@ -420,7 +420,7 @@ As response the new group description is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"The committers of MyProject."
@@ -466,7 +466,7 @@ returned that describes the options of the group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -488,7 +488,7 @@ link:#group-options-input[GroupOptionsInput] entity.
.Request
----
PUT /groups/9999c971bb4ab872aab759d8c49833ee6b9ff320/options HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"visible_to_all": true
@@ -502,7 +502,7 @@ link:#group-options-info[GroupOptionsInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -530,7 +530,7 @@ describes the owner group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -562,7 +562,7 @@ numeric group ID.
.Request
----
PUT /groups/9999c971bb4ab872aab759d8c49833ee6b9ff320/description HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"owner": "6a1e70e1a88782771a91808c8af9bbb7a9871389"
@@ -576,7 +576,7 @@ describes the new owner group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -616,7 +616,7 @@ full name, preferred email and id.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -655,7 +655,7 @@ are not visible to the calling user are ignored.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -700,7 +700,7 @@ AccountInfo] entity is returned that describes the group member.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -731,7 +731,7 @@ AccountInfo] entity is returned that describes the group member.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -764,7 +764,7 @@ as a link:#members-input[MembersInput] entity.
.Request
----
POST /groups/MyProject-Committers/members.add HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"members": [
@@ -786,7 +786,7 @@ already a member of the group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -837,7 +837,7 @@ body as a link:#members-input[MembersInput] entity.
.Request
----
POST /groups/MyProject-Committers/members.delete HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"members": [
@@ -875,7 +875,7 @@ The entries in the list are sorted by group name and UUID.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -912,7 +912,7 @@ describes the included group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -948,7 +948,7 @@ describes the included group.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -986,7 +986,7 @@ request body as a link:#groups-input[GroupsInput] entity.
.Request
----
POST /groups/MyProject-Committers/groups.add HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"groups": [
@@ -1007,7 +1007,7 @@ group was already included in the group.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1066,7 +1066,7 @@ body as a link:#groups-input[GroupsInput] entity.
.Request
----
POST /groups/MyProject-Committers/groups.delete HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"groups": [
@@ -1113,7 +1113,7 @@ Group name that uniquely identifies one group.
The `GroupInfo` entity contains information about a group. This can be
a Gerrit internal group, or an external group that is known to Gerrit.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`id` ||The URL encoded UUID of the group.
@@ -1139,7 +1139,6 @@ Only set if link:#includes[included groups] are requested.
|===========================
The type of a group can be deduced from the group's UUID:
-[width="50%"]
|============
|UUID matches "^[0-9a-f]\{40\}$"|Gerrit internal group
|UUID starts with "global:"|Gerrit system group
@@ -1152,7 +1151,7 @@ The type of a group can be deduced from the group's UUID:
The 'GroupInput' entity contains information for the creation of
a new internal group.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
|`name` |optional|The name of the group (not encoded). +
@@ -1167,26 +1166,11 @@ name. +
If not set, the new group will be self-owned.
|===========================
-[[groups-input]]
-=== GroupsInput
-The `GroupsInput` entity contains information about groups that should
-be included into a group or that should be deleted from a group.
-
-[options="header",width="50%",cols="1,^1,5"]
-|==========================
-|Field Name ||Description
-|`_one_group` |optional|
-The link:#group-id[id] of one group that should be included or deleted.
-|`groups` |optional|
-A list of link:#group-id[group ids] that identify the groups that
-should be included or deleted.
-|==========================
-
[[group-options-info]]
=== GroupOptionsInfo
Options of the group.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|=============================
|Field Name ||Description
|`visible_to_all`|not set if `false`|
@@ -1197,20 +1181,35 @@ Whether the group is visible to all registered users.
=== GroupOptionsInput
New options for a group.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|=============================
|Field Name ||Description
|`visible_to_all`|not set if `false`|
Whether the group is visible to all registered users.
|=============================
+[[groups-input]]
+=== GroupsInput
+The `GroupsInput` entity contains information about groups that should
+be included into a group or that should be deleted from a group.
+
+[options="header",cols="1,^1,5"]
+|==========================
+|Field Name ||Description
+|`_one_group` |optional|
+The link:#group-id[id] of one group that should be included or deleted.
+|`groups` |optional|
+A list of link:#group-id[group ids] that identify the groups that
+should be included or deleted.
+|==========================
+
[[members-input]]
MembersInput
~~~~~~~~~~~
The `MembersInput` entity contains information about accounts that should
be added as members to a group or that should be deleted from the group.
-[options="header",width="50%",cols="1,^1,5"]
+[options="header",cols="1,^1,5"]
|==========================
|Field Name ||Description
|`_one_member`|optional|
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
index b8f7d366e7..dfe9f0ec02 100644
--- a/Documentation/rest-api-plugins.txt
+++ b/Documentation/rest-api-plugins.txt
@@ -40,7 +40,7 @@ by plugin ID.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -76,7 +76,7 @@ a link:#plugin-input[PluginInput] entity.
.Request
----
PUT /plugins/delete-project HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"url": "file:///gerrit/plugins/delete-project/delete-project-2.8.jar"
@@ -97,7 +97,7 @@ describes the plugin.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -128,7 +128,7 @@ describes the plugin.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -157,7 +157,7 @@ describes the plugin.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -192,7 +192,7 @@ describes the plugin.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -222,7 +222,7 @@ describes the plugin.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -247,7 +247,7 @@ The ID of the plugin.
=== PluginInfo
The `PluginInfo` entity describes a plugin.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=======================
|Field Name ||Description
|`id` ||The ID of the plugin.
@@ -260,7 +260,7 @@ The `PluginInfo` entity describes a plugin.
=== PluginInput
The `PluginInput` entity describes a plugin that should be installed.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|======================
|Field Name|Description
|`url` |URL to the plugin jar.
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 2cc1e9ab8f..a909ab4c44 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -30,7 +30,7 @@ by project name.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -70,7 +70,7 @@ GET /projects/?b=master HTTP/1.0
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -103,7 +103,7 @@ GET /projects/?d HTTP/1.0
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -133,7 +133,7 @@ Query the first project in the project list:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -160,7 +160,7 @@ List all projects that start with `platform/`:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -193,7 +193,7 @@ List all projects that match regex `test.*project`:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -221,7 +221,7 @@ Query the second project in the project list:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -245,7 +245,7 @@ List all projects that match substring `test/`:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -273,7 +273,7 @@ GET /projects/?t HTTP/1.0
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -305,7 +305,7 @@ GET /projects/?type=PERMISSIONS HTTP/1.0
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -338,7 +338,7 @@ describes the project.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -364,7 +364,7 @@ link:#project-input[ProjectInput].
.Request
----
PUT /projects/MyProject HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"description": "This is a demo project.",
@@ -382,7 +382,7 @@ describes the created project.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -410,7 +410,7 @@ Retrieves the description of a project.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"Copies to other servers using the Git protocol"
@@ -432,7 +432,7 @@ a link:#project-description-input[ProjectDescriptionInput] entity.
.Request
----
PUT /projects/plugins%2Freplication/description HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"description": "Plugin for Gerrit that handles the replication.",
@@ -446,7 +446,7 @@ As response the new project description is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"Plugin for Gerrit that handles the replication."
@@ -498,7 +498,7 @@ Retrieves the name of a project's parent project. For the
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"All-Projects"
@@ -518,7 +518,7 @@ inside a link:#project-parent-input[ProjectParentInput] entity.
.Request
----
PUT /projects/plugins%2Freplication/parent HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"parent": "Public-Plugins",
@@ -532,7 +532,7 @@ As response the new parent project name is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"Public-Plugins"
@@ -555,7 +555,7 @@ Retrieves for a project the name of the branch to which `HEAD` points.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"refs/heads/master"
@@ -575,7 +575,7 @@ request body inside a link:#head-input[HeadInput] entity.
.Request
----
PUT /projects/plugins%2Freplication/HEAD HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"ref": "refs/heads/stable"
@@ -588,7 +588,7 @@ As response the new ref to which `HEAD` points is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
"refs/heads/stable"
@@ -614,7 +614,7 @@ link:#repository-statistics-info[RepositoryStatisticsInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -651,7 +651,7 @@ read access to `refs/meta/config`.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -671,6 +671,11 @@ read access to `refs/meta/config`.
"configured_value": "INHERIT",
"inherited_value": false
},
+ "create_new_change_for_all_not_in_target": {
+ "value": false,
+ "configured_value": "INHERIT",
+ "inherited_value": false
+ },
"require_change_id": {
"value": false,
"configured_value": "FALSE",
@@ -718,13 +723,14 @@ link:#config-input[ConfigInput] entity.
.Request
----
PUT /projects/myproject/config HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"description": "demo project",
"use_contributor_agreements": "FALSE",
"use_content_merge": "INHERIT",
"use_signed_off_by": "INHERIT",
+ "create_new_change_for_all_not_in_target": "INHERIT",
"require_change_id": "TRUE",
"max_object_size_limit": "10m",
"submit_type": "REBASE_IF_NECESSARY",
@@ -739,7 +745,7 @@ ConfigInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -758,6 +764,11 @@ ConfigInfo] entity.
"configured_value": "INHERIT",
"inherited_value": false
},
+ "create_new_change_for_all_not_in_target": {
+ "value": true,
+ "configured_value": "INHERIT",
+ "inherited_value": false
+ },
"require_change_id": {
"value": true,
"configured_value": "TRUE",
@@ -788,7 +799,7 @@ request body as a link:#gc-input[GCInput] entity.
.Request
----
POST /projects/plugins%2Freplication/gc HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"show_progress": true
@@ -801,7 +812,7 @@ The response is the streamed output of the garbage collection.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
collecting garbage for "plugins/replication":
Pack refs: 100% (21/21)
@@ -844,7 +855,7 @@ The caller must be project owner.
.Request
----
PUT /projects/plugins%2Freplication/ban HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"commits": [
@@ -861,7 +872,7 @@ As response a link:#ban-result-info[BanResultInfo] entity is returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -895,7 +906,7 @@ returned.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -919,6 +930,112 @@ returned.
]
----
+[[branch-options]]
+==== Branch Options
+
+Limit(n)::
+Limit the number of branches to be included in the results.
++
+.Request
+----
+ GET /projects/testproject/branches?n=1 HTTP/1.0
+----
++
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "HEAD",
+ "revision": "master",
+ "can_delete": false
+ }
+ ]
+----
+
+Skip(s)::
+Skip the given number of branches from the beginning of the list.
++
+.Request
+----
+ GET /projects/testproject/branches?n=1&s=0 HTTP/1.0
+----
++
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "HEAD",
+ "revision": "master",
+ "can_delete": false
+ }
+ ]
+----
+
+Substring(m)::
+Limit the results to those projects that match the specified substring.
++
+List all projects that match substring `test`:
++
+.Request
+----
+ GET /projects/testproject/branches?m=test HTTP/1.0
+----
++
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "refs/heads/test1",
+ "revision": "9c9d08a438e55e52f33b608415e6dddd9b18550d",
+ "can_delete": true
+ }
+ ]
+----
+
+Regex(r)::
+Limit the results to those branches that match the specified regex.
+Boundary matchers '^' and '$' are implicit. For example: the regex 't*' will
+match any branches that start with 'test' and regex '*t' will match any
+branches that end with 'test'.
++
+List all branches that match regex `t.*1`:
++
+.Request
+----
+ GET /projects/testproject/branches?r=t.*1 HTTP/1.0
+----
++
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "refs/heads/test1",
+ "revision": "9c9d08a438e55e52f33b608415e6dddd9b18550d",
+ "can_delete": true
+ }
+ ]
+----
+
[[get-branch]]
=== Get Branch
--
@@ -939,7 +1056,7 @@ describes the branch.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -962,7 +1079,7 @@ link:#branch-input[BranchInput].
.Request
----
PUT /projects/MyProject/branches/stable HTTP/1.0
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
{
"revision": "76016386a0d8ecc7b6be212424978bb45959d668"
@@ -976,7 +1093,7 @@ describes the created branch.
----
HTTP/1.1 201 Created
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1004,6 +1121,38 @@ Deletes a branch.
HTTP/1.1 204 No Content
----
+[[delete-branches]]
+=== Delete Branches
+--
+'POST /projects/link:#project-name[\{project-name\}]/branches:delete'
+--
+
+Delete one or more branches.
+
+The branches to be deleted must be provided in the request body as a
+link:#delete-branches-input[DeleteBranchesInput] entity.
+
+.Request
+----
+ POST /projects/MyProject/branches:delete HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "branches": [
+ "stable-1.0",
+ "stable-2.0"
+ ]
+ }
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+If some branches could not be deleted, the response is "`409 Conflict`" and the
+error message is contained in the response body.
+
[[get-content]]
=== Get Content
--
@@ -1023,7 +1172,7 @@ The content is returned as base64 encoded string.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
----
@@ -1051,7 +1200,7 @@ returned in reverse order.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1130,7 +1279,7 @@ returned that describe the child projects.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1170,7 +1319,7 @@ are not resolved further.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1228,7 +1377,7 @@ describes the child project.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1239,6 +1388,102 @@ describes the child project.
}
----
+[[tag-endpoints]]
+== Tag Endpoints
+
+[[list-tags]]
+=== List Tags
+--
+'GET /projects/link:#project-name[\{project-name\}]/tags/'
+--
+
+List the tags of a project.
+
+As result a list of link:#tag-info[TagInfo] entries is returned.
+
+Only includes tags under the `refs/tags/` namespace.
+
+.Request
+----
+ GET /projects/work%2Fmy-project/tags/ HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "refs/tags/v1.0",
+ "revision": "49ce77fdcfd3398dc0dedbe016d1a425fd52d666",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Annotated tag",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 07:35:03.000000000",
+ "tz": 540
+ }
+ },
+ {
+ "ref": "refs/tags/v2.0",
+ "revision": "1624f5af8ae89148d1a3730df8c290413e3dcf30"
+ },
+ {
+ "ref": "refs/tags/v3.0",
+ "revision": "c628685b3c5a3614571ecb5c8fceb85db9112313",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Signed tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.11 (GNU/Linux)\n\niQEcBAABAgAGBQJUMlqYAAoJEPI2qVPgglptp7MH/j+KFcittFbxfSnZjUl8n5IZ\nveZo7wE+syjD9sUbMH4EGv0WYeVjphNTyViBof+stGTNkB0VQzLWI8+uWmOeiJ4a\nzj0LsbDOxodOEMI5tifo02L7r4Lzj++EbqtKv8IUq2kzYoQ2xjhKfFiGjeYOn008\n9PGnhNblEHZgCHguGR6GsfN8bfA2XNl9B5Ysl5ybX1kAVs/TuLZR4oDMZ/pW2S75\nhuyNnSgcgq7vl2gLGefuPs9lxkg5Fj3GZr7XPZk4pt/x1oiH7yXxV4UzrUwg2CC1\nfHrBlNbQ4uJNY8TFcwof52Z0cagM5Qb/ZSLglHbqEDGA68HPqbwf5z2hQyU2/U4\u003d\n\u003dZtUX\n-----END PGP SIGNATURE-----",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 09:02:16.000000000",
+ "tz": 540
+ }
+ }
+ ]
+----
+
+[[get-tag]]
+=== Get Tag
+--
+'GET /projects/link:#project-name[\{project-name\}]/tags/link:#tag-id[\{tag-id\}]'
+--
+
+Retrieves a tag of a project.
+
+.Request
+----
+ GET /projects/work%2Fmy-project/tags/v1.0 HTTP/1.0
+----
+
+As response a link:#tag-info[TagInfo] entity is returned that describes the tag.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "ref": "refs/tags/v1.0",
+ "revision": "49ce77fdcfd3398dc0dedbe016d1a425fd52d666",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Annotated tag",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 07:35:03.000000000",
+ "tz": 540
+ }
+ }
+----
+
+
[[commit-endpoints]]
== Commit Endpoints
@@ -1264,7 +1509,7 @@ is returned that describes the commit.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1311,7 +1556,7 @@ The content is returned as base64 encoded string.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: text/plain;charset=UTF-8
+ Content-Type: text/plain; charset=UTF-8
Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
----
@@ -1341,7 +1586,7 @@ List all dashboards for the `work/my-project` project:
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
[
@@ -1393,7 +1638,7 @@ that describes the dashboard.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1429,7 +1674,7 @@ dashboard-id.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1469,7 +1714,7 @@ 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
+ Content-Type: application/json; charset=UTF-8
{
"id": "main:closed",
@@ -1484,7 +1729,7 @@ link:#dashboard-info[DashboardInfo] entity.
----
HTTP/1.1 200 OK
Content-Disposition: attachment
- Content-Type: application/json;charset=UTF-8
+ Content-Type: application/json; charset=UTF-8
)]}'
{
@@ -1547,6 +1792,10 @@ omitted.
=== \{commit-id\}
Commit ID.
+[[tag-id]]
+=== \{tag-id\}
+The name of a tag. The prefix `refs/tags/` can be omitted.
+
[[dashboard-id]]
=== \{dashboard-id\}
The ID of a dashboard in the format '<ref>:<path>'.
@@ -1558,29 +1807,18 @@ dashboard of a project.
=== \{project-name\}
The name of the project.
+If the name ends with `.git`, the suffix will be automatically removed.
+
[[json-entities]]
== JSON Entities
-[[branch-info]]
-=== BranchInfo
-The `BranchInfo` entity contains information about a branch.
-
-[options="header",width="50%",cols="1,^2,4"]
-|=========================
-|Field Name ||Description
-|`ref` ||The ref of the branch.
-|`revision` ||The revision to which the branch points.
-|`can_delete`|`false` if not set|
-Whether the calling user can delete this branch.
-|=========================
-
[[ban-input]]
=== BanInput
The `BanInput` entity contains information for banning commits in a
project.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=======================
|Field Name||Description
|`commits` ||List of commits to be banned.
@@ -1591,7 +1829,7 @@ project.
=== BanResultInfo
The `BanResultInfo` entity describes the result of banning commits.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`newly_banned` |optional|List of newly banned commits.
@@ -1599,12 +1837,28 @@ The `BanResultInfo` entity describes the result of banning commits.
|`ignored` |optional|List of object IDs that were ignored.
|=============================
+[[branch-info]]
+=== BranchInfo
+The `BranchInfo` entity contains information about a branch.
+
+[options="header",cols="1,^2,4"]
+|=========================
+|Field Name ||Description
+|`ref` ||The ref of the branch.
+|`revision` ||The revision to which the branch points.
+|`can_delete`|`false` if not set|
+Whether the calling user can delete this branch.
+|`web_links` |optional|
+Links to the branch in external sites as a list of
+link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
+|=========================
+
[[branch-input]]
=== BranchInput
The `BranchInput` entity contains information for the creation of
a new branch.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=======================
|Field Name||Description
|`ref` |optional|
@@ -1621,26 +1875,29 @@ If not set, `HEAD` will be used as base revision.
The `ConfigInfo` entity contains information about the effective project
configuration.
-[options="header",width="50%",cols="1,^2,4"]
-|=========================================
-|Field Name ||Description
-|`description` |optional|
+[options="header",cols="1,^2,4"]
+|=======================================================
+|Field Name ||Description
+|`description` |optional|
The description of the project.
-|`use_contributor_agreements`|optional|
+|`use_contributor_agreements` |optional|
link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
authors must complete a contributor agreement on the site before
pushing any commits or changes to this project.
-|`use_content_merge` |optional|
+|`use_content_merge` |optional|
link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
Gerrit will try to perform a 3-way merge of text file content when a
file has been modified by both the destination branch and the change
being submitted. This option only takes effect if submit type is not
FAST_FORWARD_ONLY.
-|`use_signed_off_by` |optional|
+|`use_signed_off_by` |optional|
link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
each change must contain a Signed-off-by line from either the author or
the uploader in the commit message.
-|`require_change_id` |optional|
+|`create_new_change_for_all_not_in_target` |optional|
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
+a new change is created for every commit not in target branch.
+|`require_change_id` |optional|
link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether a
valid link:user-changeid.html[Change-Id] footer in any commit uploaded
for review is required. This does not apply to commits pushed directly
@@ -1662,71 +1919,76 @@ the comment link configuration is mapped to the comment link
configuration, which has the same format as the
link:config-gerrit.html#_a_id_commentlink_a_section_commentlink[
commentlink section] of `gerrit.config`.
-|`theme` |optional|
+|`theme` |optional|
The theme that is configured for the project as a link:#theme-info[
ThemeInfo] entity.
-|`plugin_config` |optional|
+|`plugin_config` |optional|
Plugin configuration as map which maps the plugin name to a map of
parameter names to link:#config-parameter-info[ConfigParameterInfo]
entities.
-|`actions` |optional|
+|`actions` |optional|
Actions the caller might be able to perform on this project. The
information is a map of view names to
link:rest-api-changes.html#action-info[ActionInfo] entities.
-|=========================================
+|=======================================================
[[config-input]]
=== ConfigInput
The `ConfigInput` entity describes a new project configuration.
-[options="header",width="50%",cols="1,^2,4"]
-|=========================================
-|Field Name ||Description
-|`description` |optional|
+[options="header",cols="1,^2,4"]
+|======================================================
+|Field Name ||Description
+|`description` |optional|
The new description of the project. +
If not set, the description is removed.
-|`use_contributor_agreements`|optional|
+|`use_contributor_agreements` |optional|
Whether authors must complete a contributor agreement on the site
before pushing any commits or changes to this project. +
Can be `TRUE`, `FALSE` or `INHERIT`. +
If not set, this setting is not updated.
-|`use_content_merge` |optional|
+|`use_content_merge` |optional|
Whether Gerrit will try to perform a 3-way merge of text file content
when a file has been modified by both the destination branch and the
change being submitted. This option only takes effect if submit type is
not FAST_FORWARD_ONLY. +
Can be `TRUE`, `FALSE` or `INHERIT`. +
If not set, this setting is not updated.
-|`use_signed_off_by` |optional|
+|`use_signed_off_by` |optional|
Whether each change must contain a Signed-off-by line from either the
author or the uploader in the commit message. +
Can be `TRUE`, `FALSE` or `INHERIT`. +
If not set, this setting is not updated.
-|`require_change_id` |optional|
+|`create_new_change_for_all_not_in_target` |optional|
+Whether a new change will be created for every commit not in target
+branch. +
+Can be `TRUE`, `FALSE` or `INHERIT`. +
+If not set, this setting is not updated.
+|`require_change_id` |optional|
Whether a valid link:user-changeid.html[Change-Id] footer in any commit
uploaded for review is required. This does not apply to commits pushed
directly to a branch or tag. +
Can be `TRUE`, `FALSE` or `INHERIT`. +
If not set, this setting is not updated.
-|`max_object_size_limit` |optional|
+|`max_object_size_limit` |optional|
The link:config-gerrit.html#receive.maxObjectSizeLimit[max object size
limit] of this project as a link:#max-object-size-limit-info[
MaxObjectSizeLimitInfo] entity. +
If set to `0`, the max object size limit is removed. +
If not set, this setting is not updated.
-|`submit_type` |optional|
+|`submit_type` |optional|
The default submit type of the project, can be `MERGE_IF_NECESSARY`,
`FAST_FORWARD_ONLY`, `REBASE_IF_NECESSARY`, `MERGE_ALWAYS` or
`CHERRY_PICK`. +
If not set, the submit type is not updated.
-|`state` |optional|
+|`state` |optional|
The state of the project, can be `ACTIVE`, `READ_ONLY` or `HIDDEN`. +
Not set if the project state is `ACTIVE`. +
If not set, the project state is not updated.
-|`plugin_config_values` |optional|
+|`plugin_config_values` |optional|
Plugin configuration values as map which maps the plugin name to a map
of parameter names to values.
-|=========================================
+|======================================================
[[config-parameter-info]]
ConfigParameterInfo
@@ -1734,7 +1996,7 @@ ConfigParameterInfo
The `ConfigParameterInfo` entity describes a project configuration
parameter.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|===============================
|Field Name ||Description
|`display_name` |optional|
@@ -1773,7 +2035,7 @@ The list of permitted values, only set if the `type` is `LIST`.
The `DashboardInfo` entity contains information about a project
dashboard.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|===============================
|Field Name ||Description
|`id` ||
@@ -1810,7 +2072,7 @@ The list of link:#dashboard-section-info[sections] in the dashboard.
The `DashboardInput` entity contains information to create/update a
project dashboard.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`id` |optional|
@@ -1824,7 +2086,7 @@ Message that should be used to commit the change of the dashboard.
The `DashboardSectionInfo` entity contains information about a section
in a dashboard.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|===========================
|Field Name |Description
|`name` |The title of the section.
@@ -1832,12 +2094,24 @@ in a dashboard.
Tokens such as `${project}` are not resolved.
|===========================
+[[delete-branches-input]]
+=== DeleteBranchesInput
+The `DeleteBranchesInput` entity contains information about branches that should
+be deleted.
+
+[options="header",width="50%",cols="1,6"]
+|==========================
+|Field Name |Description
+|`branches` |A list of branch names that identify the branches that should be
+deleted.
+|==========================
+
[[gc-input]]
=== GCInput
The `GCInput` entity contains information to run the Git garbage
collection.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`show_progress` |`false` if not set|
@@ -1849,7 +2123,7 @@ Whether progress information should be shown.
The `HeadInput` entity contains information for setting `HEAD` for a
project.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|============================
|Field Name |Description
|`ref` |
@@ -1861,7 +2135,7 @@ omitted.
=== InheritedBooleanInfo
A boolean value that can also be inherited.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|================================
|Field Name ||Description
|`value` ||
@@ -1879,7 +2153,7 @@ The `MaxObjectSizeLimitInfo` entity contains information about the
link:config-gerrit.html#receive.maxObjectSizeLimit[max object size
limit] of a project.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|===============================
|Field Name ||Description
|`value` |optional|
@@ -1900,7 +2174,7 @@ Not set if there is no global limit for the object size.
The `ProjectDescriptionInput` entity contains information for setting a
project description.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`description` |optional|The project description. +
@@ -1915,7 +2189,7 @@ branch.
=== ProjectInfo
The `ProjectInfo` entity contains information about a project.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|===========================
|Field Name ||Description
|`id` ||The URL encoded project name.
@@ -1929,7 +2203,7 @@ is increased for each non-visible project).
|`description` |optional|The description of the project.
|`state` |optional|`ACTIVE`, `READ_ONLY` or `HIDDEN`.
|`branches` |optional|Map of branch names to HEAD revisions.
-|'web_links' |optional|
+|`web_links` |optional|
Links to the project in external sites as a list of
link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
|===========================
@@ -1939,12 +2213,13 @@ link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
The `ProjectInput` entity contains information for the creation of
a new project.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=========================================
|Field Name ||Description
|`name` |optional|
The name of the project (not encoded). +
-If set, must match the project name in the URL.
+If set, must match the project name in the URL. +
+If name ends with `.git` the suffix will be automatically removed.
|`parent` |optional|
The name of the parent project. +
If not set, the `All-Projects` project will be the parent project.
@@ -1970,17 +2245,20 @@ link:rest-api-groups.html#group-id[group-id]. +
If not set, the link:config-gerrit.html#repository.name.ownerGroup[
groups that are configured as default owners] are set as project
owners.
-|`use_contributor_agreements`|`INHERIT` if not set|
+|`use_contributor_agreements` |`INHERIT` if not set|
Whether contributor agreements should be used for the project (`TRUE`,
`FALSE`, `INHERIT`).
-|`use_signed_off_by` |`INHERIT` if not set|
+|`use_signed_off_by` |`INHERIT` if not set|
Whether the usage of 'Signed-Off-By' footers is required for the
project (`TRUE`, `FALSE`, `INHERIT`).
-|`use_content_merge` |`INHERIT` if not set|
+|`create_new_change_for_all_not_in_target` |`INHERIT` if not set|
+Whether a new change is created for every commit not in target branch
+for the project (`TRUE`, `FALSE`, `INHERIT`).
+|`use_content_merge` |`INHERIT` if not set|
Whether content merge should be enabled for the project (`TRUE`,
`FALSE`, `INHERIT`). +
`FALSE`, if the `submit_type` is `FAST_FORWARD_ONLY`.
-|`require_change_id` |`INHERIT` if not set|
+|`require_change_id` |`INHERIT` if not set|
Whether the usage of Change-Ids is required for the project (`TRUE`,
`FALSE`, `INHERIT`).
|`max_object_size_limit` |optional|
@@ -1996,7 +2274,7 @@ of parameter names to values.
The `ProjectParentInput` entity contains information for setting a
project parent.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`parent` ||The name of the parent project.
@@ -2009,7 +2287,7 @@ in the `project.config` file to the `refs/meta/config` branch.
=== ReflogEntryInfo
The `ReflogEntryInfo` entity describes an entry in a reflog.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|============================
|Field Name |Description
|`old_id` |The old commit ID.
@@ -2025,7 +2303,7 @@ link:rest-api-changes.html#git-person-info[GitPersonInfo] entity.
The `RepositoryStatisticsInfo` entity contains information about
statistics of a Git repository.
-[options="header",width="50%",cols="1,6"]
+[options="header",cols="1,6"]
|======================================
|Field Name |Description
|`number_of_loose_objects` |Number of loose objects.
@@ -2037,11 +2315,29 @@ statistics of a Git repository.
|`size_of_packed_objects` |Size of packed objects in bytes.
|======================================
+[[tag-info]]
+=== TagInfo
+The `TagInfo` entity contains information about a tag.
+
+[options="header",cols="1,^2,4"]
+|=========================
+|Field Name ||Description
+|`ref` ||The ref of the tag.
+|`revision` ||For lightweight tags, the revision of the commit to which the tag
+points. For annotated tags, the revision of the tag object.
+|`object`|Only set for annotated tags.|The revision of the object to which the
+tag points.
+|`message`|Only set for annotated tags.|The tag message. For signed tags, includes
+the signature.
+|`tagger`|Only set for annotated tags.|The tagger as a
+link:rest-api-changes.html#git-person-info[GitPersonInfo] entity.
+|=========================
+
[[theme-info]]
=== ThemeInfo
The `ThemeInfo` entity describes a theme.
-[options="header",width="50%",cols="1,^2,4"]
+[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`css` |optional|
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index b228ddad5d..a87f5b6c77 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -101,58 +101,58 @@ Here are examples that show how HTTP status codes are used in the
context of the Gerrit REST API.
==== 400 Bad Request
-`400 Bad Request` is returned if the request is not understood by the
+"`400 Bad Request`" is returned if the request is not understood by the
server due to malformed syntax.
-E.g. `400 Bad Request` is returned if JSON input is expected but the
+E.g. "`400 Bad Request`" is returned if JSON input is expected but the
'Content-Type' of the request is not 'application/json' or the request
body doesn't contain valid JSON.
-`400 Bad Request` is also returned if required input fields are not set or
+"`400 Bad Request`" is also returned if required input fields are not set or
if options are set which cannot be used together.
==== 403 Forbidden
-`403 Forbidden` is returned if the operation is not allowed because the
+"`403 Forbidden`" is returned if the operation is not allowed because the
calling user does not have sufficient permissions.
E.g. some REST endpoints require that the calling user has certain
link:access-control.html#global_capabilities[global capabilities]
assigned.
-`403 Forbidden` is also returned if `self` is used as account ID and the
+"`403 Forbidden`" is also returned if `self` is used as account ID and the
REST call was done without authentication.
==== 404 Not Found
-`404 Not Found` is returned if the resource that is specified by the
+"`404 Not Found`" is returned if the resource that is specified by the
URL is not found or is not visible to the calling user. A resource
cannot be found if the URL contains a non-existing ID or view.
==== 405 Method Not Allowed
-`405 Method Not Allowed` is returned if the resource exists but doesn't
+"`405 Method Not Allowed`" is returned if the resource exists but doesn't
support the operation.
E.g. some of the `/groups/` endpoints are only supported for Gerrit
internal groups; if they are invoked for an external group the response
-is `405 Method Not Allowed`.
+is "`405 Method Not Allowed`".
==== 409 Conflict
-`409 Conflict` is returned if the request cannot be completed because the
+"`409 Conflict`" is returned if the request cannot be completed because the
current state of the resource doesn't allow the operation.
E.g. if you try to submit a change that is abandoned, this fails with
-`409 Conflict` because the state of the change doesn't allow the submit
+"`409 Conflict`" because the state of the change doesn't allow the submit
operation.
-`409 Conflict` is also returned if you try to create a resource but the
+"`409 Conflict`" is also returned if you try to create a resource but the
name is already occupied by an existing resource.
==== 412 Precondition Failed
-`412 Precondition Failed` is returned if a precondition from the request
+"`412 Precondition Failed`" is returned if a precondition from the request
header fields is not fulfilled, as described in the link:#preconditions[
Preconditions] section.
==== 422 Unprocessable Entity
-`422 Unprocessable Entity` is returned if the ID of a resource that is
+"`422 Unprocessable Entity`" is returned if the ID of a resource that is
specified in the request body cannot be resolved.
GERRIT
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index fff14b4531..44ca6e0770 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -56,6 +56,8 @@ of the local Git repository:
$ curl -Lo .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
+or:
+
$ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
Then ensure that the execute bit is set on the hook script:
@@ -68,8 +70,8 @@ For more details, see link:cmd-hook-commit-msg.html[commit-msg].
Change Upload
--------------
-During upload by pushing to `refs/for/*`, `refs/drafts/*` or
-`refs/heads/*`, Gerrit will try to find an existing review the
+During upload by pushing to `+refs/for/*+`, `+refs/drafts/*+` or
+`+refs/heads/*+`, Gerrit will try to find an existing review the
uploaded commit relates to. For an existing review to match, the
following properties have to match:
diff --git a/Documentation/user-dashboards.txt b/Documentation/user-dashboards.txt
index 52916b9096..e64d625de2 100644
--- a/Documentation/user-dashboards.txt
+++ b/Documentation/user-dashboards.txt
@@ -1,5 +1,6 @@
= Gerrit Code Review - Dashboards
+[[custom-dashboards]]
== Custom Dashboards
A custom dashboard is shown in a layout similar to the per-user
@@ -58,7 +59,7 @@ to changes for the current user:
== Project Dashboards
It is possible to share custom dashboards at a project level. To do
-this define the dashboards in a `refs/meta/dashboards/*` branch of the
+this define the dashboards in a `+refs/meta/dashboards/*+` branch of the
project. For each dashboard create a config file. The file path/name
will be used as name (equivalent to a title in a custom dashboard) for
the dashboard.
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
new file mode 100644
index 0000000000..5ad6b39ffa
--- /dev/null
+++ b/Documentation/user-inline-edit.txt
@@ -0,0 +1,189 @@
+= Inline Edit
+
+This page explains the workflow for creating and amending changes in the
+browser.
+
+
+[[create-change]]
+== Creating a New Change
+
+A new change can be created directly in the browser, meaning it is not necessary
+to clone the whole repository to make trivial changes.
+
+The new change is created as a draft change, unless
+link:config-gerrit.html#change.allowDrafts[change.allowDrafts] is set to false,
+in which case the change is created as a normal new change.
+
+There are two different ways to create a new change:
+
+By clicking on the 'Create Change' button in the project screen:
+
+[[create-change-from-project-info-screen]]
+
+image::images/inline-edit-create-change-project-screen.png[width=800, link="images/inline-edit-create-change-project-screen.png"]
+
+The user can select the branch on which the new change should be created:
+
+image::images/inline-edit-create-change-project-screen-dialog.png[width=800, link="images/inline-edit-create-change-project-screen-dialog.png"]
+
+By clicking the 'Follow-Up' button on the change screen, to create a new change
+based on the selected change.
+
+[[create-change-from-change-screen]]
+
+image::images/inline-edit-create-follow-up-change.png[width=800, link="images/inline-edit-create-follow-up-change.png"]
+
+[[editing-change]]
+== Editing Changes
+
+To switch to edit mode, press the 'Edit' button at the top of the file list:
+
+[[switch-to-edit-mode]]
+image::images/inline-edit-enter-edit-mode-from-file-list.png[width=800, link="images/inline-edit-enter-edit-mode-from-file-list.png"]
+
+While in edit mode, it is possible to add new files to the change by clicking
+the 'Add...' button at the top of the file list.
+
+Files can be removed from the change, or restored, by clicking the icon to the
+left of the file name. Reverting a file in the change is also supported and is
+achieved in two steps: remove file from the change and restore the file in the
+change.
+
+To switch from edit mode back to review mode, click the 'Done Editing' button.
+
+image::images/inline-edit-file-list-in-edit-mode.png[width=800, link="images/inline-edit-file-list-in-edit-mode.png"]
+
+[[open-full-screen-editor]]
+While in edit mode, clicking on a file name in the file list opens a full
+screen editor for that file.
+
+To save edits, click the 'Save' button or press `CTRL-S`. To return to the
+change screen, click the 'Close' button.
+
+image::images/inline-edit-full-screen-editor.png[width=800, link="images/inline-edit-full-screen-editor.png"]
+
+If there are unsaved edits when the 'Close' button is pressed, a dialog will
+pop up asking to confirm the edits.
+
+image::images/inline-edit-confirm-unsaved-edits.png[width=800, link="images/inline-edit-confirm-unsaved-edits.png"]
+
+To discard the unsaved edits and return to the change screen, click the 'OK'
+button. To continue editing, click 'Cancel'.
+
+[[switch-to-edit-mode-from-side-by-side]]
+
+While in review mode, it is possible to switch directly to edit mode and into an
+editor for a file under review by clicking on the edit icon in the patch set list
+on the side-by-side diff view.
+
+image::images/inline-edit-enter-edit-mode-from-diff.png[width=800, link="images/inline-edit-enter-edit-mode-from-diff.png"]
+
+[[reviewing-changes-made-in-change-edit]]
+== Reviewing Change Edits
+
+Change edits are reviewed in the same way as regular patch sets, using the
+side-by-side diff screen. Change edits are shown as 'edit' in the patch list
+on the diff screen:
+
+image::images/inline-edit-edit-in-diff-screen-patch-list.png[width=800, link="images/inline-edit-edit-in-diff-screen-patch-list.png"]
+
+and on the change screen:
+
+image::images/inline-edit-edit-in-patch-list.png[width=800, link="images/inline-edit-edit-in-patch-list.png"]
+
+Note that patch sets may exist that were created after the change edit was created.
+
+For example this sequence:
+
+`1 2 3 4 5 6 7 8 9 edit 10`
+
+means that the change edit was created on top of patch set number 9 and a regular
+patch set was uploaded later.
+
+[[change-edit-actions]]
+== Change Edit Actions
+
+Change edits can be deleted, published and rebased, and a patch set that
+represents a change edit can be downloaded like a regular patch set.
+
+[[delete-change-edit]]
+
+There is a special ref for a change edit. When the change edit is deleted, this
+ref is deleted as well. To delete a change edit click on the "Delete Edit"
+button.
+
+[[publish-change-edit]]
+
+When a change edit is based on the current patch set, it can be published. By
+publishing a change edit it is promoted to a regular patch set. The special ref
+that represents the change edit is deleted on publish. To publish a change edit
+click on the "Publish Edit" button. This button is only shown when the change
+edit is based on the current patch set. Otherwise the change edit must first be
+rebased onto the current patch set.
+
+[[rebase-change-edit]]
+
+Only change edits that are based on the current patch set can be published. If
+in the meantime a new patch set was uploaded, the change edit must be rebased on
+top of the current patch set before it can be published. Rebasing a change
+edit is done by clicking on the "Rebase Edit" button. If the rebase results in
+conflicts, these conflicts cannot be resolved in the browser. In this case the
+change edit must be downloaded (see below) and the conflicts must be resolved in
+the local environment. The commit that contains the conflict resolution can then
+be uploaded by setting `edit` as option on the target ref:
+
+----
+ $ git push host HEAD:refs/for/master%edit
+----
+
+[[download-change-edit-patch]]
+
+Like regular patch sets, change edits can be downloaded by the download
+commands (e.g. provided by the `download-commands` plugin). To download a
+change edit, select the desired scheme from the "Download" dropdown and copy the
+command to your terminal. Note: only change edit owners and users that were
+granted the link:access-control.html#capability_accessDatabase[accessDatabase]
+global capability are able to access change edit refs.
+
+[[not-implemented-features]]
+== Not Implemented Features
+
+* [PENDING CHANGE]
+The inline editor uses settings decided from the user's diff preferences, but those
+preferences are only modifiable from the side-by-side diff screen. It should be possible
+to open the preferences also from within the editor.
+
+* Allow to rename files that are already contained in the change (from the file table).
+The same rename file dialog can be used with preselected and disabled original file
+name.
+
+* Changed files in change edit should be marked as changed in file table in edit mode.
+One option is to use dirty icon or "*" char in front of changed files, another option
+is to use different hyperlink color for changed files (red?), to avoid adding yet another
+column to the file table
+
+* Add navigation icons in header area of edit screen. When dozen files need to be changed
+in context of change edit, this is not the best workflow to open one file in edit screen,
+change it, save it, close edit screen and select next file from the file table to edit.
+"<-" | "->" icons in header of edit screen could be used to navigate to the next file to
+change from the file table. This would behave like the navigation icons in side by side
+with thefollowing logic on click:
+
+** "save-when-file-was-changed" or
+** "close-when-no-changes"
+
+* Allow to activate different key maps, supported by CM: Emacs, Sublime, Vim. Load key
+maps dynamically. Currently default mode is used.
+
+* Implement conflict resolution during rebase of change edit using inline edit
+feature by creating new edit on top of current patch set with auto merge content
+
+* Similarly, reuse inline edit feature for conflict resolution during rebase of regular
+patch sets
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-notify.txt b/Documentation/user-notify.txt
index dd7c4c6880..9dffa51b84 100644
--- a/Documentation/user-notify.txt
+++ b/Documentation/user-notify.txt
@@ -18,7 +18,9 @@ are visible to the user.
link:user-search.html[Change search expressions] can be used to filter
change notifications to specific subsets, for example `branch:master`
-to only see changes proposed for the master branch.
+to only see changes proposed for the master branch. If a filter would
+match at the `All-Projects` level as well as a specific project, the
+more specific project's notification settings are used.
Notification mails for new changes and new patch sets are not sent to
the change owner.
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index bb1aeefffc..dddbadc989 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -22,66 +22,58 @@ and the change status are displayed right above the commit message.
image::images/user-review-ui-change-screen-commit-message.png[width=800, link="images/user-review-ui-change-screen-commit-message.png"]
-The commit message can be edited directly in the Web UI by clicking on
-the `Edit Message` button in the change header. This opens a drop-down
-editor box in which the commit message can be edited. Saving
-modifications of the commit message automatically creates a new patch
-set for the change. The commit message may only be edited on the
-current patch set.
-
-image::images/user-review-ui-change-screen-edit-commit-message.png[width=800, link="images/user-review-ui-change-screen-edit-commit-message.png"]
-
+[[permalink]]
The numeric change ID is a link to the change and clicking on it
refreshes the change screen. By copying the link location you can get
the permalink of the change.
image::images/user-review-ui-change-screen-permalink.png[width=800, link="images/user-review-ui-change-screen-permalink.png"]
+[[change-status]]
The change status shows the state of the change:
-- `Needs <label>`:
+- [[needs]]`Needs <label>`:
+
The change is in review and an approval on the shown label is still
required to make the change submittable.
-- `Not <label>`:
+- [[not]]`Not <label>`:
+
The change is in review and a veto vote on the shown label is
preventing the submit.
-[[not-current]]
-- `Not Current`:
+- [[not-current]]`Not Current`:
+
The currently viewed patch set is outdated.
+
Please note that some operations, like voting, are not available on
outdated patch sets, but only on the current patch set.
-- `Ready to Submit`:
+- [[ready-to-submit]]`Ready to Submit`:
+
The change has all necessary approvals and may be submitted.
-- `Submitted, Merge Pending`:
+- [[submitted-merge-pending]]`Submitted, Merge Pending`:
+
The change was submitted and was added to the merge queue.
+
The change stays in the merge queue if it depends on a change that is
still in review. In this case it will get automatically merged when all
-predecessor changes have been merged.
+dependency changes have been merged.
+
This status can also mean that the change depends on an abandoned
change or on an outdated patch set of another change. In this case you
may want to rebase the change.
-- `Merged`:
+- [[merged]]`Merged`:
+
The change was successfully merged into the destination branch.
-- `Abandoned`:
+- [[abandoned]]`Abandoned`:
+
The change was abandoned.
-- `Draft`:
+- [[draft]]`Draft`:
+
The change is a draft that is only visible to the change owner, the
reviewers that were explicitly added to the change, and users who have
@@ -119,14 +111,14 @@ and offers actions on the change.
image::images/user-review-ui-change-screen-change-info.png[width=800, link="images/user-review-ui-change-screen-change-info.png"]
-- Change Owner:
+- [[change-owner]]Change Owner:
+
The owner of the change is displayed as a link to a list of the owner's
changes that have the same status as the currently viewed change.
+
image::images/user-review-ui-change-screen-change-info-owner.png[width=800, link="images/user-review-ui-change-screen-change-info-owner.png"]
-- Reviewers:
+- [[reviewers]]Reviewers:
+
The reviewers of the change are displayed as chip tokens.
+
@@ -137,6 +129,7 @@ New reviewers can be added by clicking on the `Add...` button. Typing
into the pop-up text field activates auto completion of user and group
names.
+
+[[remove-reviewer]]
Reviewers can be removed from the change by clicking on the `x` icon
in the reviewer's chip token. Removing a reviewer also removes the
current votes of the reviewer. The removal of votes is recorded as a
@@ -153,7 +146,7 @@ Removing reviewers is protected by permissions:
+
image::images/user-review-ui-change-screen-change-info-reviewers.png[width=800, link="images/user-review-ui-change-screen-change-info-reviewers.png"]
-- Project / Branch / Topic:
+- [[project-branch-topic]]Project / Branch / Topic:
+
The name of the project for which the change was done is displayed as a
link to the link:user-dashboards.html#project-default-dashboard[default
@@ -175,7 +168,7 @@ access right. To be able to set a topic on a closed change, the
+
image::images/user-review-ui-change-screen-change-info-project-branch-topic.png[width=800, link="images/user-review-ui-change-screen-change-info-project-branch-topic.png"]
-- Submit Strategy:
+- [[submit-strategy]]Submit Strategy:
+
The link:project-setup.html#submit_type[submit strategy] that will be
used to submit the change. The submit strategy is only displayed for
@@ -188,16 +181,16 @@ by a bold red `Cannot Merge` label.
+
image::images/user-review-ui-change-screen-change-info-cannot-merge.png[width=800, link="images/user-review-ui-change-screen-change-info-cannot-merge.png"]
-- Time of Last Update:
+- [[update-time]]Time of Last Update:
+
image::images/user-review-ui-change-screen-change-info-last-update.png[width=800, link="images/user-review-ui-change-screen-change-info-last-update.png"]
-- Actions:
+- [[actions]]Actions:
+
Depending on the change state and the permissions of the user, different
actions are available on the change:
-** `Submit`:
+** [[submit]]`Submit`:
+
Submits the change and adds it to the merge queue. If possible the
change is merged into the destination branch.
@@ -210,7 +203,7 @@ It is also possible to submit changes that have merge conflicts. This
allows to do the conflict resolution for a change series in a single
merge commit and submit the changes in reverse order.
-** `Abandon`:
+** [[abandon]]`Abandon`:
+
Abandons the change.
+
@@ -221,7 +214,7 @@ assigned.
When a change is abandoned, a panel appears that allows one to type a
comment message to explain why the change is being abandoned.
-** `Restore`:
+** [[restore]]`Restore`:
+
Restores the change.
+
@@ -233,7 +226,7 @@ assigned.
When a change is restored, a panel appears that allows one to type a
comment message to explain why the change is being restored.
-** `Rebase`:
+** [[rebase]]`Rebase`:
+
Rebases the change. The rebase is always done with content merge
enabled. If the rebase is successful a new patch set with the rebased
@@ -246,11 +239,15 @@ onto the tip of the destination branch.
If the change depends on another open change, it is rebased onto the
current patch set of that other change.
+
-The `Rebase` button is only available if the change can be rebased and
+It is possible to change parent revision of a change. The new parent
+revision can be another change towards the same target branch, or
+the tip of the target branch.
++
+The `Rebase` button is only available if
the link:access-control.html#category_rebase[Rebase] access right is
assigned. Rebasing merge commits is not supported.
-** `Cherry-Pick`:
+** [[cherry-pick]]`Cherry-Pick`:
+
Allows to cherry-pick the change to another branch. The destination
branch can be selected from a dialog. Cherry-picking a change creates a
@@ -264,7 +261,7 @@ open changes.
Users can only cherry-pick changes to branches for which they are
allowed to upload changes for review.
-** `Publish`:
+** [[publish]]`Publish`:
+
Publishes the currently viewed draft patch set. If this is the first
patch set of a change that is published, the change will be published
@@ -275,7 +272,7 @@ and the user is the change owner or has the
link:access-control.html#category_publish_drafts[Publish Drafts] access
right assigned.
-** `Delete Change` / `Delete Revision`:
+** [[delete]]`Delete Change` / `Delete Revision`:
+
Deletes the draft change / the currently viewed draft patch set.
+
@@ -284,12 +281,12 @@ draft patch set is viewed and the user is the change owner or has the
link:access-control.html#category_delete_drafts[Delete Drafts] access
right assigned.
-** Further actions may be available if plugins are installed.
+** [[plugin-actions]]Further actions may be available if plugins are installed.
+
image::images/user-review-ui-change-screen-change-info-actions.png[width=800, link="images/user-review-ui-change-screen-change-info-actions.png"]
-- Labels & Votes:
+- [[labels]]Labels & Votes:
+
Approving votes are colored green; veto votes are colored red.
+
@@ -303,10 +300,12 @@ patch set.
image::images/user-review-ui-change-screen-file-list.png[width=800, link="images/user-review-ui-change-screen-file-list.png"]
+[[change-screen-mark-reviewed]]
The checkboxes in front of the file names allow files to be marked as reviewed.
image::images/user-review-ui-change-screen-file-list-mark-as-reviewed.png[width=800, link="images/user-review-ui-change-screen-file-list-mark-as-reviewed.png"]
+[[modification-type]]
The type of a file modification is indicated by the character in front
of the file name:
@@ -332,15 +331,18 @@ The file is new and is copied from an existing file.
image::images/user-review-ui-change-screen-file-list-modification-type.png[width=800, link="images/user-review-ui-change-screen-file-list-modification-type.png"]
+[[rename-or-copy]]
If a file is renamed or copied, the name of the original file is
displayed in gray below the file name.
image::images/user-review-ui-change-screen-file-list-rename.png[width=800, link="images/user-review-ui-change-screen-file-list-rename.png"]
+[[repeating-path-segments]]
Repeating path segments are grayed out.
image::images/user-review-ui-change-screen-file-list-repeating-paths.png[width=800, link="images/user-review-ui-change-screen-file-list-repeating-paths.png"]
+[[inline-comments-column]]
Inline comments on a file are shown in the `Comments` column.
Draft comments, i.e. comments that have been written by the current
@@ -351,28 +353,36 @@ user last reviewed this change, are highlighted in bold.
image::images/user-review-ui-change-screen-file-list-comments.png[width=800, link="images/user-review-ui-change-screen-file-list-comments.png"]
-The size of the modifications in the files can be seen in the `Size`
-column. The footer row shows the total size of the change.
-
-For files, the `Size` column shows the sum of inserted and deleted
-lines as one number. For the total size, inserted and deleted lines are
-shown separately. In addition, the number of insertions and deletions
-is shown as a bar. The size of the bar indicates the amount of changed
-lines, and its coloring in green and red shows the proportion of
-insertions to deletions.
+[[size]]
+The size of the modifications in the files can be seen in the `Size` column. The
+footer row shows the total size of the change.
The size information is useful to easily spot the files that contain
the most modifications; these files are likely to be the most relevant
files for this change. The total change size gives an estimate of how
long a review of this change may take.
+When the "Show Change Sizes As Colored Bars" user preference is enabled, the
+`Size` column shows the sum of inserted and deleted lines as one number. In
+addition, the change size is shown as a bar. The size of the bar indicates the
+amount of changed lines, and its coloring shows the proportion of insertions
+(green) to deletions (red).
+
+When the "Show Change Sizes As Colored Bars" user preference is disabled, the
+colored bar is not shown. For added and renamed files, the `Size` column
+shows the number of inserted and deleted lines. For new files, the column only
+shows the total number of lines in the new file. No size is shown for binary
+files and deleted files.
+
image::images/user-review-ui-change-screen-file-list-size.png[width=800, link="images/user-review-ui-change-screen-file-list-size.png"]
+[[diff-against]]
In the header of the file list, the `Diff Against` selection can be
changed. This selection allows one to choose if the currently viewed
patch set should be compared against its base or against another patch
set of this change. The file list is updated accordingly.
+[[open-all]]
The file list header also provides an `Open All` button that opens the
diff views for all files in the file list.
@@ -393,6 +403,7 @@ operations are only available on the current patch set.
image::images/user-review-ui-change-screen-patch-sets.png[width=800, link="images/user-review-ui-change-screen-patch-sets.png"]
+[[patch-set-drop-down]]
The patch set drop-down list shows the list of patch sets and allows to
switch between them. The patch sets are sorted in descending order so
that the current patch set is always on top.
@@ -479,8 +490,7 @@ display each list of related changes in its own tab.
The following tabs may be displayed:
-[[related-changes-tab]]
-- `Related Changes`:
+- [[related-changes-tab]]`Related Changes`:
+
This tab page shows changes on which the current change depends
(ancestors) and open changes that depend on the current change
@@ -502,7 +512,7 @@ Related changes may be decorated with an icon to signify dependencies
on outdated patch sets, or commits that are not associated to changes
under review:
+
-** Orange Dot:
+** [[outdated]]Orange Dot:
+
The selected patch set of the change is outdated; it is not the current
patch set of the change.
@@ -518,7 +528,7 @@ old patch set of the descendant change depends on the currently viewed
patch set. It may be that the descendant was rebased in the meantime
and with the new patch set this dependency was removed.
-** Green Tilde:
+** [[indirect-descendant]]Green Tilde:
+
The selected patch set of the change is an indirect descendant of the
currently viewed patch set; it has a dependency to another patch set of
@@ -527,7 +537,7 @@ this change and the descendant change now needs to be rebased. Please
note that following the link to an indirect descendant change may
result in a completely different related changes listing.
-** Black Dot:
+** [[closed-ancestor]]Black Dot:
+
Indicates a merged ancestor, e.g. the commit was directly pushed into
the repository bypassing code review, or the ancestor change was
@@ -539,7 +549,7 @@ the commit was done on `branch-a`, but was then pushed to
+
image::images/user-review-ui-change-screen-related-changes-indicators.png[width=800, link="images/user-review-ui-change-screen-related-changes-indicators.png"]
-- `Conflicts With`:
+- [[conflicts-with]]`Conflicts With`:
+
This tab page shows changes that conflict with the current change.
Non-mergeable changes are filtered out; only conflicting changes that
@@ -551,14 +561,14 @@ conflict resolution must then be done manually.
+
image::images/user-review-ui-change-screen-conflicts-with.png[width=800, link="images/user-review-ui-change-screen-conflicts-with.png"]
-- `Same Topic`:
+- [[same-topic]]`Same Topic`:
+
This tab page shows changes that have the same topic as the current
change. Only open changes are included in the list.
+
image::images/user-review-ui-change-screen-same-topic.png[width=800, link="images/user-review-ui-change-screen-same-topic.png"]
-- `Cherry-Picks`:
+- [[cherry-picks]]`Cherry-Picks`:
+
This tab page shows changes with the same link:user-changeid.html[
Change-Id] for the current project.
@@ -586,14 +596,14 @@ Clicking on the `Reply...` button opens a popup panel.
A text box allows to type a summary comment for the currently viewed
patch set.
+Note that you can set the text and tooltip of the button in
+link:config-gerrit.html#change.replyLabel[gerrit.config].
+
+[[vote]]
If the current patch set is viewed, radio buttons are displayed for
each label on which the user is allowed to vote. Voting on non-current
patch sets is not possible.
-Typing "LGTM" (acronym for 'Looks Good To Me') in the summary comment
-text box automatically selects the highest possible score for the
-'Code-Review' label.
-
The inline draft comments that will be published are displayed in a
separate section so that they can be reviewed before publishing. There
are links to navigate to the inline comments which can be used if a
@@ -603,6 +613,7 @@ The `Post` button publishes the comments and the votes.
image::images/user-review-ui-change-screen-replying.png[width=800, link="images/user-review-ui-change-screen-replying.png"]
+[[quick-approve]]
If a user can approve a label that is still required, a quick approve
button appears in the change header that allows to add this missing
approval by a single click. The quick approve button only appears if
@@ -635,6 +646,7 @@ the current user last reviewed this change, are automatically expanded.
image::images/user-review-ui-change-screen-history.png[width=800, link="images/user-review-ui-change-screen-history.png"]
+[[reply-to-message]]
It is possible to directly reply to a change message by clicking on the
reply icon in the right upper corner of a change message. This opens
the reply popup panel and prefills the text box with the quoted comment.
@@ -645,11 +657,13 @@ line between a quoted block and the reply to it.
image::images/user-review-ui-change-screen-reply-to-comment.png[width=800, link="images/user-review-ui-change-screen-reply-to-comment.png"]
+[[inline-comments-in-history]]
Inline comments are directly displayed in the change history and there
are links to navigate to the inline comments.
image::images/user-review-ui-change-screen-inline-comments.png[width=800, link="images/user-review-ui-change-screen-inline-comments.png"]
+[[expand-all]]
The `Expand All` button expands all messages; the `Collapse All` button
collapses all messages.
@@ -675,20 +689,6 @@ controls below the change info block.
image::images/user-review-ui-change-screen-plugin-extensions.png[width=800, link="images/user-review-ui-change-screen-plugin-extensions.png"]
-[[old-change-screen]]
-=== Old Change Screen
-
-In addition to the normal change screen, this Gerrit version still
-includes the old change screen that was used in earlier Gerrit
-versions. Users that want to continue using the old change screen can
-configure it in their preferences under
-`Settings` > `Preferences` > `Change View`:
-
-image::images/user-review-ui-change-view-preference.png[width=800, link="images/user-review-ui-change-view-preference.png"]
-
-[WARNING]
-The old change screen will be removed in a later version of Gerrit.
-
[[side-by-side]]
== Side-by-Side Diff Screen
@@ -700,6 +700,7 @@ This screen allows to review a patch and to comment on it.
image::images/user-review-ui-side-by-side-diff-screen.png[width=800, link="images/user-review-ui-side-by-side-diff-screen.png"]
+[[side-by-side-header]]
In the screen header the project name and the name of the viewed patch
file are shown.
@@ -709,6 +710,7 @@ the Git web browser.
image::images/user-review-ui-side-by-side-diff-screen-project-and-file.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-project-and-file.png"]
+[[side-by-side-mark-reviewed]]
The checkbox in front of the project name and the file name allows the
patch to be marked as reviewed. The link:#mark-reviewed[Mark Reviewed]
diff preference allows to control whether the files should be
@@ -716,6 +718,7 @@ automatically marked as reviewed when they are viewed.
image::images/user-review-ui-side-by-side-diff-screen-reviewed.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-reviewed.png"]
+[[scrollbar]]
The scrollbar shows patch diffs and inline comments as annotations.
This provides a good overview of the lines in the patch that are
relevant for reviewing. By clicking on an annotation one can quickly
@@ -723,6 +726,7 @@ navigate to the corresponding line in the patch.
image::images/user-review-ui-side-by-side-diff-screen-scrollbar.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-scrollbar.png"]
+[[gaps]]
A gap between lines in the file content that is caused by aligning the
left and right side or by displaying inline comments is shown as a
vertical red bar in the line number column. This prevents a gap from
@@ -750,21 +754,25 @@ comparing the old patch against the current patch.
image::images/user-review-ui-side-by-side-diff-screen-patch-sets.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-patch-sets.png"]
+[[download-file]]
The download icon next to the patch set list allows to download the
patch. Unless the mime type of the file is configured as safe, the
download file is a zip archive that contains the patch file.
+[[no-differences]]
If the compared patches are identical, this is highlighted by a red
`No Differences` label in the screen header.
image::images/user-review-ui-side-by-side-diff-screen-no-differences.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-no-differences.png"]
+[[side-by-side-rename]]
If a file was renamed, the old and new file paths are shown in the
header together with a similarity index that shows how much of the file
content is unmodified.
image::images/user-review-ui-side-by-side-diff-screen-rename.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-rename.png"]
+[[navigation]]
For navigating between the patches in a patch set there are navigation
buttons on the right side of the screen header. The left arrow button
navigates to the previous patch; the right arrow button navigates to
@@ -786,6 +794,7 @@ highlighted by a yellow background.
Code blocks with comments may overlap. This means it is possible to
attach several comments to the same code.
+[[line-links]]
The lines of the patch file are linkable. To link to a certain line in
the patch file, '@<line-number>' must be appended to the patch link,
e.g. `http://host:8080/#/c/56857/2/Documentation/user-review-ui.txt@665`.
@@ -798,6 +807,7 @@ is set to `Expand`, all inline comments will be automatically expanded.
image::images/user-review-ui-side-by-side-diff-screen-inline-comments.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-inline-comments.png"]
+[[comment]]
In the header of the comment box, the name of the comment author and
the timestamp of the comment are shown. If avatars are configured on
the server, the avatar image of the comment author is displayed in the
@@ -806,6 +816,7 @@ the comment.
image::images/user-review-ui-side-by-side-diff-screen-comment-box.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-comment-box.png"]
+[[reply-inline-comment]]
Clicking on the `Reply` button opens an editor to type the reply.
Quoting is supported, but only by manually copying & pasting the old
@@ -824,6 +835,7 @@ Clicking on the `Discard` button deletes the inline draft comment.
image::images/user-review-ui-side-by-side-diff-screen-comment-reply.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-comment-reply.png"]
+[[draft-inline-comment]]
Draft comments are marked by the text "Draft" in the header in the
place of the comment author.
@@ -832,6 +844,7 @@ deleted by clicking on the `Discard` button.
image::images/user-review-ui-side-by-side-diff-screen-comment-edit.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-comment-edit.png"]
+[[done]]
Clicking on the `Done` button is a quick way to reply with "Done" to a
comment. This is used to mark a comment as addressed by a follow-up
patch set.
@@ -946,7 +959,7 @@ image::images/user-review-ui-side-by-side-diff-screen-preferences-popup.png[widt
The following diff preferences can be configured:
-- `Theme`:
+- [[theme]]`Theme`:
+
Controls the theme that is used to render the file content.
+
@@ -954,7 +967,7 @@ E.g. users could choose to work with a dark theme.
+
image::images/user-review-ui-side-by-side-diff-screen-dark-theme.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-dark-theme.png"]
-- `Ignore Whitespace`:
+- [[ignore-whitespace]]`Ignore Whitespace`:
+
Controls whether differences in whitespace should be ignored or not.
+
@@ -974,11 +987,11 @@ Whitespace differences at the beginning and end of lines are ignored.
+
All differences in whitespace are ignored.
-- `Tab Width`:
+- [[tab-width]]`Tab Width`:
+
Controls how many spaces should be displayed for a tab.
-- `Columns`:
+- [[columns]]`Columns`:
+
Sets the preferred line length. At this position a vertical dashed line
is displayed so that one can easily detect lines the exceed the
@@ -986,7 +999,7 @@ preferred line length.
+
image::images/user-review-ui-side-by-side-diff-screen-column.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-column.png"]
-- `Lines Of Context`:
+- [[lines-of-context]]`Lines Of Context`:
+
The number of context lines that should be displayed before and after
any diff. If the `entire file` checkbox is selected, the full file is
@@ -1003,13 +1016,13 @@ context by ten lines before and after the skipped block.
+
image::images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-expand-skipped-lines.png"]
-- `Intraline Difference`:
+- [[intraline-difference]]`Intraline Difference`:
+
Controls whether intraline differences should be highlighted.
+
image::images/user-review-ui-side-by-side-diff-screen-intraline-difference.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-intraline-difference.png"]
-- `Syntax Highlighting`:
+- [[syntax-highlighting]]`Syntax Highlighting`:
+
Controls whether syntax highlighting should be enabled.
+
@@ -1019,45 +1032,48 @@ it from the `Language` drop-down list.
+
image::images/user-review-ui-side-by-side-diff-screen-syntax-coloring.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-syntax-coloring.png"]
-- `Whitespace Errors`:
+- [[whitespace-errors]]`Whitespace Errors`:
+
Controls whether whitespace errors are highlighted.
-- `Show Tabs`:
+- [[show-tabs]]`Show Tabs`:
+
Controls whether tabs are highlighted.
-- `Line Numbers`:
+- [[line-numbers]]`Line Numbers`:
+
Controls whether line numbers are shown.
-- `Empty Pane`:
+- [[empty-pane]]`Empty Pane`:
+
Controls whether empty panes are shown or not. The Left pane is empty when a
file was added; the right pane is empty when a file was deleted.
-- `Left Side`:
+- [[left-side]]`Left Side`:
+
Controls whether the left side is shown. This preference is not
persistent and is ignored by the `Save` button. Every time a
patch diff is opened, this preference is reset to `Show`.
-- `Top Menu`:
+- [[top-menu]]`Top Menu`:
+
Controls whether the top menu is shown.
-[[mark-reviewed]]
-- `Mark Reviewed`:
+- [[auto-hide-diff-table-header]]`Auto Hide Diff Table Header`:
++
+Controls whether the diff table header should be automatically hidden
+when scrolling down more than half of a page.
+
+- [[mark-reviewed]]`Mark Reviewed`:
+
Controls whether the files of the patch set should be automatically
marked as reviewed when they are viewed.
-[[expand-all-comments]]
-- `Expand All Comments`:
+- [[expand-all-comments]]`Expand All Comments`:
+
Controls whether all comments should be automatically expanded.
-- `Render`:
+- [[render]]`Render`:
+
Controls how patch files that exceed the screen size are rendered.
+
@@ -1109,18 +1125,13 @@ new review UIs:
e.g. by selecting a code block and clicking on the popup comment
icon.
+[[limitations]]
Limitations of the new review UI:
- The new side-by-side diff screen cannot render images.
-- Unified diff view is missing:
-+
-By setting `Diff View (New Change Screen)` in the user preferences to
-`Unified Diff` the new change screen can be configured to open the file
-diffs in the old unified diff view.
-
-Users preferring the old review UI can link:#old-change-screen[
-configure the change view] in their preferences.
+- The new side-by-side diff screen isn't able to highlight line
+ endings.
GERRIT
------
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index eaf0f6ec87..3de45d2ba6 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -14,7 +14,7 @@ matches the search, the change will be presented instead of a list.
|All > Open | status:open '(or is:open)'
|All > Merged | status:merged
|All > Abandoned | status:abandoned
-|My > Drafts | is:draft
+|My > Drafts | owner:self is:draft
|My > Watched Changes | status:open is:watched
|My > Starred Changes | is:starred
|My > Draft Comments | has:draft
@@ -38,6 +38,7 @@ text and let Gerrit figure out the meaning:
|=============================================================
+[[search-operators]]
== Search Operators
Operators act as restrictions on the search. As more operators
@@ -476,6 +477,7 @@ draft text, or even how many drafts there are. The special case
of `draftby:self` will find changes where the caller has created
a draft comment.
+[[limit]]
limit:'CNT'::
+
Limit the returned results to no more than 'CNT' records. This is
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 5c54259b2b..25665a3470 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -32,6 +32,7 @@ is configured, the password can be obtained by clicking on `Obtain Password`
and then following the site-specific instructions. On sites where this URL is
not configured, the password can be obtained by clicking on `Generate Password`.
+[[ssh]]
== SSH
Each user uploading changes to Gerrit must configure one or more SSH
@@ -145,6 +146,7 @@ Other users (e.g. project owners) who have configured Gerrit to
notify them of new changes will be automatically sent an email
message when the push is completed.
+[[topic]]
To include a short tag associated with all of the changes in the
same group, such as the local topic branch name, append it after
the destination branch name. In this example the short topic tag
@@ -156,8 +158,8 @@ updates:
====
[[review_labels]]
-Review labels can be applied to the change by using the -l option
-in the reference:
+Review labels can be applied to the change by using the `label` (or `l`)
+option in the reference:
====
git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/experimental%l=Verified+1
@@ -169,6 +171,23 @@ apply multiple review labels.
The value is optional. If not specified, it defaults to +1 (if
the label range allows it).
+[[change_edit]]
+A change edit can be pushed by specifying the `edit` (or `e`) option on
+the reference:
+
+====
+ git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/master%edit
+====
+
+There is at most one change edit per user and change. In order to push
+a change edit the change must already exist.
+
+[NOTE]
+When a change edit already exists for a change then pushing with
+`%edit` replaces the existing change edit. This option is useful to
+rebase a change edit on the newest patch set when the rebase of the
+change edit in the web UI fails due to conflicts.
+
If you are frequently uploading changes to the same Gerrit server,
consider adding an SSH host block in `~/.ssh/config` to remember
your username, hostname and port number. This permits the use of
@@ -186,8 +205,8 @@ shorter URLs on the command line, such as:
====
Specific reviewers can be requested and/or additional 'carbon
-copies' of the notification message may be sent by including these
-as options in the reference
+copies' of the notification message may be sent by including the
+`reviewer` (or `r`) and `cc` options in the reference:
====
git push tr:kernel/common HEAD:refs/for/experimental%r=a@a.com,cc=b@o.com
@@ -235,7 +254,7 @@ For more about Change-Ids, see link:user-changeid.html[Change-Id Lines].
[[manual_replacement_mapping]]
==== Manual Replacement Mapping
-.Deprecation Warning
+.Note
****
The remainder of this section describes a manual method of replacing
changes by matching each commit name to an existing change number.
@@ -302,9 +321,9 @@ be rewritten.
Gerrit restricts direct pushes that bypass review to:
-* `refs/heads/*`: any branch can be updated, created, deleted,
+* `+refs/heads/*+`: any branch can be updated, created, deleted,
or rewritten by the pusher.
-* `refs/tags/*`: annotated tag objects pointing to any other type
+* `+refs/tags/*+`: annotated tag objects pointing to any other type
of Git object can be created.
To push branches, the proper access rights must be configured first.
@@ -428,8 +447,8 @@ As Gerrit implements the entire SSH and Git server stack within its
own process space, Gerrit maintains complete control over how the
repository is updated, and what responses are sent to the `git push`
client invoked by the end-user, or by `repo upload`. This allows
-Gerrit to provide magical refs, such as `refs/for/*` for new
-change submission and `refs/changes/*` for change replacement.
+Gerrit to provide magical refs, such as `+refs/for/*+` for new
+change submission and `+refs/changes/*+` for change replacement.
When a push request is received to create a ref in one of these
namespaces Gerrit performs its own logic to update the database,
and then lies to the client about the result of the operation.
diff --git a/ReleaseNotes/ReleaseNotes-2.0.18.txt b/ReleaseNotes/ReleaseNotes-2.0.18.txt
index 303690c9ff..18c5abe87a 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.18.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.18.txt
@@ -22,19 +22,19 @@ to trust any OpenID provider. Refer to `auth.trustedOpenID` in
* Caches
+
-The groups that a user is a member of is no longer stored in the
+The groups that a user is a member of is no longer stored in the
`groups` cache; it is now part of the `accounts` cache. If you
use a cron script to update the `account_groups` database table
-based upon an external data source (such as LDAP), you will need
+based upon an external data source (such as LDAP), you will need
to adjust your script to flush the `accounts` cache.
-The `diff` cache is no longer written to disk by default.
+The `diff` cache is no longer written to disk by default.
To enable the disk store again, administrators must explicitly
set `cache.directory` in the gerrit.config file prior to starting
Gerrit.
* SSH Usernames
+
-SSH usernames are no longer automatically assigned to the
+SSH usernames are no longer automatically assigned to the
local part of the user's email address. With 2.0.18, usernames
must also be unique within the database. These changes were
implemented to resolve a minor potential security issue with
diff --git a/ReleaseNotes/ReleaseNotes-2.0.19.txt b/ReleaseNotes/ReleaseNotes-2.0.19.txt
index b6a731b4ab..6c33e6b85d 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.19.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.19.txt
@@ -48,14 +48,14 @@ New Features
------------
* New ssh create-project command
+
-Thanks to Ulrik Sjölin we now have `gerrit create-project`
+Thanks to Ulrik Sjölin we now have `gerrit create-project`
available over SSH, to construct a new repository and database
record for a project. Documentation has also been updated to
reflect that the command is now available.
* Be more liberal in accepting Signed-off-by lines
+
-The "Require Signed-off-by line" feature in a project is now
+The "Require Signed-off-by line" feature in a project is now
more liberal. Gerrit now requires that the commit be signed off
by either the author or the committer. This was relaxed because
kernel developers often cherry-pick in patches signed off by
@@ -64,7 +64,7 @@ did the backport cherry-pick.
* Allow cache.name.diskLimit = 0 to disable on disk cache
+
-Setting cache.name.diskLimit to 0 will disable the disk for
+Setting cache.name.diskLimit to 0 will disable the disk for
that cache, even though cache.directory was set. This allows
sites to set cache.diff.diskLimit to 0 to avoid caching the diff
records on disk, but still allow caching web_sessions to disk,
@@ -83,7 +83,7 @@ with prior releases.
* Add native LDAP support to Gerrit
+
-Gerrit now has native LDAP support. Setting auth.type to
+Gerrit now has native LDAP support. Setting auth.type to
HTTP_LDAP and then configuring the handful of ldap properties
in gerrit.config will allow Gerrit to load group membership
directly from the organization's LDAP server. This replaces
@@ -105,7 +105,7 @@ User information loaded from LDAP, such as full name or SSH
username, cannot be modified by the end-user. This allows the
Gerrit site administrator to require that users conform to the
standard information published by the organization's directory
-service. Updates in LDAP are automatically reflected in Gerrit
+service. Updates in LDAP are automatically reflected in Gerrit
the next time the user signs-in.
* Remembers anchor during HTTP logins
@@ -113,7 +113,7 @@ the next time the user signs-in.
When using an HTTP SSO product, clicking on a Gerrit link received
out-of-band (e.g. by email or IM) often required clicking the
link twice. On the first click Gerrit redirect you to the
-organization's single-sign-on authentication system, which upon
+organization's single-sign-on authentication system, which upon
success redirected to your dashboard. The actual target of the
link was often lost, so a second click was required.
With .19 and later, if the administrator changes the frontend web
@@ -129,9 +129,9 @@ of Gerrit, this can be avoided. For example with Apache:
----
During a request for an arbitrary URL, such as '/#change,42',
Gerrit realizes the user is not logged in. Instead of sending an
- immediate redirect for authentication, Gerrit sends JavaScript
+ immediate redirect for authentication, Gerrit sends JavaScript
to save the target token (the part after the '#' in the URL)
- by redirecting the user to '/login/change,42'. This enters
+ by redirecting the user to '/login/change,42'. This enters
the secured area, and performs the authentication. When the
authenticated user returns to '/login/change,42' Gerrit sends
a redirect back to the original URL, '/#change,42'.
@@ -139,7 +139,7 @@ of Gerrit, this can be avoided. For example with Apache:
* Create check_schema_version during schema creation
+
-Schema upgrades for PostgreSQL now validate that the current
+Schema upgrades for PostgreSQL now validate that the current
schema version matches the expected schema version at the start
of the upgrade script. If the schema does not match, the script
aborts, although it will spew many errors.
@@ -148,7 +148,7 @@ aborts, although it will spew many errors.
+
Uploading commits to a project now requires that the new commits
share a common ancestry with the existing commits of that project.
-This catches and prevents problems caused by a user making a typo
+This catches and prevents problems caused by a user making a typo
in the project name, and inadvertently selecting the wrong project.
* Change-Id tags in commit messages to associate commits
@@ -156,12 +156,12 @@ in the project name, and inadvertently selecting the wrong project.
Gerrit now looks for 'Change-Id: I....' in the footer area of a
commit message and uses this to identify a change record within
the project.
-If the listed Change-Id has not been seen before, a new change
+If the listed Change-Id has not been seen before, a new change
record is created. If the Change-Id is already known, Gerrit
updates the change with the new commit. This simplifies updating
multiple changes at once, such as might happen when rebasing an
entire series of commits that are still being reviewed.
-A commit-msg hook can be installed to automatically generate
+A commit-msg hook can be installed to automatically generate
these Change-Id lines during initial commit:
{{{
scp -P 29418 review.example.com:hooks/commit-msg .git/hooks/
@@ -175,7 +175,7 @@ Bug Fixes
---------
* Fix yet another ArrayIndexOutOfBounds during side-by-s...
+
-We found yet another bug with the side-by-side view failing
+We found yet another bug with the side-by-side view failing
under certain conditions. I think this is the last bug.
* Apply URL decoding to parameter of /cat/
@@ -183,12 +183,12 @@ under certain conditions. I think this is the last bug.
+
Images weren't displaying correctly, even though
mimetype.image/png.safe was true in gerrit.config.
-Turned out to be a problem with the parameter decoding of the
+Turned out to be a problem with the parameter decoding of the
/cat/ servlet, as well as the link being generated wrong.
* Fix high memory usage seen in `gerrit show-caches`
+
-In Gerrit 2.0.18 JGit had a bug where the repository wasn't being
+In Gerrit 2.0.18 JGit had a bug where the repository wasn't being
reused in memory. This meant that we were constantly reloading
the repository data in from disk, so the server was always maxed
out at core.packedGitLimit and core.packedGitOpenFiles, as no
@@ -196,19 +196,19 @@ data was being reused from the cache. Fixed in this release.
* Fix display of timeouts in `gerrit show-caches`
+
-Timeouts were not always shown correctly, sometimes 12 hours
+Timeouts were not always shown correctly, sometimes 12 hours
was showing up as 2.5 days, which is completely wrong. Fixed.
* GERRIT-261 Fix reply button when comment is on the last line
+
-The "Reply" button didn't work if the comment was on the last
+The "Reply" button didn't work if the comment was on the last
line of the file, the browser caught an array index out of
bounds exception as we walked off the end of the table looking
for where to insert the new editor box.
* GERRIT-83 Make sign-out really invalidate the user's session
+
-The sign-out link now does more than delete the cookie from the
+The sign-out link now does more than delete the cookie from the
user's browser, it also removes the token from the server side.
By removing it from the server, we prevent replay attacks where
an attacker has observed the user's cookie and then later tries
@@ -219,16 +219,16 @@ cookie while it was still live.
* Evict account record after changing SSH username
+
-Changing the SSH username on the web immediately affected the
+Changing the SSH username on the web immediately affected the
SSH daemon, but the web still showed the old username. This
was due to the change operation not flushing the cache that
the web code was displaying from. Fixed.
* Really don't allow commits to replace in wrong project
+
-It was possible for users to upload replacement commits to the
+It was possible for users to upload replacement commits to the
wrong project, e.g. uploading a replacement commit to project
-B while picking a change number from project A. Fixed.
+B while picking a change number from project A. Fixed.
=Fixes in 2.0.19.1=
-------------------
@@ -241,7 +241,7 @@ always work as expected, due to some data not being initialized correctly.
* Ignore harmless "Pipe closed" in scp command
+
scp command on the server side threw exceptions when a client aborted the
-data transfer. We typically don't care to log such cases.
+data transfer. We typically don't care to log such cases.
* Refactor user lookup during permission checking
* GERRIT-264 Fix membership in Registered Users group
diff --git a/ReleaseNotes/ReleaseNotes-2.0.20.txt b/ReleaseNotes/ReleaseNotes-2.0.20.txt
index 11958e81c6..d46f74d9d5 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.20.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.20.txt
@@ -24,18 +24,18 @@ link:http://gerrit.googlecode.com/svn/documentation/2.0/cmd-approve.html[http://
* Support changing Google Account identity strings
+
-For various reasons, including but not being limited to server
+For various reasons, including but not being limited to server
host name changes, the Google Accounts OpenID provider service
may change the identity string it returns to users. By setting
auth.allowGoogleAccountUpgrade = true in the configuration file
-administrators may permit automatically updating an existing
+administrators may permit automatically updating an existing
account with a new identity by matching on the email address.
Bug Fixes
---------
* GERRIT-262 Disallow creating comments on line 0
+
-Users were able to create comments in dead regions of a file.
+Users were able to create comments in dead regions of a file.
That is, if a region was deleted, and thus the left hand side
showed red deletion of lines, and the right hand side showed a
grey background of nothing, users were able to place a comment on
@@ -45,8 +45,8 @@ Because line 0 does not exist (lines are numbered 1..n), these
comments become hidden and could not be seen, but showed up in
the "X comments" counter seen on the Patch History or in the
listing of files in a patch set.
-The UI and RPC layer was fixed to prevent comments on line 0,
-but existing comments need to be manually moved to a real line.
+The UI and RPC layer was fixed to prevent comments on line 0,
+but existing comments need to be manually moved to a real line.
See above for the suggested SQL UPDATE command.
* Make ID column same font size as rest of table
diff --git a/ReleaseNotes/ReleaseNotes-2.0.22.txt b/ReleaseNotes/ReleaseNotes-2.0.22.txt
index ae6d7dd796..3a35421f0c 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.22.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.22.txt
@@ -12,7 +12,7 @@ There is no schema change in this release.
* Restriction on SSH Username
+
-There is a new restriction placed on the SSH Username field
+There is a new restriction placed on the SSH Username field
within an account. Users who are using invalid names should
be asked to change their name to something more suitable.
Administrators can identify these users with the following query:
@@ -20,12 +20,12 @@ Administrators can identify these users with the following query:
-- PostgreSQL
SELECT account_id,preferred_email,ssh_user_name
FROM accounts
- WHERE NOT (ssh_user_name ~ '^[a-zA-Z][a-zA-Z0-9._-]*[a-zA-Z0-9]$');
+ WHERE NOT (ssh_user_name ~ '[a-zA-Z][a-zA-Z0-9._-]*[a-zA-Z0-9]$');
-- MySQL
SELECT account_id,preferred_email,ssh_user_name
FROM accounts
- WHERE NOT (ssh_user_name REGEXP '^[a-zA-Z][a-zA-Z0-9._-]*[a-zA-Z0-9]$');
+ WHERE NOT (ssh_user_name REGEXP '[a-zA-Z][a-zA-Z0-9._-]*[a-zA-Z0-9]$');
----
Administrators can force these users to select a new name by
setting ssh_user_name to NULL; the user will not be able to
diff --git a/ReleaseNotes/ReleaseNotes-2.0.4.txt b/ReleaseNotes/ReleaseNotes-2.0.4.txt
index 6da317dee4..4869233931 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.4.txt
@@ -47,4 +47,4 @@ Other Changes
* Make sure the WorkQueue terminates when running command ...
* Move all contact information out of database to encrypte...
* Peg the versions of JGit and MINA SSHD to something known
-* gerrit 2.0.4 \ No newline at end of file
+* gerrit 2.0.4 \ No newline at end of file
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.txt b/ReleaseNotes/ReleaseNotes-2.1.6.txt
index d1c63359ef..ce65f1a9ec 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.txt
@@ -55,7 +55,7 @@ The new flag includes approval information for all patch sets in the
resulting query output.
Notifications
-~~~~~~~~~~~~
+~~~~~~~~~~~~
* Customize email notification templates
+
Email notifications are now driven by the Velocity template engine,
diff --git a/ReleaseNotes/ReleaseNotes-2.10.txt b/ReleaseNotes/ReleaseNotes-2.10.txt
index 336f02f3ce..18d0f34c2d 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.txt
@@ -37,6 +37,12 @@ later, you may ignore this warning and upgrade directly to 2.10.x.
*WARNING:* The `auth.allowGoogleAccountUpgrade` setting is no longer supported.
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
Release Highlights
------------------
diff --git a/ReleaseNotes/ReleaseNotes-2.11.1.txt b/ReleaseNotes/ReleaseNotes-2.11.1.txt
new file mode 100644
index 0000000000..ee359514c3
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.11.1.txt
@@ -0,0 +1,177 @@
+Release notes for Gerrit 2.11.1
+===============================
+
+Gerrit 2.11.1 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.1.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.11.1.war]
+
+Gerrit 2.11.1 includes the bug fixes done with
+link:ReleaseNotes-2.10.4.html[Gerrit 2.10.4]. These bug fixes are *not* listed
+in these release notes.
+
+There are no schema changes from link:ReleaseNotes-2.11.html[2.11].
+
+
+New Features
+------------
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=321[Issue 321]:
+Use in-memory Lucene index for a better reviewer suggestion.
++
+Instead of a linear full text search through a list of accounts, use an
+in-memory Lucene index. The index is periodically refreshed. The refresh period
+is configurable via the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-gerrit.html#suggest.fullTextSearchRefresh[
+suggest.fullTextSearchRefresh] parameter.
+
+
+Bug Fixes
+---------
+
+Performance
+~~~~~~~~~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3363[Issue 3363]:
+Fix performance degrade in background mergeability checks.
++
+When neither `index.batchThreads` nor `changeMerge.threadPoolSize` was defined,
+the background mergeability check fell back to using an interactive executor.
++
+This led to a severe performance degradation during git push operations because
+the `ref-update` listener was reindexing all open changes on the target branch
+interactively. The degradation increased linearly with number of open changes on
+the target branch.
++
+Now, instead of indexing interactively, it falls back to a batch thread pool
+with the number of available logical CPUs.
+
+* Reduce unnecessary database access when queryng changes.
++
+Searching for changes was retrieving more information than necessary from the
+database. This has been optimized to reduce database access and make better use
+of the secondary index.
+
+* Remove unnecessary REST API call when opening the 'Patch Sets' drop down.
++
+The change edit information was being loaded twice.
+
+Index
+~~~~~
+
+* Fix `PatchLineCommentsUtil.draftByChangeAuthor`.
++
+There is not a native index for this, and the ReviewDb case was not properly
+filtering a result by change.
+
+* Don't show stack trace when failing to build BloomFilter during reindex.
+
+Permissions
+~~~~~~~~~~~
+
+* Require 'View Plugins' capability to list plugins through SSH.
+
+* Fix project creation with plugin config if user is not project owner.
++
+On project creation it is possible to specify plugin configuration values that
+should be stored in the `project.config` file. This failed if the calling user
+was not becoming owner of the created project, because only project owners can
+edit the `project.config` file.
+
+
+Change Screen / Diff / Inline Edit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3191[Issue 3191]:
+Always show 'Not Current' as state when looking at old patch set.
++
+For merged changes it was confusing for users to see the status as 'Merged' when
+they look at an old patch set.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3337[Issue 3337]:
+Reenable 'Revert' button when revert is cancelled.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3378[Issue 3378]:
+Improve the cursor style in side-by-side diff and inline editor.
++
+The cursor style is changed from an underscore to a solid vertical bar.
++
+In the side-by-side diff, the cursor is placed on the first column of the diff,
+rather than at the end.
+
+Web Container
+~~~~~~~~~~~~~
+
+* Fix `gc_log` when running in a web container.
++
+All logs supposed to be in the `gc_log` file were ending up in the main log
+instead when deploying Gerrit in a web container.
+
+* Fix binding of SecureStore modules.
++
+The SecureStore modules were not correctly added when Gerrit was deployed in a
+web container with the site path configured using the `gerrit.site_path`
+property.
+
+Plugins
+~~~~~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3310[Issue 3310]:
+Fix disabling plugins when Gerrit is running on Windows.
++
+When running Gerrit on Windows it was not possible to disable a plugin due to an
+error renaming the plugin's JAR file.
+
+* Replication
+
+** Fix creation of missing repositories.
++
+Missing projects were not being created on the destination.
+
+** Emit replication status events after initial full sync.
++
+When `replicateOnStartup` is enabled, the plugin was not emitting the status
+events after the initial sync.
+
+Miscellaneous
+~~~~~~~~~~~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3323[Issue 3323]:
+Fix internal server error when cloning from a slave while hiding some refs.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3342[Issue 3342]:
+Log `IOException` on failure to update project configuration.
++
+Without logging these exceptions it's hard to guess why the update of the
+project configuration is failing.
+
+* Remove temporary GitWeb config on Gerrit exit.
++
+A temporary directory was being created but not removed.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=2791[Issue 2791]:
+Fix email validation for new TLDs such as `.systems`.
+
+Documentation
+~~~~~~~~~~~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3325[Issue 3325]:
+Add missing `--newrev` parameter to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-hooks.html#_change_merged[
+change-merged hook documentation].
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3346[Issue 3346]:
+Fix typo in the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-reverseproxy.html[
+Apache 2 configuration documentation].
+
+* Fix incorrect documentatation of
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-gerrit.html#auth.registerUrl[
+auth types].
+
+Updates
+-------
+
+* Update CodeMirror to 5.0.
+
+* Update commons-validator to 1.4.1.
diff --git a/ReleaseNotes/ReleaseNotes-2.11.txt b/ReleaseNotes/ReleaseNotes-2.11.txt
new file mode 100644
index 0000000000..90519dc78c
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.11.txt
@@ -0,0 +1,873 @@
+Release notes for Gerrit 2.11
+=============================
+
+
+Gerrit 2.11 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war]
+
+Gerrit 2.11 includes the bug fixes done with
+link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1],
+link:ReleaseNotes-2.10.2.html[Gerrit 2.10.2] and
+link:ReleaseNotes-2.10.3.html[Gerrit 2.10.3].
+These bug fixes are *not* listed in these release notes.
+
+
+Important Notes
+---------------
+
+
+*WARNING:* This release contains schema changes. To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+Gerrit 2.11 requires a secondary index, which can be created offline
+by running the `reindex` program:
+
+----
+ java -jar gerrit.war reindex -d site_path
+----
+
+If the site that is upgraded already has a secondary index, the
+secondary index can be upgraded online. This is important for large
+sites since running the `reindex` program can take a long time and
+contributes significantly to the downtime that is required for the
+upgrade.
+
+Gerrit 2.11 supports online reindexing only from the index version `11`
+which is the index version of Gerrit 2.10. This means if you come from
+an older release it makes sense to first upgrade to 2.10 and then do
+the upgrade to 2.11 so that you can profit from online reindexing.
+
+In case you are upgrading from 2.10 it is *important* to check *before*
+the upgrade to 2.11 that the index version of your Gerrit 2.10 site is
+`11`. You can check the index version in
+`$site_path/index/gerrit_index.config`. Your Gerrit 2.10 site may run
+with an older index version (e.g. if online reindexing to index version
+`11` is still running or if online reindexing to version `11` has
+failed). In this case you first need to successfully migrate your index
+version of your Gerrit 2.10 site to `11` and only then start with the
+2.11 upgrade. If you start the 2.11 upgrade when the schema version of
+your Gerrit 2.10 site is older than `11`, online reindexing is no longer
+possible and you need to reindex offline by using the `reindex` program.
+
+*WARNING:* Upgrading to 2.11.x requires the server be first upgraded to 2.8 (or
+2.9) and then to 2.11.x. If you are upgrading from 2.8.x or later, you may ignore
+this warning and upgrade directly to 2.11.x.
+
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
+*WARNING:* The 'Generate HTTP Password' capability has been
+link:#remove-generate-http-password-capability[removed].
+
+*WARNING:* Google will
+link:https://developers.google.com/+/api/auth-migration[shut down their OpenID
+service on 20th April 2015]. Administrators of sites whose users are registered
+with Google OpenID accounts should encourage the users to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-sso.html#_multiple_identities[
+add an alternative identity to their account] before this date. Users who do
+not add an alternative identity before this date will need to create a new
+account and ask the site administrator to
+link:https://code.google.com/p/gerrit/wiki/SqlMergeUserAccounts[merge it].
+
+*WARNING:* The
+link:https://gerrit-review.googlesource.com/Documentation/2.10/rest-api-changes.html#message[
+Edit Commit Message] REST API endpoint is removed
+
+*WARNING:* The deprecated '/query' URL is removed and will now return `Not Found`.
+
+Release Highlights
+------------------
+
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=505[Issue 505]:
+Changes can be created and edited directly in the browser. See the
+link:#inline-editing[Inline editing] section for more details.
+
+* Many improvements in the new change screen.
+
+* The old change screen is removed.
+
+
+New Features
+------------
+
+
+Web UI
+~~~~~~
+
+[[inline-editing]]
+Inline Editing
+^^^^^^^^^^^^^^
+
+Refer to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/user-inline-edit.html[
+inline editing user guide] for detailed instructions.
+
+* New changes can be created directly in the browser via a 'Create Change'
+button on the project info screen.
+
+* New follow-up changes can be created via a 'Follow-Up' button on the change
+screen.
+
+* File content can be edited in a full screen CodeMirror editor with support for
+themes and syntax highlighting.
+
+* The CodeMirror screen can be configured in the same way as the side-by-side
+diff screen.
+
+* The file table in the change screen supports seamless navigation to the
+CodeMirror editor.
+
+* Edit mode can be started from the side-by-side diff screen with seamless
+navigation to the CodeMirror editor.
+
+* The commit message must now be changed in the context of a change edit. The
+'Edit Message' button is removed from the change screen.
+
+* Files can be added, deleted, restored and modified directly in browser.
+
+Change Screen
+^^^^^^^^^^^^^
+
+* Remove the 'Edit Message' button from the change screen.
++
+The commit message is now edited using the inline edit feature.
+
+* Add support for changing parent revision with the 'Rebase' button.
++
+Using the 'Rebase' button it is now possible to rebase a change onto a
+different change (on the same destination branch), rather than only onto the
+head of the destination branch or the latest patch set of the predecessor change.
+
+* Show the parent commit's subject as a tooltip.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=2541[Issue 2541],
+link:http://code.google.com/p/gerrit/issues/detail?id=2974[Issue 2974]:
+Allow the 'Reply' button's
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#change.replyLabel[
+label and tooltip] to be configured.
+
+* Improve file sorting for C and C++ files.
++
+Header files are now listed before implementation files.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3148[Issue 3148]:
+Allow display of colored size bars to be enabled or disabled per user.
++
+The 'Show Change Sizes As Colored Bars In Changes Table' setting is renamed to
+'Show Change Sizes As Colored Bars' and is now used to also control how the
+change size is shown per file in the file table.
++
+When enabled (which is the default), the change size per file is shown as a sum
+of lines added/removed, and also representated by a colored bar showing the
+proportion of added/removed lines.
++
+When disabled, the colored bar is not shown and the change size per file is shown
+in the same way as it used to be in the old change screen.
+
+* Show changes across all projects and branches in the `Same Topic` tab.
+
+
+Side-By-Side Diff
+^^^^^^^^^^^^^^^^^
+
+* New button to switch between side-by-side diff and unified diff.
+
+* New preference setting to toggle auto-hiding of the diff table header.
++
+The setting determines whether or not the diff table header with the patch set
+selection should be automatically hidden when scrolling down more than half of
+a page.
+
+* Highlight search results on scrollbar.
++
+Search results in vim mode are highlighted in the scrollbar with gold
+colored annotations.
+
+* Set line length to 72 characters for commit messages.
+
+* Add syntax highlighting for several new modes:
+
+** link:https://code.google.com/p/gerrit/issues/detail?id=2848[Issue 2848]: CSharp
+** Dart
+** Dockerfile
+** GLSL shader
+** Go
+** Objective C
+** RELAX NG
+** link:http://code.google.com/p/gerrit/issues/detail?id=2779[Issue 2779]: reStructured text
+** Soy
+
+
+Projects Screen
+^^^^^^^^^^^^^^^
+
+* Add pagination and filtering on the branch list page.
+
+* Add an 'Edit Config' button on the project info page.
++
+The button creates a new change on the `refs/meta/config` branch and opens the
+`project.config` file in the inline editor.
++
+This allows project owners to easily edit the `project.config` file from the
+browser, which is useful since it is possible that not all configuration options
+are available in the UI.
+
+REST
+~~~~
+
+Accounts
+^^^^^^^^
+
+* Add new link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-accounts.html#suggest-account[
+Suggest Account endpoint].
+
+Changes
+^^^^^^^
+
+* The link:https://gerrit-review.googlesource.com/Documentation/2.10/rest-api-changes.html#message[
+Edit Commit Message] endpoint is removed in favor of the new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#put-change-edit-message[
+Change commit message in Change Edit] and
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#publish-edit[
+Publish Change Edit] endpoints.
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#check-change[
+Check Change endpoint].
++
+In the past, Gerrit bugs, lack of transactions, and unreliable NoSQL backends
+have at various times produced a bewildering variety of corrupt states.
++
+This endpoint can be used to detect, explain, and repair some of these possible
+states of a change.
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-revision-actions[
+Get Revision Actions endpoint].
+
+* Add
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#change-actions[
+`CHANGE_ACTIONS`] option on the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-change-detail[
+Get Change Detail] endpoint.
+
+
+Change Edits
+^^^^^^^^^^^^
+
+Several new endpoints are added to support the inline edit feature.
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-edit-detail[
+Get Edit Detail].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#put-edit-file[
+Change file content in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#post-edit[
+Restore file content in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#put-change-edit-message[
+Change commit message in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#delete-edit-file[
+Delete file in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-edit-file[
+Retrieve file content from Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-edit-message[
+Retrieve commit message from Change Edit or current patch set of the change].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#publish-edit[
+Publish Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#rebase-edit[
+Rebase Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#delete-edit[
+Delete Change Edit].
+
+
+Projects
+^^^^^^^^
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#delete-branches[
+Delete Branches] endpoint.
+
+* Add filtering and pagination options on the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#list-branches[
+List Branches] endpoint.
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#list-tags[
+List Tags] endpoint.
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#get-tag[
+Get Tag] endpoint.
+
+
+Configuration
+~~~~~~~~~~~~~
+
+* Add support for
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#auth.httpExternalIdHeader[
+HTTP external ID header].
++
+This can be used when authenticating with a federated identity token from
+an external system, e.g. GitHub's OAuth 2.0 authentication.
+
+* Add
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-labels.html#label_copyAllScoresIfNoChange[
+`copyAllScoresIfNoChange`] setting for labels.
++
+Allows to copy scores forward when a new patch set is uploaded that has the same
+parent tree, code delta, and commit message as the previous patch set.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2786[Issue 2786]:
+Allow non-administrators to modify user accounts.
++
+A new global capability,
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/access-control.html#capability_modifyAccount[
+'Modify Account'], which allows the granted group members to modify user account
+settings via the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
+`set-account` SSH command].
++
+Modification of users' SSH keys is still restricted to administrators.
+
+* Add support for
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#ldap.useConnectionPooling[
+LDAP connection pooling].
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=699[Issue 699]: Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#receive.maxBatchChanges[
+limit max number of changes pushed in a batch].
++
+Can be overridden by members of groups that are granted the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/access-control.html#capability_batchChangesLimit[
+Batch Changes Limit] capability.
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#gerrit.disableReverseDnsLookup[
+disable reverse DNS lookup].
++
+This option can be set to improve push time from hosts without a reverse DNS
+entry.
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#cache.projects.loadOnStartup[
+load the project cache at server startup].
+
+* Allow members of groups granted the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/access-control.html#capability_accessDatabase[
+AccessDatabase capability] to view metadata refs.
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#http.addUserAsRequestAttribute[
+add the user to the http request attributes].
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#suggest.fullTextSearch[
+enable full text search in memory for review suggestions].
++
+The maximum number of reviewers evaluated can be limited with
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#suggest.fullTextSearchMaxMatches[
+suggest.fullTextSearchMaxMatches].
+
+* Allow to provide an alternative
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#gerrit.secureStoreClass[
+secure store implementation].
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1195[Issue 1195]:
+Allow projects to be configured to create a new change for every uploaded commit that is not in the target branch.
+
+* Allow to configure
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#container.daemonOpt[
+options to pass to the daemon].
+
+Daemon
+~~~~~~
+
+* Allow to enable the http daemon when running in slave mode.
++
+The `--enable-httpd` option can be used in conjunction with the `--slave` option
+to allow clients to fetch from the slave over the http protocol.
++
+HTTP Authentication may also be used when running in slave mode.
+
+* Include the submitter's name in the change message when a change is submitted.
+
+* Add a message to changes created via cherry pick.
++
+When a change is cherry-picked to another branch using the cherry-pick action,
+the message 'Patch Set <number>: Cherry Picked from branch <name>.' is added as
+a change message on the created change.
+
+* Don't send 'new patch set' notification emails for trivial rebases.
+
+
+SSH
+~~~
+
+* Add new commands
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[
+`logging ls-level`] and
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-set-level.html[
+`logging set-level`] to show and set the logging level at runtime.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=602[Issue 602]:
+Add `--json` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-review.html[
+`review` SSH command].
++
+Review input can be given to the `review` command in JSON format corresponding
+to the REST API's
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#review-input[
+ReviewInput] entity.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2824[Issue 2824]:
+Add `--rebase` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-review.html[
+`review` SSH command].
+
+* Add `--clear-http-password` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
+`set-account` SSH command].
+
+* Add `--preferred-email` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
+`set-account` SSH command].
+
+Email
+~~~~~
+
+* Add `$change.originalSubject` field for email templates.
++
+GMail threads messages together by subject and ignores the list headers included
+by Gerrit.
++
+Site administrators that run servers whose end-user base is mostly on GMail can
+modify the site's `ChangeSubject.vm` template to use `$change.originalSubject` to
+improve threading for GMail inboxes.
++
+The `originalSubject` field is automatically taken from the existing subject
+field during first use.
+
+
+Plugins
+~~~~~~~
+
+General
+^^^^^^^
+
+* Plugins can listen to account group membership changes.
++
+The audit log service allows to register listeners to group member added and
+group member deleted events. A default listener logs these events to the database
+as before, but additional listeners may now be registered for these events using
+the `GroupMemberAuditListener` interface.
+
+* Plugins can validate ref operations.
++
+Plugins implementing the `RefOperationValidationListener` interface can
+perform additional validation checks against ref creation/deletion operations
+before they are applied to the git repository.
+
+* Plugins can provide project-aware top menu extensions
++
+Plugins can provide sub-menu items within the 'Projects' context. The
+'$\{projectName\}' placeholder is replaced by the project name.
+
+* Auto register static/init.js as JavaScript plugin.
++
+When a plugin does not expose Guice Modules explicitly, auto discover and
+register static/init.js as WebUi extension if found by the plugin content
+scanner.
+
+* Plugins can validate outgoing emails.
++
+Plugins implementing `OutgoingEmailValidationListener` interface can filter
+and modify outgoing emails before they are sent.
+
+* Plugins that provide initialization steps may now use functionality
+from InitUtil in core Gerrit.
+
+* Plugins can post change reviews with historic timestamps.
++
+This allows, for example, to write a plugin that can import a project including
+review information from another Gerrit server.
+
+* New extensions in the Java Plugin API:
+
+** Set/Put topic.
+** Get mergeable status.
+** link:https://code.google.com/p/gerrit/issues/detail?id=461[Issue 461]:
+Get current user.
+** Get file content.
+** Get file diff.
+** Get comments and drafts.
+** Get change edit.
+
+Replication
+^^^^^^^^^^^
+
+* Projects can be specified with wildcard in the `start` command.
+
+
+Bug Fixes
+---------
+
+Daemon
+~~~~~~
+
+* Change 'Merge topic' to 'Merge changes from topic'.
++
+When multiple changes from a topic are submitted resulting in a merge commit,
+the title of the merge commit is now 'Merge changes from topic' instead of
+'Merge topic'.
+
+* Fix visibility checks for `refs/meta/config`.
++
+Under some conditions it was possible for the `refs/meta/config` branch to be
+erroneously considered not visible to the user.
+
+* Sort list of updated changes in output from push.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2940[Issue 2940]:
+Improve warning messages when `Change-Id` is missing in the commit message.
+
+** Add a hint to amend the commit after installing the commit-msg hook.
+** Don't show 'Suggestion for commit message' when `Change-Id` is missing.
+
+* Allow to publish draft patch sets even when `allowDrafts` is false.
++
+If a user uploaded a change while `allowDrafts` was enabled, and then it was
+disabled by the administrator, the uploaded change could not be published and
+was stuck in the draft state.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3249[Issue 3249]:
+Fix server error when checking mergeability of a change.
+
+* Workaround Guice bug "getPathInfo not decoded".
++
+Due to link:https://github.com/google/guice/issues/745[Guice issue 745], cloning
+of a repository with a space in its name was impossible.
+
+
+Secondary Index / Search
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2822[Issue 2822]:
+Improve Lucene analysis of words linked with underscore or dot.
++
+Instead of treating words linked with underscore or dot as one word, Lucene now
+treats them as separate words.
+
+* Fix support for `change~branch~id` in query syntax.
+
+
+Configuration
+~~~~~~~~~~~~~
+
+[[remove-generate-http-password-capability]]
+* Remove the 'Generate HTTP Password' capability.
++
+The 'Generate HTTP Password' capability has been removed to close a security
+vulnerability. Now only administrators are allowed to generate and delete other
+users' http passwords via the REST or SSH interface.
++
+It is encouraged to clean up your `project.config` settings after upgrading.
+
+* Fix support for multiple `footer` tokens in tracking ID config.
++
+Contrary to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#trackingid[
+the documentation], if more than one `footer` token was specified in the
+`trackingid` section, only the first was used.
+
+* Treat empty
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#hooks[
+`hooks.*`] values as missing, rather than trying to execute the hooks
+directory.
+
+* Fix `changed-merged` hook configuration.
++
+Contrary to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#hooks[
+documentation], the changed-merged hook configuration value was being
+read from `hooks.changeMerged`. Fix to use `hooks.changeMergedHook` as
+documented.
+
+Web UI
+~~~~~~
+
+Change List
+^^^^^^^^^^^
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3304[Issue 3304]:
+Always show a tooltip on the label column entries.
+
+Change Screen
+^^^^^^^^^^^^^
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3147[Issue 3147]:
+Allow to disable muting of common path prefixes in the file list.
++
+In the file table, parts of the file path that are common to the file previously
+listed are muted. The purpose of this is to make it easier to see files that all
+belong under the same path, but some users find it annoying.
++
+This feature can now be enabled or disabled, per user, with the 'Mute Common
+Path Prefixes In File List' setting.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3130[Issue 3130]:
+Remove special handling of 'LGTM' in review comments
++
+Typing 'LGTM' in the review cover message no longer automatically selects the
+highest available Code-Review score.
+
+* Show a confirmation dialog before deleting a draft change or patch set.
++
+Previously there was no confirmation and a draft change or revision patch
+set would be lost if the button was accidentally clicked.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2533[Issue 2533]:
+Improve the layout and color scheme of buttons.
++
+Several improvements have been made:
++
+** Move 'Publish' and 'Delete Change/Revision' buttons into header.
++
+If a change/revision is a draft the natural next step is to publish (or delete)
+it, hence these buttons should be displayed in a more prominent place.
+
+** Highlight the 'Publish' button in blue.
++
+If a change is a draft the natural next step is to publish it, hence
+the 'Publish' button should be highlighted similar to the quick
+approve button.
+
+** Fix the border color of buttons on the reply popup.
++
+The buttons are blue but had white borders, which was inconsistent with the
+buttons on the change screen.
+
+** Remove red color for 'Abandon' and 'Restore' buttons.
++
+There is nothing dangerous about these operations that justifies
+highlighting the buttons in red color. When the buttons are clicked
+there is a popup where the user must confirm the operation, so it can
+still be canceled.
+
+** Hide quick approve button for draft changes.
++
+A draft change cannot be submitted, hence quick approving it is not that
+important. Hiding the quick approve button on draft changes makes space in the
+header for displaying more important actions such as 'Publish'.
+
+* Differentiate between conflicts and already merged errors in cherry-pick
++
+When a cherry-pick operation failed with 'Cherry pick failed' error, there was no
+way to know the reason for the failure: merge conflict or the commit is already
+on the target branch. These failures are now differentiated and an appropriate
+error is reported.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2837[Issue 2837]:
+Improve display of long user names for collapsed comments in history.
++
+If there were several users with long user names with the same prefix, e.g.
+'AutomaticGerritVoterLinux' and 'AutomaticGerritVoterWindows', they would both
+be shown as 'AutomaticGerritVo...' and users had to expand the comment to see
+the full user name.
++
+The ellipsis is now inserted in the middle of the user name so that the start
+and end of the user name are always visible, e.g. 'AutomaticG...VoterLinux' and
+'AutomaticG...terWindows'.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2992[Issue 2992]:
+Fix display of review comments for Chrome on Android.
++
+Chrome for Android has Font Boosting, which caused the review comments to
+be displayed too large.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2909[Issue 2909]:
+Make change owner votes removable.
++
+If a change owner voted on a change, it was not possible for anyone other
+than the owner to remove the vote.
+
+* Preserve topic when cherry-picking.
++
+When a change is cherry-picked, the topic from the source change is preserved
+on the newly created change.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3007[Issue 3007]:
+Make the selected tab persistent.
++
+If a change from the 'Same Topic' tab was clicked, the selected tab would reset
+to the default tab ('Related Changes').
+
+* Left-align column titles in the file list.
+
+* Increase right margin of download box to make space for scrollbar.
++
+Under some circumstances the browser's scrollbar would be shown over the
+copy-to-clipboard icons in the download dropdown.
+
+* Display +1 score's text next to the checkbox for simple boolean labels.
++
+In the reply box, the text of the label score is displayed on the right hand
+side when a score is selected, but this was missing for simple boolean labels.
+
+* Don't show missing accounts as reviewer suggestions.
+
+* Show the email address that matched the search in reviewer suggestions.
++
+When matching accounts by email address against an external account, results
+now show the email address that matched, not the preferred email address.
+
+* Fix accidental reviewer selection on slow networks.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3120[Issue 3120]:
+Align parent weblinks with parent commits in the commit box.
+
+
+Side-By-Side Diff
+^^^^^^^^^^^^^^^^^
+
+* Return to normal mode after editing a draft comment.
++
+Previously it would remain in visual mode.
+
+* Fix C++ header and source syntax highlighting
++
+cpp and hpp files were sometimes rendered with C mode and not the extended C++
+mode. This prevented keywords like `class` from being colored by the
+highlighter.
+
+
+Project Screen
+^^^^^^^^^^^^^^
+
+* Fix alignment of checkboxes on project access screen.
++
+The 'Exclusive' checkbox was not aligned with the other checkboxes.
+
+REST API
+~~~~~~~~
+
+Changes
+^^^^^^^
+
+* Remove the administrator restriction on the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#index-change[
+index change] endpoint.
++
+The endpoint can now be used by any user who has visibility of the change.
+
+* Only include account ID in responses unless `DETAILED_ACCOUNTS` option is set.
++
+The behavior was inconsistent with the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-accounts.html#account-info[
+documentation]. In the default case it was including only the account name,
+rather than only the account ID.
+
+* Include revision's ref in responses.
++
+The ref of a revision was only returned as part of the fetch info, which is only
+available if the download commands are installed.
+
+* Correctly set the limit to the default when no limit is given in the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#suggest-reviewers[
+suggest reviewers] endpoint.
+
+* Return correct response from 'delete draft' endpoints.
++
+When the `change.allowDrafts` setting is False, it is not allowed to delete
+draft changes or patch sets.
++
+In this case the response `405 Method Not Allowed` is now returned, instead of
+`409 Conflict`.
+
+
+Projects
+^^^^^^^^
+
+* Make it mandatory to specify at least one of the `--prefix`, `--match` or `--regex`
+options in the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#list-projects[
+list projects] endpoint.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2706[Issue 2706]:
+Do not delete branches concurrently.
++
+Deleting multiple branches from the UI was resulting in a server error when
+branches were in the packed-refs.
+
+* Add retry logic for lock failure when deleting a branch.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3153[Issue 3153]:
+Fix handling of project names ending with `.git`.
++
+The projects REST API documentation states that the `.git` suffix will be
+stripped off the input project name, if present.
++
+This was working for the 'Create Project' endpoint, but not for any of the
+others.
+
+
+Plugins
+~~~~~~~
+
+Replication
+^^^^^^^^^^^
+
+* Create missing repositories on the remote when replicating with the git
+protocol.
+
+* Make `createMissingRepositories = false` take effect on `project-created` event.
++
+Previously `createMissingRepositories = false` would prevent the replication
+plugin from trying to create a new project when a `ref-updated` event was fired,
+but when a `project-created` event was fired the replication plugin would try to
+create a project on the remote.
+
+
+Upgrades
+--------
+
+* Update Antlr to 3.5.2.
+
+* Update ASM to 5.0.3.
+
+* Update CodeMirror to 4.10.0-6-gd0a2dda.
+
+* Update Guava to 18.0.
+
+* Update Guice to 4.0-beta5.
+
+* Update GWT to 2.7.
+
+* Update gwtjsonrpc to 1.7-2-g272ca32.
+
+* Update gwtorm to 1.14-14-gf54f1f1.
+
+* Update Jetty to 9.2.9.v20150224.
+
+* Update JGit to 3.7.0.201502260915-r.58-g65c379e.
+
+* Update Lucene to 4.10.2.
+
+* Update Parboiled to 1.1.7.
+
+* Update Pegdown to 1.4.2.
diff --git a/ReleaseNotes/ReleaseNotes-2.9.1.txt b/ReleaseNotes/ReleaseNotes-2.9.1.txt
index 42422ad422..656b5b2d3f 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.1.txt
@@ -7,6 +7,12 @@ Download:
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.9.1.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.9.1.war]
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
Bug Fixes
---------
diff --git a/ReleaseNotes/ReleaseNotes-2.9.2.txt b/ReleaseNotes/ReleaseNotes-2.9.2.txt
index b175546c8d..a586b45651 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.2.txt
@@ -22,6 +22,12 @@ be suppressed in batch mode.
java -jar gerrit.war init -d site_path
----
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
Bug Fixes
---------
diff --git a/ReleaseNotes/ReleaseNotes-2.9.3.txt b/ReleaseNotes/ReleaseNotes-2.9.3.txt
index 95832c899e..f3fcf161f3 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.3.txt
@@ -22,6 +22,12 @@ be suppressed in batch mode.
java -jar gerrit.war init -d site_path
----
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
Bug Fixes
---------
diff --git a/ReleaseNotes/ReleaseNotes-2.9.4.txt b/ReleaseNotes/ReleaseNotes-2.9.4.txt
index 38bf9c650b..0a7010da66 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.4.txt
@@ -22,6 +22,12 @@ be suppressed in batch mode.
java -jar gerrit.war init -d site_path
----
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
Bug Fixes
---------
diff --git a/ReleaseNotes/ReleaseNotes-2.9.txt b/ReleaseNotes/ReleaseNotes-2.9.txt
index 2a5705468d..de5c66579b 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.txt
@@ -30,6 +30,17 @@ Important Notes
java -jar gerrit.war reindex --recheck-mergeable -d site_path
----
+*WARNING:* Upgrading to 2.9.x requires the server be first upgraded to 2.1.7 (or
+a later 2.1.x version), and then to 2.9.x. If you are upgrading from 2.2.x.x or
+later, you may ignore this warning and upgrade directly to 2.9.x.
+
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
+
*WARNING:* Support for query via the SQL index is removed. The usage of
a secondary index is now mandatory.
@@ -86,10 +97,6 @@ The interactive init asks whether the plugin should be upgraded:
The plugin can be upgraded manually by copying the new plugin jar into
the site's `plugins` folder.
-*WARNING:* Upgrading to 2.9.x requires the server be first upgraded to 2.1.7 (or
-a later 2.1.x version), and then to 2.9.x. If you are upgrading from 2.2.x.x or
-later, you may ignore this warning and upgrade directly to 2.9.x.
-
Release Highlights
------------------
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 2f741b6eb2..e40a68b4cc 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -1,6 +1,12 @@
Gerrit Code Review - Release Notes
==================================
+[[2_11]]
+Version 2.11.x
+--------------
+* link:ReleaseNotes-2.11.1.html[2.11.1]
+* link:ReleaseNotes-2.11.html[2.11]
+
[[2_10]]
Version 2.10.x
--------------
diff --git a/VERSION b/VERSION
index 7770ba723c..9791ceb271 100644
--- a/VERSION
+++ b/VERSION
@@ -2,4 +2,4 @@
# Used by :api_install and :api_deploy targets
# when talking to the destination repository.
#
-GERRIT_VERSION = '2.10.5'
+GERRIT_VERSION = '2.11.1'
diff --git a/bucklets/gerrit_plugin.bucklet b/bucklets/gerrit_plugin.bucklet
index eb1045681d..ae7e1a21e4 100644
--- a/bucklets/gerrit_plugin.bucklet
+++ b/bucklets/gerrit_plugin.bucklet
@@ -13,3 +13,8 @@
#
# When compiling from standalone cookbook-plugin, bucklets directory points
# to cloned bucklets library that includes real gerrit_plugin.bucklet code.
+
+GERRIT_PLUGIN_API = ['//gerrit-plugin-api:lib']
+GERRIT_GWT_API = ['//gerrit-plugin-gwtui/gerrit:gwtui-api']
+
+STANDALONE_MODE = False
diff --git a/bucklets/java_doc.bucklet b/bucklets/java_doc.bucklet
new file mode 120000
index 0000000000..cc8b6dba8e
--- /dev/null
+++ b/bucklets/java_doc.bucklet
@@ -0,0 +1 @@
+../tools/java_doc.defs \ No newline at end of file
diff --git a/bucklets/java_sources.bucklet b/bucklets/java_sources.bucklet
new file mode 120000
index 0000000000..8a1a5dddfb
--- /dev/null
+++ b/bucklets/java_sources.bucklet
@@ -0,0 +1 @@
+../tools/java_sources.defs \ No newline at end of file
diff --git a/bucklets/local_jar.bucklet b/bucklets/local_jar.bucklet
new file mode 120000
index 0000000000..890482472c
--- /dev/null
+++ b/bucklets/local_jar.bucklet
@@ -0,0 +1 @@
+../lib/local.defs \ No newline at end of file
diff --git a/bucklets/maven_package.bucklet b/bucklets/maven_package.bucklet
new file mode 120000
index 0000000000..b5f5ea8afb
--- /dev/null
+++ b/bucklets/maven_package.bucklet
@@ -0,0 +1 @@
+../tools/maven/package.defs \ No newline at end of file
diff --git a/contrib/check-valid-commit.py b/contrib/check-valid-commit.py
index 150b310782..d26fa58c28 100755
--- a/contrib/check-valid-commit.py
+++ b/contrib/check-valid-commit.py
@@ -25,7 +25,7 @@ def main():
patchset = None
try:
- opts, args = getopt.getopt(sys.argv[1:], '', \
+ opts, _args = getopt.getopt(sys.argv[1:], '', \
['change=', 'project=', 'branch=', 'commit=', 'patchset='])
except getopt.GetoptError as err:
print('Error: %s' % (err))
diff --git a/contrib/git-push-review b/contrib/git-push-review
new file mode 100755
index 0000000000..898b023f09
--- /dev/null
+++ b/contrib/git-push-review
@@ -0,0 +1,88 @@
+#!/usr/bin/python
+# Copyright (C) 2014 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.
+
+from __future__ import print_function
+
+import argparse
+import collections
+import os
+import subprocess
+import sys
+
+
+def get_config(name):
+ args = ['git', 'config', '--get', name]
+ p = subprocess.Popen(args, stdout=subprocess.PIPE)
+ out, _ = p.communicate()
+ ret = p.poll()
+ if ret not in (0, 1):
+ raise subprocess.CalledProcessError(ret, ' '.join(args), output=out)
+ return out.strip()
+
+
+def deref(name):
+ p = subprocess.Popen(
+ ['git', 'rev-parse', '--symbolic-full-name', name],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, _ = p.communicate()
+ return out.strip()
+
+
+def main(argv):
+ p = argparse.ArgumentParser(description='Push changes to Gerrit for review')
+ p.add_argument('-r', '--remote', default='', metavar='REMOTE',
+ help='remote name or URL to push to')
+ p.add_argument('-b', '--branch', default='', metavar='BRANCH',
+ help='remote branch name, refs/for/BRANCH')
+ p.add_argument('reviewers', nargs='*', metavar='REVIEWER',
+ help='reviewer names or aliases')
+ p.add_argument('-t', '--topic', default='', metavar='TOPIC',
+ help='topic for new changes')
+ p.add_argument('--dry-run', action='store_true',
+ help='dry run, print git command and exit')
+ args = p.parse_args()
+
+ if not args.remote or not args.branch:
+ hp = 'refs/heads/'
+ upstream = deref('HEAD')
+ while upstream.startswith(hp):
+ upstream = deref(upstream[len(hp):] + '@{u}')
+
+ rp = 'refs/remotes/'
+ if upstream.startswith(rp):
+ def_remote, def_branch = upstream[len(rp):].split('/', 1)
+ else:
+ def_remote, def_branch = 'origin', 'master'
+ args.remote = args.remote or def_remote
+ args.branch = args.branch or def_branch
+
+ opts = collections.defaultdict(list)
+ opts['r'].extend((get_config('reviewer.' + r) or r) for r in args.reviewers)
+ if args.topic:
+ opts['topic'].append(args.topic)
+ opts_str = ','.join('%s=%s' % (k, v) for k in opts for v in opts[k])
+ if opts_str:
+ opts_str = '%' + opts_str
+
+ git_args = ['git', 'push', args.remote,
+ 'HEAD:refs/for/%s%s' % (args.branch, opts_str)]
+ if args.dry_run:
+ print(' '.join(git_args))
+ return 0
+ os.execvp('git', git_args)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 3c267209f2..c7bea4eb25 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -8,10 +8,12 @@ java_library(
'//gerrit-launcher:launcher',
'//gerrit-lucene:lucene',
'//gerrit-httpd:httpd',
- '//gerrit-pgm:init-base',
+ '//gerrit-pgm:init',
'//gerrit-pgm:pgm',
+ '//gerrit-pgm:util',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
+ '//gerrit-server/src/main/prolog:common',
'//gerrit-server:testutil',
'//gerrit-sshd:sshd',
@@ -24,9 +26,10 @@ java_library(
'//lib:jsch',
'//lib:junit',
'//lib:servlet-api-3_1',
+ '//lib:truth',
- '//lib/commons:httpclient',
- '//lib/commons:httpcore',
+ '//lib/httpcomponents:httpclient',
+ '//lib/httpcomponents:httpcore',
'//lib/log:impl_log4j',
'//lib/log:log4j',
'//lib/guice:guice',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 2b01a22a9b..ff61899205 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -14,33 +14,53 @@
package com.google.gerrit.acceptance;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
-import static org.junit.Assert.assertEquals;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.Util.block;
import com.google.common.base.Joiner;
import com.google.common.primitives.Chars;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
+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.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.EditInfo;
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.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.Util;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gerrit.testutil.TempFileUtil;
import com.google.gson.Gson;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.util.Providers;
import org.apache.http.HttpStatus;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.junit.Rule;
import org.junit.rules.TestRule;
@@ -60,6 +80,9 @@ public abstract class AbstractDaemonTest {
public Config baseConfig;
@Inject
+ protected AllProjectsName allProjects;
+
+ @Inject
protected AccountCreator accounts;
@Inject
@@ -77,6 +100,27 @@ public abstract class AbstractDaemonTest {
@Inject
protected PushOneCommit.Factory pushFactory;
+ @Inject
+ protected MetaDataUpdate.Server metaDataUpdateFactory;
+
+ @Inject
+ protected ProjectCache projectCache;
+
+ @Inject
+ protected GroupCache groupCache;
+
+ @Inject
+ protected GitRepositoryManager repoManager;
+
+ @Inject
+ protected ChangeIndexer indexer;
+
+ @Inject
+ protected Provider<InternalChangeQuery> queryProvider;
+
+ @Inject
+ protected @GerritServerConfig Config cfg;
+
protected Git git;
protected GerritServer server;
protected TestAccount admin;
@@ -120,6 +164,26 @@ public abstract class AbstractDaemonTest {
}
}
+ protected static Config submitWholeTopicEnabledConfig() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "submitWholeTopic", true);
+ return cfg;
+ }
+
+ protected static Config allowDraftsDisabledConfig() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "allowDrafts", false);
+ return cfg;
+ }
+
+ protected boolean isAllowDrafts() {
+ return cfg.getBoolean("change", "allowDrafts", true);
+ }
+
+ protected boolean isSubmitWholeTopicEnabled() {
+ return cfg.getBoolean("change", null, "submitWholeTopic", false);
+ }
+
private void beforeTest(Config cfg, boolean memory, boolean enableHttpd) throws Exception {
server = startServer(cfg, memory, enableHttpd);
server.getTestInjector().injectMembers(this);
@@ -159,24 +223,29 @@ public abstract class AbstractDaemonTest {
Chars.asList(new char[]{'a','b','c','d','e','f','g','h'});
protected PushOneCommit.Result amendChange(String changeId)
throws GitAPIException, IOException {
+ return amendChange(changeId, "refs/for/master");
+ }
+
+ protected PushOneCommit.Result amendChange(String changeId, String ref)
+ throws GitAPIException, IOException {
Collections.shuffle(RANDOM);
PushOneCommit push =
pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
PushOneCommit.FILE_NAME, new String(Chars.toArray(RANDOM)), changeId);
- return push.to(git, "refs/for/master");
+ return push.to(git, ref);
}
- protected ChangeJson.ChangeInfo getChange(String changeId, ListChangesOption... options)
+ protected ChangeInfo getChange(String changeId, ListChangesOption... options)
throws IOException {
return getChange(adminSession, changeId, options);
}
- protected ChangeJson.ChangeInfo getChange(RestSession session, String changeId,
+ protected ChangeInfo getChange(RestSession session, String changeId,
ListChangesOption... options) throws IOException {
String q = options.length > 0 ? "?o=" + Joiner.on("&o=").join(options) : "";
RestResponse r = session.get("/changes/" + changeId + q);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- return newGson().fromJson(r.getReader(), ChangeJson.ChangeInfo.class);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ return newGson().fromJson(r.getReader(), ChangeInfo.class);
}
protected ChangeInfo info(String id)
@@ -189,6 +258,11 @@ public abstract class AbstractDaemonTest {
return gApi.changes().id(id).get();
}
+ protected EditInfo getEdit(String id)
+ throws RestApiException {
+ return gApi.changes().id(id).getEdit();
+ }
+
protected ChangeInfo get(String id, ListChangesOption... options)
throws RestApiException {
EnumSet<ListChangesOption> s = EnumSet.noneOf(ListChangesOption.class);
@@ -218,4 +292,69 @@ public abstract class AbstractDaemonTest {
.id(r.getChangeId())
.current();
}
+
+ protected void allow(String permission, AccountGroup.UUID id, String ref)
+ throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg, permission, id, ref);
+ saveProjectConfig(project, cfg);
+ }
+
+ protected void allowGlobalCapability(String capabilityName,
+ AccountGroup.UUID id) throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+ Util.allow(cfg, capabilityName, id);
+ saveProjectConfig(allProjects, cfg);
+ }
+
+ protected void deny(String permission, AccountGroup.UUID id, String ref)
+ throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.deny(cfg, permission, id, ref);
+ saveProjectConfig(project, cfg);
+ }
+
+ protected void saveProjectConfig(Project.NameKey p, ProjectConfig cfg)
+ throws Exception {
+ MetaDataUpdate md = metaDataUpdateFactory.create(p);
+ try {
+ cfg.commit(md);
+ } finally {
+ md.close();
+ }
+ projectCache.evict(cfg.getProject());
+ }
+
+ protected void grant(String permission, Project.NameKey project, String ref)
+ throws RepositoryNotFoundException, IOException, ConfigInvalidException {
+ grant(permission, project, ref, false);
+ }
+
+ protected void grant(String permission, Project.NameKey project, String ref,
+ boolean force) throws RepositoryNotFoundException, IOException,
+ ConfigInvalidException {
+ MetaDataUpdate md = metaDataUpdateFactory.create(project);
+ md.setMessage(String.format("Grant %s on %s", permission, ref));
+ ProjectConfig config = ProjectConfig.read(md);
+ AccessSection s = config.getAccessSection(ref, true);
+ Permission p = s.getPermission(permission, true);
+ AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
+ PermissionRule rule = new PermissionRule(config.resolve(adminGroup));
+ rule.setForce(force);
+ p.add(rule);
+ config.commit(md);
+ projectCache.evict(config.getProject());
+ }
+
+ protected void blockRead(Project.NameKey project, String ref) throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ block(cfg, Permission.READ, REGISTERED_USERS, ref);
+ saveProjectConfig(project, cfg);
+ }
+
+ protected PushOneCommit.Result pushTo(String ref) throws GitAPIException,
+ IOException {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent());
+ return push.to(git, ref);
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index 6ee7efad32..2a578c2787 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.RequestCleanup;
@@ -22,7 +23,6 @@ import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Key;
@@ -164,8 +164,10 @@ public class AcceptanceTestRequestScope {
/** Returns exactly one instance per command executed. */
static final Scope REQUEST = new Scope() {
+ @Override
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
return new Provider<T>() {
+ @Override
public T get() {
return requireContext().get(key, creator);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index 0fc053eab6..a376332b97 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -14,12 +14,7 @@
package com.google.gerrit.acceptance;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -30,14 +25,18 @@ import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.ssh.SshKeyCache;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.KeyPair;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+
public class AccountCreator {
private SchemaFactory<ReviewDb> reviewDbProvider;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
index 41df3e6f9d..07d0f50032 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
@@ -42,7 +42,7 @@ class ConfigAnnotationParser {
return cfg;
}
- static private void parseAnnotation(Config cfg, GerritConfig c) {
+ private static void parseAnnotation(Config cfg, GerritConfig c) {
ArrayList<String> l = Lists.newArrayList(splitter.split(c.name()));
if (l.size() == 2) {
cfg.setString(l.get(0), null, l.get(1), c.value());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 85da2f5aac..3be81950c5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -22,22 +22,23 @@ import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.ChangeSchemas;
import com.google.gerrit.server.util.SocketUtil;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.testutil.TempFileUtil;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
-import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.util.FS;
import java.io.File;
-import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
-import java.net.UnknownHostException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
@@ -50,8 +51,10 @@ public class GerritServer {
/** Returns fully started Gerrit server */
static GerritServer start(Config cfg, boolean memory, boolean enableHttpd)
throws Exception {
+ Logger.getLogger("com.google.gerrit").setLevel(Level.DEBUG);
final CyclicBarrier serverStarted = new CyclicBarrier(2);
final Daemon daemon = new Daemon(new Runnable() {
+ @Override
public void run() {
try {
serverStarted.await();
@@ -68,6 +71,9 @@ public class GerritServer {
if (memory) {
site = null;
mergeTestConfig(cfg);
+ // Set the log4j configuration to an invalid one to prevent system logs
+ // from getting configured and creating log files.
+ System.setProperty(SystemLog.LOG4J_CONFIGURATION, "invalidConfiguration");
cfg.setBoolean("httpd", null, "requestLog", false);
cfg.setBoolean("sshd", null, "requestLog", false);
cfg.setBoolean("index", "lucene", "testInmemory", true);
@@ -83,6 +89,7 @@ public class GerritServer {
site = initSite(cfg);
daemonService = Executors.newSingleThreadExecutor();
daemonService.submit(new Callable<Void>() {
+ @Override
public Void call() throws Exception {
int rc = daemon.main(new String[] {"-d", site.getPath(), "--headless" });
if (rc != 0) {
@@ -121,14 +128,14 @@ public class GerritServer {
return tmp;
}
- private static void mergeTestConfig(Config cfg)
- throws IOException {
+ 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);
cfg.setString("sshd", null, "listenAddress", forceEphemeralPort);
+ cfg.setBoolean("sshd", null, "testUseInsecureRandom", true);
cfg.setString("cache", null, "directory", null);
cfg.setString("gerrit", null, "basePath", "git");
cfg.setBoolean("sendemail", null, "enable", false);
@@ -156,7 +163,7 @@ public class GerritServer {
return (T) f.get(obj);
}
- private static InetAddress getLocalHost() throws UnknownHostException {
+ private static InetAddress getLocalHost() {
return InetAddress.getLoopbackAddress();
}
@@ -168,7 +175,7 @@ public class GerritServer {
private InetSocketAddress httpAddress;
private GerritServer(Injector testInjector, Daemon daemon,
- ExecutorService daemonService) throws IOException, ConfigInvalidException {
+ ExecutorService daemonService) {
this.testInjector = testInjector;
this.daemon = daemon;
this.daemonService = daemonService;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
index 0356f72cdc..dee36ef0be 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
@@ -14,8 +14,12 @@
package com.google.gerrit.acceptance;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.testutil.TempFileUtil;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
@@ -29,12 +33,9 @@ import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.transport.PushResult;
@@ -45,8 +46,11 @@ import org.eclipse.jgit.util.FS;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Properties;
public class GitUtil {
@@ -100,6 +104,10 @@ public class GitUtil {
b.append("\"");
}
s.exec(b.toString());
+ if (s.hasError()) {
+ throw new IllegalStateException(
+ "gerrit create-project returned error: " + s.getError());
+ }
}
public static Git cloneProject(String url) throws GitAPIException, IOException {
@@ -122,8 +130,9 @@ public class GitUtil {
if (!p.exists() && !p.mkdirs()) {
throw new IOException("failed to create dir: " + p.getAbsolutePath());
}
- FileWriter w = new FileWriter(f);
- BufferedWriter out = new BufferedWriter(w);
+ FileOutputStream s = new FileOutputStream(f);
+ BufferedWriter out = new BufferedWriter(
+ new OutputStreamWriter(s, StandardCharsets.UTF_8));
try {
out.write(content);
} finally {
@@ -136,56 +145,41 @@ public class GitUtil {
}
public static void rm(Git gApi, String path)
- throws GitAPIException, IOException {
+ throws GitAPIException {
gApi.rm()
.addFilepattern(path)
.call();
}
public static Commit createCommit(Git git, PersonIdent i, String msg)
- throws GitAPIException, IOException {
+ throws GitAPIException {
return createCommit(git, i, msg, null);
}
public static Commit amendCommit(Git git, PersonIdent i, String msg, String changeId)
- throws GitAPIException, IOException {
+ throws GitAPIException {
msg = ChangeIdUtil.insertId(msg, ObjectId.fromString(changeId.substring(1)));
return createCommit(git, i, msg, changeId);
}
private static Commit createCommit(Git git, PersonIdent i, String msg,
- String changeId) throws GitAPIException, IOException {
+ String changeId) throws GitAPIException {
final CommitCommand commitCmd = git.commit();
commitCmd.setAmend(changeId != null);
commitCmd.setAuthor(i);
commitCmd.setCommitter(i);
-
- if (changeId == null) {
- ObjectId id = computeChangeId(git, i, msg);
- changeId = "I" + id.getName();
- }
- msg = ChangeIdUtil.insertId(msg, ObjectId.fromString(changeId.substring(1)));
commitCmd.setMessage(msg);
+ commitCmd.setInsertChangeId(changeId == null);
RevCommit c = commitCmd.call();
- return new Commit(c, changeId);
- }
- private static ObjectId computeChangeId(Git git, PersonIdent i, String msg)
- throws IOException {
- RevWalk rw = new RevWalk(git.getRepository());
- try {
- Ref head = git.getRepository().getRef(Constants.HEAD);
- if (head.getObjectId() != null) {
- RevCommit parent = rw.lookupCommit(head.getObjectId());
- return ChangeIdUtil.computeChangeId(parent.getTree(), parent.getId(), i, i, msg);
- } else {
- return ChangeIdUtil.computeChangeId(null, null, i, i, msg);
- }
- } finally {
- rw.close();
- }
+ List<String> ids = c.getFooterLines(FooterConstants.CHANGE_ID);
+ checkState(ids.size() >= 1,
+ "No Change-Id found in new commit:\n%s", c.getFullMessage());
+ changeId = ids.get(ids.size() - 1);
+
+ return new Commit(c, changeId);
}
public static void fetch(Git git, String spec) throws GitAPIException {
@@ -202,7 +196,13 @@ public class GitUtil {
public static PushResult pushHead(Git git, String ref, boolean pushTags)
throws GitAPIException {
+ return pushHead(git, ref, pushTags, false);
+ }
+
+ public static PushResult pushHead(Git git, String ref, boolean pushTags,
+ boolean force) throws GitAPIException {
PushCommand pushCmd = git.push();
+ pushCmd.setForce(force);
pushCmd.setRefSpecs(new RefSpec("HEAD:" + ref));
if (pushTags) {
pushCmd.setPushTags();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
index c58a5a2254..872c91277f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
@@ -43,7 +43,7 @@ public class HttpResponse {
public void consume() throws IllegalStateException, IOException {
Reader reader = getReader();
if (reader != null) {
- while (reader.read() != -1);
+ while (reader.read() != -1) {}
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java
index 5d819008c5..f765e7aa99 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java
@@ -52,7 +52,7 @@ public class HttpSession {
client = HttpClientBuilder
.create()
.setDefaultCredentialsProvider(creds)
- .setMaxConnPerRoute(10)
+ .setMaxConnPerRoute(512)
.setMaxConnTotal(1024)
.build();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index 32705ba727..6cd8031b92 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -14,12 +14,11 @@
package com.google.gerrit.acceptance;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.add;
import static com.google.gerrit.acceptance.GitUtil.amendCommit;
import static com.google.gerrit.acceptance.GitUtil.createCommit;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.google.common.base.Function;
import com.google.common.base.Strings;
@@ -34,11 +33,15 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.TagCommand;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidTagNameException;
@@ -57,7 +60,7 @@ import java.util.Set;
public class PushOneCommit {
public static final String SUBJECT = "test commit";
public static final String FILE_NAME = "a.txt";
- private static final String FILE_CONTENT = "some content";
+ public static final String FILE_CONTENT = "some content";
public interface Factory {
PushOneCommit create(
@@ -80,8 +83,28 @@ public class PushOneCommit {
@Assisted("changeId") String changeId);
}
+ public static class Tag {
+ public String name;
+
+ public Tag(String name) {
+ this.name = name;
+ }
+ }
+
+ public static class AnnotatedTag extends Tag {
+ public String message;
+ public PersonIdent tagger;
+
+ public AnnotatedTag(String name, String message, PersonIdent tagger) {
+ super(name);
+ this.message = message;
+ this.tagger = tagger;
+ }
+ }
+
private final ChangeNotes.Factory notesFactory;
private final ApprovalsUtil approvalsUtil;
+ private final Provider<InternalChangeQuery> queryProvider;
private final ReviewDb db;
private final PersonIdent i;
@@ -89,30 +112,36 @@ public class PushOneCommit {
private final String fileName;
private final String content;
private String changeId;
- private String tagName;
+ private Tag tag;
+ private boolean force;
@AssistedInject
PushOneCommit(ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
+ Provider<InternalChangeQuery> queryProvider,
@Assisted ReviewDb db,
@Assisted PersonIdent i) {
- this(notesFactory, approvalsUtil, db, i, SUBJECT, FILE_NAME, FILE_CONTENT);
+ this(notesFactory, approvalsUtil, queryProvider,
+ db, i, SUBJECT, FILE_NAME, FILE_CONTENT);
}
@AssistedInject
PushOneCommit(ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
+ Provider<InternalChangeQuery> queryProvider,
@Assisted ReviewDb db,
@Assisted PersonIdent i,
@Assisted("subject") String subject,
@Assisted("fileName") String fileName,
@Assisted("content") String content) {
- this(notesFactory, approvalsUtil, db, i, subject, fileName, content, null);
+ this(notesFactory, approvalsUtil, queryProvider,
+ db, i, subject, fileName, content, null);
}
@AssistedInject
PushOneCommit(ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
+ Provider<InternalChangeQuery> queryProvider,
@Assisted ReviewDb db,
@Assisted PersonIdent i,
@Assisted("subject") String subject,
@@ -122,6 +151,7 @@ public class PushOneCommit {
this.db = db;
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
+ this.queryProvider = queryProvider;
this.i = i;
this.subject = subject;
this.fileName = fileName;
@@ -129,21 +159,18 @@ public class PushOneCommit {
this.changeId = changeId;
}
- public Result to(Git git, String ref)
- throws GitAPIException, IOException {
+ public Result to(Git git, String ref) throws GitAPIException, IOException {
add(git, fileName, content);
return execute(git, ref);
}
- public Result rm(Git git, String ref)
- throws GitAPIException, IOException {
+ public Result rm(Git git, String ref) throws GitAPIException {
GitUtil.rm(git, fileName);
return execute(git, ref);
}
private Result execute(Git git, String ref) throws GitAPIException,
- IOException, ConcurrentRefUpdateException, InvalidTagNameException,
- NoHeadException {
+ ConcurrentRefUpdateException, InvalidTagNameException, NoHeadException {
Commit c;
if (changeId != null) {
c = amendCommit(git, i, subject, changeId);
@@ -151,33 +178,54 @@ public class PushOneCommit {
c = createCommit(git, i, subject);
changeId = c.getChangeId();
}
- if (tagName != null) {
- git.tag().setName(tagName).setAnnotated(false).call();
+ if (tag != null) {
+ TagCommand tagCommand = git.tag().setName(tag.name);
+ if (tag instanceof AnnotatedTag) {
+ AnnotatedTag annotatedTag = (AnnotatedTag)tag;
+ tagCommand.setAnnotated(true)
+ .setMessage(annotatedTag.message)
+ .setTagger(annotatedTag.tagger);
+ } else {
+ tagCommand.setAnnotated(false);
+ }
+ tagCommand.call();
}
- return new Result(ref, pushHead(git, ref, tagName != null), c, subject);
+ return new Result(ref, pushHead(git, ref, tag != null, force), c, subject);
}
- public void setTag(final String tagName) {
- this.tagName = tagName;
+ public void setTag(final Tag tag) {
+ this.tag = tag;
+ }
+
+ public void setForce(boolean force) {
+ this.force = force;
}
public class Result {
private final String ref;
private final PushResult result;
private final Commit commit;
- private final String subject;
+ private final String resSubj;
- private Result(String ref, PushResult result, Commit commit,
+ private Result(String ref, PushResult resSubj, Commit commit,
String subject) {
this.ref = ref;
- this.result = result;
+ this.result = resSubj;
this.commit = commit;
- this.subject = subject;
+ this.resSubj = subject;
}
- public PatchSet.Id getPatchSetId() throws OrmException {
+ public ChangeData getChange() throws OrmException {
return Iterables.getOnlyElement(
- db.changes().byKey(new Change.Key(commit.getChangeId()))).currentPatchSetId();
+ queryProvider.get().byKeyPrefix(commit.getChangeId()));
+ }
+
+ public PatchSet getPatchSet() throws OrmException {
+ return getChange().currentPatchSet();
+ }
+
+ public PatchSet.Id getPatchSetId() throws OrmException {
+ return getChange().change().currentPatchSetId();
}
public String getChangeId() {
@@ -195,11 +243,10 @@ public class PushOneCommit {
public void assertChange(Change.Status expectedStatus,
String expectedTopic, TestAccount... expectedReviewers)
throws OrmException {
- Change c =
- Iterables.getOnlyElement(db.changes().byKey(new Change.Key(commit.getChangeId())).toList());
- assertEquals(subject, c.getSubject());
- assertEquals(expectedStatus, c.getStatus());
- assertEquals(expectedTopic, Strings.emptyToNull(c.getTopic()));
+ Change c = getChange().change();
+ assertThat(resSubj).isEqualTo(c.getSubject());
+ assertThat(expectedStatus).isEqualTo(c.getStatus());
+ assertThat(expectedTopic).isEqualTo(Strings.emptyToNull(c.getTopic()));
assertReviewers(c, expectedReviewers);
}
@@ -216,11 +263,13 @@ public class PushOneCommit {
for (Account.Id accountId
: approvalsUtil.getReviewers(db, notesFactory.create(c)).values()) {
- assertTrue("unexpected reviewer " + accountId,
- expectedReviewerIds.remove(accountId));
+ assertThat(expectedReviewerIds.remove(accountId))
+ .named("unexpected reviewer " + accountId)
+ .isTrue();
}
- assertTrue("missing reviewers: " + expectedReviewerIds,
- expectedReviewerIds.isEmpty());
+ assertThat((Iterable<?>)expectedReviewerIds)
+ .named("missing reviewers: " + expectedReviewerIds)
+ .isEmpty();
}
public void assertOkStatus() {
@@ -233,15 +282,17 @@ public class PushOneCommit {
private void assertStatus(Status expectedStatus, String expectedMessage) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
- assertEquals(message(refUpdate),
- expectedStatus, refUpdate.getStatus());
- assertEquals(expectedMessage, refUpdate.getMessage());
+ assertThat(expectedStatus)
+ .named(message(refUpdate))
+ .isEqualTo(refUpdate.getStatus());
+ assertThat(expectedMessage).isEqualTo(refUpdate.getMessage());
}
public void assertMessage(String expectedMessage) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
- assertTrue(message(refUpdate), message(refUpdate).toLowerCase().contains(
- expectedMessage.toLowerCase()));
+ assertThat(message(refUpdate).toLowerCase())
+ .named(message(refUpdate))
+ .contains(expectedMessage.toLowerCase());
}
private String message(RemoteRefUpdate refUpdate) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
index 73dc1f0740..6c7dbfee38 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
@@ -19,6 +19,7 @@ import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.nio.charset.StandardCharsets;
public class RestResponse extends HttpResponse {
@@ -29,7 +30,9 @@ public class RestResponse extends HttpResponse {
@Override
public Reader getReader() throws IllegalStateException, IOException {
if (reader == null && response.getEntity() != null) {
- reader = new InputStreamReader(response.getEntity().getContent());
+ reader =
+ new InputStreamReader(response.getEntity().getContent(),
+ StandardCharsets.UTF_8);
reader.skip(JSON_MAGIC.length);
}
return reader;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 45befd06ef..bf6f928bbb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -16,9 +16,11 @@ package com.google.gerrit.acceptance;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
+import com.google.common.net.HttpHeaders;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.server.OutputFormat;
+import org.apache.http.Header;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
@@ -31,6 +33,7 @@ import org.apache.http.message.BasicHeader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
public class RestSession extends HttpSession {
@@ -40,7 +43,20 @@ public class RestSession extends HttpSession {
@Override
public RestResponse get(String endPoint) throws IOException {
+ return getWithHeader(endPoint, null);
+ }
+
+ public RestResponse getJsonAccept(String endPoint) throws IOException {
+ return getWithHeader(endPoint,
+ new BasicHeader(HttpHeaders.ACCEPT, "application/json"));
+ }
+
+ private RestResponse getWithHeader(String endPoint, Header header)
+ throws IOException {
HttpGet get = new HttpGet(url + "/a" + endPoint);
+ if (header != null) {
+ get.addHeader(header);
+ }
return new RestResponse(getClient().execute(get));
}
@@ -91,12 +107,14 @@ public class RestSession extends HttpSession {
}
- public static RawInput newRawInput(final String content) throws IOException {
- Preconditions.checkNotNull(content);
- Preconditions.checkArgument(!content.isEmpty());
- return new RawInput() {
- byte bytes[] = content.getBytes("UTF-8");
+ public static RawInput newRawInput(String content) {
+ return newRawInput(content.getBytes(StandardCharsets.UTF_8));
+ }
+ public static RawInput newRawInput(final byte[] bytes) {
+ Preconditions.checkNotNull(bytes);
+ Preconditions.checkArgument(bytes.length > 0);
+ return new RawInput() {
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
index dc648fcd82..701b337812 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -14,18 +14,19 @@
package com.google.gerrit.acceptance;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.util.Scanner;
+import static com.google.common.base.Preconditions.checkState;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
-public class SshSession {
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.Scanner;
+public class SshSession {
private final InetSocketAddress addr;
private final TestAccount account;
private Session session;
@@ -36,6 +37,10 @@ public class SshSession {
this.account = account;
}
+ public void open() throws JSchException {
+ getSession();
+ }
+
@SuppressWarnings("resource")
public String exec(String command) throws JSchException, IOException {
ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
@@ -86,6 +91,7 @@ public class SshSession {
}
public String getUrl() {
+ checkState(session != null, "session must be opened");
StringBuilder b = new StringBuilder();
b.append("ssh://");
b.append(session.getUserName());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
index 31ed136fff..bd5f19fcc3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -16,12 +16,12 @@ package com.google.gerrit.acceptance;
import com.google.gerrit.reviewdb.client.Account;
-import java.io.ByteArrayOutputStream;
-
import com.jcraft.jsch.KeyPair;
import org.eclipse.jgit.lib.PersonIdent;
+import java.io.ByteArrayOutputStream;
+
public class TestAccount {
public final Account.Id id;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
index 88d46a49e9..f4d156cba4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
@@ -31,7 +31,7 @@ public class UseGerritConfigAnnotationTest extends AbstractDaemonTest {
@Test
@GerritConfig(name="x.y", value="z")
public void testOne() {
- assertEquals("z", serverConfig.getString("x", null, "y"));
+ assertThat(serverConfig.getString("x", null, "y")).isEqualTo("z");
}
@Test
@@ -40,7 +40,7 @@ public class UseGerritConfigAnnotationTest extends AbstractDaemonTest {
@GerritConfig(name="a.b", value="c")
})
public void testMultiple() {
- assertEquals("z", serverConfig.getString("x", null, "y"));
- assertEquals("c", serverConfig.getString("a", null, "b"));
+ assertThat(serverConfig.getString("x", null, "y")).isEqualTo("z");
+ assertThat(serverConfig.getString("a", null, "b")).isEqualTo("c");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index c95f8c3b34..8945a22d79 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -14,56 +14,49 @@
package com.google.gerrit.acceptance.api.accounts;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Test;
-import java.io.IOException;
-
public class AccountIT extends AbstractDaemonTest {
@Test
- public void get() throws RestApiException {
+ public void get() throws Exception {
AccountInfo info = gApi
.accounts()
.id("admin")
.get();
- assertEquals("Administrator", info.name);
- assertEquals("admin@example.com", info.email);
- assertEquals("admin", info.username);
+ assertThat(info.name).isEqualTo("Administrator");
+ assertThat(info.email).isEqualTo("admin@example.com");
+ assertThat(info.username).isEqualTo("admin");
}
@Test
- public void self() throws RestApiException {
+ public void self() throws Exception {
AccountInfo info = gApi
.accounts()
.self()
.get();
- assertEquals("Administrator", info.name);
- assertEquals("admin@example.com", info.email);
- assertEquals("admin", info.username);
+ assertThat(info.name).isEqualTo("Administrator");
+ assertThat(info.email).isEqualTo("admin@example.com");
+ assertThat(info.username).isEqualTo("admin");
}
@Test
- public void starUnstarChange() throws GitAPIException,
- IOException, RestApiException {
+ public void starUnstarChange() throws Exception {
PushOneCommit.Result r = createChange();
String triplet = "p~master~" + r.getChangeId();
gApi.accounts()
.self()
.starChange(triplet);
- assertTrue(getChange(triplet).starred);
+ assertThat(getChange(triplet).starred).isTrue();
gApi.accounts()
.self()
.unstarChange(triplet);
- assertNull(getChange(triplet).starred);
+ assertThat(getChange(triplet).starred).isNull();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index c79b19865a..c13187f460 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -14,10 +14,7 @@
package com.google.gerrit.acceptance.api.change;
-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 com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -26,23 +23,21 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
-import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+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.ChangeStatus;
import com.google.gerrit.extensions.common.LabelInfo;
-import com.google.gerrit.extensions.common.ListChangesOption;
import com.google.gerrit.extensions.common.RevisionInfo;
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.PatchSet;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.junit.Test;
-import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@@ -51,72 +46,130 @@ import java.util.Set;
public class ChangeIT extends AbstractDaemonTest {
@Test
- public void get() throws GitAPIException,
- IOException, RestApiException {
+ public void get() throws Exception {
PushOneCommit.Result r = createChange();
String triplet = "p~master~" + r.getChangeId();
ChangeInfo c = info(triplet);
- assertEquals(triplet, c.id);
- assertEquals("p", c.project);
- assertEquals("master", c.branch);
- assertEquals(ChangeStatus.NEW, c.status);
- assertEquals("test commit", c.subject);
- assertEquals(true, c.mergeable);
- assertEquals(r.getChangeId(), c.changeId);
- assertEquals(c.created, c.updated);
- assertEquals(1, c._number);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.project).isEqualTo("p");
+ assertThat(c.branch).isEqualTo("master");
+ assertThat(c.status).isEqualTo(ChangeStatus.NEW);
+ assertThat(c.subject).isEqualTo("test commit");
+ assertThat(c.mergeable).isTrue();
+ assertThat(c.changeId).isEqualTo(r.getChangeId());
+ assertThat(c.created).isEqualTo(c.updated);
+ assertThat(c._number).is(1);
+
+ assertThat(c.owner._accountId).is(admin.getId().get());
+ assertThat(c.owner.name).isNull();
+ assertThat(c.owner.email).isNull();
+ assertThat(c.owner.username).isNull();
+ assertThat(c.owner.avatars).isNull();
}
@Test
- public void abandon() throws GitAPIException,
- IOException, RestApiException {
+ public void abandon() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.abandon();
}
@Test
- public void restore() throws GitAPIException,
- IOException, RestApiException {
+ public void restore() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.abandon();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.restore();
}
@Test
- public void revert() throws GitAPIException,
- IOException, RestApiException {
+ public void revert() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.review(ReviewInput.approve());
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.submit();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revert();
}
// Change is already up to date
@Test(expected = ResourceConflictException.class)
- public void rebase() throws GitAPIException,
- IOException, RestApiException {
+ public void rebase() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.revision(r.getCommit().name())
.rebase();
}
- private static Set<Account.Id> getReviewers(ChangeInfo ci) {
+ @Test
+ public void rebaseChangeBase() throws Exception {
+ PushOneCommit.Result r1 = createChange();
+ PushOneCommit.Result r2 = createChange();
+ PushOneCommit.Result r3 = createChange();
+ RebaseInput ri = new RebaseInput();
+
+ // rebase r3 directly onto master (break dep. towards r2)
+ ri.base = "";
+ gApi.changes()
+ .id(r3.getChangeId())
+ .revision(r3.getCommit().name())
+ .rebase(ri);
+ PatchSet ps3 = r3.getPatchSet();
+ assertThat(ps3.getId().get()).is(2);
+
+ // rebase r2 onto r3 (referenced by ref)
+ ri.base = ps3.getId().toRefName();
+ gApi.changes()
+ .id(r2.getChangeId())
+ .revision(r2.getCommit().name())
+ .rebase(ri);
+ PatchSet ps2 = r2.getPatchSet();
+ assertThat(ps2.getId().get()).is(2);
+
+ // rebase r1 onto r2 (referenced by commit)
+ ri.base = ps2.getRevision().get();
+ gApi.changes()
+ .id(r1.getChangeId())
+ .revision(r1.getCommit().name())
+ .rebase(ri);
+ PatchSet ps1 = r1.getPatchSet();
+ assertThat(ps1.getId().get()).is(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);
+ assertThat(r1.getPatchSetId().get()).is(3);
+ }
+
+ @Test(expected = ResourceConflictException.class)
+ public void rebaseChangeBaseRecursion() throws Exception {
+ PushOneCommit.Result r1 = createChange();
+ PushOneCommit.Result r2 = createChange();
+
+ RebaseInput ri = new RebaseInput();
+ ri.base = r2.getCommit().name();
+ gApi.changes()
+ .id(r1.getChangeId())
+ .revision(r1.getCommit().name())
+ .rebase(ri);
+ }
+
+ private Set<Account.Id> getReviewers(String changeId) throws Exception {
+ ChangeInfo ci = gApi.changes().id(changeId).get();
Set<Account.Id> result = Sets.newHashSet();
for (LabelInfo li : ci.labels.values()) {
for (ApprovalInfo ai : li.all) {
@@ -127,39 +180,44 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
- public void addReviewer() throws GitAPIException,
- IOException, RestApiException {
+ public void addReviewer() throws Exception {
PushOneCommit.Result r = createChange();
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
- ChangeApi cApi = gApi.changes().id("p~master~" + r.getChangeId());
- cApi.addReviewer(in);
- assertEquals(ImmutableSet.of(user.id), getReviewers(cApi.get()));
+ gApi.changes()
+ .id(r.getChangeId())
+ .addReviewer(in);
+
+ assertThat((Iterable<?>)getReviewers(r.getChangeId()))
+ .containsExactlyElementsIn(ImmutableSet.of(user.id));
}
@Test
- public void addReviewerToClosedChange() throws GitAPIException,
- IOException, RestApiException {
+ public void addReviewerToClosedChange() throws Exception {
PushOneCommit.Result r = createChange();
- ChangeApi cApi = gApi.changes().id("p~master~" + r.getChangeId());
- cApi.revision(r.getCommit().name())
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
.review(ReviewInput.approve());
- cApi.revision(r.getCommit().name())
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
.submit();
- assertEquals(ImmutableSet.of(admin.getId()), getReviewers(cApi.get()));
+ assertThat((Iterable<?>)getReviewers(r.getChangeId()))
+ .containsExactlyElementsIn(ImmutableSet.of(admin.getId()));
AddReviewerInput in = new AddReviewerInput();
in.reviewer = user.email;
gApi.changes()
- .id("p~master~" + r.getChangeId())
+ .id(r.getChangeId())
.addReviewer(in);
- assertEquals(ImmutableSet.of(admin.getId(), user.id),
- getReviewers(cApi.get()));
+ assertThat((Iterable<?>)getReviewers(r.getChangeId()))
+ .containsExactlyElementsIn(ImmutableSet.of(admin.getId(), user.id));
}
@Test
- public void createEmptyChange() throws RestApiException {
+ public void createEmptyChange() throws Exception {
ChangeInfo in = new ChangeInfo();
in.branch = Constants.MASTER;
in.subject = "Create a change from the API";
@@ -168,9 +226,9 @@ public class ChangeIT extends AbstractDaemonTest {
.changes()
.create(in)
.get();
- assertEquals(in.project, info.project);
- assertEquals(in.branch, info.branch);
- assertEquals(in.subject, info.subject);
+ assertThat(info.project).isEqualTo(in.project);
+ assertThat(info.branch).isEqualTo(in.branch);
+ assertThat(info.subject).isEqualTo(in.subject);
}
@Test
@@ -178,18 +236,18 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r1 = createChange();
PushOneCommit.Result r2 = createChange();
List<ChangeInfo> results = gApi.changes().query().get();
- assertEquals(2, results.size());
- assertEquals(r2.getChangeId(), results.get(0).changeId);
- assertEquals(r1.getChangeId(), results.get(1).changeId);
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).changeId).isEqualTo(r2.getChangeId());
+ assertThat(results.get(1).changeId).isEqualTo(r1.getChangeId());
}
@Test
public void queryChangesNoResults() throws Exception {
createChange();
List<ChangeInfo> results = query("status:open");
- assertEquals(1, results.size());
+ assertThat(results).hasSize(1);
results = query("status:closed");
- assertTrue(results.isEmpty());
+ assertThat(results).isEmpty();
}
@Test
@@ -197,9 +255,9 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r1 = createChange();
PushOneCommit.Result r2 = createChange();
List<ChangeInfo> results = query("status:open");
- assertEquals(2, results.size());
- assertEquals(r2.getChangeId(), results.get(0).changeId);
- assertEquals(r1.getChangeId(), results.get(1).changeId);
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).changeId).isEqualTo(r2.getChangeId());
+ assertThat(results.get(1).changeId).isEqualTo(r1.getChangeId());
}
@Test
@@ -207,7 +265,8 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r1 = createChange();
createChange();
List<ChangeInfo> results = query("status:open " + r1.getChangeId());
- assertEquals(r1.getChangeId(), Iterables.getOnlyElement(results).changeId);
+ assertThat(Iterables.getOnlyElement(results).changeId)
+ .isEqualTo(r1.getChangeId());
}
@Test
@@ -215,8 +274,9 @@ public class ChangeIT extends AbstractDaemonTest {
createChange();
PushOneCommit.Result r2 = createChange();
List<ChangeInfo> results = gApi.changes().query().withLimit(1).get();
- assertEquals(1, results.size());
- assertEquals(r2.getChangeId(), Iterables.getOnlyElement(results).changeId);
+ assertThat(results).hasSize(1);
+ assertThat(Iterables.getOnlyElement(results).changeId)
+ .isEqualTo(r2.getChangeId());
}
@Test
@@ -224,17 +284,18 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r1 = createChange();
createChange();
List<ChangeInfo> results = gApi.changes().query().withStart(1).get();
- assertEquals(r1.getChangeId(), Iterables.getOnlyElement(results).changeId);
+ assertThat(Iterables.getOnlyElement(results).changeId)
+ .isEqualTo(r1.getChangeId());
}
@Test
public void queryChangesNoOptions() throws Exception {
PushOneCommit.Result r = createChange();
ChangeInfo result = Iterables.getOnlyElement(query(r.getChangeId()));
- assertNull(result.labels);
- assertNull(result.messages);
- assertNull(result.revisions);
- assertNull(result.actions);
+ assertThat(result.labels).isNull();
+ assertThat((Iterable<?>)result.messages).isNull();
+ assertThat(result.revisions).isNull();
+ assertThat(result.actions).isNull();
}
@Test
@@ -244,23 +305,26 @@ public class ChangeIT extends AbstractDaemonTest {
.query(r.getChangeId())
.withOptions(EnumSet.allOf(ListChangesOption.class))
.get());
- assertEquals("Code-Review",
- Iterables.getOnlyElement(result.labels.keySet()));
- assertEquals(1, result.messages.size());
- assertFalse(result.actions.isEmpty());
+ assertThat(Iterables.getOnlyElement(result.labels.keySet()))
+ .isEqualTo("Code-Review");
+ assertThat((Iterable<?>)result.messages).hasSize(1);
+ assertThat(result.actions).isNotEmpty();
RevisionInfo rev = Iterables.getOnlyElement(result.revisions.values());
- assertEquals(r.getPatchSetId().get(), rev._number);
- assertFalse(rev.actions.isEmpty());
+ assertThat(rev._number).isEqualTo(r.getPatchSetId().get());
+ assertThat(rev.created).isNotNull();
+ assertThat(rev.uploader._accountId).is(admin.getId().get());
+ assertThat(rev.ref).isEqualTo(r.getPatchSetId().toRefName());
+ assertThat(rev.actions).isNotEmpty();
}
@Test
public void queryChangesOwnerWithDifferentUsers() throws Exception {
PushOneCommit.Result r = createChange();
- assertEquals(r.getChangeId(),
- Iterables.getOnlyElement(query("owner:self")).changeId);
+ assertThat(Iterables.getOnlyElement(query("owner:self")).changeId)
+ .isEqualTo(r.getChangeId());
setApiUser(user);
- assertTrue(query("owner:self").isEmpty());
+ assertThat(query("owner:self")).isEmpty();
}
@Test
@@ -273,9 +337,42 @@ public class ChangeIT extends AbstractDaemonTest {
.addReviewer(in);
setApiUser(user);
- assertNull(get(r.getChangeId()).reviewed);
+ assertThat(get(r.getChangeId()).reviewed).isNull();
revision(r).review(ReviewInput.recommend());
- assertTrue(get(r.getChangeId()).reviewed);
+ assertThat(get(r.getChangeId()).reviewed).isTrue();
+ }
+
+ @Test
+ public void topic() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .topic()).isEqualTo("");
+ gApi.changes()
+ .id(r.getChangeId())
+ .topic("mytopic");
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .topic()).isEqualTo("mytopic");
+ gApi.changes()
+ .id(r.getChangeId())
+ .topic("");
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .topic()).isEqualTo("");
+ }
+
+ @Test
+ public void check() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .get()
+ .problems).isNull();
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .get(EnumSet.of(ListChangesOption.CHECK))
+ .problems).isEmpty();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java
new file mode 100644
index 0000000000..475803a0de
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2014 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 com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.api.changes.FixInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+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.Change;
+
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+@NoHttpd
+public class CheckIT extends AbstractDaemonTest {
+ // Most types of tests belong in ConsistencyCheckerTest; these mostly just
+ // test paths outside of ConsistencyChecker, like API wiring.
+ @Test
+ public void currentPatchSetMissing() throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change c = getChange(r);
+ db.patchSets().deleteKeys(Collections.singleton(c.currentPatchSetId()));
+ indexer.index(db, c);
+
+ List<ProblemInfo> problems = gApi.changes()
+ .id(r.getChangeId())
+ .check()
+ .problems;
+ assertThat(problems).hasSize(1);
+ assertThat(problems.get(0).message)
+ .isEqualTo("Current patch set 1 not found");
+ }
+
+ @Test
+ public void fixReturnsUpdatedValue() 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();
+
+ Change c = getChange(r);
+ c.setStatus(Change.Status.NEW);
+ db.changes().update(Collections.singleton(c));
+ indexer.index(db, c);
+
+ ChangeInfo info = gApi.changes()
+ .id(r.getChangeId())
+ .check();
+ assertThat(info.problems).hasSize(1);
+ assertThat(info.problems.get(0).status).isNull();
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+
+ info = gApi.changes()
+ .id(r.getChangeId())
+ .check(new FixInput());
+ assertThat(info.problems).hasSize(1);
+ assertThat(info.problems.get(0).status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ private Change getChange(PushOneCommit.Result r) throws Exception {
+ return db.changes().get(new Change.Id(
+ gApi.changes().id(r.getChangeId()).get()._number));
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
index a10e02639b..7de4712917 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -14,31 +14,29 @@
package com.google.gerrit.acceptance.api.project;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
@NoHttpd
public class ProjectIT extends AbstractDaemonTest {
@Test
- public void createProjectFoo() throws RestApiException {
+ public void createProjectFoo() throws Exception {
String name = "foo";
- assertEquals(name,
+ assertThat(name).isEqualTo(
gApi.projects()
.name(name)
.create()
@@ -46,8 +44,19 @@ public class ProjectIT extends AbstractDaemonTest {
.name);
}
+ @Test
+ public void createProjectFooWithGitSuffix() throws Exception {
+ String name = "foo";
+ assertThat(name).isEqualTo(
+ gApi.projects()
+ .name(name + ".git")
+ .create()
+ .get()
+ .name);
+ }
+
@Test(expected = RestApiException.class)
- public void createProjectFooBar() throws RestApiException {
+ public void createProjectFooBar() throws Exception {
ProjectInput in = new ProjectInput();
in.name = "foo";
gApi.projects()
@@ -56,7 +65,7 @@ public class ProjectIT extends AbstractDaemonTest {
}
@Test(expected = ResourceConflictException.class)
- public void createProjectDuplicate() throws RestApiException {
+ public void createProjectDuplicate() throws Exception {
ProjectInput in = new ProjectInput();
in.name = "baz";
gApi.projects()
@@ -68,8 +77,8 @@ public class ProjectIT extends AbstractDaemonTest {
}
@Test
- public void createBranch() throws GitAPIException,
- IOException, RestApiException {
+ public void createBranch() throws Exception {
+ allow(Permission.READ, ANONYMOUS_USERS, "refs/*");
gApi.projects()
.name(project.get())
.branch("foo")
@@ -84,36 +93,36 @@ public class ProjectIT extends AbstractDaemonTest {
gApi.projects().name("bar").create();
List<ProjectInfo> allProjects = gApi.projects().list().get();
- assertEquals(initialProjects.size() + 2, allProjects.size());
+ assertThat(allProjects).hasSize(initialProjects.size() + 2);
List<ProjectInfo> projectsWithDescription = gApi.projects().list()
.withDescription(true)
.get();
- assertNotNull(projectsWithDescription.get(0).description);
+ assertThat(projectsWithDescription.get(0).description).isNotNull();
List<ProjectInfo> projectsWithoutDescription = gApi.projects().list()
.withDescription(false)
.get();
- assertNull(projectsWithoutDescription.get(0).description);
+ assertThat(projectsWithoutDescription.get(0).description).isNull();
List<ProjectInfo> noMatchingProjects = gApi.projects().list()
.withPrefix("fox")
.get();
- assertEquals(0, noMatchingProjects.size());
+ assertThat(noMatchingProjects).isEmpty();
List<ProjectInfo> matchingProject = gApi.projects().list()
.withPrefix("fo")
.get();
- assertEquals(1, matchingProject.size());
+ assertThat(matchingProject).hasSize(1);
List<ProjectInfo> limitOneProject = gApi.projects().list()
.withLimit(1)
.get();
- assertEquals(1, limitOneProject.size());
+ assertThat(limitOneProject).hasSize(1);
List<ProjectInfo> startAtOneProjects = gApi.projects().list()
.withStart(1)
.get();
- assertEquals(allProjects.size() - 1, startAtOneProjects.size());
+ assertThat(startAtOneProjects).hasSize(allProjects.size() - 1);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 9653ffaf38..9cc8f9c151 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -14,10 +14,13 @@
package com.google.gerrit.acceptance.api.revision;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.PushOneCommit.FILE_CONTENT;
+import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.junit.Assert.fail;
+import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
@@ -25,18 +28,38 @@ import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.changes.DraftApi;
+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.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.client.SubmitType;
+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.common.DiffInfo;
+import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Patch;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
import org.junit.Before;
import org.junit.Test;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
@NoHttpd
public class RevisionIT extends AbstractDaemonTest {
@@ -49,8 +72,7 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
- public void reviewTriplet() throws GitAPIException,
- IOException, RestApiException {
+ public void reviewTriplet() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
.id("p~master~" + r.getChangeId())
@@ -59,8 +81,7 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
- public void reviewCurrent() throws GitAPIException,
- IOException, RestApiException {
+ public void reviewCurrent() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
.id(r.getChangeId())
@@ -69,8 +90,7 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
- public void reviewNumber() throws GitAPIException,
- IOException, RestApiException {
+ public void reviewNumber() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
.id(r.getChangeId())
@@ -85,8 +105,7 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
- public void submit() throws GitAPIException,
- IOException, RestApiException {
+ public void submit() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
.id("p~master~" + r.getChangeId())
@@ -99,8 +118,7 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test(expected = AuthException.class)
- public void submitOnBehalfOf() throws GitAPIException,
- IOException, RestApiException {
+ public void submitOnBehalfOf() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
.id("p~master~" + r.getChangeId())
@@ -116,8 +134,7 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
- public void deleteDraft() throws GitAPIException,
- IOException, RestApiException {
+ public void deleteDraft() throws Exception {
PushOneCommit.Result r = createDraft();
gApi.changes()
.id(r.getChangeId())
@@ -126,8 +143,106 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
- public void cherryPick() throws GitAPIException,
- IOException, RestApiException {
+ public void cherryPick() throws Exception {
+ PushOneCommit.Result r = pushTo("refs/for/master%topic=someTopic");
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "foo";
+ in.message = "it goes to stable branch";
+ gApi.projects()
+ .name(project.get())
+ .branch(in.destination)
+ .create(new BranchInput());
+ ChangeApi orig = gApi.changes()
+ .id("p~master~" + r.getChangeId());
+
+ assertThat((Iterable<?>)orig.get().messages).hasSize(1);
+ ChangeApi cherry = orig.revision(r.getCommit().name())
+ .cherryPick(in);
+ assertThat((Iterable<?>)orig.get().messages).hasSize(2);
+
+ String cherryPickedRevision = cherry.get().currentRevision;
+ String expectedMessage = String.format(
+ "Patch Set 1: Cherry Picked\n\n" +
+ "This patchset was cherry picked to branch %s as commit %s",
+ in.destination, cherryPickedRevision);
+
+ Iterator<ChangeMessageInfo> origIt = orig.get().messages.iterator();
+ origIt.next();
+ assertThat(origIt.next().message).isEqualTo(expectedMessage);
+
+ assertThat((Iterable<?>)cherry.get().messages).hasSize(1);
+ Iterator<ChangeMessageInfo> cherryIt = cherry.get().messages.iterator();
+ expectedMessage = "Patch Set 1: Cherry Picked from branch master.";
+ assertThat(cherryIt.next().message).isEqualTo(expectedMessage);
+
+ assertThat(cherry.get().subject).contains(in.message);
+ assertThat(cherry.get().topic).isEqualTo("someTopic");
+ cherry.current().review(ReviewInput.approve());
+ cherry.current().submit();
+ }
+
+ @Test
+ public void cherryPickToSameBranch() throws Exception {
+ PushOneCommit.Result r = createChange();
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "master";
+ in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
+ ChangeInfo cherryInfo = gApi.changes()
+ .id("p~master~" + r.getChangeId())
+ .revision(r.getCommit().name())
+ .cherryPick(in)
+ .get();
+ assertThat((Iterable<?>)cherryInfo.messages).hasSize(2);
+ Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
+ }
+
+ @Test
+ public void cherryPickToSameBranchWithRebase() throws Exception {
+ // Push a new change, then merge it
+ PushOneCommit.Result baseChange = createChange();
+ RevisionApi baseRevision =
+ gApi.changes().id("p~master~" + baseChange.getChangeId()).current();
+ baseRevision.review(ReviewInput.approve());
+ baseRevision.submit();
+
+ // Push a new change (change 1)
+ PushOneCommit.Result r1 = createChange();
+
+ // Push another new change (change 2)
+ String subject = "Test change\n\n" +
+ "Change-Id: Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), subject,
+ "another_file.txt", "another content");
+ PushOneCommit.Result r2 = push.to(git, "refs/for/master");
+
+ // Change 2's parent should be change 1
+ assertThat(r2.getCommit().getParents()[0].name())
+ .isEqualTo(r1.getCommit().name());
+
+ // Cherry pick change 2 onto the same branch
+ ChangeApi orig = gApi.changes().id("p~master~" + r2.getChangeId());
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "master";
+ in.message = subject;
+ ChangeApi cherry = orig.revision(r2.getCommit().name()).cherryPick(in);
+ ChangeInfo cherryInfo = cherry.get();
+ assertThat((Iterable<?>)cherryInfo.messages).hasSize(2);
+ Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
+ assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
+
+ // Parent of change 2 should now be the change that was merged, i.e.
+ // change 2 is rebased onto the head of the master branch.
+ String newParent = cherryInfo.revisions.get(cherryInfo.currentRevision)
+ .commit.parents.get(0).commit;
+ assertThat(newParent).isEqualTo(baseChange.getCommit().name());
+ }
+
+ @Test
+ public void cherryPickIdenticalTree() throws Exception {
PushOneCommit.Result r = createChange();
CherryPickInput in = new CherryPickInput();
in.destination = "foo";
@@ -139,41 +254,74 @@ public class RevisionIT extends AbstractDaemonTest {
ChangeApi orig = gApi.changes()
.id("p~master~" + r.getChangeId());
- assertEquals(1, orig.get().messages.size());
+ assertThat((Iterable<?>)orig.get().messages).hasSize(1);
ChangeApi cherry = orig.revision(r.getCommit().name())
.cherryPick(in);
- assertEquals(2, orig.get().messages.size());
+ assertThat((Iterable<?>)orig.get().messages).hasSize(2);
- assertTrue(cherry.get().subject.contains(in.message));
- cherry.current()
- .review(ReviewInput.approve());
- cherry.current()
- .submit();
+ assertThat(cherry.get().subject).contains(in.message);
+ cherry.current().review(ReviewInput.approve());
+ cherry.current().submit();
+
+ try {
+ orig.revision(r.getCommit().name()).cherryPick(in);
+ fail("Cherry-pick identical tree error expected");
+ } catch (RestApiException e) {
+ assertThat(e.getMessage()).isEqualTo("Cherry pick failed: identical tree");
+ }
}
@Test
- public void canRebase()
- throws GitAPIException, IOException, RestApiException, Exception {
+ public void cherryPickConflict() throws Exception {
+ PushOneCommit.Result r = createChange();
+ CherryPickInput in = new CherryPickInput();
+ in.destination = "foo";
+ in.message = "it goes to stable branch";
+ gApi.projects()
+ .name(project.get())
+ .branch(in.destination)
+ .create(new BranchInput());
+
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ PushOneCommit.FILE_NAME, "another content");
+ push.to(git, "refs/heads/foo");
+
+ ChangeApi orig = gApi.changes().id("p~master~" + r.getChangeId());
+ assertThat((Iterable<?>)orig.get().messages).hasSize(1);
+
+ try {
+ orig.revision(r.getCommit().name()).cherryPick(in);
+ fail("Cherry-pick merge conflict error expected");
+ } catch (RestApiException e) {
+ assertThat(e.getMessage()).isEqualTo("Cherry pick failed: merge conflict");
+ }
+ }
+
+ @Test
+ public void canRebase() throws Exception {
PushOneCommit push = pushFactory.create(db, admin.getIdent());
PushOneCommit.Result r1 = push.to(git, "refs/for/master");
merge(r1);
push = pushFactory.create(db, admin.getIdent());
PushOneCommit.Result r2 = push.to(git, "refs/for/master");
- assertFalse(gApi.changes()
+ boolean canRebase = gApi.changes()
.id(r2.getChangeId())
.revision(r2.getCommit().name())
- .canRebase());
+ .canRebase();
+ assertThat(canRebase).isFalse();
merge(r2);
git.checkout().setName(r1.getCommit().name()).call();
push = pushFactory.create(db, admin.getIdent());
PushOneCommit.Result r3 = push.to(git, "refs/for/master");
- assertTrue(gApi.changes()
+ canRebase = gApi.changes()
.id(r3.getChangeId())
.revision(r3.getCommit().name())
- .canRebase());
+ .canRebase();
+ assertThat(canRebase).isTrue();
}
@Test
@@ -186,30 +334,185 @@ public class RevisionIT extends AbstractDaemonTest {
.current()
.setReviewed(PushOneCommit.FILE_NAME, true);
- assertEquals(PushOneCommit.FILE_NAME,
- Iterables.getOnlyElement(
+ assertThat(Iterables.getOnlyElement(
gApi.changes()
.id(r.getChangeId())
.current()
- .reviewed()));
+ .reviewed())).isEqualTo(PushOneCommit.FILE_NAME);
gApi.changes()
.id(r.getChangeId())
.current()
.setReviewed(PushOneCommit.FILE_NAME, false);
- assertTrue(
- gApi.changes()
- .id(r.getChangeId())
- .current()
- .reviewed()
- .isEmpty());
+ assertThat((Iterable<?>)gApi.changes().id(r.getChangeId()).current().reviewed())
+ .isEmpty();
}
- protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
- return gApi.changes()
+ @Test
+ public void mergeable() throws Exception {
+ ObjectId initial = git.getRepository().getRef(HEAD).getLeaf().getObjectId();
+
+ PushOneCommit push1 =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ PushOneCommit.FILE_NAME, "push 1 content");
+
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master");
+ assertMergeable(r1.getChangeId(), true);
+ merge(r1);
+
+ // Reset HEAD to initial so the new change is a merge conflict.
+ RefUpdate ru = git.getRepository().updateRef(HEAD);
+ ru.setNewObjectId(initial);
+ assertThat(ru.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED);
+
+ PushOneCommit push2 =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ PushOneCommit.FILE_NAME, "push 2 content");
+ PushOneCommit.Result r2 = push2.to(git, "refs/for/master");
+ assertMergeable(r2.getChangeId(), false);
+ // TODO(dborowitz): Test for other-branches.
+ }
+
+ @Test
+ public void files() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertThat(Iterables.all(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .files()
+ .keySet(), new Predicate<String>() {
+ @Override
+ public boolean apply(String file) {
+ return file.matches(FILE_NAME + '|' + Patch.COMMIT_MSG);
+ }
+ }))
+ .isTrue();
+ }
+
+ @Test
+ public void diff() throws Exception {
+ PushOneCommit.Result r = createChange();
+ DiffInfo diff = gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .file(FILE_NAME)
+ .diff();
+ assertThat(diff.metaA).isNull();
+ assertThat(diff.metaB.lines).isEqualTo(1);
+ }
+
+ @Test
+ public void content() throws Exception {
+ PushOneCommit.Result r = createChange();
+ BinaryResult bin = gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .file(FILE_NAME)
+ .content();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bin.writeTo(os);
+ String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
+ assertThat(res).isEqualTo(FILE_CONTENT);
+ }
+
+ private void assertMergeable(String id, boolean expected) throws Exception {
+ MergeableInfo m = gApi.changes().id(id).current().mergeable();
+ assertThat(m.mergeable).isEqualTo(expected);
+ assertThat(m.submitType).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(m.mergeableInto).isNull();
+ ChangeInfo c = gApi.changes().id(id).info();
+ assertThat(c.mergeable).isEqualTo(expected);
+ }
+
+ @Test
+ public void drafts() throws Exception {
+ PushOneCommit.Result r = createChange();
+ DraftInput in = new DraftInput();
+ in.line = 1;
+ in.message = "nit: trailing whitespace";
+ in.path = FILE_NAME;
+
+ DraftApi draftApi = gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .createDraft(in);
+ assertThat(draftApi
+ .get()
+ .message)
+ .isEqualTo(in.message);
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .draft(draftApi.get().id)
+ .get()
+ .message)
+ .isEqualTo(in.message);
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .drafts())
+ .hasSize(1);
+
+ in.message = "good catch!";
+ assertThat(gApi.changes()
.id(r.getChangeId())
- .current();
+ .revision(r.getCommit().name())
+ .draft(draftApi.get().id)
+ .update(in)
+ .message)
+ .isEqualTo(in.message);
+
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .draft(draftApi.get().id)
+ .get()
+ .author
+ .email)
+ .isEqualTo(admin.email);
+
+ draftApi.delete();
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .drafts())
+ .isEmpty();
+ }
+
+ @Test
+ public void comments() throws Exception {
+ PushOneCommit.Result r = createChange();
+ CommentInput in = new CommentInput();
+ in.line = 1;
+ in.message = "nit: trailing whitespace";
+ in.path = FILE_NAME;
+ ReviewInput reviewInput = new ReviewInput();
+ Map<String, List<CommentInput>> comments = new HashMap<>();
+ comments.put(FILE_NAME, Collections.singletonList(in));
+ reviewInput.comments = comments;
+ reviewInput.message = "comment test";
+ gApi.changes()
+ .id(r.getChangeId())
+ .current()
+ .review(reviewInput);
+
+ Map<String, List<CommentInfo>> out = gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .comments();
+ assertThat(out).hasSize(1);
+ CommentInfo comment = Iterables.getOnlyElement(out.get(FILE_NAME));
+ assertThat(comment.message).isEqualTo(in.message);
+ assertThat(comment.author.email).isEqualTo(admin.email);
+
+ assertThat(gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .comment(comment.id)
+ .get()
+ .message)
+ .isEqualTo(in.message);
}
private void merge(PushOneCommit.Result r) throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
new file mode 100644
index 0000000000..be6fcdc2bb
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUCK
@@ -0,0 +1,10 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+ srcs = ['ChangeEditIT.java'],
+ labels = ['edit'],
+ deps = [
+ '//lib/commons:codec',
+ '//lib/joda:joda-time',
+ ],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
new file mode 100644
index 0000000000..4726079319
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -0,0 +1,746 @@
+// Copyright (C) 2014 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.edit;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.http.HttpStatus.SC_CONFLICT;
+import static org.apache.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.http.HttpStatus.SC_NO_CONTENT;
+import static org.apache.http.HttpStatus.SC_OK;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+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.EditInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.ChangeEdits.EditMessage;
+import com.google.gerrit.server.change.ChangeEdits.Post;
+import com.google.gerrit.server.change.ChangeEdits.Put;
+import com.google.gerrit.server.change.FileContentUtil;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.edit.UnchangedCommitMessageException;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gson.stream.JsonReader;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.apache.commons.codec.binary.StringUtils;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ChangeEditIT extends AbstractDaemonTest {
+
+ private static final String FILE_NAME = "foo";
+ private static final String FILE_NAME2 = "foo2";
+ private static final String FILE_NAME3 = "foo3";
+ private static final byte[] CONTENT_OLD = "bar".getBytes(UTF_8);
+ private static final byte[] CONTENT_NEW = "baz".getBytes(UTF_8);
+ private static final String CONTENT_NEW2_STR = "quxÄÜÖßµ";
+ private static final byte[] CONTENT_NEW2 = CONTENT_NEW2_STR.getBytes(UTF_8);
+
+ @Inject
+ private SchemaFactory<ReviewDb> reviewDbProvider;
+
+ @Inject
+ ChangeEditUtil editUtil;
+
+ @Inject
+ private ChangeEditModifier modifier;
+
+ @Inject
+ private FileContentUtil fileUtil;
+
+ private Change change;
+ private String changeId;
+ private Change change2;
+ private String changeId2;
+ private PatchSet ps;
+ private PatchSet ps2;
+
+ @Before
+ public void setUp() throws Exception {
+ db = reviewDbProvider.open();
+ changeId = newChange(git, admin.getIdent());
+ ps = getCurrentPatchSet(changeId);
+ amendChange(git, admin.getIdent(), changeId);
+ change = getChange(changeId);
+ assertThat(ps).isNotNull();
+ changeId2 = newChange2(git, admin.getIdent());
+ change2 = getChange(changeId2);
+ assertThat(change2).isNotNull();
+ ps2 = getCurrentPatchSet(changeId2);
+ assertThat(ps2).isNotNull();
+ final long clockStepMs = MILLISECONDS.convert(1, SECONDS);
+ final AtomicLong clockMs = new AtomicLong(
+ new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
+ DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
+ @Override
+ public long getMillis() {
+ return clockMs.getAndAdd(clockStepMs);
+ }
+ });
+ }
+
+ @After
+ public void cleanup() {
+ DateTimeUtils.setCurrentMillisSystem();
+ db.close();
+ }
+
+ @Test
+ public void deleteEdit() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+ editUtil.delete(editUtil.byChange(change).get());
+ assertThat(editUtil.byChange(change).isPresent()).isFalse();
+ }
+
+ @Test
+ public void publishEdit() throws Exception {
+ assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId)))
+ .isEqualTo(RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW2))).isEqualTo(RefUpdate.Result.FORCED);
+ editUtil.publish(editUtil.byChange(change).get());
+ assertThat(editUtil.byChange(change).isPresent()).isFalse();
+ }
+
+ @Test
+ public void publishEditRest() throws Exception {
+ PatchSet oldCurrentPatchSet = getCurrentPatchSet(changeId);
+ assertThat(modifier.createEdit(change, oldCurrentPatchSet)).isEqualTo(
+ RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ RestResponse r = adminSession.post(urlPublish());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ edit = editUtil.byChange(change);
+ assertThat(edit.isPresent()).isFalse();
+ PatchSet newCurrentPatchSet = getCurrentPatchSet(changeId);
+ assertThat(newCurrentPatchSet.getId()).isNotEqualTo(oldCurrentPatchSet.getId());
+ }
+
+ @Test
+ public void deleteEditRest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ RestResponse r = adminSession.delete(urlEdit());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ edit = editUtil.byChange(change);
+ assertThat(edit.isPresent()).isFalse();
+ }
+
+ @Test
+ public void rebaseEdit() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+ ChangeEdit edit = editUtil.byChange(change).get();
+ PatchSet current = getCurrentPatchSet(changeId);
+ assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
+ current.getPatchSetId() - 1);
+ Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ modifier.rebaseEdit(edit, current);
+ edit = editUtil.byChange(change).get();
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+ ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+ ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
+ assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
+ current.getPatchSetId());
+ Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ assertThat(beforeRebase.equals(afterRebase)).isFalse();
+ }
+
+ @Test
+ public void rebaseEditRest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+ ChangeEdit edit = editUtil.byChange(change).get();
+ PatchSet current = getCurrentPatchSet(changeId);
+ assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
+ current.getPatchSetId() - 1);
+ Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ RestResponse r = adminSession.post(urlRebase());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ edit = editUtil.byChange(change).get();
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+ ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+ ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
+ assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
+ current.getPatchSetId());
+ Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
+ assertThat(afterRebase).isNotEqualTo(beforeRebase);
+ }
+
+ @Test
+ public void rebaseEditWithConflictsRest_Conflict() throws Exception {
+ PatchSet current = getCurrentPatchSet(changeId2);
+ assertThat(modifier.createEdit(change2, current)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(
+ modifier.modifyFile(editUtil.byChange(change2).get(), FILE_NAME,
+ RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+ ChangeEdit edit = editUtil.byChange(change2).get();
+ assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
+ current.getPatchSetId());
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT, FILE_NAME,
+ new String(CONTENT_NEW2), changeId2);
+ push.to(git, "refs/for/master").assertOkStatus();
+ RestResponse r = adminSession.post(urlRebase());
+ assertThat(r.getStatusCode()).isEqualTo(SC_CONFLICT);
+ }
+
+ @Test
+ public void updateExistingFile() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
+ editUtil.delete(edit.get());
+ edit = editUtil.byChange(change);
+ assertThat(edit.isPresent()).isFalse();
+ }
+
+ @Test
+ public void updateRootCommitMessage() throws Exception {
+ createProject(sshSession, "root-msg-test", null, false);
+ git = cloneProject(sshSession.getUrl() + "/root-msg-test");
+ changeId = newChange(git, admin.getIdent());
+ change = getChange(changeId);
+
+ assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId)))
+ .isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(edit.get().getEditCommit().getParentCount()).isEqualTo(0);
+
+ String msg = String.format("New commit message\n\nChange-Id: %s",
+ change.getKey());
+ assertThat(modifier.modifyMessage(edit.get(), msg))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(msg);
+ }
+
+ @Test
+ public void updateMessage() throws Exception {
+ assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId)))
+ .isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+
+ try {
+ modifier.modifyMessage(
+ edit.get(),
+ edit.get().getEditCommit().getFullMessage());
+ fail("UnchangedCommitMessageException expected");
+ } catch (UnchangedCommitMessageException ex) {
+ assertThat(ex.getMessage()).isEqualTo(
+ "New commit message cannot be same as existing commit message");
+ }
+
+ String msg = String.format("New commit message\n\nChange-Id: %s",
+ change.getKey());
+ assertThat(modifier.modifyMessage(edit.get(), msg)).isEqualTo(
+ RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(msg);
+
+ editUtil.publish(edit.get());
+ assertThat(editUtil.byChange(change).isPresent()).isFalse();
+
+ ChangeInfo info = get(changeId, ListChangesOption.CURRENT_COMMIT,
+ ListChangesOption.CURRENT_REVISION);
+ assertThat(info.revisions.get(info.currentRevision).commit.message)
+ .isEqualTo(msg);
+ }
+
+ @Test
+ public void updateMessageRest() throws Exception {
+ assertThat(adminSession.get(urlEditMessage()).getStatusCode())
+ .isEqualTo(SC_NOT_FOUND);
+ EditMessage.Input in = new EditMessage.Input();
+ in.message = String.format("New commit message\n\n" +
+ CONTENT_NEW2_STR + "\n\nChange-Id: %s",
+ change.getKey());
+ assertThat(adminSession.put(urlEditMessage(), in).getStatusCode())
+ .isEqualTo(SC_NO_CONTENT);
+ RestResponse r = adminSession.getJsonAccept(urlEditMessage());
+ assertThat(r.getStatusCode()).isEqualTo(SC_OK);
+ assertThat(readContentFromJson(r)).isEqualTo(in.message);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(edit.get().getEditCommit().getFullMessage())
+ .isEqualTo(in.message);
+ in.message = String.format("New commit message2\n\nChange-Id: %s",
+ change.getKey());
+ assertThat(adminSession.put(urlEditMessage(), in).getStatusCode())
+ .isEqualTo(SC_NO_CONTENT);
+ edit = editUtil.byChange(change);
+ assertThat(edit.get().getEditCommit().getFullMessage())
+ .isEqualTo(in.message);
+ }
+
+ @Test
+ public void retrieveEdit() throws Exception {
+ RestResponse r = adminSession.get(urlEdit());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ EditInfo info = toEditInfo(false);
+ assertThat(info.commit.commit).isEqualTo(edit.get().getRevision().get());
+ assertThat(info.commit.parents).hasSize(1);
+
+ edit = editUtil.byChange(change);
+ editUtil.delete(edit.get());
+
+ r = adminSession.get(urlEdit());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ }
+
+ @Test
+ public void retrieveFilesInEdit() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+
+ EditInfo info = toEditInfo(true);
+ assertThat(info.files).hasSize(2);
+ List<String> l = Lists.newArrayList(info.files.keySet());
+ assertThat(l.get(0)).isEqualTo("/COMMIT_MSG");
+ assertThat(l.get(1)).isEqualTo("foo");
+ }
+
+ @Test
+ public void deleteExistingFile() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.deleteFile(edit.get(), FILE_NAME)).isEqualTo(
+ RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void renameExistingFile() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.renameFile(edit.get(), FILE_NAME, FILE_NAME3))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME3), CONTENT_OLD);
+ try {
+ fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void createEditByDeletingExistingFileRest() throws Exception {
+ RestResponse r = adminSession.delete(urlEditFile());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void deletingNonExistingEditRest() throws Exception {
+ RestResponse r = adminSession.delete(urlEdit());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NOT_FOUND);
+ }
+
+ @Test
+ public void deleteExistingFileRest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo(
+ SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void restoreDeletedFileInPatchSet() throws Exception {
+ assertThat(modifier.createEdit(change2, ps2)).isEqualTo(
+ RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change2);
+ assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo(
+ RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change2);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
+ }
+
+ @Test
+ public void renameFileRest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Post.Input in = new Post.Input();
+ in.oldPath = FILE_NAME;
+ in.newPath = FILE_NAME3;
+ assertThat(adminSession.post(urlEdit(), in).getStatusCode()).isEqualTo(
+ SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME3), CONTENT_OLD);
+ try {
+ fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ }
+
+ @Test
+ public void restoreDeletedFileInPatchSetRest() throws Exception {
+ Post.Input in = new Post.Input();
+ in.restorePath = FILE_NAME;
+ assertThat(adminSession.post(urlEdit2(), in).getStatusCode()).isEqualTo(
+ SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change2);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
+ }
+
+ @Test
+ public void amendExistingFile() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
+ }
+
+ @Test
+ public void createAndChangeEditInOneRequestRest() throws Exception {
+ Put.Input in = new Put.Input();
+ in.content = RestSession.newRawInput(CONTENT_NEW);
+ assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
+ .isEqualTo(SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
+ in.content = RestSession.newRawInput(CONTENT_NEW2);
+ assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
+ .isEqualTo(SC_NO_CONTENT);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
+ }
+
+ @Test
+ public void changeEditRest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Put.Input in = new Put.Input();
+ in.content = RestSession.newRawInput(CONTENT_NEW);
+ assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
+ .isEqualTo(SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
+ }
+
+ @Test
+ public void emptyPutRequest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(adminSession.put(urlEditFile()).getStatusCode()).isEqualTo(
+ SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), "".getBytes());
+ }
+
+ @Test
+ public void createEmptyEditRest() throws Exception {
+ assertThat(adminSession.post(urlEdit()).getStatusCode()).isEqualTo(
+ SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
+ }
+
+ @Test
+ public void getFileContentRest() throws Exception {
+ Put.Input in = new Put.Input();
+ in.content = RestSession.newRawInput(CONTENT_NEW);
+ assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
+ .isEqualTo(SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ RestResponse r = adminSession.getJsonAccept(urlEditFile());
+ assertThat(r.getStatusCode()).isEqualTo(SC_OK);
+ assertThat(readContentFromJson(r)).isEqualTo(
+ StringUtils.newStringUtf8(CONTENT_NEW2));
+ }
+
+ @Test
+ public void getFileNotFoundRest() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo(
+ SC_NO_CONTENT);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ try {
+ fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
+ fail("ResourceNotFoundException expected");
+ } catch (ResourceNotFoundException rnfe) {
+ }
+ RestResponse r = adminSession.get(urlEditFile());
+ assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+ }
+
+ @Test
+ public void addNewFile() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
+ }
+
+ @Test
+ public void addNewFileAndAmend() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
+ assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW2)))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+ ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW2);
+ }
+
+ @Test
+ public void writeNoChanges() throws Exception {
+ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
+ try {
+ modifier.modifyFile(
+ editUtil.byChange(change).get(),
+ FILE_NAME,
+ RestSession.newRawInput(CONTENT_OLD));
+ fail();
+ } catch (InvalidChangeOperationException e) {
+ assertThat(e.getMessage()).isEqualTo("no changes were made");
+ }
+ }
+
+ @Test
+ public void editCommitMessageCopiesLabelScores() throws Exception {
+ String cr = "Code-Review";
+ ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+ cfg.getLabelSections().get(cr)
+ .setCopyAllScoresIfNoCodeChange(true);
+ saveProjectConfig(allProjects, cfg);
+
+ String changeId = change.getKey().get();
+ ReviewInput r = new ReviewInput();
+ r.labels = ImmutableMap.<String, Short> of(cr, (short) 1);
+ gApi.changes()
+ .id(changeId)
+ .revision(change.currentPatchSetId().get())
+ .review(r);
+
+ assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId)))
+ .isEqualTo(RefUpdate.Result.NEW);
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ String newSubj = "New commit message";
+ String newMsg = newSubj + "\n\nChange-Id: " + changeId + "\n";
+ assertThat(modifier.modifyMessage(edit.get(), newMsg))
+ .isEqualTo(RefUpdate.Result.FORCED);
+ edit = editUtil.byChange(change);
+ editUtil.publish(edit.get());
+
+ ChangeInfo info = get(changeId);
+ assertThat(info.subject).isEqualTo(newSubj);
+ List<ApprovalInfo> approvals = info.labels.get(cr).all;
+ assertThat(approvals).hasSize(1);
+ assertThat(approvals.get(0).value).isEqualTo(1);
+ }
+
+ private String newChange(Git git, PersonIdent ident) throws Exception {
+ PushOneCommit push =
+ pushFactory.create(db, ident, PushOneCommit.SUBJECT, FILE_NAME,
+ new String(CONTENT_OLD, StandardCharsets.UTF_8));
+ return push.to(git, "refs/for/master").getChangeId();
+ }
+
+ private String amendChange(Git git, PersonIdent ident, String changeId) throws Exception {
+ PushOneCommit push =
+ pushFactory.create(db, ident, PushOneCommit.SUBJECT, FILE_NAME2,
+ new String(CONTENT_NEW2, StandardCharsets.UTF_8), changeId);
+ return push.to(git, "refs/for/master").getChangeId();
+ }
+
+ private String newChange2(Git git, PersonIdent ident) throws Exception {
+ PushOneCommit push =
+ pushFactory.create(db, ident, PushOneCommit.SUBJECT, FILE_NAME,
+ new String(CONTENT_OLD, StandardCharsets.UTF_8));
+ return push.rm(git, "refs/for/master").getChangeId();
+ }
+
+ private Change getChange(String changeId) throws Exception {
+ return getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
+ }
+
+ private PatchSet getCurrentPatchSet(String changeId) throws Exception {
+ return db.patchSets()
+ .get(getChange(changeId).currentPatchSetId());
+ }
+
+ private static void assertByteArray(BinaryResult result, byte[] expected)
+ throws Exception {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ result.writeTo(os);
+ assertThat(os.toByteArray()).isEqualTo(expected);
+ }
+
+ private String urlEdit() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/edit";
+ }
+
+ private String urlEdit2() {
+ return "/changes/"
+ + change2.getChangeId()
+ + "/edit/";
+ }
+
+ private String urlEditMessage() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/edit:message";
+ }
+
+ private String urlEditFile() {
+ return urlEdit()
+ + "/"
+ + FILE_NAME;
+ }
+
+ private String urlGetFiles() {
+ return urlEdit()
+ + "?list";
+ }
+
+ private String urlPublish() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/edit:publish";
+ }
+
+ private String urlRebase() {
+ return "/changes/"
+ + change.getChangeId()
+ + "/edit:rebase";
+ }
+
+ private EditInfo toEditInfo(boolean files) throws IOException {
+ RestResponse r = adminSession.get(files ? urlGetFiles() : urlEdit());
+ assertThat(r.getStatusCode()).isEqualTo(SC_OK);
+ return newGson().fromJson(r.getReader(), EditInfo.class);
+ }
+
+ private String readContentFromJson(RestResponse r) throws IOException {
+ JsonReader jsonReader = new JsonReader(r.getReader());
+ jsonReader.setLenient(true);
+ return newGson().fromJson(jsonReader, String.class);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 5b3795e1d4..e65d51e29c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -14,27 +14,43 @@
package com.google.gerrit.acceptance.git;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static org.junit.Assert.assertEquals;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.testutil.ConfigSuite;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
import com.jcraft.jsch.JSchException;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
+import java.util.Set;
public abstract class AbstractPushForReview extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config noteDbEnabled() {
+ return NotesMigration.allEnabledConfig();
+ }
+
+ @Inject
+ private NotesMigration notesMigration;
+
protected enum Protocol {
SSH, HTTP
}
@@ -148,15 +164,30 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
}
@Test
+ public void testPushForMasterAsEdit() throws GitAPIException,
+ IOException, RestApiException {
+ PushOneCommit.Result r = pushTo("refs/for/master");
+ r.assertOkStatus();
+ EditInfo edit = getEdit(r.getChangeId());
+ assertThat(edit).isNull();
+
+ // specify edit as option
+ r = amendChange(r.getChangeId(), "refs/for/master%edit");
+ r.assertOkStatus();
+ edit = getEdit(r.getChangeId());
+ assertThat(edit).isNotNull();
+ }
+
+ @Test
public void testPushForMasterWithApprovals() throws GitAPIException,
IOException, RestApiException {
PushOneCommit.Result r = pushTo("refs/for/master/%l=Code-Review");
r.assertOkStatus();
ChangeInfo ci = get(r.getChangeId());
LabelInfo cr = ci.labels.get("Code-Review");
- assertEquals(1, cr.all.size());
- assertEquals("Administrator", cr.all.get(0).name);
- assertEquals(1, cr.all.get(0).value.intValue());
+ assertThat(cr.all).hasSize(1);
+ assertThat(cr.all.get(0).name).isEqualTo("Administrator");
+ assertThat(cr.all.get(0).value.intValue()).is(1);
PushOneCommit push =
pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
@@ -165,9 +196,21 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
ci = get(r.getChangeId());
cr = ci.labels.get("Code-Review");
- assertEquals(1, cr.all.size());
- assertEquals("Administrator", cr.all.get(0).name);
- assertEquals(2, cr.all.get(0).value.intValue());
+ assertThat(cr.all).hasSize(1);
+ assertThat(cr.all.get(0).name).isEqualTo("Administrator");
+ assertThat(cr.all.get(0).value.intValue()).is(2);
+ }
+
+ @Test
+ public void testPushNewPatchsetToRefsChanges() throws GitAPIException,
+ IOException, OrmException {
+ PushOneCommit.Result r = pushTo("refs/for/master");
+ r.assertOkStatus();
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent", r.getChangeId());
+ r = push.to(git, "refs/changes/" + r.getChange().change().getId().get());
+ r.assertOkStatus();
}
@Test
@@ -179,22 +222,87 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void testPushForMasterWithApprovals_ValueOutOfRange() throws GitAPIException,
- IOException, RestApiException {
+ IOException {
PushOneCommit.Result r = pushTo("refs/for/master/%l=Code-Review-3");
r.assertErrorStatus("label \"Code-Review\": -3 is not a valid value");
}
@Test
public void testPushForNonExistingBranch() throws GitAPIException,
- OrmException, IOException {
+ IOException {
String branchName = "non-existing";
PushOneCommit.Result r = pushTo("refs/for/" + branchName);
r.assertErrorStatus("branch " + branchName + " not found");
}
- private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
+ @Test
+ public void testPushForMasterWithHashtags() throws GitAPIException,
+ OrmException, IOException, RestApiException {
+
+ // Hashtags currently only work when noteDB is enabled
+ assume().that(notesMigration.enabled()).isTrue();
+
+ // specify a single hashtag as option
+ String hashtag1 = "tag1";
+ Set<String> expected = ImmutableSet.of(hashtag1);
+ PushOneCommit.Result r = pushTo("refs/for/master%hashtag=#" + hashtag1);
+ r.assertOkStatus();
+ r.assertChange(Change.Status.NEW, null);
+
+ Set<String> hashtags = gApi.changes().id(r.getChangeId()).getHashtags();
+ assertThat((Iterable<?>)hashtags).containsExactlyElementsIn(expected);
+
+ // specify a single hashtag as option in new patch set
+ String hashtag2 = "tag2";
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent", r.getChangeId());
+ r = push.to(git, "refs/for/master/%hashtag=" + hashtag2);
+ r.assertOkStatus();
+ expected = ImmutableSet.of(hashtag1, hashtag2);
+ hashtags = gApi.changes().id(r.getChangeId()).getHashtags();
+ assertThat((Iterable<?>)hashtags).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testPushForMasterWithMultipleHashtags() throws GitAPIException,
+ OrmException, IOException, RestApiException {
+
+ // Hashtags currently only work when noteDB is enabled
+ assume().that(notesMigration.enabled()).isTrue();
+
+ // specify multiple hashtags as options
+ String hashtag1 = "tag1";
+ String hashtag2 = "tag2";
+ Set<String> expected = ImmutableSet.of(hashtag1, hashtag2);
+ PushOneCommit.Result r = pushTo("refs/for/master%hashtag=#" + hashtag1
+ + ",hashtag=##" + hashtag2);
+ r.assertOkStatus();
+ r.assertChange(Change.Status.NEW, null);
+
+ Set<String> hashtags = gApi.changes().id(r.getChangeId()).getHashtags();
+ assertThat((Iterable<?>)hashtags).containsExactlyElementsIn(expected);
+
+ // specify multiple hashtags as options in new patch set
+ String hashtag3 = "tag3";
+ String hashtag4 = "tag4";
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent", r.getChangeId());
+ r = push.to(git,
+ "refs/for/master%hashtag=" + hashtag3 + ",hashtag=" + hashtag4);
+ r.assertOkStatus();
+ expected = ImmutableSet.of(hashtag1, hashtag2, hashtag3, hashtag4);
+ hashtags = gApi.changes().id(r.getChangeId()).getHashtags();
+ assertThat((Iterable<?>)hashtags).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testPushForMasterWithHashtagsNoteDbDisabled() throws GitAPIException,
IOException {
- PushOneCommit push = pushFactory.create(db, admin.getIdent());
- return push.to(git, ref);
+ // push with hashtags should fail when noteDb is disabled
+ assume().that(notesMigration.enabled()).isFalse();
+ PushOneCommit.Result r = pushTo("refs/for/master%hashtag=tag1");
+ r.assertErrorStatus("cannot add hashtags; noteDb is disabled");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
index 3ead2a1967..f36c4479f0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
@@ -1,7 +1,12 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
- srcs = ['DraftChangeBlockedIT.java', 'SubmitOnPushIT.java'],
+ srcs = [
+ 'DraftChangeBlockedIT.java',
+ 'ForcePushIT.java',
+ 'SubmitOnPushIT.java',
+ 'VisibleRefFilterIT.java',
+ ],
labels = ['git'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
index 20c8f76e73..20a698d21d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
@@ -21,14 +21,9 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Before;
import org.junit.Test;
@@ -37,15 +32,6 @@ import java.io.IOException;
@NoHttpd
public class DraftChangeBlockedIT extends AbstractDaemonTest {
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
@Before
public void setUp() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
@@ -55,27 +41,19 @@ public class DraftChangeBlockedIT extends AbstractDaemonTest {
}
@Test
- public void testPushDraftChange_Blocked() throws GitAPIException,
- OrmException, IOException {
+ public void testPushDraftChange_Blocked() throws Exception {
// create draft by pushing to 'refs/drafts/'
PushOneCommit.Result r = pushTo("refs/drafts/master");
r.assertErrorStatus("cannot upload drafts");
}
@Test
- public void testPushDraftChangeMagic_Blocked() throws GitAPIException,
- OrmException, IOException {
+ public void testPushDraftChangeMagic_Blocked() throws Exception {
// create draft by using 'draft' option
PushOneCommit.Result r = pushTo("refs/for/master%draft");
r.assertErrorStatus("cannot upload drafts");
}
- private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
- IOException {
- PushOneCommit push = pushFactory.create(db, admin.getIdent());
- return push.to(git, ref);
- }
-
private void saveProjectConfig(ProjectConfig cfg) throws IOException {
MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
try {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/ForcePushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/ForcePushIT.java
new file mode 100644
index 0000000000..5da524e160
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/ForcePushIT.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2015 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 org.eclipse.jgit.lib.Constants.HEAD;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.Permission;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.junit.Test;
+
+public class ForcePushIT extends AbstractDaemonTest {
+
+ @Test
+ public void forcePushNotAllowed() throws Exception {
+ ObjectId initial = git.getRepository().getRef(HEAD).getLeaf().getObjectId();
+ PushOneCommit push1 =
+ pushFactory.create(db, admin.getIdent(), "change1", "a.txt", "content");
+ PushOneCommit.Result r1 = push1.to(git, "refs/heads/master");
+ r1.assertOkStatus();
+
+ // Reset HEAD to initial so the new change is a non-fast forward
+ RefUpdate ru = git.getRepository().updateRef(HEAD);
+ ru.setNewObjectId(initial);
+ assertThat(ru.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED);
+
+ PushOneCommit push2 =
+ pushFactory.create(db, admin.getIdent(), "change2", "b.txt", "content");
+ push2.setForce(true);
+ PushOneCommit.Result r2 = push2.to(git, "refs/heads/master");
+ r2.assertErrorStatus("non-fast forward");
+ }
+
+ @Test
+ public void forcePushAllowed() throws Exception {
+ ObjectId initial = git.getRepository().getRef(HEAD).getLeaf().getObjectId();
+ grant(Permission.PUSH, project, "refs/*", true);
+ PushOneCommit push1 =
+ pushFactory.create(db, admin.getIdent(), "change1", "a.txt", "content");
+ PushOneCommit.Result r1 = push1.to(git, "refs/heads/master");
+ r1.assertOkStatus();
+
+ // Reset HEAD to initial so the new change is a non-fast forward
+ RefUpdate ru = git.getRepository().updateRef(HEAD);
+ ru.setNewObjectId(initial);
+ assertThat(ru.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED);
+
+ PushOneCommit push2 =
+ pushFactory.create(db, admin.getIdent(), "change2", "b.txt", "content");
+ push2.setForce(true);
+ PushOneCommit.Result r2 = push2.to(git, "refs/heads/master");
+ r2.assertOkStatus();
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
index 71f008a079..9f884c883f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
@@ -14,17 +14,13 @@
package com.google.gerrit.acceptance.git;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.junit.Before;
-import java.io.IOException;
-import java.net.URISyntaxException;
-
public class HttpPushForReviewIT extends AbstractPushForReview {
@Before
- public void selectHttpUrl() throws GitAPIException, IOException, URISyntaxException {
+ public void selectHttpUrl() throws Exception {
CredentialsProvider.setDefault(new UsernamePasswordCredentialsProvider(
admin.username, admin.httpPassword));
selectProtocol(Protocol.HTTP);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
index c037e76d66..c7da993e0f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
@@ -16,15 +16,12 @@ package com.google.gerrit.acceptance.git;
import com.google.gerrit.acceptance.NoHttpd;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Before;
-import java.io.IOException;
-
@NoHttpd
public class SshPushForReviewIT extends AbstractPushForReview {
@Before
- public void selectSshUrl() throws GitAPIException, IOException {
+ public void selectSshUrl() throws Exception {
selectProtocol(Protocol.SSH);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index 763b25fe8e..cf0945e3c0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -14,122 +14,82 @@
package com.google.gerrit.acceptance.git;
-import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.GitUtil.createProject;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
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.SshSession;
-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.reviewdb.client.AccountGroup;
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.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.git.CommitMergeStatus;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
-import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+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.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@NoHttpd
public class SubmitOnPushIT extends AbstractDaemonTest {
-
- @Inject
- private SchemaFactory<ReviewDb> reviewDbProvider;
-
- @Inject
- private GitRepositoryManager repoManager;
-
@Inject
private ApprovalsUtil approvalsUtil;
@Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private GroupCache groupCache;
-
- @Inject
private ChangeNotes.Factory changeNotesFactory;
@Inject
private @GerritPersonIdent PersonIdent serverIdent;
- @Inject
- private PushOneCommit.Factory pushFactory;
-
- private Project.NameKey project;
- private Git git;
- private ReviewDb db;
-
- @Before
- public void setUp() throws Exception {
- project = new Project.NameKey("p");
- SshSession sshSession = new SshSession(server, admin);
- createProject(sshSession, project.get());
- git = cloneProject(sshSession.getUrl() + "/" + project.get());
- sshSession.close();
-
- db = reviewDbProvider.open();
- }
-
- @After
- public void cleanup() {
- db.close();
+ @Test
+ public void submitOnPush() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ PushOneCommit.Result r = pushTo("refs/for/master%submit");
+ r.assertOkStatus();
+ r.assertChange(Change.Status.MERGED, null, admin);
+ assertSubmitApproval(r.getPatchSetId());
+ assertCommit(project, "refs/heads/master");
}
@Test
- public void submitOnPush() throws GitAPIException, OrmException,
- IOException, ConfigInvalidException {
+ public void submitOnPushWithTag() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
- PushOneCommit.Result r = pushTo("refs/for/master%submit");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+ PushOneCommit.Tag tag = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push = pushFactory.create(db, admin.getIdent());
+ push.setTag(tag);
+ PushOneCommit.Result r = push.to(git, "refs/for/master%submit");
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
assertSubmitApproval(r.getPatchSetId());
assertCommit(project, "refs/heads/master");
+ assertTag(project, "refs/heads/master", tag);
}
@Test
- public void submitOnPushWithTag() throws GitAPIException, OrmException,
- IOException, ConfigInvalidException {
+ public void submitOnPushWithAnnotatedTag() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
grant(Permission.CREATE, project, "refs/tags/*");
- final String tag = "v1.0";
+ grant(Permission.PUSH, project, "refs/tags/*");
+ PushOneCommit.AnnotatedTag tag =
+ new PushOneCommit.AnnotatedTag("v1.0", "annotation", admin.getIdent());
PushOneCommit push = pushFactory.create(db, admin.getIdent());
push.setTag(tag);
PushOneCommit.Result r = push.to(git, "refs/for/master%submit");
@@ -141,8 +101,7 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
}
@Test
- public void submitOnPushToRefsMetaConfig() throws GitAPIException,
- OrmException, IOException, ConfigInvalidException {
+ public void submitOnPushToRefsMetaConfig() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/meta/config");
git.fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config")).call();
@@ -157,8 +116,7 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
}
@Test
- public void submitOnPushMergeConflict() throws GitAPIException, OrmException,
- IOException, ConfigInvalidException {
+ public void submitOnPushMergeConflict() throws Exception {
String master = "refs/heads/master";
ObjectId objectId = git.getRepository().getRef(master).getObjectId();
push(master, "one change", "a.txt", "some content");
@@ -173,8 +131,7 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
}
@Test
- public void submitOnPushSuccessfulMerge() throws GitAPIException, OrmException,
- IOException, ConfigInvalidException {
+ public void submitOnPushSuccessfulMerge() throws Exception {
String master = "refs/heads/master";
ObjectId objectId = git.getRepository().getRef(master).getObjectId();
push(master, "one change", "a.txt", "some content");
@@ -189,8 +146,7 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
}
@Test
- public void submitOnPushNewPatchSet() throws GitAPIException,
- OrmException, IOException, ConfigInvalidException {
+ public void submitOnPushNewPatchSet() throws Exception {
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
@@ -199,23 +155,21 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
"other content", r.getChangeId());
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
- Change c = Iterables.getOnlyElement(db.changes().byKey(
- new Change.Key(r.getChangeId())).toList());
- assertEquals(2, db.patchSets().byChange(c.getId()).toList().size());
+ Change c = Iterables.getOnlyElement(
+ queryProvider.get().byKeyPrefix(r.getChangeId())).change();
+ assertThat(db.patchSets().byChange(c.getId()).toList()).hasSize(2);
assertSubmitApproval(r.getPatchSetId());
assertCommit(project, "refs/heads/master");
}
@Test
- public void submitOnPushNotAllowed_Error() throws GitAPIException,
- OrmException, IOException {
+ public void submitOnPushNotAllowed_Error() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master%submit");
r.assertErrorStatus("submit not allowed");
}
@Test
- public void submitOnPushNewPatchSetNotAllowed_Error() throws GitAPIException,
- OrmException, IOException, ConfigInvalidException {
+ public void submitOnPushNewPatchSetNotAllowed_Error() throws Exception {
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
@@ -225,15 +179,13 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
}
@Test
- public void submitOnPushingDraft_Error() throws GitAPIException,
- OrmException, IOException {
+ public void submitOnPushingDraft_Error() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master%draft,submit");
r.assertErrorStatus("cannot submit draft");
}
@Test
- public void submitOnPushToNonExistingBranch_Error() throws GitAPIException,
- OrmException, IOException {
+ public void submitOnPushToNonExistingBranch_Error() throws Exception {
String branchName = "non-existing";
PushOneCommit.Result r = pushTo("refs/for/" + branchName + "%submit");
r.assertErrorStatus("branch " + branchName + " not found");
@@ -250,22 +202,28 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
.setRefSpecs(new RefSpec(r.getCommitId().name() + ":refs/heads/master"))
.call();
assertCommit(project, "refs/heads/master");
- assertNull(getSubmitter(r.getPatchSetId()));
+ assertThat(getSubmitter(r.getPatchSetId())).isNull();
Change c = db.changes().get(r.getPatchSetId().getParentKey());
- assertEquals(Change.Status.MERGED, c.getStatus());
+ assertThat(c.getStatus()).isEqualTo(Change.Status.MERGED);
}
- private void grant(String permission, Project.NameKey project, String ref)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
- MetaDataUpdate md = metaDataUpdateFactory.create(project);
- md.setMessage(String.format("Grant %s on %s", permission, ref));
- ProjectConfig config = ProjectConfig.read(md);
- AccessSection s = config.getAccessSection(ref, true);
- Permission p = s.getPermission(permission, true);
- AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
- p.add(new PermissionRule(config.resolve(adminGroup)));
- config.commit(md);
- projectCache.evict(config.getProject());
+ @Test
+ public void mergeOnPushToBranchWithNewPatchset() throws Exception {
+ grant(Permission.PUSH, project, "refs/heads/master");
+ PushOneCommit.Result r = pushTo("refs/for/master");
+ r.assertOkStatus();
+
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent", r.getChangeId());
+
+ r = push.to(git, "refs/heads/master");
+ r.assertOkStatus();
+
+ assertCommit(project, "refs/heads/master");
+ assertThat(getSubmitter(r.getPatchSetId())).isNull();
+ Change c = db.changes().get(r.getPatchSetId().getParentKey());
+ assertThat(c.getStatus()).isEqualTo(Change.Status.MERGED);
}
private PatchSetApproval getSubmitter(PatchSet.Id patchSetId)
@@ -277,64 +235,60 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
private void assertSubmitApproval(PatchSet.Id patchSetId) throws OrmException {
PatchSetApproval a = getSubmitter(patchSetId);
- assertTrue(a.isSubmit());
- assertEquals(1, a.getValue());
- assertEquals(admin.id, a.getAccountId());
+ assertThat(a.isSubmit()).isTrue();
+ assertThat(a.getValue()).isEqualTo((short) 1);
+ assertThat(a.getAccountId()).isEqualTo(admin.id);
}
private void assertCommit(Project.NameKey project, String branch) throws IOException {
- Repository r = repoManager.openRepository(project);
- try {
- RevWalk rw = new RevWalk(r);
- try {
- RevCommit c = rw.parseCommit(r.getRef(branch).getObjectId());
- assertEquals(PushOneCommit.SUBJECT, c.getShortMessage());
- assertEquals(admin.email, c.getAuthorIdent().getEmailAddress());
- assertEquals(admin.email, c.getCommitterIdent().getEmailAddress());
- } finally {
- rw.close();
- }
- } finally {
- r.close();
+ try (Repository r = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(r)) {
+ RevCommit c = rw.parseCommit(r.getRef(branch).getObjectId());
+ assertThat(c.getShortMessage()).isEqualTo(PushOneCommit.SUBJECT);
+ assertThat(c.getAuthorIdent().getEmailAddress()).isEqualTo(admin.email);
+ assertThat(c.getCommitterIdent().getEmailAddress()).isEqualTo(
+ admin.email);
}
}
private void assertMergeCommit(String branch, String subject) throws IOException {
- Repository r = repoManager.openRepository(project);
- try {
- RevWalk rw = new RevWalk(r);
- try {
- RevCommit c = rw.parseCommit(r.getRef(branch).getObjectId());
- assertEquals(2, c.getParentCount());
- assertEquals("Merge \"" + subject + "\"", c.getShortMessage());
- assertEquals(admin.email, c.getAuthorIdent().getEmailAddress());
- assertEquals(serverIdent.getEmailAddress(), c.getCommitterIdent().getEmailAddress());
- } finally {
- rw.close();
- }
- } finally {
- r.close();
+ try (Repository r = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(r)) {
+ RevCommit c = rw.parseCommit(r.getRef(branch).getObjectId());
+ assertThat(c.getParentCount()).is(2);
+ assertThat(c.getShortMessage()).isEqualTo("Merge \"" + subject + "\"");
+ assertThat(c.getAuthorIdent().getEmailAddress()).isEqualTo(admin.email);
+ assertThat(c.getCommitterIdent().getEmailAddress()).isEqualTo(
+ serverIdent.getEmailAddress());
}
}
- private void assertTag(Project.NameKey project, String branch, String tagName)
- throws IOException {
- Repository r = repoManager.openRepository(project);
- try {
- ObjectId headCommit = r.getRef(branch).getObjectId();
- ObjectId taggedCommit = r.getRef(tagName).getObjectId();
- assertEquals(headCommit, taggedCommit);
- } finally {
- r.close();
+ private void assertTag(Project.NameKey project, String branch,
+ PushOneCommit.Tag tag) throws IOException {
+ try (Repository repo = repoManager.openRepository(project)) {
+ Ref tagRef = repo.getRef(tag.name);
+ assertThat(tagRef).isNotNull();
+ ObjectId taggedCommit = null;
+ if (tag instanceof PushOneCommit.AnnotatedTag) {
+ PushOneCommit.AnnotatedTag annotatedTag = (PushOneCommit.AnnotatedTag)tag;
+ try (RevWalk rw = new RevWalk(repo)) {
+ RevObject object = rw.parseAny(tagRef.getObjectId());
+ assertThat(object).isInstanceOf(RevTag.class);
+ RevTag tagObject = (RevTag) object;
+ assertThat(tagObject.getFullMessage())
+ .isEqualTo(annotatedTag.message);
+ assertThat(tagObject.getTaggerIdent()).isEqualTo(annotatedTag.tagger);
+ taggedCommit = tagObject.getObject();
+ }
+ } else {
+ taggedCommit = tagRef.getObjectId();
+ }
+ ObjectId headCommit = repo.getRef(branch).getObjectId();
+ assertThat(taggedCommit).isNotNull();
+ assertThat(taggedCommit).isEqualTo(headCommit);
}
}
- private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
- IOException {
- PushOneCommit push = pushFactory.create(db, admin.getIdent());
- return push.to(git, ref);
- }
-
private PushOneCommit.Result push(String ref, String subject,
String fileName, String content) throws GitAPIException, IOException {
PushOneCommit push =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
new file mode 100644
index 0000000000..4a66a4169d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -0,0 +1,265 @@
+// Copyright (C) 2014 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.common.truth.Truth.assert_;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+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.extensions.api.projects.BranchInput;
+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.RefNames;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.project.Util;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(ConfigSuite.class)
+@NoHttpd
+public class VisibleRefFilterIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config noteDbWriteEnabled() {
+ Config cfg = new Config();
+ cfg.setBoolean("notedb", "changes", "write", true);
+ return cfg;
+ }
+
+ @Inject
+ private NotesMigration notesMigration;
+
+ @Inject
+ private ChangeEditModifier editModifier;
+
+ private AccountGroup.UUID admins;
+
+ @Before
+ public void setUp() throws Exception {
+ admins = groupCache.get(new AccountGroup.NameKey("Administrators"))
+ .getGroupUUID();
+ setUpChanges();
+ setUpPermissions();
+ }
+
+ private void setUpPermissions() throws Exception {
+ ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
+ for (AccessSection sec : pc.getAccessSections()) {
+ sec.removePermission(Permission.READ);
+ }
+ saveProjectConfig(allProjects, pc);
+ }
+
+ private void setUpChanges() throws Exception {
+ gApi.projects()
+ .name(project.get())
+ .branch("branch")
+ .create(new BranchInput());
+
+ allow(Permission.SUBMIT, admins, "refs/for/refs/heads/*");
+ PushOneCommit.Result mr = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master%submit");
+ mr.assertOkStatus();
+ PushOneCommit.Result br = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/branch%submit");
+ br.assertOkStatus();
+
+ Repository repo = repoManager.openRepository(project);
+ try {
+ // master-tag -> master
+ RefUpdate mtu = repo.updateRef("refs/tags/master-tag");
+ mtu.setExpectedOldObjectId(ObjectId.zeroId());
+ mtu.setNewObjectId(repo.getRef("refs/heads/master").getObjectId());
+ assertThat(mtu.update()).isEqualTo(RefUpdate.Result.NEW);
+
+ // branch-tag -> branch
+ RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(repo.getRef("refs/heads/branch").getObjectId());
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+ } finally {
+ repo.close();
+ }
+ }
+
+ @Test
+ public void allRefsVisibleNoRefsMetaConfig() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg, Permission.READ, REGISTERED_USERS, "refs/*");
+ Util.allow(cfg, Permission.READ, admins, "refs/meta/config");
+ Util.doNotInherit(cfg, Permission.READ, "refs/meta/config");
+ saveProjectConfig(project, cfg);
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/heads/master",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void allRefsVisibleWithRefsMetaConfig() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/*");
+ allow(Permission.READ, REGISTERED_USERS, "refs/meta/config");
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/heads/master",
+ "refs/meta/config",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void subsetOfBranchesVisibleIncludingHead() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/heads/master",
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void subsetOfBranchesVisibleNotIncludingHead() throws Exception {
+ deny(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
+
+ assertRefs(
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // master branch is not visible but master-tag is reachable from branch
+ // (since PushOneCommit always bases changes on each other).
+ "refs/tags/master-tag");
+ }
+
+ @Test
+ public void subsetOfBranchesVisibleWithEdit() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
+
+ Change c1 = db.changes().get(new Change.Id(1));
+ PatchSet ps1 = db.patchSets().get(new PatchSet.Id(c1.getId(), 1));
+
+ // Admin's edit is not visible.
+ setApiUser(admin);
+ editModifier.createEdit(c1, ps1);
+
+ // User's edit is visible.
+ setApiUser(user);
+ editModifier.createEdit(c1, ps1);
+
+ assertRefs(
+ "HEAD",
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/heads/master",
+ "refs/tags/master-tag",
+ "refs/users/01/1000001/edit-1/1");
+ }
+
+ @Test
+ public void subsetOfRefsVisibleWithAccessDatabase() throws Exception {
+ deny(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
+ allowGlobalCapability(GlobalCapability.ACCESS_DATABASE, REGISTERED_USERS);
+
+ Change c1 = db.changes().get(new Change.Id(1));
+ PatchSet ps1 = db.patchSets().get(new PatchSet.Id(c1.getId(), 1));
+ setApiUser(admin);
+ editModifier.createEdit(c1, ps1);
+ setApiUser(user);
+ editModifier.createEdit(c1, ps1);
+
+ assertRefs(
+ // Change 1 is visible due to accessDatabase capability, even though
+ // refs/heads/master is not.
+ "refs/changes/01/1/1",
+ "refs/changes/01/1/meta",
+ "refs/changes/02/2/1",
+ "refs/changes/02/2/meta",
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag",
+ // All edits are visible due to accessDatabase capability.
+ "refs/users/00/1000000/edit-1/1",
+ "refs/users/01/1000001/edit-1/1");
+ }
+
+ /**
+ * Assert that refs seen by a non-admin user match expected.
+ *
+ * @param expected expected refs, in order. If notedb is disabled by the
+ * configuration, any notedb refs (i.e. ending in "/meta") are removed
+ * from the expected list before comparing to the actual results.
+ * @throws Exception
+ */
+ private void assertRefs(String... expected) throws Exception {
+ String out = sshSession.exec(String.format(
+ "gerrit ls-user-refs -p %s -u %s",
+ project.get(), user.getId().get()));
+ assert_().withFailureMessage(sshSession.getError())
+ .that(sshSession.hasError()).isFalse();
+
+ List<String> filtered = new ArrayList<>(expected.length);
+ for (String r : expected) {
+ if (notesMigration.writeChanges() || !r.endsWith(RefNames.META_SUFFIX)) {
+ filtered.add(r);
+ }
+ }
+
+ Splitter s = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings();
+ assertThat(filtered).containsSequence(
+ Ordering.natural().sortedCopy(s.split(out)));
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java
new file mode 100644
index 0000000000..c538b85d82
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2014 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.pgm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.io.Files;
+import com.google.gerrit.launcher.GerritLauncher;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.testutil.TempFileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+public class RebuildNotedbIT {
+ private File sitePath;
+
+ @Before
+ public void createTempDirectory() throws Exception {
+ sitePath = TempFileUtil.createTempDirectory();
+ }
+
+ @After
+ public void destroySite() throws Exception {
+ if (sitePath != null) {
+ TempFileUtil.cleanup();
+ }
+ }
+
+ @Test
+ public void rebuildEmptySite() throws Exception {
+ initSite();
+ Files.append(NotesMigration.allEnabledConfig().toText(),
+ new File(sitePath.toString(), "etc/gerrit.config"),
+ StandardCharsets.UTF_8);
+ runGerrit("RebuildNotedb", "-d", sitePath.toString(),
+ "--show-stack-trace");
+ }
+
+ private void initSite() throws Exception {
+ runGerrit("init", "-d", sitePath.getPath(),
+ "--batch", "--no-auto-start", "--skip-plugins", "--show-stack-trace");
+ }
+
+ private static void runGerrit(String... args) throws Exception {
+ assertThat(GerritLauncher.mainImpl(args)).isEqualTo(0);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
index 4f9ef14ab2..968456b9d0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
@@ -14,10 +14,10 @@
package com.google.gerrit.acceptance.pgm;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.acceptance.TempFileUtil;
import com.google.gerrit.launcher.GerritLauncher;
+import com.google.gerrit.testutil.TempFileUtil;
import org.junit.After;
import org.junit.Before;
@@ -43,24 +43,16 @@ public class ReindexIT {
@Test
public void reindexEmptySite() throws Exception {
initSite();
- runGerrit("reindex", "-d", sitePath.getPath(),
+ runGerrit("reindex", "-d", sitePath.toString(),
"--show-stack-trace");
}
- @Test
- public void reindexEmptySiteWithRecheckMergeable() throws Exception {
- initSite();
- runGerrit("reindex", "-d", sitePath.getPath(),
- "--show-stack-trace",
- "--recheck-mergeable");
- }
-
private void initSite() throws Exception {
runGerrit("init", "-d", sitePath.getPath(),
"--batch", "--no-auto-start", "--skip-plugins", "--show-stack-trace");
}
private static void runGerrit(String... args) throws Exception {
- assertEquals(0, GerritLauncher.mainImpl(args));
+ assertThat(GerritLauncher.mainImpl(args)).isEqualTo(0);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java
index 2c41f9aa14..c90924d61b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java
@@ -14,17 +14,16 @@
package com.google.gerrit.acceptance.rest.account;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.extensions.common.AccountInfo;
public class AccountAssert {
public static void assertAccountInfo(TestAccount a, AccountInfo ai) {
- assertTrue(a.id.get() == ai._accountId);
- assertEquals(a.fullName, ai.name);
- assertEquals(a.email, ai.email);
+ assertThat(a.id.get()).isEqualTo(ai._accountId);
+ assertThat(a.fullName).isEqualTo(ai.name);
+ assertThat(a.email).isEqualTo(ai.email);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
index 9d22ee49b2..6ce96eedc8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
@@ -14,26 +14,31 @@
package com.google.gerrit.acceptance.rest.account;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+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;
+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.PRIORITY;
+import static com.google.gerrit.common.data.GlobalCapability.QUERY_LIMIT;
+import static com.google.gerrit.common.data.GlobalCapability.RUN_AS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
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.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
-import com.google.inject.Inject;
+import org.apache.http.HttpStatus;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Test;
@@ -41,71 +46,66 @@ import java.io.IOException;
public class CapabilitiesIT extends AbstractDaemonTest {
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
@Test
- public void testCapabilitiesUser() throws IOException,
- ConfigInvalidException, IllegalArgumentException,
- IllegalAccessException, NoSuchFieldException,
- SecurityException {
+ public void testCapabilitiesUser() throws Exception {
grantAllCapabilities();
RestResponse r =
userSession.get("/accounts/self/capabilities");
- int code = r.getStatusCode();
- assertEquals(code, 200);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
CapabilityInfo info = (new Gson()).fromJson(r.getReader(),
new TypeToken<CapabilityInfo>() {}.getType());
- for (String c: GlobalCapability.getAllNames()) {
- if (GlobalCapability.ADMINISTRATE_SERVER.equals(c)) {
- assertFalse(info.administrateServer);
- } else if (GlobalCapability.PRIORITY.equals(c)) {
- assertFalse(info.priority);
- } else if (GlobalCapability.QUERY_LIMIT.equals(c)) {
- assertEquals(0, info.queryLimit.min);
- assertEquals(0, info.queryLimit.max);
+ for (String c : GlobalCapability.getAllNames()) {
+ if (ADMINISTRATE_SERVER.equals(c)) {
+ assertThat(info.administrateServer).isFalse();
+ } else if (BATCH_CHANGES_LIMIT.equals(c)) {
+ assertThat(info.batchChangesLimit.min).isEqualTo((short) 0);
+ assertThat(info.batchChangesLimit.max).isEqualTo((short) DEFAULT_MAX_BATCH_CHANGES_LIMIT);
+ } else if (PRIORITY.equals(c)) {
+ assertThat(info.priority).isFalse();
+ } else if (QUERY_LIMIT.equals(c)) {
+ assertThat(info.queryLimit.min).isEqualTo((short) 0);
+ assertThat(info.queryLimit.max).isEqualTo((short) DEFAULT_MAX_QUERY_LIMIT);
} else {
- assertTrue(String.format("capability %s was not granted", c),
- (Boolean)CapabilityInfo.class.getField(c).get(info));
+ assert_().withFailureMessage(String.format("capability %s was not granted", c))
+ .that((Boolean) CapabilityInfo.class.getField(c).get(info)).isTrue();
}
}
}
@Test
- public void testCapabilitiesAdmin() throws IOException,
- ConfigInvalidException, IllegalArgumentException,
- IllegalAccessException, NoSuchFieldException,
- SecurityException {
+ public void testCapabilitiesAdmin() throws Exception {
RestResponse r =
adminSession.get("/accounts/self/capabilities");
- int code = r.getStatusCode();
- assertEquals(code, 200);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
CapabilityInfo info = (new Gson()).fromJson(r.getReader(),
new TypeToken<CapabilityInfo>() {}.getType());
- for (String c: GlobalCapability.getAllNames()) {
- if (GlobalCapability.PRIORITY.equals(c)) {
- assertFalse(info.priority);
- } else if (GlobalCapability.QUERY_LIMIT.equals(c)) {
- assertNotNull("missing queryLimit", info.queryLimit);
- assertEquals(0, info.queryLimit.min);
- assertEquals(500, info.queryLimit.max);
- } else if (GlobalCapability.ACCESS_DATABASE.equals(c)) {
- assertFalse(info.accessDatabase);
- } else if (GlobalCapability.RUN_AS.equals(c)) {
- assertFalse(info.runAs);
+ for (String c : GlobalCapability.getAllNames()) {
+ if (BATCH_CHANGES_LIMIT.equals(c)) {
+ // It does not have default value for any user as it can override the
+ // 'receive.batchChangesLimit'. It needs to be granted explicitly.
+ assertThat(info.batchChangesLimit).isNull();
+ } else if (PRIORITY.equals(c)) {
+ assertThat(info.priority).isFalse();
+ } else if (QUERY_LIMIT.equals(c)) {
+ assert_().withFailureMessage("missing queryLimit")
+ .that(info.queryLimit).isNotNull();
+ assertThat(info.queryLimit.min).isEqualTo((short) 0);
+ assertThat(info.queryLimit.max).isEqualTo((short) DEFAULT_MAX_QUERY_LIMIT);
+ } else if (ACCESS_DATABASE.equals(c)) {
+ assertThat(info.accessDatabase).isFalse();
+ } else if (RUN_AS.equals(c)) {
+ assertThat(info.runAs).isFalse();
} else {
- assertTrue(String.format("capability %s was not granted", c),
- (Boolean)CapabilityInfo.class.getField(c).get(info));
+ assert_().withFailureMessage(String.format("capability %s was not granted", c))
+ .that((Boolean) CapabilityInfo.class.getField(c).get(info)).isTrue();
}
}
}
+ /**
+ * Grant all global capabilities except ADMINISTRATE_SERVER and PRIORITY.
+ * Set the default ranges for range permissions.
+ */
private void grantAllCapabilities() throws IOException,
ConfigInvalidException {
MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
@@ -113,14 +113,21 @@ public class CapabilitiesIT extends AbstractDaemonTest {
ProjectConfig config = ProjectConfig.read(md);
AccessSection s = config.getAccessSection(
AccessSection.GLOBAL_CAPABILITIES);
- for (String c: GlobalCapability.getAllNames()) {
- if (GlobalCapability.ADMINISTRATE_SERVER.equals(c)) {
+ for (String c : GlobalCapability.getAllNames()) {
+ if (ADMINISTRATE_SERVER.equals(c) || PRIORITY.equals(c)) {
continue;
}
Permission p = s.getPermission(c, true);
- p.add(new PermissionRule(
+ PermissionRule rule = new PermissionRule(
config.resolve(SystemGroupBackend.getGroup(
- SystemGroupBackend.REGISTERED_USERS))));
+ SystemGroupBackend.REGISTERED_USERS)));
+ if (GlobalCapability.hasRange(c)) {
+ PermissionRange.WithDefaults range = GlobalCapability.getRange(c);
+ if (range != null) {
+ rule.setRange(range.getDefaultMin(), range.getDefaultMax());
+ }
+ }
+ p.add(rule);
}
config.commit(md);
projectCache.evict(config.getProject());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
index adbf10aced..e5373731b7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
@@ -17,13 +17,14 @@ package com.google.gerrit.acceptance.rest.account;
class CapabilityInfo {
public boolean accessDatabase;
public boolean administrateServer;
+ public BatchChangesLimit batchChangesLimit;
public boolean createAccount;
public boolean createGroup;
public boolean createProject;
public boolean emailReviewers;
public boolean flushCaches;
- public boolean generateHttpPassword;
public boolean killTask;
+ public boolean modifyAccount;
public boolean priority;
public QueryLimit queryLimit;
public boolean runAs;
@@ -39,4 +40,9 @@ class CapabilityInfo {
short min;
short max;
}
+
+ static class BatchChangesLimit {
+ short min;
+ short max;
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
index 1ac9cdf82e..63c6493855 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
@@ -14,14 +14,14 @@
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 org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.server.account.AccountInfo;
import org.apache.http.HttpStatus;
import org.junit.Test;
@@ -30,13 +30,13 @@ import java.io.IOException;
public class GetAccountIT extends AbstractDaemonTest {
@Test
- public void getNonExistingAccount_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- adminSession.get("/accounts/non-existing").getStatusCode());
+ public void getNonExistingAccount_NotFound() throws Exception {
+ assertThat(adminSession.get("/accounts/non-existing").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void getAccount() throws IOException {
+ public void getAccount() throws Exception {
// by formatted string
testGetAccount("/accounts/"
+ Url.encode(admin.fullName + " <" + admin.email + ">"), admin);
@@ -60,7 +60,7 @@ public class GetAccountIT extends AbstractDaemonTest {
private void testGetAccount(String url, TestAccount expectedAccount)
throws IOException {
RestResponse r = adminSession.get(url);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
assertAccountInfo(expectedAccount, newGson()
.fromJson(r.getReader(), AccountInfo.class));
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java
index 3850513a4a..02a94f8d72 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java
@@ -14,51 +14,48 @@
package com.google.gerrit.acceptance.rest.account;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.server.account.GetDiffPreferences.DiffPreferencesInfo;
-import com.google.gwtorm.server.OrmException;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
-
public class GetDiffPreferencesIT extends AbstractDaemonTest {
@Test
public void getDiffPreferencesOfNonExistingAccount_NotFound()
- throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- adminSession.get("/accounts/non-existing/preferences.diff").getStatusCode());
+ throws Exception {
+ assertThat(adminSession.get("/accounts/non-existing/preferences.diff").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void getDiffPreferences() throws IOException, OrmException {
+ public void getDiffPreferences() throws Exception {
RestResponse r = adminSession.get("/accounts/" + admin.email + "/preferences.diff");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
DiffPreferencesInfo diffPreferences =
newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
assertDiffPreferences(new AccountDiffPreference(admin.id), diffPreferences);
}
private static void assertDiffPreferences(AccountDiffPreference expected, DiffPreferencesInfo actual) {
- assertEquals(expected.getContext(), actual.context);
- assertEquals(expected.isExpandAllComments(), toBoolean(actual.expandAllComments));
- assertEquals(expected.getIgnoreWhitespace(), actual.ignoreWhitespace);
- assertEquals(expected.isIntralineDifference(), toBoolean(actual.intralineDifference));
- assertEquals(expected.getLineLength(), actual.lineLength);
- assertEquals(expected.isManualReview(), toBoolean(actual.manualReview));
- assertEquals(expected.isRetainHeader(), toBoolean(actual.retainHeader));
- assertEquals(expected.isShowLineEndings(), toBoolean(actual.showLineEndings));
- assertEquals(expected.isShowTabs(), toBoolean(actual.showTabs));
- assertEquals(expected.isShowWhitespaceErrors(), toBoolean(actual.showWhitespaceErrors));
- assertEquals(expected.isSkipDeleted(), toBoolean(actual.skipDeleted));
- assertEquals(expected.isSkipUncommented(), toBoolean(actual.skipUncommented));
- assertEquals(expected.isSyntaxHighlighting(), toBoolean(actual.syntaxHighlighting));
- assertEquals(expected.getTabSize(), actual.tabSize);
+ assertThat(actual.context).isEqualTo(expected.getContext());
+ assertThat(toBoolean(actual.expandAllComments)).isEqualTo(expected.isExpandAllComments());
+ assertThat(actual.ignoreWhitespace).isEqualTo(expected.getIgnoreWhitespace());
+ assertThat(toBoolean(actual.intralineDifference)).isEqualTo(expected.isIntralineDifference());
+ assertThat(actual.lineLength).isEqualTo(expected.getLineLength());
+ assertThat(toBoolean(actual.manualReview)).isEqualTo(expected.isManualReview());
+ assertThat(toBoolean(actual.retainHeader)).isEqualTo(expected.isRetainHeader());
+ assertThat(toBoolean(actual.showLineEndings)).isEqualTo(expected.isShowLineEndings());
+ assertThat(toBoolean(actual.showTabs)).isEqualTo(expected.isShowTabs());
+ assertThat(toBoolean(actual.showWhitespaceErrors)).isEqualTo(expected.isShowWhitespaceErrors());
+ assertThat(toBoolean(actual.skipDeleted)).isEqualTo(expected.isSkipDeleted());
+ assertThat(toBoolean(actual.skipUncommented)).isEqualTo(expected.isSkipUncommented());
+ assertThat(toBoolean(actual.syntaxHighlighting)).isEqualTo(expected.isSyntaxHighlighting());
+ assertThat(actual.tabSize).isEqualTo(expected.getTabSize());
}
private static boolean toBoolean(Boolean b) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
index 9470df0300..face299adc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
@@ -14,17 +14,14 @@
package com.google.gerrit.acceptance.rest.account;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwtorm.server.OrmException;
-import org.eclipse.jgit.api.errors.GitAPIException;
+import org.apache.http.HttpStatus;
import org.junit.Test;
import java.io.IOException;
@@ -32,30 +29,29 @@ import java.io.IOException;
public class StarredChangesIT extends AbstractDaemonTest {
@Test
- public void starredChangeState() throws GitAPIException, IOException,
- OrmException {
+ public void starredChangeState() throws Exception {
Result c1 = createChange();
Result c2 = createChange();
- assertNull(getChange(c1.getChangeId()).starred);
- assertNull(getChange(c2.getChangeId()).starred);
+ assertThat(getChange(c1.getChangeId()).starred).isNull();
+ assertThat(getChange(c2.getChangeId()).starred).isNull();
starChange(true, c1.getPatchSetId().getParentKey());
starChange(true, c2.getPatchSetId().getParentKey());
- assertTrue(getChange(c1.getChangeId()).starred);
- assertTrue(getChange(c2.getChangeId()).starred);
+ assertThat(getChange(c1.getChangeId()).starred).isTrue();
+ assertThat(getChange(c2.getChangeId()).starred).isTrue();
starChange(false, c1.getPatchSetId().getParentKey());
starChange(false, c2.getPatchSetId().getParentKey());
- assertNull(getChange(c1.getChangeId()).starred);
- assertNull(getChange(c2.getChangeId()).starred);
+ assertThat(getChange(c1.getChangeId()).starred).isNull();
+ assertThat(getChange(c2.getChangeId()).starred).isNull();
}
private void starChange(boolean on, Change.Id id) throws IOException {
String url = "/accounts/self/starred.changes/" + id.get();
if (on) {
RestResponse r = adminSession.put(url);
- assertEquals(204, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
} else {
RestResponse r = adminSession.delete(url);
- assertEquals(204, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
}
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 6c36e37498..dec3e6547d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -14,35 +14,45 @@
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.assert_;
+import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
-import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_LABELS;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.collect.Iterables;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
+
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
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.SshSession;
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.common.EventSource;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.SubmitInput;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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.extensions.common.LabelInfo;
+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.Project;
import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.ChangeJson.LabelInfo;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.ChangeMergedEvent;
+import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
import com.google.gerrit.server.project.PutConfig;
+import com.google.gerrit.testutil.ConfigSuite;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -53,7 +63,9 @@ import org.apache.http.HttpStatus;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.Config;
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;
@@ -66,11 +78,15 @@ import java.io.IOException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
public abstract class AbstractSubmit extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config submitWholeTopicEnabled() {
+ return submitWholeTopicEnabledConfig();
+ }
- @Inject
- private GitRepositoryManager repoManager;
+ private Map<String, String> mergeResults;
@Inject
private ChangeNotes.Factory notesFactory;
@@ -78,9 +94,28 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
@Inject
private ApprovalsUtil approvalsUtil;
+ @Inject
+ private IdentifiedUser.GenericFactory factory;
+
+ @Inject
+ EventSource source;
@Before
public void setUp() throws Exception {
+ mergeResults = Maps.newHashMap();
+ CurrentUser listenerUser = factory.create(user.id);
+ source.addEventListener(new EventListener() {
+
+ @Override
+ public void onEvent(Event event) {
+ if (event instanceof ChangeMergedEvent) {
+ ChangeMergedEvent changeMergedEvent = (ChangeMergedEvent) event;
+ mergeResults.put(changeMergedEvent.change.number,
+ changeMergedEvent.newRev);
+ }
+ }
+
+ }, listenerUser);
project = new Project.NameKey("p2");
}
@@ -97,7 +132,22 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
Git git = createProject(false);
PushOneCommit.Result change = createChange(git);
submit(change.getChangeId());
- assertEquals(change.getCommitId(), getRemoteHead().getId());
+ assertThat(getRemoteHead().getId()).isEqualTo(change.getCommitId());
+ }
+
+ @Test
+ public void submitWholeTopic() throws Exception {
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+ Git git = createProject();
+ PushOneCommit.Result change1 =
+ createChange(git, "Change 1", "a.txt", "content", "test-topic");
+ PushOneCommit.Result change2 =
+ createChange(git, "Change 2", "b.txt", "content", "test-topic");
+ approve(change1.getChangeId());
+ approve(change2.getChangeId());
+ submit(change2.getChangeId());
+ change1.assertChange(Change.Status.MERGED, "test-topic", admin);
+ change2.assertChange(Change.Status.MERGED, "test-topic", admin);
}
protected Git createProject() throws JSchException, IOException,
@@ -123,7 +173,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
in.useContentMerge = InheritableBoolean.FALSE;
RestResponse r =
adminSession.put("/projects/" + project.get() + "/config", in);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
}
@@ -132,7 +182,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
in.useContentMerge = InheritableBoolean.TRUE;
RestResponse r =
adminSession.put("/projects/" + project.get() + "/config", in);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
}
@@ -149,6 +199,14 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
return push.to(git, "refs/for/master");
}
+ protected PushOneCommit.Result createChange(Git git, String subject,
+ String fileName, String content, String topic)
+ throws GitAPIException, IOException {
+ PushOneCommit push =
+ pushFactory.create(db, admin.getIdent(), subject, fileName, content);
+ return push.to(git, "refs/for/master/" + topic);
+ }
+
protected void submit(String changeId) throws IOException {
submit(changeId, HttpStatus.SC_OK);
}
@@ -160,7 +218,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
protected void submitStatusOnly(String changeId)
throws IOException, OrmException {
approve(changeId);
- Change c = db.changes().byKey(new Change.Key(changeId)).toList().get(0);
+ Change c = queryProvider.get().byKeyPrefix(changeId).get(0).change();
c.setStatus(Change.Status.SUBMITTED);
db.changes().update(Collections.singleton(c));
db.patchSetApprovals().insert(Collections.singleton(
@@ -168,9 +226,10 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
new PatchSetApproval.Key(
c.currentPatchSetId(),
admin.id,
- PatchSetApproval.LabelId.SUBMIT),
+ LabelId.SUBMIT),
(short) 1,
new Timestamp(System.currentTimeMillis()))));
+ indexer.index(db, c);
}
private void submit(String changeId, int expectedStatus) throws IOException {
@@ -179,55 +238,83 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
subm.waitForMerge = true;
RestResponse r =
adminSession.post("/changes/" + changeId + "/submit", subm);
- assertEquals(expectedStatus, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(expectedStatus);
if (expectedStatus == HttpStatus.SC_OK) {
ChangeInfo change =
newGson().fromJson(r.getReader(),
new TypeToken<ChangeInfo>() {}.getType());
- assertEquals(Change.Status.MERGED, change.status);
+ assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+
+ checkMergeResult(change);
}
r.consume();
}
+ private void checkMergeResult(ChangeInfo change) throws IOException {
+ // Get the revision of the branch after the submit to compare with the
+ // newRev of the ChangeMergedEvent.
+ RestResponse b =
+ adminSession.get("/projects/" + project.get() + "/branches/"
+ + change.branch);
+ if (b.getStatusCode() == HttpStatus.SC_OK) {
+ BranchInfo branch =
+ newGson().fromJson(b.getReader(),
+ new TypeToken<BranchInfo>() {}.getType());
+ assertThat(branch.revision).isEqualTo(
+ mergeResults.get(Integer.toString(change._number)));
+ }
+ b.consume();
+ }
+
private void approve(String changeId) throws IOException {
RestResponse r = adminSession.post(
"/changes/" + changeId + "/revisions/current/review",
new ReviewInput().label("Code-Review", 2));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
}
protected void assertCurrentRevision(String changeId, int expectedNum,
ObjectId expectedId) throws IOException {
ChangeInfo c = getChange(changeId, CURRENT_REVISION);
- assertEquals(expectedId.name(), c.currentRevision);
- assertEquals(expectedNum, c.revisions.get(expectedId.name())._number);
+ assertThat(c.currentRevision).isEqualTo(expectedId.name());
+ assertThat(c.revisions.get(expectedId.name())._number).isEqualTo(expectedNum);
+ Repository repo =
+ repoManager.openRepository(new Project.NameKey(c.project));
+ try {
+ Ref ref = repo.getRef(
+ new PatchSet.Id(new Change.Id(c._number), expectedNum).toRefName());
+ assertThat(ref).isNotNull();
+ assertThat(ref.getObjectId()).isEqualTo(expectedId);
+ } finally {
+ repo.close();
+ }
}
protected void assertApproved(String changeId) throws IOException {
ChangeInfo c = getChange(changeId, DETAILED_LABELS);
LabelInfo cr = c.labels.get("Code-Review");
- assertEquals(1, cr.all.size());
- assertEquals(2, cr.all.get(0).value.intValue());
- assertEquals("Administrator", cr.all.get(0).name);
+ assertThat(cr.all).hasSize(1);
+ assertThat(cr.all.get(0).value.intValue()).isEqualTo(2);
+ assertThat(new Account.Id(cr.all.get(0)._accountId)).isEqualTo(admin.getId());
}
protected void assertSubmitter(String changeId, int psId)
- throws OrmException, IOException {
+ throws OrmException {
ChangeNotes cn = notesFactory.create(
- Iterables.getOnlyElement(db.changes().byKey(new Change.Key(changeId))));
+ getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change());
PatchSetApproval submitter = approvalsUtil.getSubmitter(
db, cn, new PatchSet.Id(cn.getChangeId(), psId));
- assertTrue(submitter.isSubmit());
- assertEquals(admin.getId(), submitter.getAccountId());
+ assertThat(submitter.isSubmit()).isTrue();
+ assertThat(submitter.getAccountId()).isEqualTo(admin.getId());
}
protected void assertCherryPick(Git localGit, boolean contentMerge)
throws IOException {
assertRebase(localGit, contentMerge);
RevCommit remoteHead = getRemoteHead();
- assertFalse(remoteHead.getFooterLines("Reviewed-On").isEmpty());
- assertFalse(remoteHead.getFooterLines("Reviewed-By").isEmpty());
+ assertThat(remoteHead.getFooterLines("Reviewed-On")).isNotEmpty();
+ assertThat(remoteHead.getFooterLines("Reviewed-By")).isNotEmpty();
}
protected void assertRebase(Git localGit, boolean contentMerge)
@@ -235,12 +322,14 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
Repository repo = localGit.getRepository();
RevCommit localHead = getHead(repo);
RevCommit remoteHead = getRemoteHead();
- assertNotEquals(localHead.getId(), remoteHead.getId());
- assertEquals(1, remoteHead.getParentCount());
+ assert_().withFailureMessage(
+ String.format("%s not equal %s", localHead.name(), remoteHead.name()))
+ .that(localHead.getId()).isNotEqualTo(remoteHead.getId());
+ assertThat(remoteHead.getParentCount()).isEqualTo(1);
if (!contentMerge) {
- assertEquals(getLatestDiff(repo), getLatestRemoteDiff());
+ assertThat(getLatestRemoteDiff()).isEqualTo(getLatestDiff(repo));
}
- assertEquals(localHead.getShortMessage(), remoteHead.getShortMessage());
+ assertThat(remoteHead.getShortMessage()).isEqualTo(localHead.getShortMessage());
}
private RevCommit getHead(Repository repo) throws IOException {
@@ -248,40 +337,23 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
protected RevCommit getRemoteHead() throws IOException {
- Repository repo = repoManager.openRepository(project);
- try {
+ try (Repository repo = repoManager.openRepository(project)) {
return getHead(repo, "refs/heads/master");
- } finally {
- repo.close();
}
}
protected List<RevCommit> getRemoteLog() throws IOException {
- Repository repo = repoManager.openRepository(project);
- try {
- RevWalk rw = new RevWalk(repo);
- try {
- rw.markStart(rw.parseCommit(
- repo.getRef("refs/heads/master").getObjectId()));
- return Lists.newArrayList(rw);
- } finally {
- rw.close();
- }
- } finally {
- repo.close();
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ rw.markStart(rw.parseCommit(
+ repo.getRef("refs/heads/master").getObjectId()));
+ return Lists.newArrayList(rw);
}
}
private RevCommit getHead(Repository repo, String name) throws IOException {
- try {
- RevWalk rw = new RevWalk(repo);
- try {
- return rw.parseCommit(repo.getRef(name).getObjectId());
- } finally {
- rw.close();
- }
- } finally {
- repo.close();
+ try (RevWalk rw = new RevWalk(repo)) {
+ return rw.parseCommit(repo.getRef(name).getObjectId());
}
}
@@ -292,28 +364,22 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
private String getLatestRemoteDiff() throws IOException {
- Repository repo = repoManager.openRepository(project);
- try {
- RevWalk rw = new RevWalk(repo);
- try {
- ObjectId oldTreeId = repo.resolve("refs/heads/master~1^{tree}");
- ObjectId newTreeId = repo.resolve("refs/heads/master^{tree}");
- return getLatestDiff(repo, oldTreeId, newTreeId);
- } finally {
- rw.close();
- }
- } finally {
- repo.close();
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ ObjectId oldTreeId = repo.resolve("refs/heads/master~1^{tree}");
+ ObjectId newTreeId = repo.resolve("refs/heads/master^{tree}");
+ return getLatestDiff(repo, oldTreeId, newTreeId);
}
}
private String getLatestDiff(Repository repo, ObjectId oldTreeId,
ObjectId newTreeId) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- DiffFormatter fmt = new DiffFormatter(out);
- fmt.setRepository(repo);
- fmt.format(oldTreeId, newTreeId);
- fmt.flush();
- return out.toString();
+ try (DiffFormatter fmt = new DiffFormatter(out)) {
+ fmt.setRepository(repo);
+ fmt.format(oldTreeId, newTreeId);
+ fmt.flush();
+ return out.toString();
+ }
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index eb1a6be839..1d60607003 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -14,8 +14,8 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -45,9 +45,9 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
createChange(git, "Change 2", "b.txt", "other content");
submit(change2.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(2, head.getParentCount());
- assertEquals(oldHead, head.getParent(0));
- assertEquals(change2.getCommitId(), head.getParent(1));
+ assertThat(head.getParentCount()).isEqualTo(2);
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
+ assertThat(head.getParent(1)).isEqualTo(change2.getCommitId());
}
@Test
@@ -68,9 +68,9 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
createChange(git, "Change 3", "a.txt", "bbb\nccc\n");
submit(change3.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(2, head.getParentCount());
- assertEquals(oldHead, head.getParent(0));
- assertEquals(change3.getCommitId(), head.getParent(1));
+ assertThat(head.getParentCount()).isEqualTo(2);
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
+ assertThat(head.getParent(1)).isEqualTo(change3.getCommitId());
}
@Test
@@ -88,6 +88,6 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
PushOneCommit.Result change2 =
createChange(git, "Change 2", "a.txt", "other content");
submitWithConflict(change2.getChangeId());
- assertEquals(oldHead, getRemoteHead());
+ assertThat(getRemoteHead()).isEqualTo(oldHead);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
new file mode 100644
index 0000000000..afcd5d0397
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -0,0 +1,144 @@
+// Copyright (C) 2015 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.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class ActionsIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config submitWholeTopicEnabled() {
+ return submitWholeTopicEnabledConfig();
+ }
+
+ @Test
+ public void revisionActionsOneChangePerTopicUnapproved() throws Exception {
+ String changeId = createChangeWithTopic("foo1").getChangeId();
+ Map<String, ActionInfo> actions = getActions(changeId);
+ assertThat(actions).containsKey("cherrypick");
+ assertThat(actions).containsKey("rebase");
+ assertThat(actions).hasSize(2);
+ }
+
+ @Test
+ public void revisionActionsOneChangePerTopic() throws Exception {
+ String changeId = createChangeWithTopic("foo1").getChangeId();
+ approve(changeId);
+ Map<String, ActionInfo> actions = getActions(changeId);
+ commonActionsAssertions(actions);
+ if (isSubmitWholeTopicEnabled()) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isTrue();
+ assertThat(info.label).isEqualTo("Submit whole topic");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Submit all 1 changes of the same topic");
+ } else {
+ noSubmitWholeTopicAssertions(actions);
+ }
+ }
+
+ @Test
+ public void revisionActionsTwoChangeChangesInTopic() throws Exception {
+ String changeId = createChangeWithTopic("foo2").getChangeId();
+ approve(changeId);
+ // create another change with the same topic
+ createChangeWithTopic("foo2").getChangeId();
+ Map<String, ActionInfo> actions = getActions(changeId);
+ commonActionsAssertions(actions);
+ if (isSubmitWholeTopicEnabled()) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isNull();
+ assertThat(info.label).isEqualTo("Submit whole topic");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Other changes in this topic are not ready");
+ } else {
+ noSubmitWholeTopicAssertions(actions);
+ }
+ }
+
+ @Test
+ public void revisionActionsTwoChangeChangesInTopicReady() throws Exception {
+ String changeId = createChangeWithTopic("foo2").getChangeId();
+ approve(changeId);
+ // create another change with the same topic
+ String changeId2 = createChangeWithTopic("foo2").getChangeId();
+ approve(changeId2);
+ Map<String, ActionInfo> actions = getActions(changeId);
+ commonActionsAssertions(actions);
+ if (isSubmitWholeTopicEnabled()) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isTrue();
+ assertThat(info.label).isEqualTo("Submit whole topic");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Submit all 2 changes of the same topic");
+ } else {
+ noSubmitWholeTopicAssertions(actions);
+ }
+ }
+
+ private Map<String, ActionInfo> getActions(String changeId)
+ throws IOException {
+ return newGson().fromJson(
+ adminSession.get("/changes/"
+ + changeId
+ + "/revisions/1/actions").getReader(),
+ new TypeToken<Map<String, ActionInfo>>() {}.getType());
+ }
+
+ private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isTrue();
+ assertThat(info.label).isEqualTo("Submit");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Submit patch set 1 into master");
+ }
+
+ private void commonActionsAssertions(Map<String, ActionInfo> actions) {
+ assertThat(actions).hasSize(3);
+ assertThat(actions).containsKey("cherrypick");
+ assertThat(actions).containsKey("submit");
+ assertThat(actions).containsKey("rebase");
+ }
+
+ private PushOneCommit.Result createChangeWithTopic(String topic) throws GitAPIException,
+ IOException {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent());
+ assertThat(topic).isNotEmpty();
+ return push.to(git, "refs/for/master/" + topic);
+ }
+
+ private void approve(String changeId) throws IOException {
+ RestResponse r = adminSession.post(
+ "/changes/" + changeId + "/revisions/current/review",
+ new ReviewInput().label("Code-Review", 2));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ r.consume();
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index acf50db459..3c763551ef 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -14,20 +14,17 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.testutil.ConfigSuite;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Config;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
@@ -37,7 +34,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
@@ -48,10 +44,7 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
@ConfigSuite.Config
public static Config noteDbEnabled() {
- Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "changeMessages", "read", true);
- return cfg;
+ return NotesMigration.allEnabledConfig();
}
@Before
@@ -80,17 +73,17 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
String changeId = createChange().getChangeId();
postMessage(changeId, "Some nits need to be fixed.");
ChangeInfo c = info(changeId);
- assertNull(c.messages);
+ assertThat((Iterable<?>)c.messages).isNull();
}
@Test
- public void defaultMessage() throws GitAPIException, IOException,
- RestApiException {
+ public void defaultMessage() throws Exception {
String changeId = createChange().getChangeId();
ChangeInfo c = get(changeId);
- assertNotNull(c.messages);
- assertEquals(1, c.messages.size());
- assertEquals("Uploaded patch set 1.", c.messages.iterator().next().message);
+ assertThat((Iterable<?>)c.messages).isNotNull();
+ assertThat((Iterable<?>)c.messages).hasSize(1);
+ assertThat(c.messages.iterator().next().message)
+ .isEqualTo("Uploaded patch set 1.");
}
@Test
@@ -101,16 +94,16 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
String secondMessage = "I like this feature.";
postMessage(changeId, secondMessage);
ChangeInfo c = get(changeId);
- assertNotNull(c.messages);
- assertEquals(3, c.messages.size());
+ assertThat((Iterable<?>)c.messages).isNotNull();
+ assertThat((Iterable<?>)c.messages).hasSize(3);
Iterator<ChangeMessageInfo> it = c.messages.iterator();
- assertEquals("Uploaded patch set 1.", it.next().message);
+ assertThat(it.next().message).isEqualTo("Uploaded patch set 1.");
assertMessage(firstMessage, it.next().message);
assertMessage(secondMessage, it.next().message);
}
private void assertMessage(String expected, String actual) {
- assertEquals("Patch Set 1:\n\n" + expected, actual);
+ assertThat(actual).isEqualTo("Patch Set 1:\n\n" + expected);
}
private void postMessage(String changeId, String msg) throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
index 83efd30a79..8e7daecdcc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
@@ -14,11 +14,10 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.common.data.Permission.LABEL;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -33,9 +32,6 @@ import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
import org.apache.http.HttpStatus;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -47,12 +43,6 @@ import java.io.IOException;
public class ChangeOwnerIT extends AbstractDaemonTest {
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
private TestAccount user2;
private RestSession sessionOwner;
@@ -63,8 +53,7 @@ public class ChangeOwnerIT extends AbstractDaemonTest {
sessionOwner = new RestSession(server, user);
SshSession sshSession = new SshSession(server, user);
initSsh(user);
- // need to initialize intern session
- createProject(sshSession, "foo");
+ sshSession.open();
git = cloneProject(sshSession.getUrl() + "/" + project.get());
sshSession.close();
user2 = accounts.user2();
@@ -72,21 +61,18 @@ public class ChangeOwnerIT extends AbstractDaemonTest {
}
@Test
- public void testChangeOwner_OwnerACLNotGranted() throws GitAPIException,
- IOException, OrmException, ConfigInvalidException {
+ public void testChangeOwner_OwnerACLNotGranted() throws Exception {
approve(sessionOwner, createMyChange(), HttpStatus.SC_FORBIDDEN);
}
@Test
- public void testChangeOwner_OwnerACLGranted() throws GitAPIException,
- IOException, OrmException, ConfigInvalidException {
+ public void testChangeOwner_OwnerACLGranted() throws Exception {
grantApproveToChangeOwner();
approve(sessionOwner, createMyChange(), HttpStatus.SC_OK);
}
@Test
- public void testChangeOwner_NotOwnerACLGranted() throws GitAPIException,
- IOException, OrmException, ConfigInvalidException {
+ public void testChangeOwner_NotOwnerACLGranted() throws Exception {
grantApproveToChangeOwner();
approve(sessionDev, createMyChange(), HttpStatus.SC_FORBIDDEN);
}
@@ -96,7 +82,7 @@ public class ChangeOwnerIT extends AbstractDaemonTest {
RestResponse r =
s.post("/changes/" + changeId + "/revisions/current/review",
new ReviewInput().label("Code-Review", 2));
- assertEquals(expected, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(expected);
r.consume();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java
index 3370cb6ccf..6a1c0a6c13 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java
@@ -14,9 +14,8 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
@@ -24,11 +23,9 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gson.reflect.TypeToken;
-import com.jcraft.jsch.JSchException;
-
import org.apache.http.HttpStatus;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -42,18 +39,16 @@ public class ConflictsOperatorIT extends AbstractDaemonTest {
private int count;
@Test
- public void noConflictingChanges() throws JSchException, IOException,
- GitAPIException {
+ public void noConflictingChanges() throws Exception {
PushOneCommit.Result change = createChange(git, true);
createChange(git, false);
Set<String> changes = queryConflictingChanges(change);
- assertEquals(0, changes.size());
+ assertThat((Iterable<?>)changes).isEmpty();
}
@Test
- public void conflictingChanges() throws JSchException, IOException,
- GitAPIException {
+ public void conflictingChanges() throws Exception {
PushOneCommit.Result change = createChange(git, true);
PushOneCommit.Result conflictingChange1 = createChange(git, true);
PushOneCommit.Result conflictingChange2 = createChange(git, true);
@@ -78,7 +73,7 @@ public class ConflictsOperatorIT extends AbstractDaemonTest {
throws IOException {
RestResponse r =
adminSession.get("/changes/?q=conflicts:" + change.getChangeId());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Set<ChangeInfo> changes =
newGson().fromJson(r.getReader(),
new TypeToken<Set<ChangeInfo>>() {}.getType());
@@ -94,9 +89,9 @@ public class ConflictsOperatorIT extends AbstractDaemonTest {
private void assertChanges(Set<String> actualChanges,
PushOneCommit.Result... expectedChanges) {
- assertEquals(expectedChanges.length, actualChanges.size());
+ assertThat((Iterable<?>)actualChanges).hasSize(expectedChanges.length);
for (PushOneCommit.Result c : expectedChanges) {
- assertTrue(actualChanges.contains(id(c)));
+ assertThat(actualChanges.contains(id(c))).isTrue();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 2026ac1a64..311161a974 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -14,27 +14,33 @@
package com.google.gerrit.acceptance.rest.change;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ChangeStatus;
-import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.testutil.ConfigSuite;
import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
import org.junit.Test;
public class CreateChangeIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config allowDraftsDisabled() {
+ return allowDraftsDisabledConfig();
+ }
@Test
public void createEmptyChange_MissingBranch() throws Exception {
ChangeInfo ci = new ChangeInfo();
ci.project = project.get();
RestResponse r = adminSession.post("/changes/", ci);
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
- assertTrue(r.getEntityContent().contains("branch must be non-empty"));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
+ assertThat(r.getEntityContent()).contains("branch must be non-empty");
}
@Test
@@ -43,16 +49,16 @@ public class CreateChangeIT extends AbstractDaemonTest {
ci.project = project.get();
ci.branch = "master";
RestResponse r = adminSession.post("/changes/", ci);
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
- assertTrue(r.getEntityContent().contains("commit message must be non-empty"));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
+ assertThat(r.getEntityContent()).contains("commit message must be non-empty");
}
@Test
public void createEmptyChange_InvalidStatus() throws Exception {
ChangeInfo ci = newChangeInfo(ChangeStatus.SUBMITTED);
RestResponse r = adminSession.post("/changes/", ci);
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
- assertTrue(r.getEntityContent().contains("unsupported change status"));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
+ assertThat(r.getEntityContent()).contains("unsupported change status");
}
@Test
@@ -62,9 +68,19 @@ public class CreateChangeIT extends AbstractDaemonTest {
@Test
public void createDraftChange() throws Exception {
+ assume().that(isAllowDrafts()).isTrue();
assertChange(newChangeInfo(ChangeStatus.DRAFT));
}
+ @Test
+ public void createDraftChangeNotAllowed() throws Exception {
+ assume().that(isAllowDrafts()).isFalse();
+ ChangeInfo ci = newChangeInfo(ChangeStatus.DRAFT);
+ RestResponse r = adminSession.post("/changes/", ci);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_METHOD_NOT_ALLOWED);
+ assertThat(r.getEntityContent()).contains("draft workflow is disabled");
+ }
+
private ChangeInfo newChangeInfo(ChangeStatus status) {
ChangeInfo in = new ChangeInfo();
in.project = project.get();
@@ -77,15 +93,24 @@ public class CreateChangeIT extends AbstractDaemonTest {
private void assertChange(ChangeInfo in) throws Exception {
RestResponse r = adminSession.post("/changes/", in);
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
- ChangeJson.ChangeInfo info = newGson().fromJson(r.getReader(),
- ChangeJson.ChangeInfo.class);
+ ChangeInfo info = newGson().fromJson(r.getReader(), ChangeInfo.class);
ChangeInfo out = get(info.changeId);
- assertEquals(in.branch, out.branch);
- assertEquals(in.subject, out.subject);
- assertEquals(in.topic, out.topic);
- assertEquals(in.status, out.status);
+ assertThat(out.branch).isEqualTo(in.branch);
+ assertThat(out.subject).isEqualTo(in.subject);
+ assertThat(out.topic).isEqualTo(in.topic);
+ assertThat(out.status).isEqualTo(in.status);
+ assertThat(out.revisions).hasSize(1);
+ Boolean draft = Iterables.getOnlyElement(out.revisions.values()).draft;
+ assertThat(booleanToDraftStatus(draft)).isEqualTo(in.status);
+ }
+
+ private ChangeStatus booleanToDraftStatus(Boolean draft) {
+ if (draft == null) {
+ return ChangeStatus.NEW;
+ }
+ return draft ? ChangeStatus.DRAFT : ChangeStatus.NEW;
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
deleted file mode 100644
index 3ba5ed7848..0000000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
+++ /dev/null
@@ -1,116 +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.acceptance.rest.change;
-
-import static org.junit.Assert.assertEquals;
-
-import com.google.common.collect.Iterables;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ChangeStatus;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwtorm.server.OrmException;
-
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class DeleteDraftChangeIT extends AbstractDaemonTest {
-
- @Test
- public void deleteChange() throws GitAPIException,
- IOException, RestApiException {
- String changeId = createChange().getChangeId();
- String triplet = "p~master~" + changeId;
- ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.NEW, c.status);
- RestResponse r = deleteChange(changeId, adminSession);
- assertEquals("Change is not a draft", r.getEntityContent());
- assertEquals(409, r.getStatusCode());
- }
-
- @Test
- public void deleteDraftChange() throws GitAPIException,
- IOException, RestApiException, OrmException {
- String changeId = createDraftChange();
- String triplet = "p~master~" + changeId;
- ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.DRAFT, c.status);
- RestResponse r = deleteChange(changeId, adminSession);
- assertEquals(204, r.getStatusCode());
- }
-
- @Test
- public void publishDraftChange() throws GitAPIException,
- IOException, RestApiException {
- String changeId = createDraftChange();
- String triplet = "p~master~" + changeId;
- ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.DRAFT, c.status);
- RestResponse r = publishChange(changeId);
- assertEquals(204, r.getStatusCode());
- c = get(triplet);
- assertEquals(ChangeStatus.NEW, c.status);
- }
-
- @Test
- public void publishDraftPatchSet() throws GitAPIException,
- IOException, OrmException, RestApiException {
- String changeId = createDraftChange();
- String triplet = "p~master~" + changeId;
- ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.DRAFT, c.status);
- RestResponse r = publishPatchSet(changeId);
- assertEquals(204, r.getStatusCode());
- assertEquals(ChangeStatus.NEW, get(triplet).status);
- }
-
- private String createDraftChange() throws GitAPIException, IOException {
- PushOneCommit push = pushFactory.create(db, admin.getIdent());
- return push.to(git, "refs/drafts/master").getChangeId();
- }
-
- private static RestResponse deleteChange(String changeId,
- RestSession s) throws IOException {
- return s.delete("/changes/" + changeId);
- }
-
- private RestResponse publishChange(String changeId) throws IOException {
- return adminSession.post("/changes/" + changeId + "/publish");
- }
-
- private RestResponse publishPatchSet(String changeId) throws IOException,
- OrmException {
- PatchSet patchSet = db.patchSets()
- .get(Iterables.getOnlyElement(db.changes()
- .byKey(new Change.Key(changeId)))
- .currentPatchSetId());
- return adminSession.post("/changes/"
- + changeId
- + "/revisions/"
- + patchSet.getRevision().get()
- + "/publish");
- }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
index 10fbed084c..871b1cc0a2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance.rest.change;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -22,12 +22,13 @@ import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ChangeStatus;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
+import org.apache.http.HttpStatus;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Test;
@@ -41,11 +42,11 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
PatchSet ps = getCurrentPatchSet(changeId);
String triplet = "p~master~" + changeId;
ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.NEW, c.status);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.NEW);
RestResponse r = deletePatchSet(changeId, ps, adminSession);
- assertEquals("Patch set is not a draft.", r.getEntityContent());
- assertEquals(409, r.getStatusCode());
+ assertThat(r.getEntityContent()).isEqualTo("Patch set is not a draft");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
}
@Test
@@ -54,11 +55,11 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
PatchSet ps = getCurrentPatchSet(changeId);
String triplet = "p~master~" + changeId;
ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.DRAFT, c.status);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
RestResponse r = deletePatchSet(changeId, ps, userSession);
- assertEquals("Not found", r.getEntityContent());
- assertEquals(404, r.getStatusCode());
+ assertThat(r.getEntityContent()).isEqualTo("Not found: " + changeId);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
@@ -67,19 +68,15 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
PatchSet ps = getCurrentPatchSet(changeId);
String triplet = "p~master~" + changeId;
ChangeInfo c = get(triplet);
- assertEquals(triplet, c.id);
- assertEquals(ChangeStatus.DRAFT, c.status);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
RestResponse r = deletePatchSet(changeId, ps, adminSession);
- assertEquals(204, r.getStatusCode());
- Change change = Iterables.getOnlyElement(db.changes().byKey(
- new Change.Key(changeId)).toList());
- assertEquals(1, db.patchSets().byChange(change.getId())
- .toList().size());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ assertThat(getChange(changeId).patches().size()).isEqualTo(1);
ps = getCurrentPatchSet(changeId);
r = deletePatchSet(changeId, ps, adminSession);
- assertEquals(204, r.getStatusCode());
- assertEquals(0, db.changes().byKey(new Change.Key(changeId))
- .toList().size());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ assertThat(queryProvider.get().byKeyPrefix(changeId)).isEmpty();
}
private String createDraftChangeWith2PS() throws GitAPIException,
@@ -92,10 +89,11 @@ public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
}
private PatchSet getCurrentPatchSet(String changeId) throws OrmException {
- return db.patchSets()
- .get(Iterables.getOnlyElement(db.changes()
- .byKey(new Change.Key(changeId)))
- .currentPatchSetId());
+ return getChange(changeId).currentPatchSet();
+ }
+
+ private ChangeData getChange(String changeId) throws OrmException {
+ return Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(changeId));
}
private static RestResponse deletePatchSet(String changeId,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java
new file mode 100644
index 0000000000..a3809a9655
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java
@@ -0,0 +1,132 @@
+// 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.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gwtorm.server.OrmException;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class DraftChangeIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config allowDraftsDisabled() {
+ return allowDraftsDisabledConfig();
+ }
+
+ @Test
+ public void deleteChange() throws Exception {
+ PushOneCommit.Result result = createChange();
+ result.assertOkStatus();
+ String changeId = result.getChangeId();
+ String triplet = "p~master~" + changeId;
+ ChangeInfo c = get(triplet);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.NEW);
+ RestResponse response = deleteChange(changeId, adminSession);
+ assertThat(response.getEntityContent()).isEqualTo("Change is not a draft");
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
+ }
+
+ @Test
+ public void deleteDraftChange() throws Exception {
+ assume().that(isAllowDrafts()).isTrue();
+ PushOneCommit.Result result = createDraftChange();
+ result.assertOkStatus();
+ String changeId = result.getChangeId();
+ String triplet = "p~master~" + changeId;
+ ChangeInfo c = get(triplet);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
+ RestResponse response = deleteChange(changeId, adminSession);
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ }
+
+ @Test
+ public void publishDraftChange() throws Exception {
+ assume().that(isAllowDrafts()).isTrue();
+ PushOneCommit.Result result = createDraftChange();
+ result.assertOkStatus();
+ String changeId = result.getChangeId();
+ String triplet = "p~master~" + changeId;
+ ChangeInfo c = get(triplet);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
+ RestResponse response = publishChange(changeId);
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ c = get(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.NEW);
+ }
+
+ @Test
+ public void publishDraftPatchSet() throws Exception {
+ assume().that(isAllowDrafts()).isTrue();
+ PushOneCommit.Result result = createDraftChange();
+ result.assertOkStatus();
+ String changeId = result.getChangeId();
+ String triplet = "p~master~" + changeId;
+ ChangeInfo c = get(triplet);
+ assertThat(c.id).isEqualTo(triplet);
+ assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
+ RestResponse response = publishPatchSet(changeId);
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ assertThat(get(triplet).status).isEqualTo(ChangeStatus.NEW);
+ }
+
+ @Test
+ public void createDraftChangeWhenDraftsNotAllowed() throws Exception {
+ assume().that(isAllowDrafts()).isFalse();
+ PushOneCommit.Result r = createDraftChange();
+ r.assertErrorStatus("draft workflow is disabled");
+ }
+
+ private PushOneCommit.Result createDraftChange() throws Exception {
+ return pushTo("refs/drafts/master");
+ }
+
+ private static RestResponse deleteChange(String changeId,
+ RestSession s) throws IOException {
+ return s.delete("/changes/" + changeId);
+ }
+
+ private RestResponse publishChange(String changeId) throws IOException {
+ return adminSession.post("/changes/" + changeId + "/publish");
+ }
+
+ private RestResponse publishPatchSet(String changeId) throws IOException,
+ OrmException {
+ PatchSet patchSet = Iterables.getOnlyElement(
+ queryProvider.get().byKeyPrefix(changeId)).currentPatchSet();
+ return adminSession.post("/changes/"
+ + changeId
+ + "/revisions/"
+ + patchSet.getRevision().get()
+ + "/publish");
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
new file mode 100644
index 0000000000..26d6a1ed71
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
@@ -0,0 +1,233 @@
+// Copyright (C) 2014 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.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+public class HashtagsIT extends AbstractDaemonTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return NotesMigration.allEnabledConfig();
+ }
+
+ private void assertResult(RestResponse r, List<String> expected)
+ throws IOException {
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ List<String> result = toHashtagList(r);
+ assertThat(result).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testGetNoHashtags() throws Exception {
+ // GET hashtags on a change with no hashtags returns an empty list
+ String changeId = createChange().getChangeId();
+ assertResult(GET(changeId), ImmutableList.<String>of());
+ }
+
+ @Test
+ public void testAddSingleHashtag() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // POST adding a single hashtag returns a single hashtag
+ List<String> expected = Arrays.asList("tag2");
+ assertResult(POST(changeId, "tag2", null), expected);
+ assertResult(GET(changeId), expected);
+
+ // POST adding another single hashtag to change that already has one
+ // hashtag returns a sorted list of hashtags with existing and new
+ expected = Arrays.asList("tag1", "tag2");
+ assertResult(POST(changeId, "tag1", null), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ @Test
+ public void testAddMultipleHashtags() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // POST adding multiple hashtags returns a sorted list of hashtags
+ List<String> expected = Arrays.asList("tag1", "tag3");
+ assertResult(POST(changeId, "tag3, tag1", null), expected);
+ assertResult(GET(changeId), expected);
+
+ // POST adding multiple hashtags to change that already has hashtags
+ // returns a sorted list of hashtags with existing and new
+ expected = Arrays.asList("tag1", "tag2", "tag3", "tag4");
+ assertResult(POST(changeId, "tag2, tag4", null), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ @Test
+ public void testAddAlreadyExistingHashtag() throws Exception {
+ // POST adding a hashtag that already exists on the change returns a
+ // sorted list of hashtags without duplicates
+ String changeId = createChange().getChangeId();
+ List<String> expected = Arrays.asList("tag2");
+ assertResult(POST(changeId, "tag2", null), expected);
+ assertResult(GET(changeId), expected);
+ assertResult(POST(changeId, "tag2", null), expected);
+ assertResult(GET(changeId), expected);
+ expected = Arrays.asList("tag1", "tag2");
+ assertResult(POST(changeId, "tag2, tag1", null), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ @Test
+ public void testHashtagsWithPrefix() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // Leading # is stripped from added tag
+ List<String> expected = Arrays.asList("tag1");
+ assertResult(POST(changeId, "#tag1", null), expected);
+ assertResult(GET(changeId), expected);
+
+ // Leading # is stripped from multiple added tags
+ expected = Arrays.asList("tag1", "tag2", "tag3");
+ assertResult(POST(changeId, "#tag2, #tag3", null), expected);
+ assertResult(GET(changeId), expected);
+
+ // Leading # is stripped from removed tag
+ expected = Arrays.asList("tag1", "tag3");
+ assertResult(POST(changeId, null, "#tag2"), expected);
+ assertResult(GET(changeId), expected);
+
+ // Leading # is stripped from multiple removed tags
+ expected = Collections.emptyList();
+ assertResult(POST(changeId, null, "#tag1, #tag3"), expected);
+ assertResult(GET(changeId), expected);
+
+ // Leading # and space are stripped from added tag
+ expected = Arrays.asList("tag1");
+ assertResult(POST(changeId, "# tag1", null), expected);
+ assertResult(GET(changeId), expected);
+
+ // Multiple leading # are stripped from added tag
+ expected = Arrays.asList("tag1", "tag2");
+ assertResult(POST(changeId, "##tag2", null), expected);
+ assertResult(GET(changeId), expected);
+
+ // Multiple leading spaces and # are stripped from added tag
+ expected = Arrays.asList("tag1", "tag2", "tag3");
+ assertResult(POST(changeId, " # # tag3", null), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ @Test
+ public void testRemoveSingleHashtag() throws Exception {
+ // POST removing a single tag from a change that only has that tag
+ // returns an empty list
+ String changeId = createChange().getChangeId();
+ List<String> expected = Arrays.asList("tag1");
+ assertResult(POST(changeId, "tag1", null), expected);
+ assertResult(POST(changeId, null, "tag1"), ImmutableList.<String>of());
+ assertResult(GET(changeId), ImmutableList.<String>of());
+
+ // POST removing a single tag from a change that has multiple tags
+ // returns a sorted list of remaining tags
+ expected = Arrays.asList("tag1", "tag2", "tag3");
+ assertResult(POST(changeId, "tag1, tag2, tag3", null), expected);
+ expected = Arrays.asList("tag1", "tag3");
+ assertResult(POST(changeId, null, "tag2"), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ @Test
+ public void testRemoveMultipleHashtags() throws Exception {
+ // POST removing multiple tags from a change that only has those tags
+ // returns an empty list
+ String changeId = createChange().getChangeId();
+ List<String> expected = Arrays.asList("tag1", "tag2");
+ assertResult(POST(changeId, "tag1, tag2", null), expected);
+ assertResult(POST(changeId, null, "tag1, tag2"), ImmutableList.<String>of());
+ assertResult(GET(changeId), ImmutableList.<String>of());
+
+ // POST removing multiple tags from a change that has multiple changes
+ // returns a sorted list of remaining changes
+ expected = Arrays.asList("tag1", "tag2", "tag3", "tag4");
+ assertResult(POST(changeId, "tag1, tag2, tag3, tag4", null), expected);
+ expected = Arrays.asList("tag2", "tag4");
+ assertResult(POST(changeId, null, "tag1, tag3"), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ @Test
+ public void testRemoveNotExistingHashtag() throws Exception {
+ // POST removing a single hashtag from change that has no hashtags
+ // returns an empty list
+ String changeId = createChange().getChangeId();
+ assertResult(POST(changeId, null, "tag1"), ImmutableList.<String>of());
+ assertResult(GET(changeId), ImmutableList.<String>of());
+
+ // POST removing a single non-existing tag from a change that only
+ // has one other tag returns a list of only one tag
+ List<String> expected = Arrays.asList("tag1");
+ assertResult(POST(changeId, "tag1", null), expected);
+ assertResult(POST(changeId, null, "tag4"), expected);
+ assertResult(GET(changeId), expected);
+
+ // POST removing a single non-existing tag from a change that has multiple
+ // tags returns a sorted list of tags without any deleted
+ expected = Arrays.asList("tag1", "tag2", "tag3");
+ assertResult(POST(changeId, "tag1, tag2, tag3", null), expected);
+ assertResult(POST(changeId, null, "tag4"), expected);
+ assertResult(GET(changeId), expected);
+ }
+
+ private RestResponse GET(String changeId) throws IOException {
+ return adminSession.get("/changes/" + changeId + "/hashtags/");
+ }
+
+ private RestResponse POST(String changeId, String toAdd, String toRemove)
+ throws IOException {
+ HashtagsInput input = new HashtagsInput();
+ if (toAdd != null) {
+ input.add = new HashSet<>(
+ Lists.newArrayList(Splitter.on(CharMatcher.anyOf(",")).split(toAdd)));
+ }
+ if (toRemove != null) {
+ input.remove = new HashSet<>(
+ Lists.newArrayList(Splitter.on(CharMatcher.anyOf(",")).split(toRemove)));
+ }
+ return adminSession.post("/changes/" + changeId + "/hashtags/", input);
+ }
+
+ private static List<String> toHashtagList(RestResponse r)
+ throws IOException {
+ List<String> result =
+ newGson().fromJson(r.getReader(),
+ new TypeToken<List<String>>() {}.getType());
+ return result;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
new file mode 100644
index 0000000000..1b00c39c9a
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2014 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.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+public class IndexChangeIT extends AbstractDaemonTest {
+ @Test
+ public void indexChange() throws Exception {
+ String changeId = createChange().getChangeId();
+ RestResponse r = userSession.post("/changes/" + changeId + "/index/");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ }
+
+ @Test
+ public void indexChangeOnNonVisibleBranch() throws Exception {
+ String changeId = createChange().getChangeId();
+ blockRead(project, "refs/heads/master");
+ RestResponse r = userSession.post("/changes/" + changeId + "/index/");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
index 02ecbd80ed..e6494152cf 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
@@ -14,11 +14,10 @@
package com.google.gerrit.acceptance.rest.change;
-import static com.google.gerrit.extensions.common.ListChangesOption.ALL_REVISIONS;
-import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
-import static com.google.gerrit.extensions.common.ListChangesOption.MESSAGES;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
@@ -61,36 +60,38 @@ public class ListChangesOptionsIT extends AbstractDaemonTest {
@Test
public void noRevisionOptions() throws Exception {
ChangeInfo c = info(changeId);
- assertNull(c.currentRevision);
- assertNull(c.revisions);
+ assertThat(c.currentRevision).isNull();
+ assertThat(c.revisions).isNull();
}
@Test
public void currentRevision() throws Exception {
ChangeInfo c = get(changeId, CURRENT_REVISION);
- assertEquals(commitId(2), c.currentRevision);
- assertEquals(ImmutableSet.of(commitId(2)), c.revisions.keySet());
- assertEquals(3, c.revisions.get(commitId(2))._number);
+ assertThat(c.currentRevision).isEqualTo(commitId(2));
+ assertThat((Iterable<?>)c.revisions.keySet()).containsAllIn(
+ ImmutableSet.of(commitId(2)));
+ assertThat(c.revisions.get(commitId(2))._number).isEqualTo(3);
}
@Test
public void currentRevisionAndMessages() throws Exception {
ChangeInfo c = get(changeId, CURRENT_REVISION, MESSAGES);
- assertEquals(1, c.revisions.size());
- assertEquals(commitId(2), c.currentRevision);
- assertEquals(ImmutableSet.of(commitId(2)), c.revisions.keySet());
- assertEquals(3, c.revisions.get(commitId(2))._number);
+ assertThat(c.revisions).hasSize(1);
+ assertThat(c.currentRevision).isEqualTo(commitId(2));
+ assertThat((Iterable<?>)c.revisions.keySet()).containsAllIn(
+ ImmutableSet.of(commitId(2)));
+ assertThat(c.revisions.get(commitId(2))._number).isEqualTo(3);
}
@Test
public void allRevisions() throws Exception {
ChangeInfo c = get(changeId, ALL_REVISIONS);
- assertEquals(commitId(2), c.currentRevision);
- assertEquals(ImmutableSet.of(commitId(0), commitId(1), commitId(2)),
- c.revisions.keySet());
- assertEquals(1, c.revisions.get(commitId(0))._number);
- assertEquals(2, c.revisions.get(commitId(1))._number);
- assertEquals(3, c.revisions.get(commitId(2))._number);
+ assertThat(c.currentRevision).isEqualTo(commitId(2));
+ assertThat((Iterable<?>)c.revisions.keySet()).containsAllIn(
+ ImmutableSet.of(commitId(0), commitId(1), commitId(2)));
+ assertThat(c.revisions.get(commitId(0))._number).isEqualTo(1);
+ assertThat(c.revisions.get(commitId(1))._number).isEqualTo(2);
+ assertThat(c.revisions.get(commitId(2))._number).isEqualTo(3);
}
private String commitId(int i) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index 12d002ed10..4863c3e657 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -14,22 +14,20 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gwtorm.server.OrmException;
-import com.google.gerrit.extensions.common.SubmitType;
-
-import com.jcraft.jsch.JSchException;
+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.ChangeInfo;
import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
public class SubmitByCherryPickIT extends AbstractSubmit {
@@ -40,14 +38,13 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitWithCherryPickIfFastForwardPossible() throws JSchException,
- IOException, GitAPIException {
+ public void submitWithCherryPickIfFastForwardPossible() throws Exception {
Git git = createProject();
PushOneCommit.Result change = createChange(git);
submit(change.getChangeId());
assertCherryPick(git, false);
- assertEquals(change.getCommit().getParent(0),
- getRemoteHead().getParent(0));
+ assertThat(getRemoteHead().getParent(0))
+ .isEqualTo(change.getCommit().getParent(0));
}
@Test
@@ -65,8 +62,8 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
submit(change2.getChangeId());
assertCherryPick(git, false);
RevCommit newHead = getRemoteHead();
- assertEquals(1, newHead.getParentCount());
- assertEquals(oldHead, newHead.getParent(0));
+ assertThat(newHead.getParentCount()).isEqualTo(1);
+ assertThat(newHead.getParent(0)).isEqualTo(oldHead);
assertCurrentRevision(change2.getChangeId(), 2, newHead);
assertSubmitter(change2.getChangeId(), 1);
assertSubmitter(change2.getChangeId(), 2);
@@ -90,7 +87,7 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
submit(change3.getChangeId());
assertCherryPick(git, true);
RevCommit newHead = getRemoteHead();
- assertEquals(oldHead, newHead.getParent(0));
+ assertThat(newHead.getParent(0)).isEqualTo(oldHead);
assertApproved(change3.getChangeId());
assertCurrentRevision(change3.getChangeId(), 2, newHead);
assertSubmitter(change2.getChangeId(), 1);
@@ -111,7 +108,7 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
PushOneCommit.Result change2 =
createChange(git, "Change 2", "a.txt", "other content");
submitWithConflict(change2.getChangeId());
- assertEquals(oldHead, getRemoteHead());
+ assertThat(getRemoteHead()).isEqualTo(oldHead);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommitId());
assertSubmitter(change2.getChangeId(), 1);
}
@@ -132,7 +129,7 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
submit(change3.getChangeId());
assertCherryPick(git, false);
RevCommit newHead = getRemoteHead();
- assertEquals(oldHead, newHead.getParent(0));
+ assertThat(newHead.getParent(0)).isEqualTo(oldHead);
assertApproved(change3.getChangeId());
assertCurrentRevision(change3.getChangeId(), 2, newHead);
assertSubmitter(change3.getChangeId(), 1);
@@ -153,14 +150,13 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
PushOneCommit.Result change3 =
createChange(git, "Change 3", "b.txt", "different content");
submitWithConflict(change3.getChangeId());
- assertEquals(oldHead, getRemoteHead());
+ assertThat(getRemoteHead()).isEqualTo(oldHead);
assertCurrentRevision(change3.getChangeId(), 1, change3.getCommitId());
assertSubmitter(change3.getChangeId(), 1);
}
@Test
- public void submitMultipleChanges()
- throws JSchException, IOException, GitAPIException, OrmException {
+ public void submitMultipleChanges() throws Exception {
Git git = createProject();
RevCommit initialHead = getRemoteHead();
@@ -178,21 +174,130 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
submit(change4.getChangeId());
List<RevCommit> log = getRemoteLog();
- assertEquals(
- change4.getCommit().getShortMessage(),
- log.get(0).getShortMessage());
- assertSame(log.get(1), log.get(0).getParent(0));
-
- assertEquals(
- change3.getCommit().getShortMessage(),
- log.get(1).getShortMessage());
- assertSame(log.get(2), log.get(1).getParent(0));
-
- assertEquals(
- change2.getCommit().getShortMessage(),
- log.get(2).getShortMessage());
- assertSame(log.get(3), log.get(2).getParent(0));
-
- assertEquals(initialHead.getId(), log.get(3).getId());
+ assertThat(log.get(0).getShortMessage()).isEqualTo(
+ change4.getCommit().getShortMessage());
+ assertThat(log.get(0).getParent(0)).isEqualTo(log.get(1));
+
+ assertThat(log.get(1).getShortMessage()).isEqualTo(
+ change3.getCommit().getShortMessage());
+ assertThat(log.get(1).getParent(0)).isEqualTo(log.get(2));
+
+ assertThat(log.get(2).getShortMessage()).isEqualTo(
+ change2.getCommit().getShortMessage());
+ assertThat(log.get(2).getParent(0)).isEqualTo(log.get(3));
+
+ assertThat(log.get(3).getId()).isEqualTo(initialHead.getId());
+ }
+
+ @Test
+ public void submitDependentNonConflictingChangesOutOfOrder() throws Exception {
+ Git git = createProject();
+ RevCommit initialHead = getRemoteHead();
+
+ checkout(git, initialHead.getId().getName());
+ PushOneCommit.Result change2 = createChange(git, "Change 2", "b", "b");
+ PushOneCommit.Result change3 = createChange(git, "Change 3", "c", "c");
+ assertThat(change3.getCommit().getParent(0)).isEqualTo(change2.getCommit());
+
+ // Submit succeeds; change3 is successfully cherry-picked onto head.
+ submit(change3.getChangeId());
+ // Submit succeeds; change2 is successfully cherry-picked onto head
+ // (which was change3's cherry-pick).
+ submit(change2.getChangeId());
+
+ // change2 is the new tip.
+ List<RevCommit> log = getRemoteLog();
+ assertThat(log.get(0).getShortMessage()).isEqualTo(
+ change2.getCommit().getShortMessage());
+ assertThat(log.get(0).getParent(0)).isEqualTo(log.get(1));
+
+ assertThat(log.get(1).getShortMessage()).isEqualTo(
+ change3.getCommit().getShortMessage());
+ assertThat(log.get(1).getParent(0)).isEqualTo(log.get(2));
+
+ assertThat(log.get(2).getId()).isEqualTo(initialHead.getId());
+ }
+
+ @Test
+ public void submitDependentConflictingChangesOutOfOrder() throws Exception {
+ Git git = createProject();
+ RevCommit initialHead = getRemoteHead();
+
+ checkout(git, initialHead.getId().getName());
+ PushOneCommit.Result change2 = createChange(git, "Change 2", "b", "b1");
+ PushOneCommit.Result change3 = createChange(git, "Change 3", "b", "b2");
+ assertThat(change3.getCommit().getParent(0)).isEqualTo(change2.getCommit());
+
+ // Submit fails; change3 contains the delta "b1" -> "b2", which cannot be
+ // applied against tip.
+ submitWithConflict(change3.getChangeId());
+
+ ChangeInfo info3 = get(change3.getChangeId(), ListChangesOption.MESSAGES);
+ assertThat(info3.status).isEqualTo(ChangeStatus.NEW);
+ assertThat(Iterables.getLast(info3.messages).message.toLowerCase())
+ .contains("path conflict");
+
+ // Tip has not changed.
+ List<RevCommit> log = getRemoteLog();
+ assertThat(log.get(0)).isEqualTo(initialHead.getId());
+ }
+
+ @Test
+ public void submitSubsetOfDependentChanges() throws Exception {
+ Git git = createProject();
+ RevCommit initialHead = getRemoteHead();
+
+ checkout(git, initialHead.getId().getName());
+ createChange(git, "Change 2", "b", "b");
+ PushOneCommit.Result change3 = createChange(git, "Change 3", "c", "c");
+ createChange(git, "Change 4", "d", "d");
+ PushOneCommit.Result change5 = createChange(git, "Change 5", "e", "e");
+
+ // Out of the above, only submit 3 and 5.
+ submitStatusOnly(change3.getChangeId());
+ submit(change5.getChangeId());
+
+ ChangeInfo info3 = get(change3.getChangeId());
+ assertThat(info3.status).isEqualTo(ChangeStatus.MERGED);
+
+ List<RevCommit> log = getRemoteLog();
+ assertThat(log.get(0).getShortMessage())
+ .isEqualTo(change5.getCommit().getShortMessage());
+ assertThat(log.get(1).getShortMessage())
+ .isEqualTo(change3.getCommit().getShortMessage());
+ assertThat(log.get(2).getShortMessage())
+ .isEqualTo(initialHead.getShortMessage());
+ }
+
+ @Test
+ public void submitChangeAfterParentFailsDueToConflict() throws Exception {
+ Git git = createProject();
+ RevCommit initialHead = getRemoteHead();
+
+ checkout(git, initialHead.getId().getName());
+ PushOneCommit.Result change2 = createChange(git, "Change 2", "b", "b1");
+ submit(change2.getChangeId());
+
+ checkout(git, initialHead.getId().getName());
+ PushOneCommit.Result change3 = createChange(git, "Change 3", "b", "b2");
+ assertThat(change3.getCommit().getParent(0)).isEqualTo(initialHead);
+ PushOneCommit.Result change4 = createChange(git, "Change 3", "c", "c3");
+
+ submitStatusOnly(change3.getChangeId());
+ submitStatusOnly(change4.getChangeId());
+
+ // Merge fails; change3 contains the delta "b1" -> "b2", which cannot be
+ // applied against tip.
+ submitWithConflict(change3.getChangeId());
+
+ // change4 is a clean merge, so should succeed in the same run where change3
+ // failed.
+ ChangeInfo info4 = get(change4.getChangeId());
+ assertThat(info4.status).isEqualTo(ChangeStatus.MERGED);
+ List<RevCommit> log = getRemoteLog();
+ assertThat(log.get(0).getShortMessage())
+ .isEqualTo(change4.getCommit().getShortMessage());
+ assertThat(log.get(1).getShortMessage())
+ .isEqualTo(change2.getCommit().getShortMessage());
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 2ef4ecc87f..d4e7e84999 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -14,11 +14,11 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -38,8 +38,8 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
PushOneCommit.Result change = createChange(git);
submit(change.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(change.getCommitId(), head.getId());
- assertEquals(oldHead, head.getParent(0));
+ assertThat(head.getId()).isEqualTo(change.getCommitId());
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
assertSubmitter(change.getChangeId(), 1);
}
@@ -56,7 +56,7 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
PushOneCommit.Result change2 =
createChange(git, "Change 2", "b.txt", "other content");
submitWithConflict(change2.getChangeId());
- assertEquals(oldHead, getRemoteHead());
+ assertThat(getRemoteHead()).isEqualTo(oldHead);
assertSubmitter(change.getChangeId(), 1);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
index 9512c7a69c..fa913d978e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
@@ -14,21 +14,16 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.extensions.common.SubmitType;
-import com.google.gwtorm.server.OrmException;
-
-import com.jcraft.jsch.JSchException;
+import com.google.gerrit.extensions.client.SubmitType;
import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
@@ -45,15 +40,14 @@ public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
PushOneCommit.Result change = createChange(git);
submit(change.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(2, head.getParentCount());
- assertEquals(oldHead, head.getParent(0));
- assertEquals(change.getCommitId(), head.getParent(1));
+ assertThat(head.getParentCount()).isEqualTo(2);
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
+ assertThat(head.getParent(1)).isEqualTo(change.getCommitId());
assertSubmitter(change.getChangeId(), 1);
}
@Test
- public void submitMultipleChanges()
- throws JSchException, IOException, GitAPIException, OrmException {
+ public void submitMultipleChanges() throws Exception {
Git git = createProject();
RevCommit initialHead = getRemoteHead();
@@ -72,20 +66,17 @@ public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
List<RevCommit> log = getRemoteLog();
RevCommit tip = log.get(0);
- assertEquals(
- change4.getCommit().getShortMessage(),
- tip.getParent(1).getShortMessage());
+ assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
+ change4.getCommit().getShortMessage());
tip = tip.getParent(0);
- assertEquals(
- change3.getCommit().getShortMessage(),
- tip.getParent(1).getShortMessage());
+ assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
+ change3.getCommit().getShortMessage());
tip = tip.getParent(0);
- assertEquals(
- change2.getCommit().getShortMessage(),
- tip.getParent(1).getShortMessage());
+ assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
+ change2.getCommit().getShortMessage());
- assertEquals(initialHead.getId(), tip.getParent(0).getId());
+ assertThat(tip.getParent(0).getId()).isEqualTo(initialHead.getId());
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index fde462b097..c1ece455ae 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -1,20 +1,15 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.extensions.common.SubmitType;
-import com.google.gwtorm.server.OrmException;
-
-import com.jcraft.jsch.JSchException;
+import com.google.gerrit.extensions.client.SubmitType;
import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
@@ -31,14 +26,13 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
PushOneCommit.Result change = createChange(git);
submit(change.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(change.getCommitId(), head.getId());
- assertEquals(oldHead, head.getParent(0));
+ assertThat(head.getId()).isEqualTo(change.getCommitId());
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
assertSubmitter(change.getChangeId(), 1);
}
@Test
- public void submitMultipleChanges()
- throws JSchException, IOException, GitAPIException, OrmException {
+ public void submitMultipleChanges() throws Exception {
Git git = createProject();
RevCommit initialHead = getRemoteHead();
@@ -57,20 +51,17 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
List<RevCommit> log = getRemoteLog();
RevCommit tip = log.get(0);
- assertEquals(
- change4.getCommit().getShortMessage(),
- tip.getParent(1).getShortMessage());
+ assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
+ change4.getCommit().getShortMessage());
tip = tip.getParent(0);
- assertEquals(
- change3.getCommit().getShortMessage(),
- tip.getParent(1).getShortMessage());
+ assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
+ change3.getCommit().getShortMessage());
tip = tip.getParent(0);
- assertEquals(
- change2.getCommit().getShortMessage(),
- tip.getShortMessage());
+ assertThat(tip.getShortMessage()).isEqualTo(
+ change2.getCommit().getShortMessage());
- assertEquals(initialHead.getId(), tip.getParent(0).getId());
+ assertThat(tip.getParent(0).getId()).isEqualTo(initialHead.getId());
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index 7b4d23ddb5..d3e8cb549a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -14,11 +14,11 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -38,8 +38,8 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
PushOneCommit.Result change = createChange(git);
submit(change.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(change.getCommitId(), head.getId());
- assertEquals(oldHead, head.getParent(0));
+ assertThat(head.getId()).isEqualTo(change.getCommitId());
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
assertApproved(change.getChangeId());
assertCurrentRevision(change.getChangeId(), 1, head);
assertSubmitter(change.getChangeId(), 1);
@@ -60,7 +60,7 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
submit(change2.getChangeId());
assertRebase(git, false);
RevCommit head = getRemoteHead();
- assertEquals(oldHead, head.getParent(0));
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
assertApproved(change2.getChangeId());
assertCurrentRevision(change2.getChangeId(), 2, head);
assertSubmitter(change2.getChangeId(), 1);
@@ -85,7 +85,7 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
submit(change3.getChangeId());
assertRebase(git, true);
RevCommit head = getRemoteHead();
- assertEquals(oldHead, head.getParent(0));
+ assertThat(head.getParent(0)).isEqualTo(oldHead);
assertApproved(change3.getChangeId());
assertCurrentRevision(change3.getChangeId(), 2, head);
assertSubmitter(change3.getChangeId(), 1);
@@ -107,7 +107,7 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
createChange(git, "Change 2", "a.txt", "other content");
submitWithConflict(change2.getChangeId());
RevCommit head = getRemoteHead();
- assertEquals(oldHead, head);
+ assertThat(head).isEqualTo(oldHead);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommitId());
assertSubmitter(change2.getChangeId(), 1);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 7266c9750a..7fee9f4fd1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -14,8 +14,7 @@
package com.google.gerrit.acceptance.rest.change;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -27,18 +26,16 @@ 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.common.data.PermissionRule;
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.CreateGroupArgs;
import com.google.gerrit.server.account.PerformCreateGroup;
-import com.google.gerrit.server.change.SuggestReviewers.SuggestedReviewerInfo;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Before;
import org.junit.Test;
@@ -50,15 +47,6 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
@Inject
private PerformCreateGroup.Factory createGroupFactory;
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private ProjectCache projectCache;
-
private AccountGroup group1;
private TestAccount user1;
private TestAccount user2;
@@ -70,19 +58,20 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
group("users2");
group("users3");
- user1 = accounts.create("user1", "user1@example.com", "User1", "users1");
- user2 = accounts.create("user2", "user2@example.com", "User2", "users2");
- user3 = accounts.create("user3", "user3@example.com", "User3",
+ user1 = accounts.create("user1", "user1@example.com", "First1 Last1",
+ "users1");
+ user2 = accounts.create("user2", "user2@example.com", "First2 Last2",
+ "users2");
+ user3 = accounts.create("user3", "user3@example.com", "First3 Last3",
"users1", "users2");
}
@Test
@GerritConfig(name = "suggest.accounts", value = "false")
- public void suggestReviewersNoResult1() throws GitAPIException, IOException,
- Exception {
+ public void suggestReviewersNoResult1() throws Exception {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
- assertEquals(reviewers.size(), 0);
+ assertThat(reviewers).isEmpty();
}
@Test
@@ -91,32 +80,29 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
@GerritConfig(name = "suggest.from", value = "1"),
@GerritConfig(name = "accounts.visibility", value = "NONE")
})
- public void suggestReviewersNoResult2() throws GitAPIException, IOException,
- Exception {
+ public void suggestReviewersNoResult2() throws Exception {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
- assertEquals(reviewers.size(), 0);
+ assertThat(reviewers).isEmpty();
}
@Test
@GerritConfig(name = "suggest.from", value = "2")
- public void suggestReviewersNoResult3() throws GitAPIException, IOException,
- Exception {
+ public void suggestReviewersNoResult3() throws Exception {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
- assertEquals(reviewers.size(), 0);
+ assertThat(reviewers).isEmpty();
}
@Test
- public void suggestReviewersChange() throws GitAPIException,
- IOException, Exception {
+ public void suggestReviewersChange() throws Exception {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
- assertEquals(reviewers.size(), 6);
+ assertThat(reviewers).hasSize(6);
reviewers = suggestReviewers(changeId, "u", 5);
- assertEquals(reviewers.size(), 5);
+ assertThat(reviewers).hasSize(5);
reviewers = suggestReviewers(changeId, "users3", 10);
- assertEquals(reviewers.size(), 1);
+ assertThat(reviewers).hasSize(1);
}
@Test
@@ -126,22 +112,25 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
List<SuggestedReviewerInfo> reviewers;
reviewers = suggestReviewers(changeId, "user2", 2);
- assertEquals(1, reviewers.size());
- assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+ assertThat(reviewers).hasSize(1);
+ assertThat(Iterables.getOnlyElement(reviewers).account.name).isEqualTo(
+ "First2 Last2");
reviewers = suggestReviewers(new RestSession(server, user1),
changeId, "user2", 2);
- assertTrue(reviewers.isEmpty());
+ assertThat(reviewers).isEmpty();
reviewers = suggestReviewers(new RestSession(server, user2),
changeId, "user2", 2);
- assertEquals(1, reviewers.size());
- assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+ assertThat(reviewers).hasSize(1);
+ assertThat(Iterables.getOnlyElement(reviewers).account.name).isEqualTo(
+ "First2 Last2");
reviewers = suggestReviewers(new RestSession(server, user3),
changeId, "user2", 2);
- assertEquals(1, reviewers.size());
- assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+ assertThat(reviewers).hasSize(1);
+ assertThat(Iterables.getOnlyElement(reviewers).account.name).isEqualTo(
+ "First2 Last2");
}
@Test
@@ -152,13 +141,99 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
reviewers = suggestReviewers(new RestSession(server, user1),
changeId, "user2", 2);
- assertTrue(reviewers.isEmpty());
+ assertThat(reviewers).isEmpty();
grantCapability(GlobalCapability.VIEW_ALL_ACCOUNTS, group1);
reviewers = suggestReviewers(new RestSession(server, user1),
changeId, "user2", 2);
- assertEquals(1, reviewers.size());
- assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+ assertThat(reviewers).hasSize(1);
+ assertThat(Iterables.getOnlyElement(reviewers).account.name).isEqualTo(
+ "First2 Last2");
+ }
+
+ @Test
+ @GerritConfig(name = "suggest.maxSuggestedReviewers", value = "2")
+ public void suggestReviewersMaxNbrSuggestions() throws Exception {
+ String changeId = createChange().getChangeId();
+ List<SuggestedReviewerInfo> reviewers =
+ suggestReviewers(changeId, "user", 5);
+ assertThat(reviewers).hasSize(2);
+ }
+
+ @Test
+ @GerritConfig(name = "suggest.fullTextSearch", value = "true")
+ public void suggestReviewersFullTextSearch() throws Exception {
+ String changeId = createChange().getChangeId();
+ List<SuggestedReviewerInfo> reviewers;
+
+ reviewers = suggestReviewers(changeId, "first", 4);
+ assertThat(reviewers).hasSize(3);
+
+ reviewers = suggestReviewers(changeId, "first1", 2);
+ assertThat(reviewers).hasSize(1);
+
+ reviewers = suggestReviewers(changeId, "last", 4);
+ assertThat(reviewers).hasSize(3);
+
+ reviewers = suggestReviewers(changeId, "last1", 2);
+ assertThat(reviewers).hasSize(1);
+
+ reviewers = suggestReviewers(changeId, "fi la", 4);
+ assertThat(reviewers).hasSize(3);
+
+ reviewers = suggestReviewers(changeId, "la fi", 4);
+ assertThat(reviewers).hasSize(3);
+
+ reviewers = suggestReviewers(changeId, "first1 la", 2);
+ assertThat(reviewers).hasSize(1);
+
+ reviewers = suggestReviewers(changeId, "fi last1", 2);
+ assertThat(reviewers).hasSize(1);
+
+ reviewers = suggestReviewers(changeId, "first1 last2", 1);
+ assertThat(reviewers).hasSize(0);
+
+ reviewers = suggestReviewers(changeId, "user", 8);
+ assertThat(reviewers).hasSize(7);
+
+ reviewers = suggestReviewers(changeId, "user1", 2);
+ assertThat(reviewers).hasSize(1);
+
+ reviewers = suggestReviewers(changeId, "example.com", 6);
+ assertThat(reviewers).hasSize(5);
+
+ reviewers = suggestReviewers(changeId, "user1@example.com", 2);
+ assertThat(reviewers).hasSize(1);
+
+ reviewers = suggestReviewers(changeId, "user1 example", 2);
+ assertThat(reviewers).hasSize(1);
+ }
+
+ @Test
+ @GerritConfigs(
+ {@GerritConfig(name = "suggest.fulltextsearch", value = "true"),
+ @GerritConfig(name = "suggest.fullTextSearchMaxMatches", value = "2")
+ })
+ public void suggestReviewersFullTextSearchLimitMaxMatches() throws Exception {
+ String changeId = createChange().getChangeId();
+ List<SuggestedReviewerInfo> reviewers =
+ suggestReviewers(changeId, "user", 3);
+ assertThat(reviewers).hasSize(3);
+ }
+
+ @Test
+ public void suggestReviewersWithoutLimitOptionSpecified() throws Exception {
+ String changeId = createChange().getChangeId();
+ String query = "users3";
+ List<SuggestedReviewerInfo> suggestedReviewerInfos = newGson().fromJson(
+ adminSession.get("/changes/"
+ + changeId
+ + "/suggest_reviewers?q="
+ + query)
+ .getReader(),
+ new TypeToken<List<SuggestedReviewerInfo>>() {}
+ .getType());
+ assertThat(suggestedReviewerInfos).hasSize(1);
}
private List<SuggestedReviewerInfo> suggestReviewers(RestSession session,
@@ -167,7 +242,7 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
session.get("/changes/"
+ changeId
+ "/suggest_reviewers?q="
- + query
+ + Url.encode(query)
+ "&n="
+ n)
.getReader(),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
index d2174bceed..304abc835a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
@@ -14,26 +14,21 @@
package com.google.gerrit.acceptance.rest.config;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH;
import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH_ALL;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.allow;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.config.ListCaches.CacheInfo;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PostCaches;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
+import com.google.gerrit.server.project.Util;
import org.apache.http.HttpStatus;
import org.junit.Test;
@@ -43,115 +38,106 @@ import java.util.Arrays;
public class CacheOperationsIT extends AbstractDaemonTest {
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
@Test
- public void flushAll() throws IOException {
+ public void flushAll() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/project_list");
CacheInfo cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(cacheInfo.entries.mem.longValue() > 0);
+ assertThat(cacheInfo.entries.mem.longValue()).isGreaterThan((long) 0);
r = adminSession.post("/config/server/caches/", new PostCaches.Input(FLUSH_ALL));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
r = adminSession.get("/config/server/caches/project_list");
cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertNull(cacheInfo.entries.mem);
+ assertThat(cacheInfo.entries.mem).isNull();
}
@Test
- public void flushAll_Forbidden() throws IOException {
+ public void flushAll_Forbidden() throws Exception {
RestResponse r = userSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH_ALL));
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
- public void flushAll_BadRequest() throws IOException {
+ public void flushAll_BadRequest() throws Exception {
RestResponse r = adminSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH_ALL, Arrays.asList("projects")));
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
}
@Test
- public void flush() throws IOException {
+ public void flush() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/project_list");
CacheInfo cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(cacheInfo.entries.mem.longValue() > 0);
+ assertThat(cacheInfo.entries.mem.longValue()).isGreaterThan((long)0);
r = adminSession.get("/config/server/caches/projects");
cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(cacheInfo.entries.mem.longValue() > 1);
+ assertThat(cacheInfo.entries.mem.longValue()).isGreaterThan((long)1);
r = adminSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH, Arrays.asList("accounts", "project_list")));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
r = adminSession.get("/config/server/caches/project_list");
cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertNull(cacheInfo.entries.mem);
+ assertThat(cacheInfo.entries.mem).isNull();
r = adminSession.get("/config/server/caches/projects");
cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(cacheInfo.entries.mem.longValue() > 1);
+ assertThat(cacheInfo.entries.mem.longValue()).isGreaterThan((long)1);
}
@Test
- public void flush_Forbidden() throws IOException {
+ public void flush_Forbidden() throws Exception {
RestResponse r = userSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH, Arrays.asList("projects")));
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
- public void flush_BadRequest() throws IOException {
+ public void flush_BadRequest() throws Exception {
RestResponse r = adminSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH));
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
}
@Test
- public void flush_UnprocessableEntity() throws IOException {
+ public void flush_UnprocessableEntity() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/projects");
CacheInfo cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(cacheInfo.entries.mem.longValue() > 0);
+ assertThat(cacheInfo.entries.mem.longValue()).isGreaterThan((long)0);
r = adminSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH, Arrays.asList("projects", "unprocessable")));
- assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
r.consume();
r = adminSession.get("/config/server/caches/projects");
cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(cacheInfo.entries.mem.longValue() > 0);
+ assertThat(cacheInfo.entries.mem.longValue()).isGreaterThan((long)0);
}
@Test
- public void flushWebSessions_Forbidden() throws IOException {
+ public void flushWebSessions_Forbidden() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
AccountGroup.UUID registeredUsers =
SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
- allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
saveProjectConfig(cfg);
RestResponse r = userSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH, Arrays.asList("projects")));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
r = userSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH, Arrays.asList("web_sessions")));
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
private void saveProjectConfig(ProjectConfig cfg) throws IOException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
index aa8d7ba5b0..9f7b419dd9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
@@ -14,23 +14,18 @@
package com.google.gerrit.acceptance.rest.config;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.allow;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
+import com.google.gerrit.server.project.Util;
import org.apache.http.HttpStatus;
import org.junit.Test;
@@ -39,69 +34,60 @@ import java.io.IOException;
public class FlushCacheIT extends AbstractDaemonTest {
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
@Test
- public void flushCache() throws IOException {
+ public void flushCache() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/groups");
CacheInfo result = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertTrue(result.entries.mem.longValue() > 0);
+ assertThat(result.entries.mem.longValue()).isGreaterThan((long)0);
r = adminSession.post("/config/server/caches/groups/flush");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
r = adminSession.get("/config/server/caches/groups");
result = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertNull(result.entries.mem);
+ assertThat(result.entries.mem).isNull();
}
@Test
- public void flushCache_Forbidden() throws IOException {
+ public void flushCache_Forbidden() throws Exception {
RestResponse r = userSession.post("/config/server/caches/accounts/flush");
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
- public void flushCache_NotFound() throws IOException {
+ public void flushCache_NotFound() throws Exception {
RestResponse r = adminSession.post("/config/server/caches/nonExisting/flush");
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void flushCacheWithGerritPrefix() throws IOException {
+ public void flushCacheWithGerritPrefix() throws Exception {
RestResponse r = adminSession.post("/config/server/caches/gerrit-accounts/flush");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
@Test
- public void flushWebSessionsCache() throws IOException {
+ public void flushWebSessionsCache() throws Exception {
RestResponse r = adminSession.post("/config/server/caches/web_sessions/flush");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
@Test
- public void flushWebSessionsCache_Forbidden() throws IOException {
+ public void flushWebSessionsCache_Forbidden() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
AccountGroup.UUID registeredUsers =
SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
- allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers);
+ Util.allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers);
saveProjectConfig(cfg);
RestResponse r = userSession.post("/config/server/caches/accounts/flush");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
r = userSession.post("/config/server/caches/web_sessions/flush");
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
private void saveProjectConfig(ProjectConfig cfg) throws IOException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
index 398d0c851f..f59752c851 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
@@ -14,10 +14,7 @@
package com.google.gerrit.acceptance.rest.config;
-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 com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
@@ -27,49 +24,47 @@ import com.google.gerrit.server.config.ListCaches.CacheType;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
-
public class GetCacheIT extends AbstractDaemonTest {
@Test
- public void getCache() throws IOException {
+ public void getCache() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/accounts");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
CacheInfo result = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertEquals("accounts", result.name);
- assertEquals(CacheType.MEM, result.type);
- assertEquals(1, result.entries.mem.longValue());
- assertNotNull(result.averageGet);
- assertTrue(result.averageGet.endsWith("s"));
- assertNull(result.entries.disk);
- assertNull(result.entries.space);
- assertTrue(result.hitRatio.mem >= 0);
- assertTrue(result.hitRatio.mem <= 100);
- assertNull(result.hitRatio.disk);
+ assertThat(result.name).isEqualTo("accounts");
+ assertThat(result.type).isEqualTo(CacheType.MEM);
+ assertThat(result.entries.mem.longValue()).isEqualTo(1);
+ assertThat(result.averageGet).isNotNull();
+ assertThat(result.averageGet).endsWith("s");
+ assertThat(result.entries.disk).isNull();
+ assertThat(result.entries.space).isNull();
+ assertThat(result.hitRatio.mem).isAtLeast(0);
+ assertThat(result.hitRatio.mem).isAtMost(100);
+ assertThat(result.hitRatio.disk).isNull();
userSession.get("/config/server/version").consume();
r = adminSession.get("/config/server/caches/accounts");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = newGson().fromJson(r.getReader(), CacheInfo.class);
- assertEquals(2, result.entries.mem.longValue());
+ assertThat(result.entries.mem.longValue()).isEqualTo(2);
}
@Test
- public void getCache_Forbidden() throws IOException {
+ public void getCache_Forbidden() throws Exception {
RestResponse r = userSession.get("/config/server/caches/accounts");
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
- public void getCache_NotFound() throws IOException {
+ public void getCache_NotFound() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/nonExisting");
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void getCacheWithGerritPrefix() throws IOException {
+ public void getCacheWithGerritPrefix() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/gerrit-accounts");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
index 2feeab84eb..acd900c5cd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
@@ -14,8 +14,7 @@
package com.google.gerrit.acceptance.rest.config;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
@@ -25,33 +24,32 @@ import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
public class GetTaskIT extends AbstractDaemonTest {
@Test
- public void getTask() throws IOException {
+ public void getTask() throws Exception {
RestResponse r =
adminSession.get("/config/server/tasks/" + getLogFileCompressorTaskId());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
TaskInfo info =
newGson().fromJson(r.getReader(),
new TypeToken<TaskInfo>() {}.getType());
- assertNotNull(info.id);
+ assertThat(info.id).isNotNull();
Long.parseLong(info.id, 16);
- assertEquals("Log File Compressor", info.command);
- assertNotNull(info.startTime);
+ assertThat(info.command).isEqualTo("Log File Compressor");
+ assertThat(info.startTime).isNotNull();
}
@Test
- public void getTask_NotFound() throws IOException {
+ public void getTask_NotFound() throws Exception {
RestResponse r =
userSession.get("/config/server/tasks/" + getLogFileCompressorTaskId());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
- private String getLogFileCompressorTaskId() throws IOException {
+ private String getLogFileCompressorTaskId() throws Exception {
RestResponse r = adminSession.get("/config/server/tasks/");
List<TaskInfo> result =
newGson().fromJson(r.getReader(),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
index 90cb7ccf09..26299e23a5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
@@ -14,8 +14,7 @@
package com.google.gerrit.acceptance.rest.config;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
@@ -25,40 +24,39 @@ import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
public class KillTaskIT extends AbstractDaemonTest {
@Test
- public void killTask() throws IOException {
+ public void killTask() throws Exception {
RestResponse r = adminSession.get("/config/server/tasks/");
List<TaskInfo> result = newGson().fromJson(r.getReader(),
new TypeToken<List<TaskInfo>>() {}.getType());
r.consume();
int taskCount = result.size();
- assertTrue(taskCount > 0);
+ assertThat(taskCount).isGreaterThan(0);
r = adminSession.delete("/config/server/tasks/" + result.get(0).id);
- assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
r.consume();
r = adminSession.get("/config/server/tasks/");
result = newGson().fromJson(r.getReader(),
new TypeToken<List<TaskInfo>>() {}.getType());
r.consume();
- assertEquals(taskCount - 1, result.size());
+ assertThat(result).hasSize(taskCount - 1);
}
@Test
- public void killTask_NotFound() throws IOException {
+ public void killTask_NotFound() throws Exception {
RestResponse r = adminSession.get("/config/server/tasks/");
List<TaskInfo> result = newGson().fromJson(r.getReader(),
new TypeToken<List<TaskInfo>>() {}.getType());
r.consume();
- assertTrue(result.size() > 0);
+ assertThat(result.size()).isGreaterThan(0);
r = userSession.delete("/config/server/tasks/" + result.get(0).id);
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
index db7fb7ff05..0d0a94e08c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
@@ -14,11 +14,8 @@
package com.google.gerrit.acceptance.rest.config;
+import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
-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 com.google.common.collect.Ordering;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -31,7 +28,6 @@ import org.apache.http.HttpStatus;
import org.eclipse.jgit.util.Base64;
import org.junit.Test;
-import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -39,65 +35,65 @@ import java.util.Map;
public class ListCachesIT extends AbstractDaemonTest {
@Test
- public void listCaches() throws IOException {
+ public void listCaches() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, CacheInfo> result =
newGson().fromJson(r.getReader(),
new TypeToken<Map<String, CacheInfo>>() {}.getType());
- assertTrue(result.containsKey("accounts"));
+ assertThat(result).containsKey("accounts");
CacheInfo accountsCacheInfo = result.get("accounts");
- assertEquals(CacheType.MEM, accountsCacheInfo.type);
- assertEquals(1, accountsCacheInfo.entries.mem.longValue());
- assertNotNull(accountsCacheInfo.averageGet);
- assertTrue(accountsCacheInfo.averageGet.endsWith("s"));
- assertNull(accountsCacheInfo.entries.disk);
- assertNull(accountsCacheInfo.entries.space);
- assertTrue(accountsCacheInfo.hitRatio.mem >= 0);
- assertTrue(accountsCacheInfo.hitRatio.mem <= 100);
- assertNull(accountsCacheInfo.hitRatio.disk);
+ assertThat(accountsCacheInfo.type).isEqualTo(CacheType.MEM);
+ assertThat(accountsCacheInfo.entries.mem.longValue()).isEqualTo(1);
+ assertThat(accountsCacheInfo.averageGet).isNotNull();
+ assertThat(accountsCacheInfo.averageGet).endsWith("s");
+ assertThat(accountsCacheInfo.entries.disk).isNull();
+ assertThat(accountsCacheInfo.entries.space).isNull();
+ assertThat(accountsCacheInfo.hitRatio.mem).isAtLeast(0);
+ assertThat(accountsCacheInfo.hitRatio.mem).isAtMost(100);
+ assertThat(accountsCacheInfo.hitRatio.disk).isNull();
userSession.get("/config/server/version").consume();
r = adminSession.get("/config/server/caches/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = newGson().fromJson(r.getReader(),
new TypeToken<Map<String, CacheInfo>>() {}.getType());
- assertEquals(2, result.get("accounts").entries.mem.longValue());
+ assertThat(result.get("accounts").entries.mem.longValue()).isEqualTo(2);
}
@Test
- public void listCaches_Forbidden() throws IOException {
+ public void listCaches_Forbidden() throws Exception {
RestResponse r = userSession.get("/config/server/caches/");
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
- public void listCacheNames() throws IOException {
+ public void listCacheNames() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/?format=LIST");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
List<String> result =
newGson().fromJson(r.getReader(),
new TypeToken<List<String>>() {}.getType());
- assertTrue(result.contains("accounts"));
- assertTrue(result.contains("projects"));
- assertTrue(Ordering.natural().isOrdered(result));
+ assertThat(result).contains("accounts");
+ assertThat(result).contains("projects");
+ assertThat(Ordering.natural().isOrdered(result)).isTrue();
}
@Test
- public void listCacheNamesTextList() throws IOException {
+ public void listCacheNamesTextList() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/?format=TEXT_LIST");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
String result = new String(Base64.decode(r.getEntityContent()), UTF_8.name());
List<String> list = Arrays.asList(result.split("\n"));
- assertTrue(list.contains("accounts"));
- assertTrue(list.contains("projects"));
- assertTrue(Ordering.natural().isOrdered(list));
+ assertThat(list).contains("accounts");
+ assertThat(list).contains("projects");
+ assertThat(Ordering.natural().isOrdered(list)).isTrue();
}
@Test
- public void listCaches_BadRequest() throws IOException {
+ public void listCaches_BadRequest() throws Exception {
RestResponse r = adminSession.get("/config/server/caches/?format=NONSENSE");
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
index 205a701a0d..58f336103b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
@@ -14,9 +14,7 @@
package com.google.gerrit.acceptance.rest.config;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
@@ -26,40 +24,39 @@ import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
public class ListTasksIT extends AbstractDaemonTest {
@Test
- public void listTasks() throws IOException {
+ public void listTasks() throws Exception {
RestResponse r = adminSession.get("/config/server/tasks/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
List<TaskInfo> result =
newGson().fromJson(r.getReader(),
new TypeToken<List<TaskInfo>>() {}.getType());
- assertTrue(result.size() > 0);
+ assertThat(result).isNotEmpty();
boolean foundLogFileCompressorTask = false;
for (TaskInfo info : result) {
if ("Log File Compressor".equals(info.command)) {
foundLogFileCompressorTask = true;
}
- assertNotNull(info.id);
+ assertThat(info.id).isNotNull();
Long.parseLong(info.id, 16);
- assertNotNull(info.command);
- assertNotNull(info.startTime);
+ assertThat(info.command).isNotNull();
+ assertThat(info.startTime).isNotNull();
}
- assertTrue(foundLogFileCompressorTask);
+ assertThat(foundLogFileCompressorTask).isTrue();
}
@Test
- public void listTasksWithoutViewQueueCapability() throws IOException {
+ public void listTasksWithoutViewQueueCapability() throws Exception {
RestResponse r = userSession.get("/config/server/tasks/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
List<TaskInfo> result =
newGson().fromJson(r.getReader(),
new TypeToken<List<TaskInfo>>() {}.getType());
- assertTrue(result.isEmpty());
+ assertThat(result).isEmpty();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java
index 3646e57a80..b9778b8164 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java
@@ -14,12 +14,9 @@
package com.google.gerrit.acceptance.rest.group;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.account.AccountAssert.assertAccountInfo;
import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -27,13 +24,11 @@ import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.AddIncludedGroups;
import com.google.gerrit.server.group.AddMembers;
import com.google.gerrit.server.group.CreateGroup;
@@ -41,12 +36,8 @@ import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
import org.apache.http.HttpStatus;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@@ -56,55 +47,36 @@ import java.util.Map;
import java.util.Set;
public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
-
- @Inject
- private SchemaFactory<ReviewDb> reviewDbProvider;
-
- @Inject
- private GroupCache groupCache;
-
- private ReviewDb db;
-
- @Before
- public void setUp() throws Exception {
- db = reviewDbProvider.open();
- }
-
- @After
- public void tearDown() {
- db.close();
- }
-
@Test
- public void addToNonExistingGroup_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- PUT("/groups/non-existing/members/admin").getStatusCode());
+ public void addToNonExistingGroup_NotFound() throws Exception {
+ assertThat(PUT("/groups/non-existing/members/admin").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void removeFromNonExistingGroup_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- DELETE("/groups/non-existing/members/admin"));
+ public void removeFromNonExistingGroup_NotFound() throws Exception {
+ assertThat(DELETE("/groups/non-existing/members/admin"))
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
public void addRemoveMember() throws Exception {
RestResponse r = PUT("/groups/Administrators/members/user");
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
AccountInfo ai = newGson().fromJson(r.getReader(), AccountInfo.class);
assertAccountInfo(user, ai);
assertMembers("Administrators", admin, user);
r.consume();
- assertEquals(HttpStatus.SC_NO_CONTENT,
- DELETE("/groups/Administrators/members/user"));
+ assertThat(DELETE("/groups/Administrators/members/user"))
+ .isEqualTo(HttpStatus.SC_NO_CONTENT);
assertMembers("Administrators", admin);
}
@Test
- public void addExistingMember_OK() throws IOException {
- assertEquals(HttpStatus.SC_OK,
- PUT("/groups/Administrators/members/admin").getStatusCode());
+ public void addExistingMember_OK() throws Exception {
+ assertThat(PUT("/groups/Administrators/members/admin").getStatusCode())
+ .isEqualTo(HttpStatus.SC_OK);
}
@Test
@@ -126,14 +98,14 @@ public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
public void includeRemoveGroup() throws Exception {
group("newGroup");
RestResponse r = PUT("/groups/Administrators/groups/newGroup");
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
GroupInfo i = newGson().fromJson(r.getReader(), GroupInfo.class);
r.consume();
assertGroupInfo(groupCache.get(new AccountGroup.NameKey("newGroup")), i);
assertIncludes("Administrators", "newGroup");
- assertEquals(HttpStatus.SC_NO_CONTENT,
- DELETE("/groups/Administrators/groups/newGroup"));
+ assertThat(DELETE("/groups/Administrators/groups/newGroup"))
+ .isEqualTo(HttpStatus.SC_NO_CONTENT);
assertNoIncludes("Administrators");
}
@@ -141,8 +113,8 @@ public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
public void includeExistingGroup_OK() throws Exception {
group("newGroup");
PUT("/groups/Administrators/groups/newGroup").consume();
- assertEquals(HttpStatus.SC_OK,
- PUT("/groups/Administrators/groups/newGroup").getStatusCode());
+ assertThat(PUT("/groups/Administrators/groups/newGroup").getStatusCode())
+ .isEqualTo(HttpStatus.SC_OK);
}
@Test
@@ -193,9 +165,9 @@ public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
for (AccountGroupMember m : all) {
ids.add(m.getAccountId());
}
- assertTrue(ids.size() == members.length);
+ assertThat((Iterable<?>)ids).hasSize(members.length);
for (TestAccount a : members) {
- assertTrue(ids.contains(a.id));
+ assertThat((Iterable<?>)ids).contains(a.id);
}
}
@@ -207,10 +179,10 @@ public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
for (TestAccount a : members) {
AccountInfo i = infoById.get(a.id.get());
- assertNotNull(i);
+ assertThat(i).isNotNull();
assertAccountInfo(a, i);
}
- assertEquals(ai.size(), members.length);
+ assertThat(ai).hasSize(members.length);
}
private void assertIncludes(String group, String... includes)
@@ -222,11 +194,11 @@ public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
for (AccountGroupById m : all) {
ids.add(m.getIncludeUUID());
}
- assertTrue(ids.size() == includes.length);
+ assertThat((Iterable<?>)ids).hasSize(includes.length);
for (String i : includes) {
AccountGroup.UUID id = groupCache.get(
new AccountGroup.NameKey(i)).getGroupUUID();
- assertTrue(ids.contains(id));
+ assertThat((Iterable<?>)ids).contains(id);
}
}
@@ -238,16 +210,16 @@ public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
for (String name : includes) {
GroupInfo i = groupsByName.get(name);
- assertNotNull(i);
+ assertThat(i).isNotNull();
assertGroupInfo(groupCache.get(new AccountGroup.NameKey(name)), i);
}
- assertEquals(gi.size(), includes.length);
+ assertThat(gi).hasSize(includes.length);
}
private void assertNoIncludes(String group) throws OrmException {
AccountGroup g = groupCache.get(new AccountGroup.NameKey(group));
Iterator<AccountGroupById> it =
db.accountGroupById().byGroup(g.getId()).iterator();
- assertFalse(it.hasNext());
+ assertThat(it.hasNext()).isFalse();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
index da34c1de3f..726c7cf7e9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
@@ -16,7 +16,9 @@ java_library(
'//gerrit-extension-api:api',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
+ '//lib:guava',
'//lib:gwtorm',
'//lib:junit',
+ '//lib:truth',
],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java
index a6954b28e9..7de450b738 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java
@@ -14,45 +14,33 @@
package com.google.gerrit.acceptance.rest.group;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.CreateGroup;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
-
public class CreateGroupIT extends AbstractDaemonTest {
-
- @Inject
- private GroupCache groupCache;
-
@Test
- public void testCreateGroup() throws IOException {
+ public void testCreateGroup() throws Exception {
final String newGroupName = "newGroup";
RestResponse r = adminSession.put("/groups/" + newGroupName);
GroupInfo g = newGson().fromJson(r.getReader(), GroupInfo.class);
- assertEquals(newGroupName, g.name);
+ assertThat(g.name).isEqualTo(newGroupName);
AccountGroup group = groupCache.get(new AccountGroup.NameKey(newGroupName));
- assertNotNull(group);
+ assertThat(group).isNotNull();
assertGroupInfo(group, g);
}
@Test
- public void testCreateGroupWithProperties() throws IOException {
+ public void testCreateGroupWithProperties() throws Exception {
final String newGroupName = "newGroup";
CreateGroup.Input in = new CreateGroup.Input();
in.description = "Test description";
@@ -60,24 +48,23 @@ public class CreateGroupIT extends AbstractDaemonTest {
in.ownerId = groupCache.get(new AccountGroup.NameKey("Administrators")).getGroupUUID().get();
RestResponse r = adminSession.put("/groups/" + newGroupName, in);
GroupInfo g = newGson().fromJson(r.getReader(), GroupInfo.class);
- assertEquals(newGroupName, g.name);
+ assertThat(g.name).isEqualTo(newGroupName);
AccountGroup group = groupCache.get(new AccountGroup.NameKey(newGroupName));
- assertEquals(in.description, group.getDescription());
- assertEquals(in.visibleToAll, group.isVisibleToAll());
- assertEquals(in.ownerId, group.getOwnerGroupUUID().get());
+ assertThat(group.getDescription()).isEqualTo(in.description);
+ assertThat(group.isVisibleToAll()).isEqualTo(in.visibleToAll);
+ assertThat(group.getOwnerGroupUUID().get()).isEqualTo(in.ownerId);
}
@Test
- public void testCreateGroupWithoutCapability_Forbidden() throws OrmException,
- JSchException, IOException {
+ public void testCreateGroupWithoutCapability_Forbidden() throws Exception {
RestResponse r = (new RestSession(server, user)).put("/groups/newGroup");
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
public void testCreateGroupWhenGroupAlreadyExists_Conflict()
- throws OrmException, JSchException, IOException {
+ throws Exception {
RestResponse r = adminSession.put("/groups/Administrators");
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
index 0d229ca529..8365d7988d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
@@ -14,7 +14,8 @@
package com.google.gerrit.acceptance.rest.group;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -24,13 +25,9 @@ import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.OrmException;
-
-import com.jcraft.jsch.JSchException;
import org.junit.Test;
-import java.io.IOException;
import java.util.Map;
import java.util.Set;
@@ -44,33 +41,35 @@ import java.util.Set;
public class DefaultGroupsIT extends AbstractDaemonTest {
@Test
- public void defaultGroupsCreated_ssh() throws JSchException, IOException {
+ public void defaultGroupsCreated_ssh() throws Exception {
SshSession session = new SshSession(server, admin);
String result = session.exec("gerrit ls-groups");
- assertTrue(result.contains("Administrators"));
- assertTrue(result.contains("Non-Interactive Users"));
+ assert_().withFailureMessage(session.getError())
+ .that(session.hasError()).isFalse();
+ assertThat(result).contains("Administrators");
+ assertThat(result).contains("Non-Interactive Users");
session.close();
}
@Test
- public void defaultGroupsCreated_rest() throws IOException {
+ public void defaultGroupsCreated_rest() throws Exception {
RestSession session = new RestSession(server, admin);
RestResponse r = session.get("/groups/");
Map<String, GroupInfo> result =
newGson().fromJson(r.getReader(),
new TypeToken<Map<String, GroupInfo>>() {}.getType());
Set<String> names = result.keySet();
- assertTrue(names.contains("Administrators"));
- assertTrue(names.contains("Non-Interactive Users"));
+ assertThat((Iterable<?>)names).contains("Administrators");
+ assertThat((Iterable<?>)names).contains("Non-Interactive Users");
}
@Test
- public void defaultGroupsCreated_internals() throws OrmException {
+ public void defaultGroupsCreated_internals() throws Exception {
Set<String> names = Sets.newHashSet();
for (AccountGroup g : db.accountGroups().all()) {
names.add(g.getName());
}
- assertTrue(names.contains("Administrators"));
- assertTrue(names.contains("Non-Interactive Users"));
+ assertThat((Iterable<?>)names).contains("Administrators");
+ assertThat((Iterable<?>)names).contains("Non-Interactive Users");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java
index 91c9898490..8dfb5ffd86 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java
@@ -19,21 +19,15 @@ import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInf
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
-import com.google.inject.Inject;
import org.junit.Test;
import java.io.IOException;
public class GetGroupIT extends AbstractDaemonTest {
-
- @Inject
- private GroupCache groupCache;
-
@Test
- public void testGetGroup() throws IOException {
+ public void testGetGroup() throws Exception {
AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
// by UUID
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java
index 70107fe85e..97503f4d8b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java
@@ -14,8 +14,8 @@
package com.google.gerrit.acceptance.rest.group;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -27,22 +27,24 @@ public class GroupAssert {
public static void assertGroups(Iterable<String> expected, Set<String> actual) {
for (String g : expected) {
- assertTrue("missing group " + g, actual.remove(g));
+ assert_().withFailureMessage("missing group " + g)
+ .that(actual.remove(g)).isTrue();
}
- assertTrue("unexpected groups: " + actual, actual.isEmpty());
+ assert_().withFailureMessage("unexpected groups: " + actual)
+ .that((Iterable<?>)actual).isEmpty();
}
public static void assertGroupInfo(AccountGroup group, GroupInfo info) {
if (info.name != null) {
// 'name' is not set if returned in a map
- assertEquals(group.getName(), info.name);
+ assertThat(info.name).isEqualTo(group.getName());
}
- assertEquals(group.getGroupUUID().get(), Url.decode(info.id));
- assertEquals(Integer.valueOf(group.getId().get()), info.groupId);
- assertEquals("#/admin/groups/uuid-" + Url.encode(group.getGroupUUID().get()), info.url);
- assertEquals(group.isVisibleToAll(), toBoolean(info.options.visibleToAll));
- assertEquals(group.getDescription(), info.description);
- assertEquals(group.getOwnerGroupUUID().get(), Url.decode(info.ownerId));
+ assertThat(Url.decode(info.id)).isEqualTo(group.getGroupUUID().get());
+ assertThat(info.groupId).isEqualTo(Integer.valueOf(group.getId().get()));
+ assertThat(info.url).isEqualTo("#/admin/groups/uuid-" + Url.encode(group.getGroupUUID().get()));
+ assertThat(toBoolean(info.options.visibleToAll)).isEqualTo(group.isVisibleToAll());
+ assertThat(info.description).isEqualTo(group.getDescription());
+ assertThat(Url.decode(info.ownerId)).isEqualTo(group.getOwnerGroupUUID().get());
}
public static boolean toBoolean(Boolean b) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java
index c6309190d3..e9dd776367 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java
@@ -14,17 +14,14 @@
package com.google.gerrit.acceptance.rest.group;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
import static com.google.gerrit.acceptance.rest.group.GroupAssert.toBoolean;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
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.GroupJson.GroupInfo;
import com.google.gerrit.server.group.GroupOptionsInfo;
import com.google.gerrit.server.group.PutDescription;
@@ -32,46 +29,39 @@ import com.google.gerrit.server.group.PutName;
import com.google.gerrit.server.group.PutOptions;
import com.google.gerrit.server.group.PutOwner;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.inject.Inject;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
-
public class GroupPropertiesIT extends AbstractDaemonTest {
-
- @Inject
- private GroupCache groupCache;
-
@Test
- public void testGroupName() throws IOException {
+ public void testGroupName() throws Exception {
AccountGroup.NameKey adminGroupName = new AccountGroup.NameKey("Administrators");
String url = "/groups/" + groupCache.get(adminGroupName).getGroupUUID().get() + "/name";
// get name
RestResponse r = adminSession.get(url);
String name = newGson().fromJson(r.getReader(), String.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertEquals("Administrators", name);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(name).isEqualTo("Administrators");
r.consume();
// set name with name conflict
String newGroupName = "newGroup";
r = adminSession.put("/groups/" + newGroupName);
r.consume();
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
PutName.Input in = new PutName.Input();
in.name = newGroupName;
r = adminSession.put(url, in);
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
r.consume();
// set name to same name
in = new PutName.Input();
in.name = "Administrators";
r = adminSession.put(url, in);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
// rename
@@ -79,15 +69,15 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
in.name = "Admins";
r = adminSession.put(url, in);
String newName = newGson().fromJson(r.getReader(), String.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertNotNull(groupCache.get(new AccountGroup.NameKey(in.name)));
- assertNull(groupCache.get(adminGroupName));
- assertEquals(in.name, newName);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(groupCache.get(new AccountGroup.NameKey(in.name))).isNotNull();
+ assertThat(groupCache.get(adminGroupName)).isNull();
+ assertThat(newName).isEqualTo(in.name);
r.consume();
}
@Test
- public void testGroupDescription() throws IOException {
+ public void testGroupDescription() throws Exception {
AccountGroup.NameKey adminGroupName = new AccountGroup.NameKey("Administrators");
AccountGroup adminGroup = groupCache.get(adminGroupName);
String url = "/groups/" + adminGroup.getGroupUUID().get() + "/description";
@@ -95,8 +85,8 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
// get description
RestResponse r = adminSession.get(url);
String description = newGson().fromJson(r.getReader(), String.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertEquals(adminGroup.getDescription(), description);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(description).isEqualTo(adminGroup.getDescription());
r.consume();
// set description
@@ -104,29 +94,29 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
in.description = "All users that can administrate the Gerrit Server.";
r = adminSession.put(url, in);
String newDescription = newGson().fromJson(r.getReader(), String.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertEquals(in.description, newDescription);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(newDescription).isEqualTo(in.description);
adminGroup = groupCache.get(adminGroupName);
- assertEquals(in.description, adminGroup.getDescription());
+ assertThat(adminGroup.getDescription()).isEqualTo(in.description);
r.consume();
// delete description
r = adminSession.delete(url);
- assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
adminGroup = groupCache.get(adminGroupName);
- assertNull(adminGroup.getDescription());
+ assertThat(adminGroup.getDescription()).isNull();
// set description to empty string
in = new PutDescription.Input();
in.description = "";
r = adminSession.put(url, in);
- assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
adminGroup = groupCache.get(adminGroupName);
- assertNull(adminGroup.getDescription());
+ assertThat(adminGroup.getDescription()).isNull();
}
@Test
- public void testGroupOptions() throws IOException {
+ public void testGroupOptions() throws Exception {
AccountGroup.NameKey adminGroupName = new AccountGroup.NameKey("Administrators");
AccountGroup adminGroup = groupCache.get(adminGroupName);
String url = "/groups/" + adminGroup.getGroupUUID().get() + "/options";
@@ -134,8 +124,8 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
// get options
RestResponse r = adminSession.get(url);
GroupOptionsInfo options = newGson().fromJson(r.getReader(), GroupOptionsInfo.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertEquals(adminGroup.isVisibleToAll(), toBoolean(options.visibleToAll));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(toBoolean(options.visibleToAll)).isEqualTo(adminGroup.isVisibleToAll());
r.consume();
// set options
@@ -143,15 +133,15 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
in.visibleToAll = !adminGroup.isVisibleToAll();
r = adminSession.put(url, in);
GroupOptionsInfo newOptions = newGson().fromJson(r.getReader(), GroupOptionsInfo.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertEquals(in.visibleToAll, toBoolean(newOptions.visibleToAll));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(toBoolean(newOptions.visibleToAll)).isEqualTo(in.visibleToAll);
adminGroup = groupCache.get(adminGroupName);
- assertEquals(in.visibleToAll, adminGroup.isVisibleToAll());
+ assertThat(adminGroup.isVisibleToAll()).isEqualTo(in.visibleToAll);
r.consume();
}
@Test
- public void testGroupOwner() throws IOException {
+ public void testGroupOwner() throws Exception {
AccountGroup.NameKey adminGroupName = new AccountGroup.NameKey("Administrators");
AccountGroup adminGroup = groupCache.get(adminGroupName);
String url = "/groups/" + adminGroup.getGroupUUID().get() + "/owner";
@@ -159,7 +149,7 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
// get owner
RestResponse r = adminSession.get(url);
GroupInfo options = newGson().fromJson(r.getReader(), GroupInfo.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
assertGroupInfo(groupCache.get(adminGroup.getOwnerGroupUUID()), options);
r.consume();
@@ -168,30 +158,29 @@ public class GroupPropertiesIT extends AbstractDaemonTest {
in.owner = "Registered Users";
r = adminSession.put(url, in);
GroupInfo newOwner = newGson().fromJson(r.getReader(), GroupInfo.class);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- assertEquals(in.owner, newOwner.name);
- assertEquals(
- SystemGroupBackend.getGroup(SystemGroupBackend.REGISTERED_USERS).getName(),
- newOwner.name);
- assertEquals(
- SystemGroupBackend.REGISTERED_USERS.get(),
- Url.decode(newOwner.id));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(newOwner.name).isEqualTo(in.owner);
+ assertThat(newOwner.name).isEqualTo(
+ SystemGroupBackend.getGroup(SystemGroupBackend.REGISTERED_USERS).getName());
+ assertThat(SystemGroupBackend.REGISTERED_USERS.get())
+ .isEqualTo(Url.decode(newOwner.id));
r.consume();
// set owner by UUID
in = new PutOwner.Input();
in.owner = adminGroup.getGroupUUID().get();
r = adminSession.put(url, in);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
adminGroup = groupCache.get(adminGroupName);
- assertEquals(in.owner, groupCache.get(adminGroup.getOwnerGroupUUID()).getGroupUUID().get());
+ assertThat(groupCache.get(adminGroup.getOwnerGroupUUID()).getGroupUUID().get())
+ .isEqualTo(in.owner);
r.consume();
// set non existing owner
in = new PutOwner.Input();
in.owner = "Non-Existing Group";
r = adminSession.put(url, in);
- assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
r.consume();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java
index d639f5475a..5dc49c6dfc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java
@@ -14,8 +14,7 @@
package com.google.gerrit.acceptance.rest.group;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
@@ -37,13 +36,13 @@ public class ListGroupIncludesIT extends AbstractDaemonTest {
@Test
public void listNonExistingGroupIncludes_NotFound() throws Exception {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- adminSession.get("/groups/non-existing/groups/").getStatusCode());
+ assertThat(adminSession.get("/groups/non-existing/groups/").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
public void listEmptyGroupIncludes() throws Exception {
- assertTrue(GET("/groups/Administrators/groups/").isEmpty());
+ assertThat(GET("/groups/Administrators/groups/")).isEmpty();
}
@Test
@@ -63,19 +62,19 @@ public class ListGroupIncludesIT extends AbstractDaemonTest {
PUT("/groups/Administrators/groups/gx");
PUT("/groups/Administrators/groups/gy");
- assertEquals(GET_ONE("/groups/Administrators/groups/gx").name, "gx");
+ assertThat(GET_ONE("/groups/Administrators/groups/gx").name).isEqualTo("gx");
}
private List<GroupInfo> GET(String endpoint) throws IOException {
RestResponse r = adminSession.get(endpoint);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
return newGson().fromJson(r.getReader(),
new TypeToken<List<GroupInfo>>() {}.getType());
}
private GroupInfo GET_ONE(String endpoint) throws IOException {
RestResponse r = adminSession.get(endpoint);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
return newGson().fromJson(r.getReader(), GroupInfo.class);
}
@@ -98,10 +97,10 @@ public class ListGroupIncludesIT extends AbstractDaemonTest {
return info.name;
}
});
- assertTrue(includeNames.contains(name));
+ assertThat((Iterable<?>)includeNames).contains(name);
for (String n : names) {
- assertTrue(includeNames.contains(n));
+ assertThat((Iterable<?>)includeNames).contains(n);
}
- assertEquals(includes.size(), names.length + 1);
+ assertThat(includes).hasSize(names.length + 1);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java
index 4db13e5760..80fb960147 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java
@@ -14,15 +14,14 @@
package com.google.gerrit.acceptance.rest.group;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.server.group.CreateGroup;
import com.google.gson.reflect.TypeToken;
@@ -37,14 +36,14 @@ public class ListGroupMembersIT extends AbstractDaemonTest {
@Test
public void listNonExistingGroupMembers_NotFound() throws Exception {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- adminSession.get("/groups/non-existing/members/").getStatusCode());
+ assertThat(adminSession.get("/groups/non-existing/members/").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
public void listEmptyGroupMembers() throws Exception {
group("empty", "Administrators");
- assertTrue(GET("/groups/empty/members/").isEmpty());
+ assertThat(GET("/groups/empty/members/")).isEmpty();
}
@Test
@@ -57,9 +56,9 @@ public class ListGroupMembersIT extends AbstractDaemonTest {
}
@Test
- public void listOneGroupMember() throws IOException {
- assertEquals(GET_ONE("/groups/Administrators/members/admin").name,
- admin.fullName);
+ public void listOneGroupMember() throws Exception {
+ assertThat(GET_ONE("/groups/Administrators/members/admin").name)
+ .isEqualTo(admin.fullName);
}
@Test
@@ -78,14 +77,14 @@ public class ListGroupMembersIT extends AbstractDaemonTest {
private List<AccountInfo> GET(String endpoint) throws IOException {
RestResponse r = adminSession.get(endpoint);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
return newGson().fromJson(r.getReader(),
new TypeToken<List<AccountInfo>>() {}.getType());
}
private AccountInfo GET_ONE(String endpoint) throws IOException {
RestResponse r = adminSession.get(endpoint);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
return newGson().fromJson(r.getReader(), AccountInfo.class);
}
@@ -110,10 +109,10 @@ public class ListGroupMembersIT extends AbstractDaemonTest {
}
});
- assertTrue(memberNames.contains(name));
+ assertThat((Iterable<?>)memberNames).contains(name);
for (String n : names) {
- assertTrue(memberNames.contains(n));
+ assertThat((Iterable<?>)memberNames).contains(n);
}
- assertEquals(members.size(), names.length + 1);
+ assertThat(members).hasSize(names.length + 1);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
index 8b5dde6465..763c36ac0f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
@@ -14,10 +14,9 @@
package com.google.gerrit.acceptance.rest.group;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroups;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
@@ -26,29 +25,19 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.CreateGroup;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
import java.util.Map;
import java.util.Set;
public class ListGroupsIT extends AbstractDaemonTest {
-
- @Inject
- private GroupCache groupCache;
-
@Test
- public void testListAllGroups() throws IOException, OrmException {
+ public void testListAllGroups() throws Exception {
Iterable<String> expectedGroups = Iterables.transform(groupCache.all(),
new Function<AccountGroup, String>() {
@Override
@@ -65,8 +54,7 @@ public class ListGroupsIT extends AbstractDaemonTest {
}
@Test
- public void testOnlyVisibleGroupsReturned() throws OrmException,
- JSchException, IOException {
+ public void testOnlyVisibleGroupsReturned() throws Exception {
String newGroupName = "newGroup";
CreateGroup.Input in = new CreateGroup.Input();
in.description = "a hidden group";
@@ -80,11 +68,11 @@ public class ListGroupsIT extends AbstractDaemonTest {
Map<String, GroupInfo> result =
newGson().fromJson(r.getReader(),
new TypeToken<Map<String, GroupInfo>>() {}.getType());
- assertTrue("no groups visible", result.isEmpty());
+ assertThat(result).isEmpty();
- assertEquals(HttpStatus.SC_CREATED, adminSession.put(
- String.format("/groups/%s/members/%s", newGroupName, user.username)
- ).getStatusCode());
+ r = adminSession.put(
+ String.format("/groups/%s/members/%s", newGroupName, user.username));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
r = userSession.get("/groups/");
result = newGson().fromJson(r.getReader(),
@@ -93,8 +81,7 @@ public class ListGroupsIT extends AbstractDaemonTest {
}
@Test
- public void testAllGroupInfoFieldsSetCorrectly() throws IOException,
- OrmException {
+ public void testAllGroupInfoFieldsSetCorrectly() throws Exception {
AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
RestResponse r = adminSession.get("/groups/?q=" + adminGroup.getName());
Map<String, GroupInfo> result =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
index fa8b10ee80..1efaa60e29 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
@@ -17,6 +17,7 @@ java_library(
deps = [
'//lib:guava',
'//lib:junit',
+ '//lib:truth',
'//gerrit-server:server',
],
)
@@ -33,5 +34,6 @@ java_library(
'//lib:gwtorm',
'//lib:guava',
'//lib:junit',
+ '//lib:truth',
],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
index f4414a2211..ceed9b6e7e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
@@ -14,12 +14,10 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.add;
import static com.google.gerrit.acceptance.GitUtil.createCommit;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -29,35 +27,33 @@ import com.google.gerrit.server.project.BanCommit;
import com.google.gerrit.server.project.BanCommit.BanResultInfo;
import org.apache.http.HttpStatus;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.PushResult;
import org.junit.Test;
-import java.io.IOException;
-
public class BanCommitIT extends AbstractDaemonTest {
@Test
- public void banCommit() throws IOException, GitAPIException {
+ public void banCommit() throws Exception {
add(git, "a.txt", "some content");
Commit c = createCommit(git, admin.getIdent(), "subject");
RestResponse r =
adminSession.put("/projects/" + project.get() + "/ban/",
BanCommit.Input.fromCommits(c.getCommit().getName()));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
BanResultInfo info = newGson().fromJson(r.getReader(), BanResultInfo.class);
- assertEquals(c.getCommit().getName(), Iterables.getOnlyElement(info.newlyBanned));
- assertNull(info.alreadyBanned);
- assertNull(info.ignored);
+ assertThat(Iterables.getOnlyElement(info.newlyBanned))
+ .isEqualTo(c.getCommit().getName());
+ assertThat(info.alreadyBanned).isNull();
+ assertThat(info.ignored).isNull();
PushResult pushResult = pushHead(git, "refs/heads/master", false);
- assertTrue(pushResult.getRemoteUpdate("refs/heads/master").getMessage()
- .startsWith("contains banned commit"));
+ assertThat(pushResult.getRemoteUpdate("refs/heads/master").getMessage())
+ .startsWith("contains banned commit");
}
@Test
- public void banAlreadyBannedCommit() throws IOException, GitAPIException {
+ public void banAlreadyBannedCommit() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get() + "/ban/",
BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
@@ -65,18 +61,19 @@ public class BanCommitIT extends AbstractDaemonTest {
r = adminSession.put("/projects/" + project.get() + "/ban/",
BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
BanResultInfo info = newGson().fromJson(r.getReader(), BanResultInfo.class);
- assertEquals("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96", Iterables.getOnlyElement(info.alreadyBanned));
- assertNull(info.newlyBanned);
- assertNull(info.ignored);
+ assertThat(Iterables.getOnlyElement(info.alreadyBanned))
+ .isEqualTo("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96");
+ assertThat(info.newlyBanned).isNull();
+ assertThat(info.ignored).isNull();
}
@Test
- public void banCommit_Forbidden() throws IOException {
+ public void banCommit_Forbidden() throws Exception {
RestResponse r =
userSession.put("/projects/" + project.get() + "/ban/",
BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
index 5b0dfca658..c706e171b7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
@@ -14,9 +14,7 @@
package com.google.gerrit.acceptance.rest.project;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
@@ -38,20 +36,19 @@ public class BranchAssert {
return info.ref.equals(b.ref);
}
}, null);
- assertNotNull("missing branch: " + b.ref, info);
+ assertThat(info).named("branch " + b.ref).isNotNull();
assertBranchInfo(b, info);
missingBranches.remove(info);
}
- assertTrue("unexpected branches: " + missingBranches,
- missingBranches.isEmpty());
+ assertThat(missingBranches).named("" + missingBranches).isEmpty();
}
public static void assertBranchInfo(BranchInfo expected, BranchInfo actual) {
- assertEquals(expected.ref, actual.ref);
+ assertThat(actual.ref).isEqualTo(expected.ref);
if (expected.revision != null) {
- assertEquals(expected.revision, actual.revision);
+ assertThat(actual.revision).isEqualTo(expected.revision);
}
- assertEquals(expected.canDelete, toBoolean(actual.canDelete));
+ assertThat(toBoolean(actual.canDelete)).isEqualTo(expected.canDelete);
}
private static boolean toBoolean(Boolean b) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 23cd278494..3cadf66c58 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -14,40 +14,22 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
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.Util.allow;
import static com.google.gerrit.server.project.Util.block;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
import org.apache.http.HttpStatus;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class CreateBranchIT extends AbstractDaemonTest {
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
private Branch.NameKey branch;
@Before
@@ -56,102 +38,86 @@ public class CreateBranchIT extends AbstractDaemonTest {
}
@Test
- public void createBranch_Forbidden() throws IOException {
+ public void createBranch_Forbidden() throws Exception {
RestResponse r =
userSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
- public void createBranchByAdmin() throws IOException {
+ public void createBranchByAdmin() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
r.consume();
r = adminSession.get("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
@Test
- public void branchAlreadyExists_Conflict() throws IOException {
+ public void branchAlreadyExists_Conflict() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
r.consume();
r = adminSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
}
@Test
- public void createBranchByProjectOwner() throws IOException,
- ConfigInvalidException {
+ public void createBranchByProjectOwner() throws Exception {
grantOwner();
RestResponse r =
userSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
r.consume();
r = adminSession.get("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
@Test
- public void createBranchByAdminCreateReferenceBlocked() throws IOException,
- ConfigInvalidException {
+ public void createBranchByAdminCreateReferenceBlocked() throws Exception {
blockCreateReference();
RestResponse r =
adminSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
r.consume();
r = adminSession.get("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
@Test
public void createBranchByProjectOwnerCreateReferenceBlocked_Forbidden()
- throws IOException, ConfigInvalidException {
+ throws Exception {
grantOwner();
blockCreateReference();
RestResponse r =
userSession.put("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
- private void blockCreateReference() throws IOException, ConfigInvalidException {
+ private void blockCreateReference() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
block(cfg, Permission.CREATE, ANONYMOUS_USERS, "refs/*");
saveProjectConfig(allProjects, cfg);
- projectCache.evict(cfg.getProject());
- }
-
- private void grantOwner() throws IOException, ConfigInvalidException {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- allow(cfg, Permission.OWNER, REGISTERED_USERS, "refs/*");
- saveProjectConfig(project, cfg);
- projectCache.evict(cfg.getProject());
}
- private void saveProjectConfig(Project.NameKey p, ProjectConfig cfg)
- throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
+ private void grantOwner() throws Exception {
+ allow(Permission.OWNER, REGISTERED_USERS, "refs/*");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index d441f96873..68c9a5e894 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -14,35 +14,23 @@
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.acceptance.rest.project.ProjectAssert.assertProjectOwners;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
-import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ProjectInfo;
-import com.google.gerrit.extensions.common.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.reviewdb.client.RefNames;
-import com.google.gerrit.server.account.GroupCache;
-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.project.ProjectState;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -58,54 +46,64 @@ import java.util.Collections;
import java.util.Set;
public class CreateProjectIT extends AbstractDaemonTest {
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private GroupCache groupCache;
-
- @Inject
- private GitRepositoryManager git;
-
- @Inject
- private GerritApi gApi;
+ @Test
+ public void testCreateProjectApi() throws Exception {
+ final String newProjectName = "newProject";
+ ProjectInfo p = gApi.projects().name(newProjectName).create().get();
+ assertThat(p.name).isEqualTo(newProjectName);
+ ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ assertThat(projectState).isNotNull();
+ assertProjectInfo(projectState.getProject(), p);
+ assertHead(newProjectName, "refs/heads/master");
+ }
@Test
- public void testCreateProjectApi() throws RestApiException, IOException {
+ public void testCreateProjectApiWithGitSuffix() throws Exception {
final String newProjectName = "newProject";
- ProjectApi projectApi = gApi.projects().name(newProjectName).create();
- ProjectInfo p = projectApi.get();
- assertEquals(newProjectName, p.name);
+ ProjectInfo p = gApi.projects().name(newProjectName + ".git").create().get();
+ assertThat(p.name).isEqualTo(newProjectName);
ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
- assertNotNull(projectState);
+ assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
}
@Test
- public void testCreateProject() throws IOException {
+ public void testCreateProject() throws Exception {
final String newProjectName = "newProject";
RestResponse r = adminSession.put("/projects/" + newProjectName);
- assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
- assertEquals(newProjectName, p.name);
+ assertThat(p.name).isEqualTo(newProjectName);
ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
- assertNotNull(projectState);
+ assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
}
@Test
- public void testCreateProjectWithNameMismatch_BadRequest() throws IOException {
+ public void testCreateProjectWithGitSuffix() throws Exception {
+ final String newProjectName = "newProject";
+ RestResponse r = adminSession.put("/projects/" + newProjectName + ".git");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
+ ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
+ assertThat(p.name).isEqualTo(newProjectName);
+ ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ assertThat(projectState).isNotNull();
+ assertProjectInfo(projectState.getProject(), p);
+ assertHead(newProjectName, "refs/heads/master");
+ }
+
+ @Test
+ public void testCreateProjectWithNameMismatch_BadRequest() throws Exception {
ProjectInput in = new ProjectInput();
in.name = "otherName";
RestResponse r = adminSession.put("/projects/someName", in);
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
}
@Test
- public void testCreateProjectWithProperties() throws IOException {
+ public void testCreateProjectWithProperties() throws Exception {
final String newProjectName = "newProject";
ProjectInput in = new ProjectInput();
in.description = "Test description";
@@ -116,19 +114,19 @@ public class CreateProjectIT extends AbstractDaemonTest {
in.requireChangeId = InheritableBoolean.TRUE;
RestResponse r = adminSession.put("/projects/" + newProjectName, in);
ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
- assertEquals(newProjectName, p.name);
+ assertThat(p.name).isEqualTo(newProjectName);
Project project = projectCache.get(new Project.NameKey(newProjectName)).getProject();
assertProjectInfo(project, p);
- assertEquals(in.description, project.getDescription());
- assertEquals(in.submitType, project.getSubmitType());
- assertEquals(in.useContributorAgreements, project.getUseContributorAgreements());
- assertEquals(in.useSignedOffBy, project.getUseSignedOffBy());
- assertEquals(in.useContentMerge, project.getUseContentMerge());
- assertEquals(in.requireChangeId, project.getRequireChangeID());
+ assertThat(project.getDescription()).isEqualTo(in.description);
+ assertThat(project.getSubmitType()).isEqualTo(in.submitType);
+ assertThat(project.getUseContributorAgreements()).isEqualTo(in.useContributorAgreements);
+ assertThat(project.getUseSignedOffBy()).isEqualTo(in.useSignedOffBy);
+ assertThat(project.getUseContentMerge()).isEqualTo(in.useContentMerge);
+ assertThat(project.getRequireChangeID()).isEqualTo(in.requireChangeId);
}
@Test
- public void testCreateChildProject() throws IOException {
+ public void testCreateChildProject() throws Exception {
final String parentName = "parent";
RestResponse r = adminSession.put("/projects/" + parentName);
r.consume();
@@ -137,20 +135,20 @@ public class CreateProjectIT extends AbstractDaemonTest {
in.parent = parentName;
r = adminSession.put("/projects/" + childName, in);
Project project = projectCache.get(new Project.NameKey(childName)).getProject();
- assertEquals(in.parent, project.getParentName());
+ assertThat(project.getParentName()).isEqualTo(in.parent);
}
@Test
public void testCreateChildProjectUnderNonExistingParent_UnprocessableEntity()
- throws IOException {
+ throws Exception {
ProjectInput in = new ProjectInput();
in.parent = "non-existing-project";
RestResponse r = adminSession.put("/projects/child", in);
- assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
}
@Test
- public void testCreateProjectWithOwner() throws IOException {
+ public void testCreateProjectWithOwner() throws Exception {
final String newProjectName = "newProject";
ProjectInput in = new ProjectInput();
in.owners = Lists.newArrayListWithCapacity(3);
@@ -169,15 +167,15 @@ public class CreateProjectIT extends AbstractDaemonTest {
@Test
public void testCreateProjectWithNonExistingOwner_UnprocessableEntity()
- throws IOException {
+ throws Exception {
ProjectInput in = new ProjectInput();
in.owners = Collections.singletonList("non-existing-group");
RestResponse r = adminSession.put("/projects/newProject", in);
- assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
}
@Test
- public void testCreatePermissionOnlyProject() throws IOException {
+ public void testCreatePermissionOnlyProject() throws Exception {
final String newProjectName = "newProject";
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
@@ -186,7 +184,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
}
@Test
- public void testCreateProjectWithEmptyCommit() throws IOException {
+ public void testCreateProjectWithEmptyCommit() throws Exception {
final String newProjectName = "newProject";
ProjectInput in = new ProjectInput();
in.createEmptyCommit = true;
@@ -195,7 +193,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
}
@Test
- public void testCreateProjectWithBranches() throws IOException {
+ public void testCreateProjectWithBranches() throws Exception {
final String newProjectName = "newProject";
ProjectInput in = new ProjectInput();
in.createEmptyCommit = true;
@@ -210,17 +208,16 @@ public class CreateProjectIT extends AbstractDaemonTest {
}
@Test
- public void testCreateProjectWithoutCapability_Forbidden() throws OrmException,
- JSchException, IOException {
+ public void testCreateProjectWithoutCapability_Forbidden() throws Exception {
RestResponse r = userSession.put("/projects/newProject");
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
public void testCreateProjectWhenProjectAlreadyExists_Conflict()
- throws OrmException, JSchException, IOException {
+ throws Exception {
RestResponse r = adminSession.put("/projects/All-Projects");
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
}
private AccountGroup.UUID groupUuid(String groupName) {
@@ -229,10 +226,11 @@ public class CreateProjectIT extends AbstractDaemonTest {
private void assertHead(String projectName, String expectedRef)
throws RepositoryNotFoundException, IOException {
- Repository repo = git.openRepository(new Project.NameKey(projectName));
+ Repository repo =
+ repoManager.openRepository(new Project.NameKey(projectName));
try {
- assertEquals(expectedRef, repo.getRef(Constants.HEAD).getTarget()
- .getName());
+ assertThat(repo.getRef(Constants.HEAD).getTarget().getName())
+ .isEqualTo(expectedRef);
} finally {
repo.close();
}
@@ -240,21 +238,17 @@ public class CreateProjectIT extends AbstractDaemonTest {
private void assertEmptyCommit(String projectName, String... refs)
throws RepositoryNotFoundException, IOException {
- Repository repo = git.openRepository(new Project.NameKey(projectName));
- RevWalk rw = new RevWalk(repo);
- TreeWalk tw = new TreeWalk(repo);
- try {
+ Project.NameKey projectKey = new Project.NameKey(projectName);
+ try (Repository repo = repoManager.openRepository(projectKey);
+ RevWalk rw = new RevWalk(repo);
+ TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
for (String ref : refs) {
RevCommit commit = rw.lookupCommit(repo.getRef(ref).getObjectId());
rw.parseBody(commit);
tw.addTree(commit.getTree());
- assertFalse("ref " + ref + " has non empty commit", tw.next());
+ assertThat(tw.next()).isFalse();
tw.reset();
}
- } finally {
- tw.close();
- rw.close();
- repo.close();
}
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index 0d6ec5b7fb..8be6c92752 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -14,41 +14,23 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
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.Util.allow;
import static com.google.gerrit.server.project.Util.block;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
import org.apache.http.HttpStatus;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class DeleteBranchIT extends AbstractDaemonTest {
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
private Branch.NameKey branch;
@Before
@@ -59,93 +41,78 @@ public class DeleteBranchIT extends AbstractDaemonTest {
}
@Test
- public void deleteBranch_Forbidden() throws IOException {
+ public void deleteBranch_Forbidden() throws Exception {
RestResponse r =
userSession.delete("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
r.consume();
}
@Test
- public void deleteBranchByAdmin() throws IOException {
+ public void deleteBranchByAdmin() throws Exception {
RestResponse r =
adminSession.delete("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
r.consume();
r = adminSession.get("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
r.consume();
}
@Test
- public void deleteBranchByProjectOwner() throws IOException,
- ConfigInvalidException {
+ public void deleteBranchByProjectOwner() throws Exception {
grantOwner();
RestResponse r =
userSession.delete("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
r.consume();
r = userSession.get("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
r.consume();
}
@Test
- public void deleteBranchByAdminForcePushBlocked() throws IOException,
- ConfigInvalidException {
+ public void deleteBranchByAdminForcePushBlocked() throws Exception {
blockForcePush();
RestResponse r =
adminSession.delete("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
r.consume();
r = adminSession.get("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
r.consume();
}
@Test
public void deleteBranchByProjectOwnerForcePushBlocked_Forbidden()
- throws IOException, ConfigInvalidException {
+ throws Exception {
grantOwner();
blockForcePush();
RestResponse r =
userSession.delete("/projects/" + project.get()
+ "/branches/" + branch.getShortName());
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
r.consume();
}
- private void blockForcePush() throws IOException, ConfigInvalidException {
+ private void blockForcePush() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
block(cfg, Permission.PUSH, ANONYMOUS_USERS, "refs/heads/*").setForce(true);
saveProjectConfig(allProjects, cfg);
- projectCache.evict(cfg.getProject());
- }
-
- private void grantOwner() throws IOException, ConfigInvalidException {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- allow(cfg, Permission.OWNER, REGISTERED_USERS, "refs/*");
- saveProjectConfig(project, cfg);
- projectCache.evict(cfg.getProject());
}
- private void saveProjectConfig(Project.NameKey p, ProjectConfig cfg) throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
+ private void grantOwner() throws Exception {
+ allow(Permission.OWNER, REGISTERED_USERS, "refs/*");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
index 26585ba8dd..6aa3af6ef2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
@@ -14,20 +14,16 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.createProject;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GcAssert;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.jcraft.jsch.JSchException;
-
import org.apache.http.HttpStatus;
import org.junit.Before;
import org.junit.Test;
@@ -37,42 +33,36 @@ import java.io.IOException;
public class GarbageCollectionIT extends AbstractDaemonTest {
@Inject
- private AllProjectsName allProjects;
-
- @Inject
private GcAssert gcAssert;
- private Project.NameKey project1;
private Project.NameKey project2;
@Before
public void setUp() throws Exception {
- project1 = new Project.NameKey("p1");
- createProject(sshSession, project1.get());
-
project2 = new Project.NameKey("p2");
createProject(sshSession, project2.get());
}
@Test
- public void testGcNonExistingProject_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND, POST("/projects/non-existing/gc"));
+ public void testGcNonExistingProject_NotFound() throws Exception {
+ assertThat(POST("/projects/non-existing/gc")).isEqualTo(
+ HttpStatus.SC_NOT_FOUND);
}
@Test
- public void testGcNotAllowed_Forbidden() throws IOException, OrmException,
- JSchException {
- assertEquals(HttpStatus.SC_FORBIDDEN,
+ public void testGcNotAllowed_Forbidden() throws Exception {
+ assertThat(
userSession.post("/projects/" + allProjects.get() + "/gc")
- .getStatusCode());
+ .getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
}
@Test
@UseLocalDisk
- public void testGcOneProject() throws JSchException, IOException {
- assertEquals(HttpStatus.SC_OK, POST("/projects/" + allProjects.get() + "/gc"));
+ public void testGcOneProject() throws Exception {
+ assertThat(POST("/projects/" + allProjects.get() + "/gc")).isEqualTo(
+ HttpStatus.SC_OK);
gcAssert.assertHasPackFile(allProjects);
- gcAssert.assertHasNoPackFile(project1, project2);
+ gcAssert.assertHasNoPackFile(project, project2);
}
private int POST(String endPoint) throws IOException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
index 10d59d2da9..f49408e66c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -14,20 +14,15 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.SshSession;
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.project.ProjectCache;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.junit.Test;
@@ -36,59 +31,55 @@ import java.io.IOException;
public class GetChildProjectIT extends AbstractDaemonTest {
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private ProjectCache projectCache;
-
@Test
- public void getNonExistingChildProject_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- GET("/projects/" + allProjects.get() + "/children/non-existing").getStatusCode());
+ public void getNonExistingChildProject_NotFound() throws Exception {
+ assertThat(
+ GET("/projects/" + allProjects.get() + "/children/non-existing")
+ .getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void getNonChildProject_NotFound() throws IOException, JSchException {
+ public void getNonChildProject_NotFound() throws Exception {
SshSession sshSession = new SshSession(server, admin);
Project.NameKey p1 = new Project.NameKey("p1");
createProject(sshSession, p1.get());
Project.NameKey p2 = new Project.NameKey("p2");
createProject(sshSession, p2.get());
sshSession.close();
- assertEquals(HttpStatus.SC_NOT_FOUND,
- GET("/projects/" + p1.get() + "/children/" + p2.get()).getStatusCode());
+ assertThat(
+ GET("/projects/" + p1.get() + "/children/" + p2.get()).getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void getChildProject() throws IOException, JSchException {
+ public void getChildProject() throws Exception {
SshSession sshSession = new SshSession(server, admin);
Project.NameKey child = new Project.NameKey("p1");
createProject(sshSession, child.get());
sshSession.close();
- RestResponse r = GET("/projects/" + allProjects.get() + "/children/" + child.get());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ RestResponse r =
+ GET("/projects/" + allProjects.get() + "/children/" + child.get());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
ProjectInfo childInfo =
newGson().fromJson(r.getReader(), ProjectInfo.class);
assertProjectInfo(projectCache.get(child).getProject(), childInfo);
}
@Test
- public void getGrandChildProject_NotFound() throws IOException, JSchException {
+ public void getGrandChildProject_NotFound() throws Exception {
SshSession sshSession = new SshSession(server, admin);
Project.NameKey child = new Project.NameKey("p1");
createProject(sshSession, child.get());
Project.NameKey grandChild = new Project.NameKey("p1.1");
createProject(sshSession, grandChild.get(), child);
sshSession.close();
- assertEquals(HttpStatus.SC_NOT_FOUND,
+ assertThat(
GET("/projects/" + allProjects.get() + "/children/" + grandChild.get())
- .getStatusCode());
+ .getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void getGrandChildProjectWithRecursiveFlag() throws IOException,
- JSchException {
+ public void getGrandChildProjectWithRecursiveFlag() throws Exception {
SshSession sshSession = new SshSession(server, admin);
Project.NameKey child = new Project.NameKey("p1");
createProject(sshSession, child.get());
@@ -98,7 +89,7 @@ public class GetChildProjectIT extends AbstractDaemonTest {
RestResponse r =
GET("/projects/" + allProjects.get() + "/children/" + grandChild.get()
+ "?recursive");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
ProjectInfo grandChildInfo =
newGson().fromJson(r.getReader(), ProjectInfo.class);
assertProjectInfo(projectCache.get(grandChild).getProject(), grandChildInfo);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
index 2aacf2060b..4a20957fd6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -14,98 +14,133 @@
package com.google.gerrit.acceptance.rest.project;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+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.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ListBranches.BranchInfo;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
import org.apache.http.HttpStatus;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class GetCommitIT extends AbstractDaemonTest {
+ private TestRepository<Repository> repo;
- @Inject
- private ProjectCache projectCache;
+ @Before
+ public void setUp() throws Exception {
+ repo = new TestRepository<>(repoManager.openRepository(project));
- @Inject
- private AllProjectsName allProjects;
+ ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
+ for (AccessSection sec : pc.getAccessSections()) {
+ sec.removePermission(Permission.READ);
+ }
+ saveProjectConfig(allProjects, pc);
+ }
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
+ @After
+ public void tearDown() throws Exception {
+ if (repo != null) {
+ repo.getRepository().close();
+ }
+ }
@Test
- public void getCommit() throws IOException {
- RestResponse r =
- adminSession.get("/projects/" + project.get() + "/branches/"
- + IdString.fromDecoded(RefNames.REFS_CONFIG).encoded());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- BranchInfo branchInfo =
- newGson().fromJson(r.getReader(), BranchInfo.class);
- r.consume();
+ public void getNonExistingCommit_NotFound() throws Exception {
+ assertNotFound(
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ }
- r = adminSession.get("/projects/" + project.get() + "/commits/"
- + branchInfo.revision);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- CommitInfo commitInfo =
- newGson().fromJson(r.getReader(), CommitInfo.class);
- assertEquals(branchInfo.revision, commitInfo.commit);
- assertEquals("Created project", commitInfo.subject);
- assertEquals("Created project\n", commitInfo.message);
- assertNotNull(commitInfo.author);
- assertEquals("Administrator", commitInfo.author.name);
- assertNotNull(commitInfo.committer);
- assertEquals("Gerrit Code Review", commitInfo.committer.name);
- assertTrue(commitInfo.parents.isEmpty());
+ @Test
+ public void getMergedCommit_Found() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
+ RevCommit commit = repo.parseBody(repo.branch("master")
+ .commit()
+ .message("Create\n\nNew commit\n")
+ .create());
+
+ CommitInfo info = getCommit(commit);
+ assertThat(info.commit).isEqualTo(commit.name());
+ assertThat(info.subject).isEqualTo("Create");
+ assertThat(info.message).isEqualTo("Create\n\nNew commit\n");
+ assertThat(info.author.name).isEqualTo("J. Author");
+ assertThat(info.author.email).isEqualTo("jauthor@example.com");
+ assertThat(info.committer.name).isEqualTo("J. Committer");
+ assertThat(info.committer.email).isEqualTo("jcommitter@example.com");
+
+ CommitInfo parent = Iterables.getOnlyElement(info.parents);
+ assertThat(parent.commit).isEqualTo(commit.getParent(0).name());
+ assertThat(parent.subject).isEqualTo("Initial empty repository");
+ assertThat(parent.message).isNull();
+ assertThat(parent.author).isNull();
+ assertThat(parent.committer).isNull();
}
@Test
- public void getNonExistingCommit_NotFound() throws IOException {
- RestResponse r = adminSession.get("/projects/" + project.get() + "/commits/"
- + ObjectId.zeroId().name());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ public void getMergedCommit_NotFound() throws Exception {
+ RevCommit commit = repo.parseBody(repo.branch("master")
+ .commit()
+ .message("Create\n\nNew commit\n")
+ .create());
+ assertNotFound(commit);
}
@Test
- public void getNonVisibleCommit_NotFound() throws IOException {
- RestResponse r =
- adminSession.get("/projects/" + project.get() + "/branches/"
- + IdString.fromDecoded(RefNames.REFS_CONFIG).encoded());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- BranchInfo branchInfo =
- newGson().fromJson(r.getReader(), BranchInfo.class);
- r.consume();
+ public void getOpenChange_Found() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
+ PushOneCommit.Result r = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master");
+ r.assertOkStatus();
+
+ CommitInfo info = getCommit(r.getCommitId());
+ assertThat(info.commit).isEqualTo(r.getCommitId().name());
+ assertThat(info.subject).isEqualTo("test commit");
+ assertThat(info.message).isEqualTo(
+ "test commit\n\nChange-Id: " + r.getChangeId() + "\n");
+ assertThat(info.author.name).isEqualTo("admin");
+ assertThat(info.author.email).isEqualTo("admin@example.com");
+ assertThat(info.committer.name).isEqualTo("admin");
+ assertThat(info.committer.email).isEqualTo("admin@example.com");
+
+ CommitInfo parent = Iterables.getOnlyElement(info.parents);
+ assertThat(parent.commit).isEqualTo(r.getCommit().getParent(0).name());
+ assertThat(parent.subject).isEqualTo("Initial empty repository");
+ assertThat(parent.message).isNull();
+ assertThat(parent.author).isNull();
+ assertThat(parent.committer).isNull();
+ }
- ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
- cfg.getAccessSection("refs/*", false).removePermission(Permission.READ);
- saveProjectConfig(cfg);
- projectCache.evict(cfg.getProject());
+ @Test
+ public void getOpenChange_NotFound() throws Exception {
+ PushOneCommit.Result r = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master");
+ r.assertOkStatus();
+ assertNotFound(r.getCommitId());
+ }
- r = adminSession.get("/projects/" + project.get() + "/commits/"
- + branchInfo.revision);
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+ private void assertNotFound(ObjectId id) throws Exception {
+ RestResponse r = userSession.get(
+ "/projects/" + project.get() + "/commits/" + id.name());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
- private void saveProjectConfig(ProjectConfig cfg) throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
+ private CommitInfo getCommit(ObjectId id) throws Exception {
+ RestResponse r = userSession.get(
+ "/projects/" + project.get() + "/commits/" + id.name());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ CommitInfo result = newGson().fromJson(r.getReader(), CommitInfo.class);
+ r.consume();
+ return result;
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
new file mode 100644
index 0000000000..761e282189
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2015 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.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.common.ProjectInfo;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+public class GetProjectIT extends AbstractDaemonTest {
+
+ @Test
+ public void getProject() throws Exception {
+ String name = project.get();
+ RestResponse r = adminSession.get("/projects/" + name);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
+ assertThat(p.name).isEqualTo(name);
+ }
+
+ @Test
+ public void getProjectWithGitSuffix() throws Exception {
+ String name = project.get();
+ RestResponse r = adminSession.get("/projects/" + name + ".git");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
+ assertThat(p.name).isEqualTo(name);
+ }
+
+ @Test
+ public void getProjectNotExisting() throws Exception {
+ RestResponse r = adminSession.get("/projects/does-not-exist");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
index e6fde73a52..48f9ad8901 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -14,32 +14,18 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.rest.project.BranchAssert.assertBranches;
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.Util.block;
-import static org.junit.Assert.assertEquals;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.ListBranches.BranchInfo;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.junit.Test;
import java.io.IOException;
@@ -47,29 +33,22 @@ import java.util.Collections;
import java.util.List;
public class ListBranchesIT extends AbstractDaemonTest {
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- private ProjectCache projectCache;
-
@Test
- public void listBranchesOfNonExistingProject_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- GET("/projects/non-existing/branches").getStatusCode());
+ public void listBranchesOfNonExistingProject_NotFound() throws Exception {
+ assertThat(GET("/projects/non-existing/branches").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void listBranchesOfNonVisibleProject_NotFound() throws IOException,
- OrmException, JSchException, ConfigInvalidException {
+ public void listBranchesOfNonVisibleProject_NotFound() throws Exception {
blockRead(project, "refs/*");
- assertEquals(HttpStatus.SC_NOT_FOUND,
- userSession.get("/projects/" + project.get() + "/branches").getStatusCode());
+ assertThat(
+ userSession.get("/projects/" + project.get() + "/branches")
+ .getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void listBranchesOfEmptyProject() throws IOException, JSchException {
+ public void listBranchesOfEmptyProject() throws Exception {
Project.NameKey emptyProject = new Project.NameKey("empty");
createProject(sshSession, emptyProject.get(), null, false);
RestResponse r = adminSession.get("/projects/" + emptyProject.get() + "/branches");
@@ -82,7 +61,7 @@ public class ListBranchesIT extends AbstractDaemonTest {
}
@Test
- public void listBranches() throws IOException, GitAPIException {
+ public void listBranches() throws Exception {
pushTo("refs/heads/master");
String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
pushTo("refs/heads/dev");
@@ -99,15 +78,14 @@ public class ListBranchesIT extends AbstractDaemonTest {
assertBranches(expected, result);
// verify correct sorting
- assertEquals("HEAD", result.get(0).ref);
- assertEquals("refs/meta/config", result.get(1).ref);
- assertEquals("refs/heads/dev", result.get(2).ref);
- assertEquals("refs/heads/master", result.get(3).ref);
+ assertThat(result.get(0).ref).isEqualTo("HEAD");
+ assertThat(result.get(1).ref).isEqualTo("refs/meta/config");
+ assertThat(result.get(2).ref).isEqualTo("refs/heads/dev");
+ assertThat(result.get(3).ref).isEqualTo("refs/heads/master");
}
@Test
- public void listBranchesSomeHidden() throws IOException, GitAPIException,
- ConfigInvalidException, OrmException, JSchException {
+ public void listBranchesSomeHidden() throws Exception {
blockRead(project, "refs/heads/dev");
pushTo("refs/heads/master");
String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
@@ -123,8 +101,7 @@ public class ListBranchesIT extends AbstractDaemonTest {
}
@Test
- public void listBranchesHeadHidden() throws IOException, GitAPIException,
- ConfigInvalidException, OrmException, JSchException {
+ public void listBranchesHeadHidden() throws Exception {
blockRead(project, "refs/heads/master");
pushTo("refs/heads/master");
pushTo("refs/heads/dev");
@@ -135,16 +112,88 @@ public class ListBranchesIT extends AbstractDaemonTest {
devCommit, false)), toBranchInfoList(r));
}
- private RestResponse GET(String endpoint) throws IOException {
- return adminSession.get(endpoint);
+ @Test
+ public void listBranchesUsingPagination() throws Exception {
+ pushTo("refs/heads/master");
+ pushTo("refs/heads/someBranch1");
+ pushTo("refs/heads/someBranch2");
+ pushTo("refs/heads/someBranch3");
+
+ // using only limit
+ RestResponse r =
+ adminSession.get("/projects/" + project.get() + "/branches?n=4");
+ List<BranchInfo> result = toBranchInfoList(r);
+ assertThat(result).hasSize(4);
+ assertThat(result.get(0).ref).isEqualTo("HEAD");
+ assertThat(result.get(1).ref).isEqualTo("refs/meta/config");
+ assertThat(result.get(2).ref).isEqualTo("refs/heads/master");
+ assertThat(result.get(3).ref).isEqualTo("refs/heads/someBranch1");
+
+ // limit higher than total number of branches
+ r = adminSession.get("/projects/" + project.get() + "/branches?n=25");
+ result = toBranchInfoList(r);
+ assertThat(result).hasSize(6);
+ assertThat(result.get(0).ref).isEqualTo("HEAD");
+ assertThat(result.get(1).ref).isEqualTo("refs/meta/config");
+ assertThat(result.get(2).ref).isEqualTo("refs/heads/master");
+ assertThat(result.get(3).ref).isEqualTo("refs/heads/someBranch1");
+ assertThat(result.get(4).ref).isEqualTo("refs/heads/someBranch2");
+ assertThat(result.get(5).ref).isEqualTo("refs/heads/someBranch3");
+
+ // using skip only
+ r = adminSession.get("/projects/" + project.get() + "/branches?s=2");
+ result = toBranchInfoList(r);
+ assertThat(result).hasSize(4);
+ assertThat(result.get(0).ref).isEqualTo("refs/heads/master");
+ assertThat(result.get(1).ref).isEqualTo("refs/heads/someBranch1");
+ assertThat(result.get(2).ref).isEqualTo("refs/heads/someBranch2");
+ assertThat(result.get(3).ref).isEqualTo("refs/heads/someBranch3");
+
+ // skip more branches than the number of available branches
+ r = adminSession.get("/projects/" + project.get() + "/branches?s=7");
+ result = toBranchInfoList(r);
+ assertThat(result).isEmpty();
+
+ // using skip and limit
+ r = adminSession.get("/projects/" + project.get() + "/branches?s=2&n=2");
+ result = toBranchInfoList(r);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).ref).isEqualTo("refs/heads/master");
+ assertThat(result.get(1).ref).isEqualTo("refs/heads/someBranch1");
+ }
+
+ @Test
+ public void listBranchesUsingFilter() throws Exception {
+ pushTo("refs/heads/master");
+ pushTo("refs/heads/someBranch1");
+ pushTo("refs/heads/someBranch2");
+ pushTo("refs/heads/someBranch3");
+
+ //using substring
+ RestResponse r =
+ adminSession.get("/projects/" + project.get() + "/branches?m=some");
+ List<BranchInfo> result = toBranchInfoList(r);
+ assertThat(result).hasSize(3);
+ assertThat(result.get(0).ref).isEqualTo("refs/heads/someBranch1");
+ assertThat(result.get(1).ref).isEqualTo("refs/heads/someBranch2");
+ assertThat(result.get(2).ref).isEqualTo("refs/heads/someBranch3");
+
+ r = adminSession.get("/projects/" + project.get() + "/branches?m=Branch");
+ result = toBranchInfoList(r);
+ assertThat(result).hasSize(3);
+ assertThat(result.get(0).ref).isEqualTo("refs/heads/someBranch1");
+ assertThat(result.get(1).ref).isEqualTo("refs/heads/someBranch2");
+ assertThat(result.get(2).ref).isEqualTo("refs/heads/someBranch3");
+
+ //using regex
+ r = adminSession.get("/projects/" + project.get() + "/branches?r=.*ast.*r");
+ result = toBranchInfoList(r);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).ref).isEqualTo("refs/heads/master");
}
- private void blockRead(Project.NameKey project, String ref)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- block(cfg, Permission.READ, REGISTERED_USERS, ref);
- saveProjectConfig(project, cfg);
- projectCache.evict(cfg.getProject());
+ private RestResponse GET(String endpoint) throws IOException {
+ return adminSession.get(endpoint);
}
private static List<BranchInfo> toBranchInfoList(RestResponse r)
@@ -154,19 +203,4 @@ public class ListBranchesIT extends AbstractDaemonTest {
new TypeToken<List<BranchInfo>>() {}.getType());
return result;
}
-
- private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
- IOException {
- PushOneCommit push = pushFactory.create(db, admin.getIdent());
- return push.to(git, ref);
- }
-
- private void saveProjectConfig(Project.NameKey p, ProjectConfig cfg) throws IOException {
- MetaDataUpdate md = metaDataUpdateFactory.create(p);
- try {
- cfg.commit(md);
- } finally {
- md.close();
- }
- }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
index 466a239ca4..0d3b467863 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -14,20 +14,15 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gson.reflect.TypeToken;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.junit.Test;
@@ -38,26 +33,23 @@ import java.util.List;
public class ListChildProjectsIT extends AbstractDaemonTest {
- @Inject
- private AllProjectsName allProjects;
-
@Test
- public void listChildrenOfNonExistingProject_NotFound() throws IOException {
- assertEquals(HttpStatus.SC_NOT_FOUND,
- GET("/projects/non-existing/children/").getStatusCode());
+ public void listChildrenOfNonExistingProject_NotFound() throws Exception {
+ assertThat(GET("/projects/non-existing/children/").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void listNoChildren() throws IOException {
+ public void listNoChildren() throws Exception {
RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
List<ProjectInfo> projectInfoList = toProjectInfoList(r);
// Project 'p' was already created in the base class
- assertTrue(projectInfoList.size() == 2);
+ assertThat(projectInfoList).hasSize(2);
}
@Test
- public void listChildren() throws IOException, JSchException {
+ public void listChildren() throws Exception {
Project.NameKey existingProject = new Project.NameKey("p");
Project.NameKey child1 = new Project.NameKey("p1");
createProject(sshSession, child1.get());
@@ -66,7 +58,7 @@ public class ListChildProjectsIT extends AbstractDaemonTest {
createProject(sshSession, "p1.1", child1);
RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
assertProjects(
Arrays.asList(
new Project.NameKey("All-Users"),
@@ -75,7 +67,7 @@ public class ListChildProjectsIT extends AbstractDaemonTest {
}
@Test
- public void listChildrenRecursively() throws IOException, JSchException {
+ public void listChildrenRecursively() throws Exception {
Project.NameKey child1 = new Project.NameKey("p1");
createProject(sshSession, child1.get());
createProject(sshSession, "p2");
@@ -89,7 +81,7 @@ public class ListChildProjectsIT extends AbstractDaemonTest {
createProject(sshSession, child1_1_1_1.get(), child1_1_1);
RestResponse r = GET("/projects/" + child1.get() + "/children/?recursive");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
assertProjects(Arrays.asList(child1_1, child1_2,
child1_1_1, child1_1_1_1), toProjectInfoList(r));
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index 923d752bdf..0f5bed1808 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -14,25 +14,19 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.common.ProjectInfo;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
-import com.jcraft.jsch.JSchException;
-
import org.apache.http.HttpStatus;
import org.junit.Test;
@@ -43,36 +37,33 @@ import java.util.Map;
public class ListProjectsIT extends AbstractDaemonTest {
@Inject
- private AllProjectsName allProjects;
-
- @Inject
private AllUsersName allUsers;
@Test
- public void listProjects() throws IOException, JSchException {
+ public void listProjects() throws Exception {
Project.NameKey someProject = new Project.NameKey("some-project");
createProject(sshSession, someProject.get());
RestResponse r = GET("/projects/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(Arrays.asList(allUsers, someProject, project),
result.values());
}
@Test
- public void listProjectsWithBranch() throws IOException, JSchException {
+ public void listProjectsWithBranch() throws Exception {
RestResponse r = GET("/projects/?b=master");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
- assertNotNull(result.get(project.get()));
- assertNotNull(result.get(project.get()).branches);
- assertEquals(1, result.get(project.get()).branches.size());
- assertNotNull(result.get(project.get()).branches.get("master"));
+ assertThat(result.get(project.get())).isNotNull();
+ assertThat(result.get(project.get()).branches).isNotNull();
+ assertThat(result.get(project.get()).branches).hasSize(1);
+ assertThat(result.get(project.get()).branches.get("master")).isNotNull();
}
@Test
- public void listProjectWithDescription() throws RestApiException, IOException {
+ public void listProjectWithDescription() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = "some-project";
projectInput.description = "Description of some-project";
@@ -80,39 +71,39 @@ public class ListProjectsIT extends AbstractDaemonTest {
// description not be included in the results by default.
RestResponse r = GET("/projects/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
- assertNotNull(result.get(projectInput.name));
- assertNull(result.get(projectInput.name).description);
+ assertThat(result.get(projectInput.name)).isNotNull();
+ assertThat(result.get(projectInput.name).description).isNull();
r = GET("/projects/?d");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = toProjectInfoMap(r);
- assertNotNull(result.get(projectInput.name));
- assertEquals(projectInput.description,
- result.get(projectInput.name).description);
+ assertThat(result.get(projectInput.name)).isNotNull();
+ assertThat(result.get(projectInput.name).description).isEqualTo(
+ projectInput.description);
}
@Test
- public void listProjectsWithLimit() throws IOException, JSchException {
+ public void listProjectsWithLimit() throws Exception {
for (int i = 0; i < 5; i++) {
createProject(sshSession, new Project.NameKey("someProject" + i).get());
}
RestResponse r = GET("/projects/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
- assertEquals(7, result.size()); // 5 plus 2 existing projects: p and
- // All-Users
+ assertThat(result).hasSize(7); // 5 plus 2 existing projects: p and
+ // All-Users
r = GET("/projects/?n=2");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = toProjectInfoMap(r);
- assertEquals(2, result.size());
+ assertThat(result).hasSize(2);
}
@Test
- public void listProjectsWithPrefix() throws IOException, JSchException {
+ public void listProjectsWithPrefix() throws Exception {
Project.NameKey someProject = new Project.NameKey("some-project");
createProject(sshSession, someProject.get());
Project.NameKey someOtherProject =
@@ -121,15 +112,20 @@ public class ListProjectsIT extends AbstractDaemonTest {
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertThat(GET("/projects/?p=some&r=.*").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+ assertThat(GET("/projects/?p=some&m=some").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+
RestResponse r = GET("/projects/?p=some");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(Arrays.asList(someProject, someOtherProject),
result.values());
}
@Test
- public void listProjectsWithRegex() throws IOException, JSchException {
+ public void listProjectsWithRegex() throws Exception {
Project.NameKey someProject = new Project.NameKey("some-project");
createProject(sshSession, someProject.get());
Project.NameKey someOtherProject =
@@ -138,41 +134,50 @@ public class ListProjectsIT extends AbstractDaemonTest {
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertThat(GET("/projects/?r=[.*some").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+ assertThat(GET("/projects/?r=.*&p=s").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+ assertThat(GET("/projects/?r=.*&m=s").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+
RestResponse r = GET("/projects/?r=.*some");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(Arrays.asList(projectAwesome), result.values());
- r = GET("/projects/?r=[.*some");
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ r = GET("/projects/?r=some-project$");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ result = toProjectInfoMap(r);
+ assertProjects(Arrays.asList(someProject), result.values());
r = GET("/projects/?r=.*");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = toProjectInfoMap(r);
assertProjects(Arrays.asList(someProject, someOtherProject, projectAwesome,
project, allUsers), result.values());
}
@Test
- public void listProjectsWithSkip() throws IOException, JSchException {
+ public void listProjectsWithSkip() throws Exception {
for (int i = 0; i < 5; i++) {
createProject(sshSession, new Project.NameKey("someProject" + i).get());
}
RestResponse r = GET("/projects/");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
- assertEquals(7, result.size()); // 5 plus 2 existing projects: p and
- // All-Users
+ assertThat(result).hasSize(7); // 5 plus 2 existing projects: p and
+ // All-Users
r = GET("/projects/?S=6");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = toProjectInfoMap(r);
- assertEquals(1, result.size());
+ assertThat(result).hasSize(1);
}
@Test
- public void listProjectsWithSubstring() throws IOException, JSchException {
+ public void listProjectsWithSubstring() throws Exception {
Project.NameKey someProject = new Project.NameKey("some-project");
createProject(sshSession, someProject.get());
Project.NameKey someOtherProject =
@@ -181,8 +186,13 @@ public class ListProjectsIT extends AbstractDaemonTest {
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertThat(GET("/projects/?m=some&r=.*").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+ assertThat(GET("/projects/?m=some&p=some").getStatusCode()).isEqualTo(
+ HttpStatus.SC_BAD_REQUEST);
+
RestResponse r = GET("/projects/?m=some");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(
Arrays.asList(someProject, someOtherProject, projectAwesome),
@@ -190,7 +200,7 @@ public class ListProjectsIT extends AbstractDaemonTest {
}
@Test
- public void listProjectsWithTree() throws IOException, JSchException {
+ public void listProjectsWithTree() throws Exception {
Project.NameKey someParentProject =
new Project.NameKey("some-parent-project");
createProject(sshSession, someParentProject.get());
@@ -199,25 +209,25 @@ public class ListProjectsIT extends AbstractDaemonTest {
createProject(sshSession, someChildProject.get(), someParentProject);
RestResponse r = GET("/projects/?tree");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
- assertNotNull(result.get(someChildProject.get()));
- assertEquals(someParentProject.get(),
- result.get(someChildProject.get()).parent);
+ assertThat(result.get(someChildProject.get())).isNotNull();
+ assertThat(result.get(someChildProject.get()).parent).isEqualTo(
+ someParentProject.get());
}
@Test
- public void listProjectWithType() throws RestApiException, IOException {
+ public void listProjectWithType() throws Exception {
RestResponse r = GET("/projects/?type=PERMISSIONS");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
Map<String, ProjectInfo> result = toProjectInfoMap(r);
- assertEquals(1, result.size());
- assertNotNull(result.get(allProjects.get()));
+ assertThat(result).hasSize(1);
+ assertThat(result.get(allProjects.get())).isNotNull();
r = GET("/projects/?type=ALL");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
result = toProjectInfoMap(r);
- assertEquals(3, result.size());
+ assertThat(result).hasSize(3);
assertProjects(Arrays.asList(allProjects, allUsers, project),
result.values());
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
index 3354cb81e2..95f46e8385 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
@@ -14,9 +14,7 @@
package com.google.gerrit.acceptance.rest.project;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
@@ -42,29 +40,33 @@ public class ProjectAssert {
return new Project.NameKey(info.name != null ? info.name : Url
.decode(info.id)).equals(p);
}}, null);
- assertNotNull("missing project: " + p, info);
+ assertThat(info).isNotNull();
actual.remove(info);
}
- assertTrue("unexpected projects: " + actual, actual.isEmpty());
+ assertThat((Iterable<?>)actual).isEmpty();
}
public static void assertProjectInfo(Project project, ProjectInfo info) {
if (info.name != null) {
// 'name' is not set if returned in a map
- assertEquals(project.getName(), info.name);
+ assertThat(info.name).isEqualTo(project.getName());
}
- assertEquals(project.getName(), Url.decode(info.id));
+ assertThat(Url.decode(info.id)).isEqualTo(project.getName());
Project.NameKey parentName = project.getParent(new Project.NameKey("All-Projects"));
- assertEquals(parentName != null ? parentName.get() : null, info.parent);
- assertEquals(project.getDescription(), Strings.nullToEmpty(info.description));
+ if (parentName != null) {
+ assertThat(info.parent).isEqualTo(parentName.get());
+ } else {
+ assertThat(info.parent).isNull();
+ }
+ assertThat(Strings.nullToEmpty(info.description)).isEqualTo(
+ project.getDescription());
}
public static void assertProjectOwners(Set<AccountGroup.UUID> expectedOwners,
ProjectState state) {
for (AccountGroup.UUID g : state.getOwners()) {
- assertTrue("unexpected owner group " + g, expectedOwners.remove(g));
+ assertThat(expectedOwners.remove(g)).isTrue();
}
- assertTrue("missing owner groups: " + expectedOwners,
- expectedOwners.isEmpty());
+ assertThat((Iterable<?>)expectedOwners).isEmpty();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
index fb830e4ec9..7e2af6520f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -14,72 +14,30 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.checkout;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.GitUtil.createProject;
import static com.google.gerrit.acceptance.GitUtil.fetch;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.AllProjectsNameProvider;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Config;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class ProjectLevelConfigIT extends AbstractDaemonTest {
-
- @Inject
- private SchemaFactory<ReviewDb> reviewDbProvider;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsNameProvider allProjects;
-
- @Inject
- private PushOneCommit.Factory pushFactory;
-
- private ReviewDb db;
- private SshSession sshSession;
- private String project;
- private Git git;
-
@Before
public void setUp() throws Exception {
- sshSession = new SshSession(server, admin);
-
- project = "p";
- createProject(sshSession, project, null, true);
- git = cloneProject(sshSession.getUrl() + "/" + project);
fetch(git, RefNames.REFS_CONFIG + ":refs/heads/config");
checkout(git, "refs/heads/config");
-
- db = reviewDbProvider.open();
- }
-
- @After
- public void cleanup() {
- db.close();
}
@Test
- public void accessProjectSpecificConfig() throws GitAPIException, IOException {
+ public void accessProjectSpecificConfig() throws Exception {
String configName = "test.config";
Config cfg = new Config();
cfg.setString("s1", null, "k1", "v1");
@@ -89,18 +47,19 @@ public class ProjectLevelConfigIT extends AbstractDaemonTest {
configName, cfg.toText());
push.to(git, RefNames.REFS_CONFIG);
- ProjectState state = projectCache.get(new Project.NameKey(project));
- assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
+ ProjectState state = projectCache.get(project);
+ assertThat(state.getConfig(configName).get().toText()).isEqualTo(
+ cfg.toText());
}
@Test
public void nonExistingConfig() {
- ProjectState state = projectCache.get(new Project.NameKey(project));
- assertEquals("", state.getConfig("test.config").get().toText());
+ ProjectState state = projectCache.get(project);
+ assertThat(state.getConfig("test.config").get().toText()).isEqualTo("");
}
@Test
- public void withInheritance() throws GitAPIException, IOException {
+ public void withInheritance() throws Exception {
String configName = "test.config";
Config parentCfg = new Config();
@@ -110,7 +69,7 @@ public class ProjectLevelConfigIT extends AbstractDaemonTest {
parentCfg.setString("s2", "ss", "k4", "parentValue4");
Git parentGit =
- cloneProject(sshSession.getUrl() + "/" + allProjects.get().get(), false);
+ cloneProject(sshSession.getUrl() + "/" + allProjects.get(), false);
fetch(parentGit, RefNames.REFS_CONFIG + ":refs/heads/config");
checkout(parentGit, "refs/heads/config");
PushOneCommit push =
@@ -125,7 +84,7 @@ public class ProjectLevelConfigIT extends AbstractDaemonTest {
configName, cfg.toText());
push.to(git, RefNames.REFS_CONFIG);
- ProjectState state = projectCache.get(new Project.NameKey(project));
+ ProjectState state = projectCache.get(project);
Config expectedCfg = new Config();
expectedCfg.setString("s1", null, "k1", "childValue1");
@@ -133,9 +92,10 @@ public class ProjectLevelConfigIT extends AbstractDaemonTest {
expectedCfg.setString("s2", "ss", "k3", "childValue2");
expectedCfg.setString("s2", "ss", "k4", "parentValue4");
- assertEquals(expectedCfg.toText(), state.getConfig(configName)
- .getWithInheritance().toText());
+ assertThat(state.getConfig(configName).getWithInheritance().toText())
+ .isEqualTo(expectedCfg.toText());
- assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
+ assertThat(state.getConfig(configName).get().toText()).isEqualTo(
+ cfg.toText());
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
index 7c6f280341..ac90ac0c19 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
@@ -14,95 +14,85 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.createProject;
-import static org.junit.Assert.assertEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.project.SetParent;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
import org.apache.http.HttpStatus;
import org.junit.Test;
-import java.io.IOException;
-
public class SetParentIT extends AbstractDaemonTest {
-
- @Inject
- private AllProjectsNameProvider allProjects;
-
@Test
- public void setParent_Forbidden() throws IOException, JSchException {
+ public void setParent_Forbidden() throws Exception {
String parent = "parent";
createProject(sshSession, parent, null, true);
RestResponse r =
userSession.put("/projects/" + project.get() + "/parent",
newParentInput(parent));
- assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN);
r.consume();
}
@Test
- public void setParent() throws IOException, JSchException {
+ public void setParent() throws Exception {
String parent = "parent";
createProject(sshSession, parent, null, true);
RestResponse r =
adminSession.put("/projects/" + project.get() + "/parent",
newParentInput(parent));
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
r.consume();
r = adminSession.get("/projects/" + project.get() + "/parent");
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
String newParent =
newGson().fromJson(r.getReader(), String.class);
- assertEquals(parent, newParent);
+ assertThat(newParent).isEqualTo(parent);
r.consume();
}
@Test
- public void setParentForAllProjects_Conflict() throws IOException {
+ public void setParentForAllProjects_Conflict() throws Exception {
RestResponse r =
adminSession.put("/projects/" + allProjects.get() + "/parent",
newParentInput(project.get()));
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
r.consume();
}
@Test
- public void setInvalidParent_Conflict() throws IOException, JSchException {
+ public void setInvalidParent_Conflict() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get() + "/parent",
newParentInput(project.get()));
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
r.consume();
String child = "child";
createProject(sshSession, child, project, true);
r = adminSession.put("/projects/" + project.get() + "/parent",
newParentInput(child));
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
r.consume();
String grandchild = "grandchild";
createProject(sshSession, grandchild, new Project.NameKey(child), true);
r = adminSession.put("/projects/" + project.get() + "/parent",
newParentInput(grandchild));
- assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
r.consume();
}
@Test
- public void setNonExistingParent_UnprocessibleEntity() throws IOException {
+ public void setNonExistingParent_UnprocessibleEntity() throws Exception {
RestResponse r =
adminSession.put("/projects/" + project.get() + "/parent",
newParentInput("non-existing"));
- assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
r.consume();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
new file mode 100644
index 0000000000..c6cf647a6d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2014 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.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+import java.util.List;
+
+public class TagsIT extends AbstractDaemonTest {
+ @Test
+ public void listTagsOfNonExistingProject_NotFound() throws Exception {
+ assertThat(adminSession.get("/projects/non-existing/tags").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
+ }
+
+ @Test
+ public void listTagsOfNonVisibleProject_NotFound() throws Exception {
+ blockRead(project, "refs/*");
+ assertThat(
+ userSession.get("/projects/" + project.get() + "/tags").getStatusCode())
+ .isEqualTo(HttpStatus.SC_NOT_FOUND);
+ }
+
+ @Test
+ public void listTags() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+
+ PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent());
+ push1.setTag(tag1);
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master%submit");
+ r1.assertOkStatus();
+
+ PushOneCommit.AnnotatedTag tag2 =
+ new PushOneCommit.AnnotatedTag("v2.0", "annotation", admin.getIdent());
+ PushOneCommit push2 = pushFactory.create(db, admin.getIdent());
+ push2.setTag(tag2);
+ PushOneCommit.Result r2 = push2.to(git, "refs/for/master%submit");
+ r2.assertOkStatus();
+
+ List<TagInfo> result =
+ toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ assertThat(result).hasSize(2);
+
+ TagInfo t = result.get(0);
+ assertThat(t.ref).isEqualTo("refs/tags/" + tag1.name);
+ assertThat(t.revision).isEqualTo(r1.getCommitId().getName());
+
+ t = result.get(1);
+ assertThat(t.ref).isEqualTo("refs/tags/" + tag2.name);
+ assertThat(t.object).isEqualTo(r2.getCommitId().getName());
+ assertThat(t.message).isEqualTo(tag2.message);
+ assertThat(t.tagger.name).isEqualTo(tag2.tagger.getName());
+ assertThat(t.tagger.email).isEqualTo(tag2.tagger.getEmailAddress());
+ }
+
+ @Test
+ public void listTagsOfNonVisibleBranch() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/hidden");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+
+ PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent());
+ push1.setTag(tag1);
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master%submit");
+ r1.assertOkStatus();
+
+ pushTo("refs/heads/hidden");
+ PushOneCommit.Tag tag2 = new PushOneCommit.Tag("v2.0");
+ PushOneCommit push2 = pushFactory.create(db, admin.getIdent());
+ push2.setTag(tag2);
+ PushOneCommit.Result r2 = push2.to(git, "refs/for/hidden%submit");
+ r2.assertOkStatus();
+
+ List<TagInfo> result =
+ toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).ref).isEqualTo("refs/tags/" + tag1.name);
+ assertThat(result.get(0).revision).isEqualTo(r1.getCommitId().getName());
+ assertThat(result.get(1).ref).isEqualTo("refs/tags/" + tag2.name);
+ assertThat(result.get(1).revision).isEqualTo(r2.getCommitId().getName());
+
+ blockRead(project, "refs/heads/hidden");
+ result =
+ toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).ref).isEqualTo("refs/tags/" + tag1.name);
+ assertThat(result.get(0).revision).isEqualTo(r1.getCommitId().getName());
+ }
+
+ @Test
+ public void getTag() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+
+ PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent());
+ push1.setTag(tag1);
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master%submit");
+ r1.assertOkStatus();
+
+ RestResponse response =
+ adminSession.get("/projects/" + project.get() + "/tags/" + tag1.name);
+ TagInfo tagInfo =
+ newGson().fromJson(response.getReader(), TagInfo.class);
+ assertThat(tagInfo.ref).isEqualTo("refs/tags/" + tag1.name);
+ assertThat(tagInfo.revision).isEqualTo(r1.getCommitId().getName());
+ }
+
+ private static List<TagInfo> toTagInfoList(RestResponse r) throws Exception {
+ List<TagInfo> result =
+ newGson().fromJson(r.getReader(),
+ new TypeToken<List<TagInfo>>() {}.getType());
+ return result;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
new file mode 100644
index 0000000000..541d1b8496
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -0,0 +1,278 @@
+// Copyright (C) 2014 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.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.Comment;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.change.PostReview;
+import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.change.Revisions;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CommentsIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config noteDbEnabled() {
+ return NotesMigration.allEnabledConfig();
+ }
+
+ @Inject
+ private Provider<ChangesCollection> changes;
+
+ @Inject
+ private Provider<Revisions> revisions;
+
+ @Inject
+ private Provider<PostReview> postReview;
+
+ private final Integer lines[] = {0, 1};
+
+ @Test
+ public void createDraft() throws Exception {
+ for (Integer line : lines) {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Side.REVISION, line, "comment 1");
+ addDraft(changeId, revId, comment);
+ Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
+ assertThat(result).hasSize(1);
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ }
+ }
+
+ @Test
+ public void postComment() throws Exception {
+ for (Integer line : lines) {
+ String file = "file";
+ String contents = "contents " + line;
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+ "first subject", file, contents);
+ PushOneCommit.Result r = push.to(git, "refs/for/master");
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput input = new ReviewInput();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ file, Side.REVISION, line, "comment 1");
+ input.comments = new HashMap<>();
+ input.comments.put(comment.path, Lists.newArrayList(comment));
+ revision(r).review(input);
+ Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
+ assertThat(result).isNotEmpty();
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ }
+ }
+
+ @Test
+ public void putDraft() throws Exception {
+ for (Integer line : lines) {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Side.REVISION, line, "comment 1");
+ addDraft(changeId, revId, comment);
+ Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ String uuid = actual.id;
+ comment.message = "updated comment 1";
+ updateDraft(changeId, revId, comment, uuid);
+ result = getDraftComments(changeId, revId);
+ actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ }
+ }
+
+ @Test
+ public void getDraft() throws Exception {
+ for (Integer line : lines) {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Side.REVISION, line, "comment 1");
+ CommentInfo returned = addDraft(changeId, revId, comment);
+ CommentInfo actual = getDraftComment(changeId, revId, returned.id);
+ assertCommentInfo(comment, actual);
+ }
+ }
+
+ @Test
+ public void deleteDraft() throws Exception {
+ for (Integer line : lines) {
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ "file1", Side.REVISION, line, "comment 1");
+ CommentInfo returned = addDraft(changeId, revId, comment);
+ deleteDraft(changeId, revId, returned.id);
+ Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
+ assertThat(drafts).isEmpty();
+ }
+ }
+
+ @Test
+ public void insertCommentsWithHistoricTimestamp() throws Exception {
+ Timestamp timestamp = new Timestamp(0);
+ for (Integer line : lines) {
+ String file = "file";
+ String contents = "contents " + line;
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+ "first subject", file, contents);
+ PushOneCommit.Result r = push.to(git, "refs/for/master");
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+ ReviewInput input = new ReviewInput();
+ ReviewInput.CommentInput comment = newCommentInfo(
+ file, Side.REVISION, line, "comment 1");
+ comment.updated = timestamp;
+ input.comments = new HashMap<>();
+ input.comments.put(comment.path, Lists.newArrayList(comment));
+ ChangeResource changeRsrc =
+ changes.get().parse(TopLevelResource.INSTANCE,
+ IdString.fromDecoded(changeId));
+ RevisionResource revRsrc =
+ revisions.get().parse(changeRsrc, IdString.fromDecoded(revId));
+ postReview.get().apply(revRsrc, input, timestamp);
+ Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
+ assertThat(result).isNotEmpty();
+ CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+ assertCommentInfo(comment, actual);
+ assertThat(comment.updated).isEqualTo(timestamp);
+ }
+ }
+
+ private CommentInfo addDraft(String changeId, String revId,
+ ReviewInput.CommentInput c) throws IOException {
+ RestResponse r = userSession.put(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts", c);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
+ return newGson().fromJson(r.getReader(), CommentInfo.class);
+ }
+
+ private void updateDraft(String changeId, String revId,
+ ReviewInput.CommentInput c, String uuid) throws IOException {
+ RestResponse r = userSession.put(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/" + uuid, c);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ }
+
+ private void deleteDraft(String changeId, String revId, String uuid)
+ throws IOException {
+ RestResponse r = userSession.delete(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/" + uuid);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+ }
+
+ private Map<String, List<CommentInfo>> getPublishedComments(String changeId,
+ String revId) throws IOException {
+ RestResponse r = userSession.get(
+ "/changes/" + changeId + "/revisions/" + revId + "/comments/");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ Type mapType = new TypeToken<Map<String, List<CommentInfo>>>() {}.getType();
+ return newGson().fromJson(r.getReader(), mapType);
+ }
+
+ private Map<String, List<CommentInfo>> getDraftComments(String changeId,
+ String revId) throws IOException {
+ RestResponse r = userSession.get(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/");
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ Type mapType = new TypeToken<Map<String, List<CommentInfo>>>() {}.getType();
+ return newGson().fromJson(r.getReader(), mapType);
+ }
+
+ private CommentInfo getDraftComment(String changeId, String revId,
+ String uuid) throws IOException {
+ RestResponse r = userSession.get(
+ "/changes/" + changeId + "/revisions/" + revId + "/drafts/" + uuid);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ return newGson().fromJson(r.getReader(), CommentInfo.class);
+ }
+
+ private static void assertCommentInfo(ReviewInput.CommentInput expected,
+ CommentInfo actual) {
+ assertThat(actual.line).isEqualTo(expected.line);
+ assertThat(actual.message).isEqualTo(expected.message);
+ assertThat(actual.inReplyTo).isEqualTo(expected.inReplyTo);
+ assertCommentRange(expected.range, actual.range);
+ if (actual.side == null) {
+ assertThat(Side.REVISION).isEqualTo(expected.side);
+ }
+ }
+
+ private static void assertCommentRange(Comment.Range expected,
+ Comment.Range actual) {
+ if (expected == null) {
+ assertThat(actual).isNull();
+ } else {
+ assertThat(actual).isNotNull();
+ assertThat(actual.startLine).isEqualTo(expected.startLine);
+ assertThat(actual.startCharacter).isEqualTo(expected.startCharacter);
+ assertThat(actual.endLine).isEqualTo(expected.endLine);
+ assertThat(actual.endCharacter).isEqualTo(expected.endCharacter);
+ }
+ }
+
+ private ReviewInput.CommentInput newCommentInfo(String path,
+ Side side, int line, String message) {
+ ReviewInput.CommentInput input = new ReviewInput.CommentInput();
+ input.path = path;
+ input.side = side;
+ input.line = line != 0 ? line : null;
+ input.message = message;
+ if (line != 0) {
+ Comment.Range range = new Comment.Range();
+ range.startLine = 1;
+ range.startCharacter = 1;
+ range.endLine = 1;
+ range.endCharacter = 5;
+ input.range = range;
+ }
+ return input;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 1456086c95..6cd39abe52 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -14,43 +14,50 @@
package com.google.gerrit.acceptance.server.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.add;
import static com.google.gerrit.acceptance.GitUtil.createCommit;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import static org.junit.Assert.assertEquals;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil.Commit;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.change.GetRelated.ChangeAndCommit;
import com.google.gerrit.server.change.GetRelated.RelatedInfo;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
import org.eclipse.jgit.api.ResetCommand.ResetType;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class GetRelatedIT extends AbstractDaemonTest {
+ @Inject
+ private ChangeEditUtil editUtil;
+
+ @Inject
+ private ChangeEditModifier editModifier;
@Test
- public void getRelatedNoResult() throws GitAPIException,
- IOException, Exception {
+ public void getRelatedNoResult() throws Exception {
PushOneCommit push = pushFactory.create(db, admin.getIdent());
PatchSet.Id ps = push.to(git, "refs/for/master").getPatchSetId();
List<ChangeAndCommit> related = getRelated(ps);
- assertEquals(0, related.size());
+ assertThat(related).isEmpty();
}
@Test
- public void getRelatedLinear() throws GitAPIException,
- IOException, Exception {
+ public void getRelatedLinear() throws Exception {
add(git, "a.txt", "1");
Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
add(git, "b.txt", "2");
@@ -59,15 +66,16 @@ public class GetRelatedIT extends AbstractDaemonTest {
for (Commit c : ImmutableList.of(c2, c1)) {
List<ChangeAndCommit> related = getRelated(getPatchSetId(c));
- assertEquals(2, related.size());
- assertEquals("related to " + c.getChangeId(), c2.getChangeId(), related.get(0).changeId);
- assertEquals("related to " + c.getChangeId(), c1.getChangeId(), related.get(1).changeId);
+ assertThat(related).hasSize(2);
+ assertThat(related.get(0).changeId)
+ .named("related to " + c.getChangeId()).isEqualTo(c2.getChangeId());
+ assertThat(related.get(1).changeId)
+ .named("related to " + c.getChangeId()).isEqualTo(c1.getChangeId());
}
}
@Test
- public void getRelatedReorder() throws GitAPIException,
- IOException, Exception {
+ public void getRelatedReorder() throws Exception {
// Create two commits and push.
add(git, "a.txt", "1");
Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
@@ -86,22 +94,25 @@ public class GetRelatedIT extends AbstractDaemonTest {
for (PatchSet.Id ps : ImmutableList.of(c2ps2, c1ps2)) {
List<ChangeAndCommit> related = getRelated(ps);
- assertEquals(2, related.size());
- assertEquals("related to " + ps, c1.getChangeId(), related.get(0).changeId);
- assertEquals("related to " + ps, c2.getChangeId(), related.get(1).changeId);
+ assertThat(related).hasSize(2);
+ assertThat(related.get(0).changeId).named("related to " + ps).isEqualTo(
+ c1.getChangeId());
+ assertThat(related.get(1).changeId).named("related to " + ps).isEqualTo(
+ c2.getChangeId());
}
for (PatchSet.Id ps : ImmutableList.of(c2ps1, c1ps1)) {
List<ChangeAndCommit> related = getRelated(ps);
- assertEquals(2, related.size());
- assertEquals("related to " + ps, c2.getChangeId(), related.get(0).changeId);
- assertEquals("related to " + ps, c1.getChangeId(), related.get(1).changeId);
+ assertThat(related).hasSize(2);
+ assertThat(related.get(0).changeId).named("related to " + ps).isEqualTo(
+ c2.getChangeId());
+ assertThat(related.get(1).changeId).named("related to " + ps).isEqualTo(
+ c1.getChangeId());
}
}
@Test
- public void getRelatedReorderAndExtend() throws GitAPIException,
- IOException, Exception {
+ public void getRelatedReorderAndExtend() throws Exception {
// Create two commits and push.
add(git, "a.txt", "1");
Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
@@ -124,30 +135,79 @@ public class GetRelatedIT extends AbstractDaemonTest {
for (PatchSet.Id ps : ImmutableList.of(c3ps1, c2ps2, c1ps2)) {
List<ChangeAndCommit> related = getRelated(ps);
- assertEquals(3, related.size());
- assertEquals("related to " + ps, c3.getChangeId(), related.get(0).changeId);
- assertEquals("related to " + ps, c1.getChangeId(), related.get(1).changeId);
- assertEquals("related to " + ps, c2.getChangeId(), related.get(2).changeId);
+ assertThat(related).hasSize(3);
+ assertThat(related.get(0).changeId).named("related to " + ps).isEqualTo(
+ c3.getChangeId());
+ assertThat(related.get(1).changeId).named("related to " + ps).isEqualTo(
+ c1.getChangeId());
+ assertThat(related.get(2).changeId).named("related to " + ps).isEqualTo(
+ c2.getChangeId());
}
for (PatchSet.Id ps : ImmutableList.of(c2ps1, c1ps1)) {
List<ChangeAndCommit> related = getRelated(ps);
- assertEquals(3, related.size());
- assertEquals("related to " + ps, c3.getChangeId(), related.get(0).changeId);
- assertEquals("related to " + ps, c2.getChangeId(), related.get(1).changeId);
- assertEquals("related to " + ps, c1.getChangeId(), related.get(2).changeId);
+ assertThat(related).hasSize(3);
+ assertThat(related.get(0).changeId).named("related to " + ps).isEqualTo(
+ c3.getChangeId());
+ assertThat(related.get(1).changeId).named("related to " + ps).isEqualTo(
+ c2.getChangeId());
+ assertThat(related.get(2).changeId).named("related to " + ps).isEqualTo(
+ c1.getChangeId());
}
}
+ @Test
+ public void getRelatedEdit() throws Exception {
+ add(git, "a.txt", "1");
+ Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
+ add(git, "b.txt", "2");
+ Commit c2 = createCommit(git, admin.getIdent(), "subject: 2");
+ add(git, "b.txt", "3");
+ Commit c3 = createCommit(git, admin.getIdent(), "subject: 3");
+ pushHead(git, "refs/for/master", false);
+
+ Change ch2 = getChange(c2).change();
+ editModifier.createEdit(ch2, getPatchSet(ch2));
+ editModifier.modifyFile(editUtil.byChange(ch2).get(), "a.txt",
+ RestSession.newRawInput(new byte[] {'a'}));
+ String editRev = editUtil.byChange(ch2).get().getRevision().get();
+
+ List<ChangeAndCommit> related = getRelated(ch2.getId(), 0);
+ assertThat(related).hasSize(3);
+ assertThat(related.get(0).changeId).named("related to " + c2.getChangeId())
+ .isEqualTo(c3.getChangeId());
+ assertThat(related.get(1).changeId).named("related to " + c2.getChangeId())
+ .isEqualTo(c2.getChangeId());
+ assertThat(related.get(1)._revisionNumber.intValue()).named(
+ "has edit revision number").isEqualTo(0);
+ assertThat(related.get(1).commit.commit).named(
+ "has edit revision " + editRev).isEqualTo(editRev);
+ assertThat(related.get(2).changeId).named("related to " + c2.getChangeId())
+ .isEqualTo(c1.getChangeId());
+ }
+
private List<ChangeAndCommit> getRelated(PatchSet.Id ps) throws IOException {
+ return getRelated(ps.getParentKey(), ps.get());
+ }
+
+ private List<ChangeAndCommit> getRelated(Change.Id changeId, int ps)
+ throws IOException {
String url = String.format("/changes/%d/revisions/%d/related",
- ps.getParentKey().get(), ps.get());
+ changeId.get(), ps);
return newGson().fromJson(adminSession.get(url).getReader(),
RelatedInfo.class).changes;
}
private PatchSet.Id getPatchSetId(Commit c) throws OrmException {
+ return getChange(c).change().currentPatchSetId();
+ }
+
+ private PatchSet getPatchSet(Change c) throws OrmException {
+ return db.patchSets().get(c.currentPatchSetId());
+ }
+
+ private ChangeData getChange(Commit c) throws OrmException {
return Iterables.getOnlyElement(
- db.changes().byKey(new Change.Key(c.getChangeId()))).currentPatchSetId();
+ queryProvider.get().byKeyPrefix(c.getChangeId()));
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
index 70a0a602ba..d598b06bc7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
@@ -14,16 +14,16 @@
package com.google.gerrit.acceptance.server.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.add;
import static com.google.gerrit.acceptance.GitUtil.amendCommit;
import static com.google.gerrit.acceptance.GitUtil.createCommit;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.GitUtil.rm;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil.Commit;
+import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Patch;
@@ -32,17 +32,15 @@ import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import org.eclipse.jgit.api.ResetCommand.ResetType;
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-import java.io.IOException;
import java.util.List;
+@NoHttpd
public class PatchListCacheIT extends AbstractDaemonTest {
private static String SUBJECT_1 = "subject 1";
private static String SUBJECT_2 = "subject 2";
@@ -56,8 +54,7 @@ public class PatchListCacheIT extends AbstractDaemonTest {
private PatchListCache patchListCache;
@Test
- public void listPatchesAgainstBase() throws GitAPIException, IOException,
- PatchListNotAvailableException, OrmException, RestApiException {
+ public void listPatchesAgainstBase() throws Exception {
add(git, FILE_D, "4");
createCommit(git, admin.getIdent(), SUBJECT_1);
pushHead(git, "refs/heads/master", false);
@@ -70,7 +67,7 @@ public class PatchListCacheIT extends AbstractDaemonTest {
// Compare Change 1,1 with Base (+FILE_A, -FILE_D)
List<PatchListEntry> entries = getCurrentPatches(c.getChangeId());
- assertEquals(3, entries.size());
+ assertThat(entries).hasSize(3);
assertAdded(Patch.COMMIT_MSG, entries.get(0));
assertAdded(FILE_A, entries.get(1));
assertDeleted(FILE_D, entries.get(2));
@@ -82,7 +79,7 @@ public class PatchListCacheIT extends AbstractDaemonTest {
entries = getCurrentPatches(c.getChangeId());
// Compare Change 1,2 with Base (+FILE_A, +FILE_B, -FILE_D)
- assertEquals(4, entries.size());
+ assertThat(entries).hasSize(4);
assertAdded(Patch.COMMIT_MSG, entries.get(0));
assertAdded(FILE_A, entries.get(1));
assertAdded(FILE_B, entries.get(2));
@@ -90,9 +87,7 @@ public class PatchListCacheIT extends AbstractDaemonTest {
}
@Test
- public void listPatchesAgainstBaseWithRebase() throws GitAPIException,
- IOException, PatchListNotAvailableException, OrmException,
- RestApiException {
+ public void listPatchesAgainstBaseWithRebase() throws Exception {
add(git, FILE_D, "4");
createCommit(git, admin.getIdent(), SUBJECT_1);
pushHead(git, "refs/heads/master", false);
@@ -103,7 +98,7 @@ public class PatchListCacheIT extends AbstractDaemonTest {
Commit c = createCommit(git, admin.getIdent(), SUBJECT_2);
pushHead(git, "refs/for/master", false);
List<PatchListEntry> entries = getCurrentPatches(c.getChangeId());
- assertEquals(3, entries.size());
+ assertThat(entries).hasSize(3);
assertAdded(Patch.COMMIT_MSG, entries.get(0));
assertAdded(FILE_A, entries.get(1));
assertDeleted(FILE_D, entries.get(2));
@@ -120,16 +115,14 @@ public class PatchListCacheIT extends AbstractDaemonTest {
// Compare Change 1,2 with Base (+FILE_A, -FILE_D))
entries = getCurrentPatches(c.getChangeId());
- assertEquals(3, entries.size());
+ assertThat(entries).hasSize(3);
assertAdded(Patch.COMMIT_MSG, entries.get(0));
assertAdded(FILE_A, entries.get(1));
assertDeleted(FILE_D, entries.get(2));
}
@Test
- public void listPatchesAgainstOtherPatchSet() throws GitAPIException,
- IOException, PatchListNotAvailableException, OrmException,
- RestApiException {
+ public void listPatchesAgainstOtherPatchSet() throws Exception {
add(git, FILE_D, "4");
createCommit(git, admin.getIdent(), SUBJECT_1);
pushHead(git, "refs/heads/master", false);
@@ -151,15 +144,13 @@ public class PatchListCacheIT extends AbstractDaemonTest {
// Compare Change 1,1 with Change 1,2 (+FILE_B)
List<PatchListEntry> entries = getPatches(a, b);
- assertEquals(2, entries.size());
+ assertThat(entries).hasSize(2);
assertModified(Patch.COMMIT_MSG, entries.get(0));
assertAdded(FILE_B, entries.get(1));
}
@Test
- public void listPatchesAgainstOtherPatchSetWithRebase()
- throws GitAPIException, IOException, PatchListNotAvailableException,
- OrmException, RestApiException {
+ public void listPatchesAgainstOtherPatchSetWithRebase() throws Exception {
add(git, FILE_D, "4");
createCommit(git, admin.getIdent(), SUBJECT_1);
pushHead(git, "refs/heads/master", false);
@@ -186,42 +177,42 @@ public class PatchListCacheIT extends AbstractDaemonTest {
// Compare Change 1,1 with Change 1,2 (+FILE_C)
List<PatchListEntry> entries = getPatches(a, b);
- assertEquals(2, entries.size());
+ assertThat(entries).hasSize(2);
assertModified(Patch.COMMIT_MSG, entries.get(0));
assertAdded(FILE_C, entries.get(1));
}
private static void assertAdded(String expectedNewName, PatchListEntry e) {
assertName(expectedNewName, e);
- assertEquals(ChangeType.ADDED, e.getChangeType());
+ assertThat(e.getChangeType()).isEqualTo(ChangeType.ADDED);
}
private static void assertModified(String expectedNewName, PatchListEntry e) {
assertName(expectedNewName, e);
- assertEquals(ChangeType.MODIFIED, e.getChangeType());
+ assertThat(e.getChangeType()).isEqualTo(ChangeType.MODIFIED);
}
private static void assertDeleted(String expectedNewName, PatchListEntry e) {
assertName(expectedNewName, e);
- assertEquals(ChangeType.DELETED, e.getChangeType());
+ assertThat(e.getChangeType()).isEqualTo(ChangeType.DELETED);
}
private static void assertName(String expectedNewName, PatchListEntry e) {
- assertEquals(expectedNewName, e.getNewName());
- assertNull(e.getOldName());
+ assertThat(e.getNewName()).isEqualTo(expectedNewName);
+ assertThat(e.getOldName()).isNull();
}
private List<PatchListEntry> getCurrentPatches(String changeId)
- throws PatchListNotAvailableException, OrmException, RestApiException {
+ throws PatchListNotAvailableException, RestApiException {
return patchListCache.get(getKey(null, getCurrentRevisionId(changeId))).getPatches();
}
private List<PatchListEntry> getPatches(ObjectId revisionIdA, ObjectId revisionIdB)
- throws PatchListNotAvailableException, OrmException {
+ throws PatchListNotAvailableException {
return patchListCache.get(getKey(revisionIdA, revisionIdB)).getPatches();
}
- private PatchListKey getKey(ObjectId revisionIdA, ObjectId revisionIdB) throws OrmException {
+ private PatchListKey getKey(ObjectId revisionIdA, ObjectId revisionIdB) {
return new PatchListKey(project, revisionIdA, revisionIdB, Whitespace.IGNORE_NONE);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 2a20a2cba8..cd76c12991 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -14,14 +14,10 @@
package com.google.gerrit.acceptance.server.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
-import static com.google.gerrit.server.project.Util.allow;
import static com.google.gerrit.server.project.Util.category;
import static com.google.gerrit.server.project.Util.value;
-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 com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
@@ -32,12 +28,10 @@ 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.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
+import com.google.gerrit.server.project.Util;
import org.junit.Before;
import org.junit.Test;
@@ -45,15 +39,6 @@ import org.junit.Test;
@NoHttpd
public class CustomLabelIT extends AbstractDaemonTest {
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
private final LabelType Q = category("CustomLabel",
value(1, "Positive"),
value(0, "No score"),
@@ -64,7 +49,7 @@ public class CustomLabelIT extends AbstractDaemonTest {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
AccountGroup.UUID anonymousUsers =
SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- allow(cfg, Permission.forLabel(Q.getName()), -1, 1, anonymousUsers,
+ Util.allow(cfg, Permission.forLabel(Q.getName()), -1, 1, anonymousUsers,
"refs/heads/*");
saveProjectConfig(cfg);
}
@@ -77,9 +62,9 @@ public class CustomLabelIT extends AbstractDaemonTest {
revision(r).review(new ReviewInput().label(Q.getName(), -1));
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(Q.getName());
- assertEquals(1, q.all.size());
- assertNotNull(q.rejected);
- assertNull(q.blocking);
+ assertThat(q.all).hasSize(1);
+ assertThat(q.rejected).isNotNull();
+ assertThat(q.blocking).isNull();
}
@Test
@@ -90,9 +75,9 @@ public class CustomLabelIT extends AbstractDaemonTest {
revision(r).review(new ReviewInput().label(Q.getName(), -1));
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(Q.getName());
- assertEquals(1, q.all.size());
- assertNotNull(q.rejected);
- assertNull(q.blocking);
+ assertThat(q.all).hasSize(1);
+ assertThat(q.rejected).isNotNull();
+ assertThat(q.blocking).isNull();
}
@Test
@@ -103,9 +88,9 @@ public class CustomLabelIT extends AbstractDaemonTest {
revision(r).review(new ReviewInput().label(Q.getName(), -1));
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(Q.getName());
- assertEquals(1, q.all.size());
- assertNotNull(q.rejected);
- assertNull(q.blocking);
+ assertThat(q.all).hasSize(1);
+ assertThat(q.rejected).isNotNull();
+ assertThat(q.blocking).isNull();
}
@Test
@@ -116,10 +101,10 @@ public class CustomLabelIT extends AbstractDaemonTest {
revision(r).review(new ReviewInput().label(Q.getName(), -1));
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(Q.getName());
- assertEquals(1, q.all.size());
- assertNull(q.disliked);
- assertNotNull(q.rejected);
- assertTrue(q.blocking);
+ assertThat(q.all).hasSize(1);
+ assertThat(q.disliked).isNull();
+ assertThat(q.rejected).isNotNull();
+ assertThat(q.blocking).isTrue();
}
@Test
@@ -129,10 +114,10 @@ public class CustomLabelIT extends AbstractDaemonTest {
revision(r).review(new ReviewInput().label(Q.getName(), -1));
ChangeInfo c = get(r.getChangeId());
LabelInfo q = c.labels.get(Q.getName());
- assertEquals(1, q.all.size());
- assertNull(q.disliked);
- assertNotNull(q.rejected);
- assertTrue(q.blocking);
+ assertThat(q.all).hasSize(1);
+ assertThat(q.disliked).isNull();
+ assertThat(q.rejected).isNotNull();
+ assertThat(q.blocking).isTrue();
}
private void saveLabelConfig() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
index bc311fd32d..efb461511b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
@@ -15,7 +15,7 @@
package com.google.gerrit.acceptance.server.project;
import static com.google.common.base.Preconditions.checkNotNull;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
@@ -25,13 +25,10 @@ import com.google.gerrit.extensions.api.changes.CherryPickInput;
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.server.config.AllProjectsName;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.testutil.ConfigSuite;
-import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -42,40 +39,25 @@ import org.junit.Test;
public class LabelTypeIT extends AbstractDaemonTest {
@ConfigSuite.Config
public static Config noteDbEnabled() {
- Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
- return cfg;
+ return NotesMigration.allEnabledConfig();
}
- @Inject
- private GitRepositoryManager repoManager;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AllProjectsName allProjects;
-
- @Inject
- private MetaDataUpdate.Server metaDataUpdateFactory;
-
private LabelType codeReview;
@Before
public void setUp() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
codeReview = checkNotNull(cfg.getLabelSections().get("Code-Review"));
- codeReview.setCopyMinScore(false);
- codeReview.setCopyMaxScore(false);
- codeReview.setCopyAllScoresOnTrivialRebase(false);
- codeReview.setCopyAllScoresIfNoCodeChange(false);
codeReview.setDefaultValue((short)-1);
saveProjectConfig(cfg);
}
@Test
public void noCopyMinScoreOnRework() throws Exception {
+ //allProjects only has it true by default
+ codeReview.setCopyMinScore(false);
+ saveLabelConfig();
+
PushOneCommit.Result r = createChange();
revision(r).review(ReviewInput.reject());
assertApproval(r, -2);
@@ -89,7 +71,7 @@ public class LabelTypeIT extends AbstractDaemonTest {
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(ReviewInput.reject());
- //assertApproval(r, -2);
+ assertApproval(r, -2);
r = amendChange(r.getChangeId());
assertApproval(r, -2);
}
@@ -141,6 +123,22 @@ public class LabelTypeIT extends AbstractDaemonTest {
}
@Test
+ public void noCopyAllScoresIfNoChange() throws Exception {
+ codeReview.setCopyAllScoresIfNoChange(false);
+ saveLabelConfig();
+ PushOneCommit.Result patchSet = readyPatchSetForNoChangeRebase();
+ rebase(patchSet);
+ assertApproval(patchSet, 0);
+ }
+
+ @Test
+ public void copyAllScoresIfNoChange() throws Exception {
+ PushOneCommit.Result patchSet = readyPatchSetForNoChangeRebase();
+ rebase(patchSet);
+ assertApproval(patchSet, 1);
+ }
+
+ @Test
public void noCopyAllScoresIfNoCodeChange() throws Exception {
String file = "a.txt";
String contents = "contents";
@@ -285,6 +283,34 @@ public class LabelTypeIT extends AbstractDaemonTest {
.get());
}
+ private PushOneCommit.Result readyPatchSetForNoChangeRebase()
+ throws Exception {
+ String file = "a.txt";
+ String contents = "contents";
+
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+ PushOneCommit.SUBJECT, file, contents);
+ PushOneCommit.Result base = push.to(git, "refs/for/master");
+ merge(base);
+
+ push = pushFactory.create(db, admin.getIdent(),
+ PushOneCommit.SUBJECT, file, contents + "M");
+ PushOneCommit.Result basePlusM = push.to(git, "refs/for/master");
+ merge(basePlusM);
+
+ push = pushFactory.create(db, admin.getIdent(),
+ PushOneCommit.SUBJECT, file, contents);
+ PushOneCommit.Result basePlusMMinusM = push.to(git, "refs/for/master");
+ merge(basePlusMMinusM);
+
+ git.checkout().setName(base.getCommit().name()).call();
+ push = pushFactory.create(db, admin.getIdent(),
+ PushOneCommit.SUBJECT, file, contents + "MM");
+ PushOneCommit.Result patchSet = push.to(git, "refs/for/master");
+ revision(patchSet).review(ReviewInput.recommend());
+ return patchSet;
+ }
+
private void saveLabelConfig() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
cfg.getLabelSections().clear();
@@ -306,8 +332,8 @@ public class LabelTypeIT extends AbstractDaemonTest {
revision(r).submit();
Repository repo = repoManager.openRepository(project);
try {
- assertEquals(r.getCommitId(),
- repo.getRef("refs/heads/master").getObjectId());
+ assertThat(repo.getRef("refs/heads/master").getObjectId()).isEqualTo(
+ r.getCommitId());
} finally {
repo.close();
}
@@ -327,9 +353,9 @@ public class LabelTypeIT extends AbstractDaemonTest {
private void doAssertApproval(int expected, ChangeInfo c) {
LabelInfo cr = c.labels.get("Code-Review");
- assertEquals(-1, (int) cr.defaultValue);
- assertEquals(1, cr.all.size());
- assertEquals("Administrator", cr.all.get(0).name);
- assertEquals(expected, cr.all.get(0).value.intValue());
+ assertThat((int) cr.defaultValue).isEqualTo(-1);
+ assertThat(cr.all).hasSize(1);
+ assertThat(cr.all.get(0).name).isEqualTo("Administrator");
+ assertThat(cr.all.get(0).value.intValue()).isEqualTo(expected);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
index 74b26ba677..2ea5dec1bd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
@@ -2,6 +2,5 @@ include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
srcs = glob(['*IT.java']),
- deps = ['//gerrit-acceptance-tests:lib'],
labels = ['ssh'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java
index bfce52304a..e4837168cd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BanCommitIT.java
@@ -14,39 +14,38 @@
package com.google.gerrit.acceptance.ssh;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.acceptance.GitUtil.add;
import static com.google.gerrit.acceptance.GitUtil.createCommit;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil.Commit;
+import com.google.gerrit.acceptance.NoHttpd;
-import com.jcraft.jsch.JSchException;
-
-import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.PushResult;
import org.junit.Test;
-import java.io.IOException;
import java.util.Locale;
+@NoHttpd
public class BanCommitIT extends AbstractDaemonTest {
@Test
- public void banCommit() throws IOException, GitAPIException, JSchException {
+ public void banCommit() throws Exception {
add(git, "a.txt", "some content");
Commit c = createCommit(git, admin.getIdent(), "subject");
String response =
sshSession.exec("gerrit ban-commit " + project.get() + " "
+ c.getCommit().getName());
- assertFalse(sshSession.hasError());
- assertFalse(response, response.toLowerCase(Locale.US).contains("error"));
+ assert_().withFailureMessage(sshSession.getError())
+ .that(sshSession.hasError()).isFalse();
+ assertThat(response.toLowerCase(Locale.US)).doesNotContain("error");
PushResult pushResult = pushHead(git, "refs/heads/master", false);
- assertTrue(pushResult.getRemoteUpdate("refs/heads/master").getMessage()
- .startsWith("contains banned commit"));
+ assertThat(pushResult.getRemoteUpdate("refs/heads/master").getMessage())
+ .startsWith("contains banned commit");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index 9f859dc723..2bdd894848 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -14,38 +14,31 @@
package com.google.gerrit.acceptance.ssh;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.acceptance.GitUtil.createProject;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GcAssert;
+import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.common.data.GarbageCollectionResult;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GarbageCollectionQueue;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.jcraft.jsch.JSchException;
-
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
+@NoHttpd
public class GarbageCollectionIT extends AbstractDaemonTest {
@Inject
- private AllProjectsName allProjects;
-
- @Inject
private GarbageCollection.Factory garbageCollectionFactory;
@Inject
@@ -54,15 +47,11 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
@Inject
private GcAssert gcAssert;
- private Project.NameKey project1;
private Project.NameKey project2;
private Project.NameKey project3;
@Before
public void setUp() throws Exception {
- project1 = new Project.NameKey("p1");
- createProject(sshSession, project1.get());
-
project2 = new Project.NameKey("p2");
createProject(sshSession, project2.get());
@@ -72,28 +61,29 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
@Test
@UseLocalDisk
- public void testGc() throws JSchException, IOException {
+ public void testGc() throws Exception {
String response =
- sshSession.exec("gerrit gc \"" + project1.get() + "\" \""
+ sshSession.exec("gerrit gc \"" + project.get() + "\" \""
+ project2.get() + "\"");
- assertFalse(sshSession.hasError());
+ assert_().withFailureMessage(sshSession.getError())
+ .that(sshSession.hasError()).isFalse();
assertNoError(response);
- gcAssert.assertHasPackFile(project1, project2);
+ gcAssert.assertHasPackFile(project, project2);
gcAssert.assertHasNoPackFile(allProjects, project3);
}
@Test
@UseLocalDisk
- public void testGcAll() throws JSchException, IOException {
+ public void testGcAll() throws Exception {
String response = sshSession.exec("gerrit gc --all");
- assertFalse(sshSession.hasError());
+ assert_().withFailureMessage(sshSession.getError())
+ .that(sshSession.hasError()).isFalse();
assertNoError(response);
- gcAssert.assertHasPackFile(allProjects, project1, project2, project3);
+ gcAssert.assertHasPackFile(allProjects, project, project2, project3);
}
@Test
- public void testGcWithoutCapability_Error() throws IOException, OrmException,
- JSchException {
+ public void testGcWithoutCapability_Error() throws Exception {
SshSession s = new SshSession(server, user);
s.exec("gerrit gc --all");
assertError("Capability runGC is required to access this resource", s.getError());
@@ -102,22 +92,23 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
@Test
@UseLocalDisk
- public void testGcAlreadyScheduled() {
- gcQueue.addAll(Arrays.asList(project1));
+ public void testGcAlreadyScheduled() throws Exception {
+ gcQueue.addAll(Arrays.asList(project));
GarbageCollectionResult result = garbageCollectionFactory.create().run(
- Arrays.asList(allProjects, project1, project2, project3));
- assertTrue(result.hasErrors());
- assertEquals(1, result.getErrors().size());
+ Arrays.asList(allProjects, project, project2, project3));
+ assertThat(result.hasErrors()).isTrue();
+ assertThat(result.getErrors().size()).isEqualTo(1);
GarbageCollectionResult.Error error = result.getErrors().get(0);
- assertEquals(GarbageCollectionResult.Error.Type.GC_ALREADY_SCHEDULED, error.getType());
- assertEquals(project1, error.getProjectName());
+ assertThat(error.getType()).isEqualTo(
+ GarbageCollectionResult.Error.Type.GC_ALREADY_SCHEDULED);
+ assertThat(error.getProjectName()).isEqualTo(project);
}
private void assertError(String expectedError, String response) {
- assertTrue(response, response.contains(expectedError));
+ assertThat(response).contains(expectedError);
}
private void assertNoError(String response) {
- assertFalse(response, response.toLowerCase(Locale.US).contains("error"));
+ assertThat(response.toLowerCase(Locale.US)).doesNotContain("error");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java
index 9bbc12597a..c9e0a89291 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java
@@ -14,13 +14,13 @@
package com.google.gerrit.acceptance.ssh;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
-import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -62,6 +62,6 @@ public class JschVerifyFalseBugIT extends AbstractDaemonTest {
for (Future<Void> future : futures) {
future.get();
}
- Assert.assertEquals(threads, futures.size());
+ assertThat(futures.size()).isEqualTo(threads);
}
}
diff --git a/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g b/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
index 423acb43f7..98f1af937d 100644
--- a/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
+++ b/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
@@ -168,7 +168,7 @@ fragment NON_WORD
: ( '\u0000'..' '
| '!'
| '"'
- | '#'
+ // '#' permit
| '$'
| '%'
| '&'
@@ -188,6 +188,6 @@ fragment NON_WORD
| '?'
| '[' | ']'
| '{' | '}'
- | '~'
+ // | '~' permit
)
;
diff --git a/gerrit-cache-h2/BUCK b/gerrit-cache-h2/BUCK
index d3e8994a2f..c7a2221e23 100644
--- a/gerrit-cache-h2/BUCK
+++ b/gerrit-cache-h2/BUCK
@@ -2,6 +2,7 @@ java_library(
name = 'cache-h2',
srcs = glob(['src/main/java/**/*.java']),
deps = [
+ '//gerrit-common:server',
'//gerrit-extension-api:api',
'//gerrit-server:server',
'//lib:guava',
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 65bb034eab..5870f91434 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -152,7 +152,7 @@ class H2CacheFactory implements PersistentCacheFactory, LifecycleListener {
}
}
- @SuppressWarnings({"unchecked", "cast"})
+ @SuppressWarnings({"unchecked"})
@Override
public <K, V> Cache<K, V> build(CacheBinding<K, V> def) {
long limit = config.getLong("cache", def.name(), "diskLimit", 128 << 20);
@@ -163,7 +163,7 @@ class H2CacheFactory implements PersistentCacheFactory, LifecycleListener {
SqlStore<K, V> store = newSqlStore(def.name(), def.keyType(), limit,
def.expireAfterWrite(TimeUnit.SECONDS));
- H2CacheImpl<K, V> cache = new H2CacheImpl<K, V>(
+ H2CacheImpl<K, V> cache = new H2CacheImpl<>(
executor, store, def.keyType(),
(Cache<K, ValueHolder<V>>) defaultFactory.create(def, true).build());
synchronized (caches) {
@@ -187,9 +187,9 @@ class H2CacheFactory implements PersistentCacheFactory, LifecycleListener {
def.expireAfterWrite(TimeUnit.SECONDS));
Cache<K, ValueHolder<V>> mem = (Cache<K, ValueHolder<V>>)
defaultFactory.create(def, true)
- .build((CacheLoader<K, V>) new H2CacheImpl.Loader<K, V>(
+ .build((CacheLoader<K, V>) new H2CacheImpl.Loader<>(
executor, store, loader));
- H2CacheImpl<K, V> cache = new H2CacheImpl<K, V>(
+ H2CacheImpl<K, V> cache = new H2CacheImpl<>(
executor, store, def.keyType(), mem);
caches.add(cache);
return cache;
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 55639882f4..123bb9afd4 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -11,8 +11,8 @@ import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.Funnels;
import com.google.common.hash.PrimitiveSink;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.server.cache.PersistentCache;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.TypeLiteral;
import org.h2.jdbc.JdbcSQLException;
@@ -363,21 +363,15 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
SqlHandle c = null;
try {
c = acquire();
- Statement s = c.conn.createStatement();
- try {
- ResultSet r;
+ try (Statement s = c.conn.createStatement()) {
if (estimatedSize <= 0) {
- r = s.executeQuery("SELECT COUNT(*) FROM data");
- try {
+ try (ResultSet r = s.executeQuery("SELECT COUNT(*) FROM data")) {
estimatedSize = r.next() ? r.getInt(1) : 0;
- } finally {
- r.close();
}
}
BloomFilter<K> b = newBloomFilter();
- r = s.executeQuery("SELECT k FROM data");
- try {
+ try (ResultSet r = s.executeQuery("SELECT k FROM data")) {
while (r.next()) {
b.put(keyType.get(r, 1));
}
@@ -390,15 +384,11 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
} else {
throw e;
}
- } finally {
- r.close();
}
return b;
- } finally {
- s.close();
}
} catch (SQLException e) {
- log.warn("Cannot build BloomFilter for " + url, e);
+ log.warn("Cannot build BloomFilter for " + url + ": " + e.getMessage());
c = close(c);
return null;
} finally {
@@ -414,8 +404,7 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
c.get = c.conn.prepareStatement("SELECT v, created FROM data WHERE k=?");
}
keyType.set(c.get, 1, key);
- ResultSet r = c.get.executeQuery();
- try {
+ try (ResultSet r = c.get.executeQuery()) {
if (!r.next()) {
missCount.incrementAndGet();
return null;
@@ -436,7 +425,6 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
touch(c, key);
return h;
} finally {
- r.close();
c.get.clearParameters();
}
} catch (SQLException e) {
@@ -533,11 +521,8 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
SqlHandle c = null;
try {
c = acquire();
- Statement s = c.conn.createStatement();
- try {
+ try (Statement s = c.conn.createStatement()) {
s.executeUpdate("DELETE FROM data");
- } finally {
- s.close();
}
bloomFilter = newBloomFilter();
} catch (SQLException e) {
@@ -552,28 +537,23 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
SqlHandle c = null;
try {
c = acquire();
- Statement s = c.conn.createStatement();
- try {
+ try (Statement s = c.conn.createStatement()) {
long used = 0;
- ResultSet r = s.executeQuery("SELECT"
+ try (ResultSet r = s.executeQuery("SELECT"
+ " SUM(OCTET_LENGTH(k) + OCTET_LENGTH(v))"
- + " FROM data");
- try {
+ + " FROM data")) {
used = r.next() ? r.getLong(1) : 0;
- } finally {
- r.close();
}
if (used <= maxSize) {
return;
}
- r = s.executeQuery("SELECT"
+ try (ResultSet r = s.executeQuery("SELECT"
+ " k"
+ ",OCTET_LENGTH(k) + OCTET_LENGTH(v)"
+ ",created"
+ " FROM data"
- + " ORDER BY accessed");
- try {
+ + " ORDER BY accessed")) {
while (maxSize < used && r.next()) {
K key = keyType.get(r, 1);
Timestamp created = r.getTimestamp(3);
@@ -584,11 +564,7 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
used -= r.getLong(2);
}
}
- } finally {
- r.close();
}
- } finally {
- s.close();
}
} catch (SQLException e) {
log.warn("Cannot prune cache " + url, e);
@@ -604,22 +580,15 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
SqlHandle c = null;
try {
c = acquire();
- Statement s = c.conn.createStatement();
- try {
- ResultSet r = s.executeQuery("SELECT"
- + " COUNT(*)"
- + ",SUM(OCTET_LENGTH(k) + OCTET_LENGTH(v))"
- + " FROM data");
- try {
- if (r.next()) {
- size = r.getLong(1);
- space = r.getLong(2);
- }
- } finally {
- r.close();
+ try (Statement s = c.conn.createStatement();
+ ResultSet r = s.executeQuery("SELECT"
+ + " COUNT(*)"
+ + ",SUM(OCTET_LENGTH(k) + OCTET_LENGTH(v))"
+ + " FROM data")) {
+ if (r.next()) {
+ size = r.getLong(1);
+ space = r.getLong(2);
}
- } finally {
- s.close();
}
} catch (SQLException e) {
log.warn("Cannot get DiskStats for " + url, e);
@@ -665,16 +634,13 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
SqlHandle(String url, KeyType<?> type) throws SQLException {
this.url = url;
this.conn = org.h2.Driver.load().connect(url, null);
- Statement stmt = conn.createStatement();
- try {
+ try (Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS data"
+ "(k " + type.columnType() + " NOT NULL PRIMARY KEY HASH"
+ ",v OTHER NOT NULL"
+ ",created TIMESTAMP NOT NULL"
+ ",accessed TIMESTAMP NOT NULL"
+ ")");
- } finally {
- stmt.close();
}
}
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
index 3ba22c5323..88e503e93b 100644
--- a/gerrit-common/BUCK
+++ b/gerrit-common/BUCK
@@ -7,8 +7,11 @@ ANNOTATIONS = [
]
EXCLUDES = [
+ SRC + 'common/SiteLibraryLoaderUtil.java',
SRC + 'common/PluginData.java',
SRC + 'common/FileUtil.java',
+ SRC + 'common/IoUtil.java',
+ SRC + 'common/TimeUtil.java',
]
java_library(
@@ -47,13 +50,17 @@ java_library(
'//lib:gwtorm',
'//lib:guava',
'//lib/jgit:jgit',
+ '//lib/joda:joda-time',
],
visibility = ['PUBLIC'],
)
+TEST = 'src/test/java/com/google/gerrit/common/'
+AUTO_VALUE_TEST_SRCS = [TEST + 'AutoValueTest.java']
+
java_test(
name = 'client_tests',
- srcs = glob(['src/test/java/**/*.java']),
+ srcs = glob(['src/test/java/**/*.java'], excludes = AUTO_VALUE_TEST_SRCS),
deps = [
':client',
'//lib:guava',
@@ -61,3 +68,14 @@ java_test(
],
source_under_test = [':client'],
)
+
+java_test(
+ name = 'auto_value_tests',
+ srcs = AUTO_VALUE_TEST_SRCS,
+ deps = [
+ '//lib:guava',
+ '//lib:junit',
+ '//lib:truth',
+ '//lib/auto:auto-value',
+ ],
+)
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
index d9a64fc69b..c45d9f9f64 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.common;
import com.google.common.collect.Sets;
@@ -46,6 +46,7 @@ public final class IoUtil {
try {
src.close();
} catch (IOException e2) {
+ // Ignore
}
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index c0382da04f..b8046075dc 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -50,6 +50,10 @@ public class PageLinks {
return toChange(c.getId());
}
+ public static String toChangeInEditMode(Change.Id c) {
+ return "/c/" + c + ",edit/";
+ }
+
public static String toChange(final Change.Id c) {
return "/c/" + c + "/";
}
@@ -68,7 +72,7 @@ public class PageLinks {
}
public static String toChange(final PatchSet.Id ps) {
- return "/c/" + ps.getParentKey() + "/" + ps.get();
+ return "/c/" + ps.getParentKey() + "/" + ps.getId();
}
public static String toProject(final Project.NameKey p) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
index fc5bb56b7b..ffdae9d46b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
@@ -14,9 +14,8 @@
package com.google.gerrit.common;
-import com.google.common.base.Objects;
-
import java.io.File;
+import java.util.Objects;
public class PluginData {
public final String name;
@@ -33,14 +32,14 @@ public class PluginData {
public boolean equals(Object obj) {
if (obj instanceof PluginData) {
PluginData o = (PluginData) obj;
- return Objects.equal(name, o.name) && Objects.equal(version, o.version)
- && Objects.equal(pluginFile, o.pluginFile);
+ return Objects.equals(name, o.name) && Objects.equals(version, o.version)
+ && Objects.equals(pluginFile, o.pluginFile);
}
return super.equals(obj);
}
@Override
public int hashCode() {
- return Objects.hashCode(name, version, pluginFile);
+ return Objects.hash(name, version, pluginFile);
}
} \ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
new file mode 100644
index 0000000000..a98e0a58ea
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 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.common;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public final class SiteLibraryLoaderUtil {
+
+ public static void loadSiteLib(File libdir) {
+ File[] jars = listJars(libdir);
+ if (jars != null && 0 < jars.length) {
+ Arrays.sort(jars, new Comparator<File>() {
+ @Override
+ public int compare(File a, File b) {
+ // Sort by reverse last-modified time so newer JARs are first.
+ int cmp = Long.compare(b.lastModified(), a.lastModified());
+ if (cmp != 0) {
+ return cmp;
+ }
+ return a.getName().compareTo(b.getName());
+ }
+ });
+ IoUtil.loadJARs(jars);
+ }
+ }
+
+ public static File[] listJars(File libdir) {
+ File[] jars = libdir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File path) {
+ String name = path.getName();
+ return (name.endsWith(".jar") || name.endsWith(".zip"))
+ && path.isFile();
+ }
+ });
+ return jars;
+ }
+
+ private SiteLibraryLoaderUtil() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
index 6bc261fe76..4274b5aa0b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/TimeUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.util;
+package com.google.gerrit.common;
import org.joda.time.DateTimeUtils;
@@ -28,9 +28,8 @@ public class TimeUtil {
return new Timestamp(nowMs());
}
- public static Timestamp roundTimestampToSecond(Timestamp t) {
- long milliseconds = (t.getTime()/1000) * 1000;
- return new Timestamp(milliseconds);
+ public static Timestamp roundToSecond(Timestamp t) {
+ return new Timestamp((t.getTime() / 1000) * 1000);
}
private TimeUtil() {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index 9a222ed231..f21f894fca 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -124,7 +124,7 @@ public class AccessSection extends RefConfigSection implements
if (!super.equals(obj) || !(obj instanceof AccessSection)) {
return false;
}
- return new HashSet<Permission>(getPermissions()).equals(new HashSet<>(
+ return new HashSet<>(getPermissions()).equals(new HashSet<>(
((AccessSection) obj).getPermissions()));
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
index 5a7559def2..54a573dacc 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
@@ -23,8 +23,8 @@ import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
+import com.google.gwtjsonrpc.common.VoidResult;
import java.util.List;
import java.util.Set;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
deleted file mode 100644
index f09241d404..0000000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
+++ /dev/null
@@ -1,126 +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.common.data;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class ApprovalDetail {
- public static List<ApprovalDetail> sort(Collection<ApprovalDetail> ads,
- final int owner) {
- List<ApprovalDetail> sorted = new ArrayList<>(ads);
- Collections.sort(sorted, new Comparator<ApprovalDetail>() {
- public int compare(ApprovalDetail o1, ApprovalDetail o2) {
- int byOwner = (o2.account.get() == owner ? 1 : 0)
- - (o1.account.get() == owner ? 1 : 0);
- return byOwner != 0 ? byOwner : (o1.hasNonZero - o2.hasNonZero);
- }
- });
- return sorted;
- }
-
- protected Account.Id account;
- protected List<PatchSetApproval> approvals;
- protected boolean canRemove;
- private Set<String> votable;
-
- private transient Set<String> approved;
- private transient Set<String> rejected;
- private transient Map<String, Integer> values;
- private transient int hasNonZero;
-
- protected ApprovalDetail() {
- }
-
- public ApprovalDetail(final Account.Id id) {
- account = id;
- approvals = new ArrayList<>();
- }
-
- public Account.Id getAccount() {
- return account;
- }
-
- public boolean canRemove() {
- return canRemove;
- }
-
- public void setCanRemove(boolean removeable) {
- canRemove = removeable;
- }
-
- public void approved(String label) {
- if (approved == null) {
- approved = new HashSet<>();
- }
- approved.add(label);
- hasNonZero = 1;
- }
-
- public void rejected(String label) {
- if (rejected == null) {
- rejected = new HashSet<>();
- }
- rejected.add(label);
- hasNonZero = 1;
- }
-
- public void votable(String label) {
- if (votable == null) {
- votable = new HashSet<>();
- }
- votable.add(label);
- }
-
- public void value(String label, int value) {
- if (values == null) {
- values = new HashMap<>();
- }
- values.put(label, value);
- if (value != 0) {
- hasNonZero = 1;
- }
- }
-
- public boolean isApproved(String label) {
- return approved != null && approved.contains(label);
- }
-
- public boolean isRejected(String label) {
- return rejected != null && rejected.contains(label);
- }
-
- public boolean canVote(String label) {
- return votable != null && votable.contains(label);
- }
-
- public int getValue(String label) {
- if (values == null) {
- return 0;
- }
- Integer v = values.get(label);
- return v != null ? v : 0;
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
deleted file mode 100644
index e067f0624f..0000000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
+++ /dev/null
@@ -1,268 +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.common.data;
-
-import com.google.gerrit.extensions.common.SubmitType;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-
-import java.util.List;
-import java.util.Set;
-
-/** Detail necessary to display a change. */
-public class ChangeDetail {
- protected AccountInfoCache accounts;
- protected boolean allowsAnonymous;
- protected boolean canAbandon;
- protected boolean canEditCommitMessage;
- protected boolean canCherryPick;
- protected boolean canPublish;
- protected boolean canRebase;
- protected boolean canRestore;
- protected boolean canRevert;
- protected boolean canDeleteDraft;
- protected Change change;
- protected boolean starred;
- protected List<ChangeInfo> dependsOn;
- protected List<ChangeInfo> neededBy;
- protected List<PatchSet> patchSets;
- protected Set<PatchSet.Id> patchSetsWithDraftComments;
- protected List<SubmitRecord> submitRecords;
- protected SubmitType submitType;
- protected SubmitTypeRecord submitTypeRecord;
- protected boolean canSubmit;
- protected List<ChangeMessage> messages;
- protected PatchSet.Id currentPatchSetId;
- protected PatchSetDetail currentDetail;
- protected boolean canEdit;
- protected boolean canEditTopicName;
-
- public ChangeDetail() {
- }
-
- public AccountInfoCache getAccounts() {
- return accounts;
- }
-
- public void setAccounts(AccountInfoCache aic) {
- accounts = aic;
- }
-
- public boolean isAllowsAnonymous() {
- return allowsAnonymous;
- }
-
- public void setAllowsAnonymous(final boolean anon) {
- allowsAnonymous = anon;
- }
-
- public boolean canAbandon() {
- return canAbandon;
- }
-
- public void setCanAbandon(final boolean a) {
- canAbandon = a;
- }
-
- public boolean canEditCommitMessage() {
- return canEditCommitMessage;
- }
-
- public void setCanEditCommitMessage(final boolean a) {
- canEditCommitMessage = a;
- }
-
- public boolean canCherryPick() {
- return canCherryPick;
- }
-
- public void setCanCherryPick(final boolean a) {
- canCherryPick = a;
- }
-
- public boolean canPublish() {
- return canPublish;
- }
-
- public void setCanPublish(final boolean a) {
- canPublish = a;
- }
-
- public boolean canRebase() {
- return canRebase;
- }
-
- public void setCanRebase(final boolean a) {
- canRebase = a;
- }
-
- public boolean canRestore() {
- return canRestore;
- }
-
- public void setCanRestore(final boolean a) {
- canRestore = a;
- }
-
- public boolean canRevert() {
- return canRevert;
- }
-
- public void setCanRevert(boolean a) {
- canRevert = a;
- }
-
- public boolean canSubmit() {
- return canSubmit;
- }
-
- public void setCanSubmit(boolean a) {
- canSubmit = a;
- }
-
- public boolean canDeleteDraft() {
- return canDeleteDraft;
- }
-
- public void setCanDeleteDraft(boolean a) {
- canDeleteDraft = a;
- }
-
- public boolean canEditTopicName() {
- return canEditTopicName;
- }
-
- public void setCanEditTopicName(boolean a) {
- canEditTopicName = a;
- }
-
- public Change getChange() {
- return change;
- }
-
- public void setChange(final Change change) {
- this.change = change;
- this.currentPatchSetId = change.currentPatchSetId();
- }
-
- public boolean isStarred() {
- return starred;
- }
-
- public void setStarred(final boolean s) {
- starred = s;
- }
-
- public List<ChangeInfo> getDependsOn() {
- return dependsOn;
- }
-
- public void setDependsOn(List<ChangeInfo> d) {
- dependsOn = d;
- }
-
- public List<ChangeInfo> getNeededBy() {
- return neededBy;
- }
-
- public void setNeededBy(List<ChangeInfo> d) {
- neededBy = d;
- }
-
- public List<ChangeMessage> getMessages() {
- return messages;
- }
-
- public void setMessages(List<ChangeMessage> m) {
- messages = m;
- }
-
- public List<PatchSet> getPatchSets() {
- return patchSets;
- }
-
- public void setPatchSets(List<PatchSet> s) {
- patchSets = s;
- }
-
- public void setPatchSetsWithDraftComments(Set<PatchSet.Id> pwdc) {
- this.patchSetsWithDraftComments = pwdc;
- }
-
- public boolean hasDraftComments(PatchSet.Id id) {
- return patchSetsWithDraftComments.contains(id);
- }
-
- public void setSubmitRecords(List<SubmitRecord> all) {
- submitRecords = all;
- }
-
- public List<SubmitRecord> getSubmitRecords() {
- return submitRecords;
- }
-
- public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
- this.submitTypeRecord = submitTypeRecord;
- }
-
- public SubmitTypeRecord getSubmitTypeRecord() {
- return submitTypeRecord;
- }
-
- public boolean isCurrentPatchSet(final PatchSetDetail detail) {
- return currentPatchSetId != null
- && detail.getPatchSet().getId().equals(currentPatchSetId);
- }
-
- public PatchSet getCurrentPatchSet() {
- if (currentPatchSetId != null) {
- // We search through the list backwards because its *very* likely
- // that the current patch set is also the last patch set.
- //
- for (int i = patchSets.size() - 1; i >= 0; i--) {
- final PatchSet ps = patchSets.get(i);
- if (ps.getId().equals(currentPatchSetId)) {
- return ps;
- }
- }
- }
- return null;
- }
-
- public PatchSetDetail getCurrentPatchSetDetail() {
- return currentDetail;
- }
-
- public void setCurrentPatchSetDetail(PatchSetDetail d) {
- currentDetail = d;
- }
-
- public void setCurrentPatchSetId(final PatchSet.Id id) {
- currentPatchSetId = id;
- }
-
- public String getDescription() {
- return currentDetail != null ? currentDetail.getInfo().getMessage() : "";
- }
-
- public void setCanEdit(boolean a) {
- canEdit = a;
- }
-
- public boolean canEdit() {
- return canEdit;
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java
index 31fd82776e..a744122a08 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java
@@ -31,7 +31,6 @@ public class ChangeInfo {
protected String topic;
protected boolean starred;
protected Timestamp lastUpdatedOn;
- protected String sortKey;
protected PatchSet.Id patchSetId;
protected boolean latest;
@@ -52,7 +51,6 @@ public class ChangeInfo {
branch = c.getDest().getShortName();
topic = c.getTopic();
lastUpdatedOn = c.getLastUpdatedOn();
- sortKey = c.getSortKey();
patchSetId = patchId;
latest = patchSetId == null || patchSetId.equals(c.currentPatchSetId());
}
@@ -112,8 +110,4 @@ public class ChangeInfo {
public java.sql.Timestamp getLastUpdatedOn() {
return lastUpdatedOn;
}
-
- public String getSortKey() {
- return sortKey;
- }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index 66309eafed..d911390367 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -16,7 +16,6 @@ package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.FieldName;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.client.AuthType;
@@ -53,10 +52,11 @@ public class GerritConfig implements Cloneable {
protected String anonymousCowardName;
protected int suggestFrom;
protected int changeUpdateDelay;
- protected AccountGeneralPreferences.ChangeScreen changeScreen;
protected List<String> archiveFormats;
protected int largeChangeSize;
- protected boolean newFeatures;
+ protected String replyLabel;
+ protected String replyTitle;
+ protected boolean allowDraftChanges;
public String getLoginUrl() {
return loginUrl;
@@ -277,14 +277,6 @@ public class GerritConfig implements Cloneable {
changeUpdateDelay = seconds;
}
- public AccountGeneralPreferences.ChangeScreen getChangeScreen() {
- return changeScreen;
- }
-
- public void setChangeScreen(AccountGeneralPreferences.ChangeScreen ui) {
- this.changeScreen = ui;
- }
-
public int getLargeChangeSize() {
return largeChangeSize;
}
@@ -301,11 +293,27 @@ public class GerritConfig implements Cloneable {
archiveFormats = formats;
}
- public boolean getNewFeatures() {
- return newFeatures;
+ public String getReplyTitle() {
+ return replyTitle;
+ }
+
+ public void setReplyTitle(String r) {
+ replyTitle = r;
+ }
+
+ public String getReplyLabel() {
+ return replyLabel;
+ }
+
+ public void setReplyLabel(String r) {
+ replyLabel = r;
+ }
+
+ public boolean isAllowDraftChanges() {
+ return allowDraftChanges;
}
- public void setNewFeatures(boolean n) {
- newFeatures = n;
+ public void setAllowDraftChanges(boolean b) {
+ allowDraftChanges = b;
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index f0551365c3..d50e7541aa 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -35,6 +35,16 @@ public class GlobalCapability {
*/
public static final String ADMINISTRATE_SERVER = "administrateServer";
+ /** Maximum number of changes that may be pushed in a batch. */
+ public static final String BATCH_CHANGES_LIMIT = "batchChangesLimit";
+
+ /**
+ * Default maximum number of changes that may be pushed in a batch, 0 means no
+ * limit. This is just used as a suggestion for prepopulating the field in the
+ * access UI.
+ */
+ public static final int DEFAULT_MAX_BATCH_CHANGES_LIMIT = 0;
+
/** Can create any account on the server. */
public static final String CREATE_ACCOUNT = "createAccount";
@@ -58,12 +68,12 @@ public class GlobalCapability {
/** Can flush any cache except the active web_sessions cache. */
public static final String FLUSH_CACHES = "flushCaches";
- /** Can generate HTTP passwords for user other than self. */
- public static final String GENERATE_HTTP_PASSWORD = "generateHttpPassword";
-
/** Can terminate any task using the kill command. */
public static final String KILL_TASK = "killTask";
+ /** Can modify any account on the server. */
+ public static final String MODIFY_ACCOUNT = "modifyAccount";
+
/** Queue a user can access to submit their tasks to. */
public static final String PRIORITY = "priority";
@@ -104,12 +114,14 @@ public class GlobalCapability {
NAMES_ALL = new ArrayList<>();
NAMES_ALL.add(ACCESS_DATABASE);
NAMES_ALL.add(ADMINISTRATE_SERVER);
+ NAMES_ALL.add(BATCH_CHANGES_LIMIT);
NAMES_ALL.add(CREATE_ACCOUNT);
NAMES_ALL.add(CREATE_GROUP);
NAMES_ALL.add(CREATE_PROJECT);
NAMES_ALL.add(EMAIL_REVIEWERS);
NAMES_ALL.add(FLUSH_CACHES);
NAMES_ALL.add(KILL_TASK);
+ NAMES_ALL.add(MODIFY_ACCOUNT);
NAMES_ALL.add(PRIORITY);
NAMES_ALL.add(QUERY_LIMIT);
NAMES_ALL.add(RUN_AS);
@@ -139,7 +151,8 @@ public class GlobalCapability {
/** @return true if the capability should have a range attached. */
public static boolean hasRange(String varName) {
- return QUERY_LIMIT.equalsIgnoreCase(varName);
+ return QUERY_LIMIT.equalsIgnoreCase(varName)
+ || BATCH_CHANGES_LIMIT.equalsIgnoreCase(varName);
}
/** @return the valid range for the capability if it has one, otherwise null. */
@@ -150,6 +163,12 @@ public class GlobalCapability {
0, Integer.MAX_VALUE,
0, DEFAULT_MAX_QUERY_LIMIT);
}
+ if (BATCH_CHANGES_LIMIT.equalsIgnoreCase(varName)) {
+ return new PermissionRange.WithDefaults(
+ varName,
+ 0, Integer.MAX_VALUE,
+ 0, DEFAULT_MAX_BATCH_CHANGES_LIMIT);
+ }
return null;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
index 323af236c4..430c23c8bc 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
@@ -30,6 +30,7 @@ public class HostPageData {
public Theme theme;
public List<String> plugins;
public List<Message> messages;
+ public boolean isNoteDbEnabled;
public static class Theme {
public String backgroundColor;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index 5018df9afa..cf6f7564fb 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -14,8 +14,8 @@
package com.google.gerrit.common.data;
+import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import java.util.ArrayList;
import java.util.Collections;
@@ -25,6 +25,13 @@ import java.util.List;
import java.util.Map;
public class LabelType {
+ public static final boolean DEF_CAN_OVERRIDE = true;
+ public static final boolean DEF_COPY_ALL_SCORES_IF_NO_CHANGE = true;
+ 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_MAX_SCORE = false;
+ public static final boolean DEF_COPY_MIN_SCORE = false;
+
public static LabelType withDefaultValues(String name) {
checkName(name);
List<LabelValue> values = new ArrayList<>(2);
@@ -66,6 +73,7 @@ public class LabelType {
return Collections.unmodifiableList(values);
}
Collections.sort(values, new Comparator<LabelValue>() {
+ @Override
public int compare(LabelValue o1, LabelValue o2) {
return o1.getValue() - o2.getValue();
}
@@ -93,6 +101,7 @@ public class LabelType {
protected boolean copyMaxScore;
protected boolean copyAllScoresOnTrivialRebase;
protected boolean copyAllScoresIfNoCodeChange;
+ protected boolean copyAllScoresIfNoChange;
protected short defaultValue;
protected List<LabelValue> values;
@@ -125,6 +134,12 @@ public class LabelType {
maxPositive = values.get(values.size() - 1).getValue();
}
}
+ setCanOverride(DEF_CAN_OVERRIDE);
+ setCopyAllScoresIfNoChange(DEF_COPY_ALL_SCORES_IF_NO_CHANGE);
+ setCopyAllScoresIfNoCodeChange(DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
+ setCopyAllScoresOnTrivialRebase(DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
+ setCopyMaxScore(DEF_COPY_MAX_SCORE);
+ setCopyMinScore(DEF_COPY_MIN_SCORE);
}
public String getName() {
@@ -218,6 +233,14 @@ public class LabelType {
this.copyAllScoresIfNoCodeChange = copyAllScoresIfNoCodeChange;
}
+ public boolean isCopyAllScoresIfNoChange() {
+ return copyAllScoresIfNoChange;
+ }
+
+ public void setCopyAllScoresIfNoChange(boolean copyAllScoresIfNoChange) {
+ this.copyAllScoresIfNoChange = copyAllScoresIfNoChange;
+ }
+
public boolean isMaxNegative(PatchSetApproval ca) {
return maxNegative == ca.getValue();
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java
index 18928f23b4..66f6a8e610 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
+import com.google.gerrit.reviewdb.client.LabelId;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
index 454324b788..4ed296fc7e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
@@ -145,7 +145,7 @@ public class ParameterizedString {
}
}
- private static abstract class Format {
+ private abstract static class Format {
abstract void format(StringBuilder b, Map<String, String> p);
}
@@ -200,7 +200,7 @@ public class ParameterizedString {
}
}
- private static abstract class Function {
+ private abstract static class Function {
abstract String apply(String a);
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index 914e69f0bd..046df1d4d9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -56,6 +56,7 @@ public class PatchScript {
protected boolean intralineDifference;
protected boolean intralineFailure;
protected boolean intralineTimeout;
+ protected boolean binary;
public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
final String nn, final FileMode om, final FileMode nm,
@@ -64,7 +65,7 @@ public class PatchScript {
final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
final String mta, final String mtb, final CommentDetail cd,
final List<Patch> hist, final boolean hf, final boolean id,
- final boolean idf, final boolean idt) {
+ final boolean idf, final boolean idt, boolean bin) {
changeId = ck;
changeType = ct;
oldName = on;
@@ -86,6 +87,7 @@ public class PatchScript {
intralineDifference = id;
intralineFailure = idf;
intralineTimeout = idt;
+ binary = bin;
}
protected PatchScript() {
@@ -194,4 +196,8 @@ public class PatchScript {
}
return new EditList(edits, ctx, a.size(), b.size()).getHunks();
}
+
+ public boolean isBinary() {
+ return binary;
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
deleted file mode 100644
index a9b6335071..0000000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
+++ /dev/null
@@ -1,79 +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.common.data;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-
-import java.util.List;
-
-public class PatchSetPublishDetail {
- protected AccountInfoCache accounts;
- protected PatchSetInfo patchSetInfo;
- protected Change change;
- protected List<PatchLineComment> drafts;
- protected List<SubmitRecord> submitRecords;
- protected SubmitTypeRecord submitTypeRecord;
- protected boolean canSubmit;
-
- public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
- this.submitTypeRecord = submitTypeRecord;
- }
-
- public SubmitTypeRecord getSubmitTypeRecord() {
- return submitTypeRecord;
- }
-
- public void setAccounts(AccountInfoCache accounts) {
- this.accounts = accounts;
- }
-
- public void setPatchSetInfo(PatchSetInfo patchSetInfo) {
- this.patchSetInfo = patchSetInfo;
- }
-
- public void setChange(Change change) {
- this.change = change;
- }
-
- public void setDrafts(List<PatchLineComment> drafts) {
- this.drafts = drafts;
- }
-
- public void setCanSubmit(boolean allowed) {
- canSubmit = allowed;
- }
-
- public AccountInfoCache getAccounts() {
- return accounts;
- }
-
- public Change getChange() {
- return change;
- }
-
- public PatchSetInfo getPatchSetInfo() {
- return patchSetInfo;
- }
-
- public List<PatchLineComment> getDrafts() {
- return drafts;
- }
-
- public boolean canSubmit() {
- return canSubmit;
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 2379b4ac9e..a041dc632c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -24,6 +24,7 @@ public class Permission implements Comparable<Permission> {
public static final String ABANDON = "abandon";
public static final String CREATE = "create";
public static final String DELETE_DRAFTS = "deleteDrafts";
+ public static final String EDIT_HASHTAGS = "editHashtags";
public static final String EDIT_TOPIC_NAME = "editTopicName";
public static final String FORGE_AUTHOR = "forgeAuthor";
public static final String FORGE_COMMITTER = "forgeCommitter";
@@ -68,6 +69,7 @@ public class Permission implements Comparable<Permission> {
NAMES_LC.add(SUBMIT_AS.toLowerCase());
NAMES_LC.add(VIEW_DRAFTS.toLowerCase());
NAMES_LC.add(EDIT_TOPIC_NAME.toLowerCase());
+ NAMES_LC.add(EDIT_HASHTAGS.toLowerCase());
NAMES_LC.add(DELETE_DRAFTS.toLowerCase());
NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase());
@@ -263,8 +265,7 @@ public class Permission implements Comparable<Permission> {
if (!name.equals(other.name) || exclusiveGroup != other.exclusiveGroup) {
return false;
}
- return new HashSet<PermissionRule>(getRules())
- .equals(new HashSet<PermissionRule>(other.getRules()));
+ return new HashSet<>(getRules()).equals(new HashSet<>(other.getRules()));
}
@Override
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
index bd05baf9da..3ba7adfb23 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
@@ -182,10 +182,14 @@ public class PermissionRule implements Comparable<PermissionRule> {
}
if (canUseRange && (getMin() != 0 || getMax() != 0)) {
- if (0 <= getMin()) r.append('+');
+ if (0 <= getMin()) {
+ r.append('+');
+ }
r.append(getMin());
r.append("..");
- if (0 <= getMax()) r.append('+');
+ if (0 <= getMax()) {
+ r.append('+');
+ }
r.append(getMax());
r.append(' ');
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
index f82f434479..76785d8f65 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
@@ -85,7 +85,10 @@ public class ReviewResult {
DEST_BRANCH_NOT_FOUND,
/** Not permitted to edit the topic name */
- EDIT_TOPIC_NAME_NOT_PERMITTED
+ EDIT_TOPIC_NAME_NOT_PERMITTED,
+
+ /** Not permitted to edit the hashtags */
+ EDIT_HASHTAGS_NOT_PERMITTED
}
protected Type type;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
deleted file mode 100644
index 28a8340cd8..0000000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
+++ /dev/null
@@ -1,58 +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.common.data;
-
-/**
- * Suggested reviewer for a change. Can be a user ({@link AccountInfo}) or a
- * group ({@link GroupReference}).
- */
-public class ReviewerInfo implements Comparable<ReviewerInfo> {
- private AccountInfo accountInfo;
- private GroupReference groupReference;
-
- protected ReviewerInfo() {
- }
-
- public ReviewerInfo(final AccountInfo accountInfo) {
- this.accountInfo = accountInfo;
- }
-
- public ReviewerInfo(final GroupReference groupReference) {
- this.groupReference = groupReference;
- }
-
- public AccountInfo getAccountInfo() {
- return accountInfo;
- }
-
- public GroupReference getGroup() {
- return groupReference;
- }
-
- @Override
- public int compareTo(final ReviewerInfo o) {
- return getSortValue().compareTo(o.getSortValue());
- }
-
- private String getSortValue() {
- if (accountInfo != null) {
- if (accountInfo.getPreferredEmail() != null) {
- return accountInfo.getPreferredEmail();
- }
- return accountInfo.getFullName();
- }
- return groupReference.getName();
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
deleted file mode 100644
index d696137982..0000000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
+++ /dev/null
@@ -1,117 +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.common.data;
-
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Result from adding or removing a reviewer from a change.
- */
-public class ReviewerResult {
- protected List<Error> errors;
- protected ChangeDetail change;
- protected int memberCount;
- protected boolean askForConfirmation;
-
- public ReviewerResult() {
- errors = new ArrayList<>();
- }
-
- public void addError(final Error e) {
- errors.add(e);
- }
-
- public List<Error> getErrors() {
- return errors;
- }
-
- public ChangeDetail getChange() {
- return change;
- }
-
- public void setChange(final ChangeDetail d) {
- change = d;
- }
-
- public int getMemberCount() {
- return memberCount;
- }
-
- public void setMemberCount(final int memberCount) {
- this.memberCount = memberCount;
- }
-
- public boolean askForConfirmation() {
- return askForConfirmation;
- }
-
- public void setAskForConfirmation(final boolean askForConfirmation) {
- this.askForConfirmation = askForConfirmation;
- }
-
- public static class Error {
- public static enum Type {
- /** Name supplied does not match to a registered account or account group. */
- REVIEWER_NOT_FOUND,
-
- /** The account is inactive. */
- ACCOUNT_INACTIVE,
-
- /** The account is not permitted to see the change. */
- CHANGE_NOT_VISIBLE,
-
- /** The groups has no members. */
- GROUP_EMPTY,
-
- /** The groups has too many members. */
- GROUP_HAS_TOO_MANY_MEMBERS,
-
- /** The group is not allowed to be added as reviewer. */
- GROUP_NOT_ALLOWED,
-
- /** Could not remove this reviewer from the change due to ORMException. */
- COULD_NOT_REMOVE,
-
- /** Not permitted to remove this reviewer from the change. */
- REMOVE_NOT_PERMITTED
- }
-
- protected Type type;
- protected String name;
-
- protected Error() {
- }
-
- public Error(final Type type, final String who) {
- this.type = type;
- this.name = who;
- }
-
- public Type getType() {
- return type;
- }
-
- public String getName() {
- return name;
- }
-
- @Override
- public String toString() {
- return type + " " + name;
- }
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
index 7b19f4c60d..5a79d08ecd 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
/**
* Describes the submit type for a change.
@@ -42,6 +42,7 @@ public class SubmitTypeRecord {
public SubmitType type;
public String errorMessage;
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(status);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
index d05dfc2179..7b25a23de2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
@@ -14,7 +14,6 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -25,34 +24,6 @@ import java.util.List;
@RpcImpl(version = Version.V2_0)
public interface SuggestService extends RemoteJsonService {
- void suggestAccount(String query, Boolean enabled, int limit,
- AsyncCallback<List<AccountInfo>> callback);
-
- /**
- * @see #suggestAccountGroupForProject(com.google.gerrit.reviewdb.client.Project.NameKey, String, int, AsyncCallback)
- */
- @Deprecated
- void suggestAccountGroup(String query, int limit,
- AsyncCallback<List<GroupReference>> callback);
-
void suggestAccountGroupForProject(Project.NameKey project, String query,
int limit, AsyncCallback<List<GroupReference>> callback);
-
- /**
- * @see #suggestChangeReviewer(com.google.gerrit.reviewdb.client.Change.Id, String, int, AsyncCallback)
- */
- @Deprecated
- void suggestReviewer(Project.NameKey project, String query, int limit,
- AsyncCallback<List<ReviewerInfo>> callback);
-
- /**
- * Suggests reviewers. A reviewer can be a user or a group. Inactive users,
- * the system groups {@code SystemGroupBackend#ANONYMOUS_USERS} and
- * {@code SystemGroupBackend#REGISTERED_USERS} and groups that have more than
- * the configured {@code addReviewer.maxAllowed} members are not suggested as
- * reviewers.
- * @param changeId the change for which reviewers should be suggested
- */
- void suggestChangeReviewer(Change.Id changeId, String query, int limit,
- AsyncCallback<List<ReviewerInfo>> callback);
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
index 4a45350db0..5e80ac5122 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
@@ -15,12 +15,12 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.AllowCrossSiteRequest;
+import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
+import com.google.gwtjsonrpc.common.VoidResult;
import java.util.List;
diff --git a/gerrit-common/src/test/java/com/google/gerrit/common/AutoValueTest.java b/gerrit-common/src/test/java/com/google/gerrit/common/AutoValueTest.java
new file mode 100644
index 0000000000..5febd80826
--- /dev/null
+++ b/gerrit-common/src/test/java/com/google/gerrit/common/AutoValueTest.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2014 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.AutoValue;
+
+import org.junit.Test;
+
+public class AutoValueTest {
+ @AutoValue
+ abstract static class Auto {
+ static Auto create(String val) {
+ return new AutoValue_AutoValueTest_Auto(val);
+ }
+
+ abstract String val();
+ }
+
+ @Test
+ public void autoValue() {
+ Auto a = Auto.create("foo");
+ assertThat(a.val()).isEqualTo("foo");
+ assertThat(a.toString()).isEqualTo("Auto{val=foo}");
+ }
+}
diff --git a/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java b/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
index 7c662aeaa8..816f715df9 100644
--- a/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
+++ b/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
@@ -14,9 +14,10 @@
package com.google.gerrit.common.data;
-import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
public class EncodePathSeparatorTest {
@Test
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index aad79d7adb..77a6b1358b 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -1,19 +1,22 @@
SRC = 'src/main/java/com/google/gerrit/extensions/'
SRCS = glob([SRC + '**/*.java'])
+EXT_API_SRCS = glob([SRC + 'client/*.java'])
+
gwt_module(
name = 'client',
- srcs = glob([
- SRC + 'api/projects/ProjectState.java',
- SRC + 'common/InheritableBoolean.java',
- SRC + 'common/ListChangesOption.java',
- SRC + 'common/SubmitType.java',
- SRC + 'webui/GerritTopMenu.java',
- ]),
+ srcs = EXT_API_SRCS,
gwt_xml = SRC + 'Extensions.gwt.xml',
visibility = ['PUBLIC'],
)
+java_library(
+ name = 'client-lib',
+ srcs = EXT_API_SRCS,
+ resources = EXT_API_SRCS + glob([SRC + 'Extensions.gwt.xml']),
+ visibility = ['PUBLIC'],
+)
+
java_binary(
name = 'extension-api',
deps = [':lib'],
@@ -47,7 +50,7 @@ java_sources(
java_doc(
name = 'extension-api-javadoc',
title = 'Gerrit Review Extension API Documentation',
- pkg = 'com.google.gerrit.extensions',
+ pkgs = ['com.google.gerrit.extensions'],
paths = ['src/main/java'],
srcs = SRCS,
deps = [
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index 408e2b0289..38b802a812 100644
--- a/gerrit-extension-api/pom.xml
+++ b/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>2.10.5</version>
+ <version>2.11.1</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Extension API</name>
<description>API for Gerrit Extensions</description>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
index 757e046107..c857b604d9 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
@@ -14,7 +14,5 @@
limitations under the License.
-->
<module>
- <source path='api' />
- <source path='common' />
- <source path='webui' />
+ <source path='client' />
</module>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
index 749b12aa41..71a93d3cb5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
@@ -18,7 +18,27 @@ import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
public interface Accounts {
+ /**
+ * Look up an account by ID.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the account. Methods that
+ * mutate the account do not necessarily re-read the account. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code AccountApi} instances.
+ *
+ * @param id any identifier supported by the REST API, including numeric ID,
+ * email, or username.
+ * @return API for accessing the account.
+ * @throws RestApiException if an error occurred.
+ */
AccountApi id(String id) throws RestApiException;
+
+ /**
+ * Look up the account of the current in-scope user.
+ *
+ * @see #id(String)
+ */
AccountApi self() throws RestApiException;
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 3382b765df..06f0a75e31 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -14,18 +14,46 @@
package com.google.gerrit.extensions.api.changes;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
public interface ChangeApi {
String id();
+ /**
+ * Look up the current revision for the change.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the revision. Methods that
+ * mutate the revision do not necessarily re-read the revision. Therefore,
+ * calling a getter method on an instance after calling a mutation method on
+ * that same instance is not guaranteed to reflect the mutation. It is not
+ * recommended to store references to {@code RevisionApi} instances.
+ *
+ * @return API for accessing the revision.
+ * @throws RestApiException if an error occurred.
+ */
RevisionApi current() throws RestApiException;
+
+ /**
+ * Look up a revision of a change by number.
+ *
+ * @see #current()
+ */
RevisionApi revision(int id) throws RestApiException;
+
+ /**
+ * Look up a revision of a change by commit SHA-1.
+ *
+ * @see #current()
+ */
RevisionApi revision(String id) throws RestApiException;
void abandon() throws RestApiException;
@@ -34,18 +62,77 @@ public interface ChangeApi {
void restore() throws RestApiException;
void restore(RestoreInput in) throws RestApiException;
+ /**
+ * Create a new change that reverts this change.
+ *
+ * @see Changes#id(int)
+ */
ChangeApi revert() throws RestApiException;
+
+ /**
+ * Create a new change that reverts this change.
+ *
+ * @see Changes#id(int)
+ */
ChangeApi revert(RevertInput in) throws RestApiException;
+ String topic() throws RestApiException;
+ void topic(String topic) throws RestApiException;
+
void addReviewer(AddReviewerInput in) throws RestApiException;
void addReviewer(String in) throws RestApiException;
+ SuggestedReviewersRequest suggestReviewers() throws RestApiException;
+ SuggestedReviewersRequest suggestReviewers(String query) throws RestApiException;
+
ChangeInfo get(EnumSet<ListChangesOption> options) throws RestApiException;
- /** {@code get} with {@link ListChangesOption} set to ALL. */
+ /** {@code get} with {@link ListChangesOption} set to all except CHECK. */
ChangeInfo get() throws RestApiException;
- /** {@code get} with {@link ListChangesOption} set to NONE. */
+ /** {@code get} with {@link ListChangesOption} set to none. */
ChangeInfo info() throws RestApiException;
+ /** Retrieve change edit when exists. */
+ EditInfo getEdit() throws RestApiException;
+
+ /**
+ * Set hashtags on a change
+ **/
+ void setHashtags(HashtagsInput input) throws RestApiException;
+
+ /**
+ * Get hashtags on a change.
+ * @return hashtags
+ * @throws RestApiException
+ */
+ Set<String> getHashtags() throws RestApiException;
+
+ ChangeInfo check() throws RestApiException;
+ ChangeInfo check(FixInput fix) throws RestApiException;
+
+ public abstract class SuggestedReviewersRequest {
+ private String query;
+ private int limit;
+
+ public abstract List<SuggestedReviewerInfo> get() throws RestApiException;
+
+ public SuggestedReviewersRequest withQuery(String query) {
+ this.query = query;
+ return this;
+ }
+
+ public SuggestedReviewersRequest withLimit(int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public String getQuery() {
+ return query;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+ }
/**
* A default implementation which allows source compatibility
@@ -103,6 +190,16 @@ public interface ChangeApi {
}
@Override
+ public String topic() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void topic(String topic) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void addReviewer(AddReviewerInput in) throws RestApiException {
throw new NotImplementedException();
}
@@ -113,6 +210,16 @@ public interface ChangeApi {
}
@Override
+ public SuggestedReviewersRequest suggestReviewers() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public SuggestedReviewersRequest suggestReviewers(String query) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public ChangeInfo get(EnumSet<ListChangesOption> options) throws RestApiException {
throw new NotImplementedException();
}
@@ -126,5 +233,30 @@ public interface ChangeApi {
public ChangeInfo info() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public EditInfo getEdit() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void setHashtags(HashtagsInput input) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Set<String> getHashtags() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public ChangeInfo check() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public ChangeInfo check(FixInput fix) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
index 201a0bd7bd..8ab30803f8 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -14,8 +14,8 @@
package com.google.gerrit.extensions.api.changes;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ListChangesOption;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -24,10 +24,40 @@ import java.util.EnumSet;
import java.util.List;
public interface Changes {
+ /**
+ * Look up a change by numeric ID.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the change. Methods that
+ * mutate the change do not necessarily re-read the change. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code ChangeApi} instances.
+ *
+ * @param id change number.
+ * @return API for accessing the change.
+ * @throws RestApiException if an error occurred.
+ */
ChangeApi id(int id) throws RestApiException;
- ChangeApi id(String triplet) throws RestApiException;
+
+ /**
+ * Look up a change by string ID.
+ *
+ * @see #id(int)
+ * @param id any identifier supported by the REST API, including change
+ * number, Change-Id, or project~branch~Change-Id triplet.
+ * @return API for accessing the change.
+ * @throws RestApiException if an error occurred.
+ */
+ ChangeApi id(String id) throws RestApiException;
+
+ /**
+ * Look up a change by project, branch, and change ID.
+ *
+ * @see #id(int)
+ */
ChangeApi id(String project, String branch, String id)
throws RestApiException;
+
ChangeApi create(ChangeInfo in) throws RestApiException;
QueryRequest query();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java
new file mode 100644
index 0000000000..d0c5633f1c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CommentApi.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface CommentApi {
+ CommentInfo get() throws RestApiException;
+
+ /**
+ * A default implementation which allows source compatibility
+ * when adding new methods to the interface.
+ **/
+ public class NotImplemented implements CommentApi {
+ @Override
+ public CommentInfo get() throws RestApiException {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftApi.java
new file mode 100644
index 0000000000..80a71f853e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftApi.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface DraftApi extends CommentApi {
+ CommentInfo update(DraftInput in) throws RestApiException;
+ void delete() throws RestApiException;
+
+ /**
+ * A default implementation which allows source compatibility
+ * when adding new methods to the interface.
+ **/
+ public class NotImplemented extends CommentApi.NotImplemented
+ implements DraftApi {
+ @Override
+ public CommentInfo update(DraftInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void delete() throws RestApiException {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftInput.java
new file mode 100644
index 0000000000..dd8f488d9f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DraftInput.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.client.Comment;
+
+public class DraftInput extends Comment {
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FileApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FileApi.java
new file mode 100644
index 0000000000..f5f087c328
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FileApi.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface FileApi {
+ BinaryResult content() throws RestApiException;
+
+ /**
+ * Diff against the revision's parent version of the file.
+ */
+ DiffInfo diff() throws RestApiException;
+
+ /**
+ * @param base revision id of the revision to be used as the
+ * diff base
+ */
+ DiffInfo diff(String base) throws RestApiException;
+
+ /**
+ * A default implementation which allows source compatibility
+ * when adding new methods to the interface.
+ **/
+ public class NotImplemented implements FileApi {
+ @Override
+ public BinaryResult content() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public DiffInfo diff() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public DiffInfo diff(String base) throws RestApiException {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java
new file mode 100644
index 0000000000..c8856e7e66
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/FixInput.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2014 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.api.changes;
+
+public class FixInput {
+ public boolean deletePatchSetIfCommitMissing;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java
new file mode 100644
index 0000000000..bf84ccb02f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+import java.util.Set;
+
+public class HashtagsInput {
+ @DefaultInput
+ public Set<String> add;
+ public Set<String> remove;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RebaseInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
new file mode 100644
index 0000000000..5f4a014e59
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2015 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.api.changes;
+
+public class RebaseInput {
+ public String base;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index cf2d930dbc..dd2ce9250c 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -14,7 +14,7 @@
package com.google.gerrit.extensions.api.changes;
-import com.google.gerrit.extensions.common.Comment;
+import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.restapi.DefaultInput;
import java.util.LinkedHashMap;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 20bcea9bb8..b940cc9963 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -14,9 +14,14 @@
package com.google.gerrit.extensions.api.changes;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
public interface RevisionApi {
@@ -29,11 +34,26 @@ public interface RevisionApi {
void publish() throws RestApiException;
ChangeApi cherryPick(CherryPickInput in) throws RestApiException;
ChangeApi rebase() throws RestApiException;
+ ChangeApi rebase(RebaseInput in) throws RestApiException;
boolean canRebase();
void setReviewed(String path, boolean reviewed) throws RestApiException;
Set<String> reviewed() throws RestApiException;
+ Map<String, FileInfo> files() throws RestApiException;
+ Map<String, FileInfo> files(String base) throws RestApiException;
+ FileApi file(String path);
+ MergeableInfo mergeable() throws RestApiException;
+ MergeableInfo mergeableOtherBranches() throws RestApiException;
+
+ Map<String, List<CommentInfo>> comments() throws RestApiException;
+ Map<String, List<CommentInfo>> drafts() throws RestApiException;
+
+ DraftApi createDraft(DraftInput in) throws RestApiException;
+ DraftApi draft(String id) throws RestApiException;
+
+ CommentApi comment(String id) throws RestApiException;
+
/**
* A default implementation which allows source compatibility
* when adding new methods to the interface.
@@ -75,6 +95,11 @@ public interface RevisionApi {
}
@Override
+ public ChangeApi rebase(RebaseInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public boolean canRebase() {
throw new NotImplementedException();
}
@@ -88,5 +113,55 @@ public interface RevisionApi {
public Set<String> reviewed() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public MergeableInfo mergeable() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public MergeableInfo mergeableOtherBranches() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Map<String, FileInfo> files(String base) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Map<String, FileInfo> files() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public FileApi file(String path) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Map<String, List<CommentInfo>> comments() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Map<String, List<CommentInfo>> drafts() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public DraftApi createDraft(DraftInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public DraftApi draft(String id) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public CommentApi comment(String id) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index d013c5d66c..07a48a1a92 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -22,6 +22,19 @@ public interface ProjectApi {
ProjectApi create() throws RestApiException;
ProjectApi create(ProjectInput in) throws RestApiException;
ProjectInfo get();
+
+ /**
+ * Look up a branch by refname.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the branch. Methods that
+ * mutate the branch do not necessarily re-read the branch. Therefore, calling
+ * a getter method on an instance after calling a mutation method on that same
+ * instance is not guaranteed to reflect the mutation. It is not recommended
+ * to store references to {@code BranchApi} instances.
+ *
+ * @param ref branch name, with or without "refs/heads/" prefix.
+ * @return API for accessing the branch.
+ */
BranchApi branch(String ref);
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
index 74b2be87eb..27bdf16556 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
@@ -14,8 +14,8 @@
package com.google.gerrit.extensions.api.projects;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
import java.util.List;
import java.util.Map;
@@ -33,6 +33,7 @@ public class ProjectInput {
public InheritableBoolean useSignedOffBy;
public InheritableBoolean useContentMerge;
public InheritableBoolean requireChangeId;
+ public InheritableBoolean createNewChangeForAllNotInTarget;
public String maxObjectSizeLimit;
public Map<String, Map<String, ConfigValue>> pluginConfigValues;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
index 9c0cfd8a8f..736d3755f3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -21,6 +21,19 @@ import com.google.gerrit.extensions.restapi.RestApiException;
import java.util.List;
public interface Projects {
+ /**
+ * Look up a project by name.
+ * <p>
+ * <strong>Note:</strong> This method eagerly reads the project. Methods that
+ * mutate the project do not necessarily re-read the project. Therefore,
+ * calling a getter method on an instance after calling a mutation method on
+ * that same instance is not guaranteed to reflect the mutation. It is not
+ * recommended to store references to {@code ProjectApi} instances.
+ *
+ * @param name project name.
+ * @return API for accessing the project.
+ * @throws RestApiException if an error occurred.
+ */
ProjectApi name(String name) throws RestApiException;
ListRequest list();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeStatus.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java
index 9af66f29f6..f3fc887c74 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeStatus.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.common;
+package com.google.gerrit.extensions.client;
/* Current state within the basic workflow of the change **/
public enum ChangeStatus {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Comment.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java
index b7ad9a7dd1..e79df1c026 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Comment.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.common;
+package com.google.gerrit.extensions.client;
import java.sql.Timestamp;
@@ -20,16 +20,12 @@ public abstract class Comment {
public String id;
public String path;
public Side side;
- public int line;
+ public Integer line;
public Range range;
public String inReplyTo;
public Timestamp updated;
public String message;
- public static enum Side {
- PARENT, REVISION
- }
-
public static class Range {
public int startLine;
public int startCharacter;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/GerritTopMenu.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GerritTopMenu.java
index e3821fca74..81d6149f50 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/GerritTopMenu.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GerritTopMenu.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.webui;
+package com.google.gerrit.extensions.client;
public enum GerritTopMenu {
ALL, MY, DIFFERENCES, PROJECTS, PEOPLE, PLUGINS, DOCUMENTATION;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/InheritableBoolean.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java
index 676c4d3a01..57d4849dda 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/InheritableBoolean.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.common;
+package com.google.gerrit.extensions.client;
public enum InheritableBoolean {
TRUE,
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
index f9f8b62b19..54617a7734 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.common;
+package com.google.gerrit.extensions.client;
import java.util.EnumSet;
@@ -52,7 +52,13 @@ public enum ListChangesOption {
DOWNLOAD_COMMANDS(13),
/** Include patch set weblinks. */
- WEB_LINKS(14);
+ WEB_LINKS(14),
+
+ /** Include consistency check results. */
+ CHECK(15),
+
+ /** Include allowed change actions client could perform. */
+ CHANGE_ACTIONS(16);
private final int value;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectState.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
index 407b7c75d7..6f4190d61f 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectState.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.api.projects;
+package com.google.gerrit.extensions.client;
public enum ProjectState {
ACTIVE,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Side.java
index 4a9ddf8cc6..5d5af75d11 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Side.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2014 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,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.changes;
+package com.google.gerrit.extensions.client;
-/** The side on which a comment was added. */
public enum Side {
PARENT, REVISION
} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SubmitType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
index 95a9693b11..2b916f13b7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SubmitType.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.common;
+package com.google.gerrit.extensions.client;
public enum SubmitType {
FAST_FORWARD_ONLY,
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java
new file mode 100644
index 0000000000..39730b7a89
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2014 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.client;
+
+public enum Theme {
+ // Light themes
+ DEFAULT,
+ ECLIPSE,
+ ELEGANT,
+ NEAT,
+
+ // Dark themes
+ MIDNIGHT,
+ NIGHT,
+ TWILIGHT;
+
+ public boolean isDark() {
+ switch (this) {
+ case MIDNIGHT:
+ case NIGHT:
+ case TWILIGHT:
+ return true;
+ default:
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java
index 5130f9f708..39d98de149 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java
@@ -14,9 +14,16 @@
package com.google.gerrit.extensions.common;
+import java.util.List;
+
public class AccountInfo {
public Integer _accountId;
public String name;
public String email;
public String username;
+ public List<AvatarInfo> avatars;
+
+ public AccountInfo(Integer id) {
+ this._accountId = id;
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java
index 2176e8f22b..ce120cd78b 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java
@@ -19,4 +19,8 @@ import java.sql.Timestamp;
public class ApprovalInfo extends AccountInfo {
public Integer value;
public Timestamp date;
+
+ public ApprovalInfo(Integer id) {
+ super(id);
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AvatarInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AvatarInfo.java
new file mode 100644
index 0000000000..793aa2498c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AvatarInfo.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2014 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.common;
+
+public class AvatarInfo {
+ /**
+ * Size in pixels the UI prefers an avatar image to be.
+ *
+ * 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 String url;
+ public Integer height;
+ public Integer width;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 653ec37f11..13baf6b215 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -14,8 +14,11 @@
package com.google.gerrit.extensions.common;
+import com.google.gerrit.extensions.client.ChangeStatus;
+
import java.sql.Timestamp;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
public class ChangeInfo {
@@ -23,6 +26,7 @@ public class ChangeInfo {
public String project;
public String branch;
public String topic;
+ public Collection<String> hashtags;
public String changeId;
public String subject;
public ChangeStatus status;
@@ -33,11 +37,21 @@ public class ChangeInfo {
public Boolean mergeable;
public Integer insertions;
public Integer deletions;
+
+ public String baseChange;
+ public int _number;
+
public AccountInfo owner;
- public String currentRevision;
+
public Map<String, ActionInfo> actions;
public Map<String, LabelInfo> labels;
+ public Map<String, Collection<String>> permittedLabels;
+ public Collection<AccountInfo> removableReviewers;
public Collection<ChangeMessageInfo> messages;
+
+ public String currentRevision;
public Map<String, RevisionInfo> revisions;
- public int _number;
+ public Boolean _moreChanges;
+
+ public List<ProblemInfo> problems;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeType.java
new file mode 100644
index 0000000000..d55580c681
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeType.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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.common;
+
+/** Type of modification made to the file path. */
+public enum ChangeType {
+ /** Path is being created/introduced by this patch. */
+ ADDED,
+
+ /** Path already exists, and has updated content. */
+ MODIFIED,
+
+ /** Path existed, but is being removed by this patch. */
+ DELETED,
+
+ /** Path existed but was moved. */
+ RENAMED,
+
+ /** Path was copied from source. */
+ COPIED,
+
+ /** Sufficient amount of content changed to claim the file was rewritten. */
+ REWRITE;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommentInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommentInfo.java
index cef1718f4c..b1f8183f57 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommentInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommentInfo.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.common;
+import com.google.gerrit.extensions.client.Comment;
+
public class CommentInfo extends Comment {
public AccountInfo author;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java
index 7313ab3ce8..a4e4071ca3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java
@@ -23,4 +23,5 @@ public class CommitInfo {
public GitPerson committer;
public String subject;
public String message;
+ public List<WebLinkInfo> webLinks;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffInfo.java
new file mode 100644
index 0000000000..62b1dc7a0f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffInfo.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2014 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.common;
+
+import java.util.List;
+
+/* This entity contains information about the diff of a file in a revision. */
+public class DiffInfo {
+ // Meta information about the file on side A
+ public FileMeta metaA;
+ // Meta information about the file on side B
+ public FileMeta metaB;
+ // Intraline status
+ public IntraLineStatus intralineStatus;
+ // The type of change
+ public ChangeType changeType;
+ // A list of strings representing the patch set diff header
+ public List<String> diffHeader;
+ // The content differences in the file as a list of entities
+ public List<ContentEntry> content;
+ // Links to the file diff in external sites
+ public List<DiffWebLinkInfo> webLinks;
+ // Binary file
+ public Boolean binary;
+
+ public static enum IntraLineStatus {
+ OK,
+ TIMEOUT,
+ FAILURE
+ }
+
+ public static class FileMeta {
+ // The name of the file
+ public String name;
+ // The content type of the file
+ public String contentType;
+ // The total number of lines in the file
+ public Integer lines;
+ // Links to the file in external sites
+ public List<WebLinkInfo> webLinks;
+ }
+
+ public static final class ContentEntry {
+ // Common lines to both sides.
+ public List<String> ab;
+ // Lines of a.
+ public List<String> a;
+ // Lines of b.
+ public List<String> b;
+
+ // A list of changed sections of the corresponding line list.
+ // Each entry is a character <offset, length> pair. The offset is from the
+ // beginning of the first line in the list. Also, the offset includes an
+ // implied trailing newline character for each line.
+ public List<List<Integer>> editA;
+ public List<List<Integer>> editB;
+
+ // a and b are actually common with this whitespace ignore setting.
+ public Boolean common;
+
+ // Number of lines to skip on both sides.
+ public Integer skip;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffWebLinkInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffWebLinkInfo.java
new file mode 100644
index 0000000000..71acca357a
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DiffWebLinkInfo.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2014 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.common;
+
+public class DiffWebLinkInfo extends WebLinkInfo {
+ public Boolean showOnSideBySideDiffView;
+ public Boolean showOnUnifiedDiffView;
+
+ public static DiffWebLinkInfo forSideBySideDiffView(String name,
+ String imageUrl, String url, String target) {
+ return new DiffWebLinkInfo(name, imageUrl, url, target, true, false);
+ }
+
+ public static DiffWebLinkInfo forUnifiedDiffView(String name,
+ String imageUrl, String url, String target) {
+ return new DiffWebLinkInfo(name, imageUrl, url, target, false, true);
+ }
+
+ public static DiffWebLinkInfo forSideBySideAndUnifiedDiffView(String name,
+ String imageUrl, String url, String target) {
+ return new DiffWebLinkInfo(name, imageUrl, url, target, true, true);
+ }
+
+ private DiffWebLinkInfo(String name, String imageUrl, String url,
+ String target, boolean showOnSideBySideDiffView,
+ boolean showOnUnifiedDiffView) {
+ super(name, imageUrl, url, target);
+ this.showOnSideBySideDiffView = showOnSideBySideDiffView ? true : null;
+ this.showOnUnifiedDiffView = showOnUnifiedDiffView ? true : null;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
new file mode 100644
index 0000000000..9dc92a804e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2014 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.common;
+
+import java.util.Map;
+
+public class EditInfo {
+ public CommitInfo commit;
+ public String baseRevision;
+ public Map<String, FetchInfo> fetch;
+ public Map<String, FileInfo> files;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GroupBaseInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GroupBaseInfo.java
new file mode 100644
index 0000000000..288adb6212
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GroupBaseInfo.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2014 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.common;
+
+public class GroupBaseInfo {
+ public String id;
+ public String name;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java
index 1e4edcda89..76dd93dd6a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java
@@ -23,7 +23,9 @@ public class LabelInfo {
public AccountInfo recommended;
public AccountInfo disliked;
public List<ApprovalInfo> all;
+
public Map<String, String> values;
+
public Short value;
public Short defaultValue;
public Boolean optional;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergeableInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergeableInfo.java
new file mode 100644
index 0000000000..9c38055e56
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergeableInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 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.common;
+
+import com.google.gerrit.extensions.client.SubmitType;
+
+import java.util.List;
+
+public class MergeableInfo {
+ public SubmitType submitType;
+ public boolean mergeable;
+ public List<String> mergeableInto;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java
new file mode 100644
index 0000000000..a117d071a6
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProblemInfo.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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.common;
+
+public class ProblemInfo {
+ public static enum Status {
+ FIXED, FIX_FAILED;
+ }
+
+ public String message;
+ public Status status;
+ public String outcome;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(getClass().getSimpleName())
+ .append('[').append(message);
+ if (status != null || outcome != null) {
+ sb.append(" (").append(status).append(": ").append(outcome)
+ .append(')');
+ }
+ return sb.append(']').toString();
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
index bb07e441d6..4036740de7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
@@ -14,7 +14,7 @@
package com.google.gerrit.extensions.common;
-import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.client.ProjectState;
import java.util.List;
import java.util.Map;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
index 8f61aa211f..4b8eec1e99 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -14,7 +14,7 @@
package com.google.gerrit.extensions.common;
-import java.util.List;
+import java.sql.Timestamp;
import java.util.Map;
public class RevisionInfo {
@@ -22,9 +22,11 @@ public class RevisionInfo {
public Boolean draft;
public Boolean hasDraftComments;
public int _number;
+ public Timestamp created;
+ public AccountInfo uploader;
+ public String ref;
public Map<String, FetchInfo> fetch;
public CommitInfo commit;
public Map<String, FileInfo> files;
public Map<String, ActionInfo> actions;
- public List<WebLinkInfo> webLinks;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestedReviewerInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestedReviewerInfo.java
new file mode 100644
index 0000000000..d371f35bbe
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestedReviewerInfo.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2014 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.common;
+
+public class SuggestedReviewerInfo {
+ public AccountInfo account;
+ public GroupBaseInfo group;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java
new file mode 100644
index 0000000000..3e3d8dbdac
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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.common;
+
+public class TagInfo {
+ public String ref;
+ public String revision;
+ public String object;
+ public String message;
+ public GitPerson tagger;
+
+ public TagInfo(String ref, String revision) {
+ this.ref = ref;
+ this.revision = revision;
+ }
+
+ public TagInfo(String ref, String revision, String object,
+ String message, GitPerson tagger) {
+ this(ref, revision);
+ this.object = object;
+ this.message = message;
+ this.tagger = tagger;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java
index 7695c8c8f9..d9a34bf245 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java
@@ -16,10 +16,14 @@ package com.google.gerrit.extensions.common;
public class WebLinkInfo {
public String name;
+ public String imageUrl;
public String url;
+ public String target;
- public WebLinkInfo(String name, String url) {
+ public WebLinkInfo(String name, String imageUrl, String url, String target) {
this.name = name;
+ this.imageUrl = imageUrl;
this.url = url;
+ this.target = target;
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java
index 7edfaed1a9..1388637ad3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java
@@ -200,19 +200,20 @@ public class DynamicItem<T> {
}
private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
- private final Key<T> key;
+ private final Key<T> handleKey;
private final NamedProvider<T> item;
private final NamedProvider<T> defaultItem;
- ReloadableHandle(Key<T> key, NamedProvider<T> item, NamedProvider<T> defaultItem) {
- this.key = key;
+ ReloadableHandle(Key<T> handleKey, NamedProvider<T> item,
+ NamedProvider<T> defaultItem) {
+ this.handleKey = handleKey;
this.item = item;
this.defaultItem = defaultItem;
}
@Override
public Key<T> getKey() {
- return key;
+ return handleKey;
}
@Override
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
index 9b09d15b6e..01551e605e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItemProvider.java
@@ -36,6 +36,7 @@ class DynamicItemProvider<T> implements Provider<DynamicItem<T>> {
this.key = key;
}
+ @Override
public DynamicItem<T> get() {
return new DynamicItem<>(key, find(injector, type), "gerrit");
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
index 42518918d1..abf944a4d3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -139,6 +139,7 @@ public abstract class DynamicMap<T> implements Iterable<DynamicMap.Entry<T>> {
}
/** Iterate through all entries in an undefined order. */
+ @Override
public Iterator<Entry<T>> iterator() {
final Iterator<Map.Entry<NamePair, Provider<T>>> i =
items.entrySet().iterator();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
index 2554673fec..8fcbdd97e1 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
@@ -32,6 +32,7 @@ class DynamicMapProvider<T> implements Provider<DynamicMap<T>> {
this.type = type;
}
+ @Override
public DynamicMap<T> get() {
PrivateInternals_DynamicMapImpl<T> m =
new PrivateInternals_DynamicMapImpl<>();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
index 628745a225..82613c77f7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -129,7 +129,7 @@ public class DynamicSet<T> implements Iterable<T> {
}
public static <T> DynamicSet<T> emptySet() {
- return new DynamicSet<T>(
+ return new DynamicSet<>(
Collections.<AtomicReference<Provider<T>>> emptySet());
}
@@ -139,6 +139,10 @@ public class DynamicSet<T> implements Iterable<T> {
items = new CopyOnWriteArrayList<>(base);
}
+ public DynamicSet() {
+ this(Collections.<AtomicReference<Provider<T>>>emptySet());
+ }
+
@Override
public Iterator<T> iterator() {
final Iterator<AtomicReference<Provider<T>>> itr = items.iterator();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
index 9ea96d4ff1..d8b027bcef 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
@@ -35,6 +35,7 @@ class DynamicSetProvider<T> implements Provider<DynamicSet<T>> {
this.type = type;
}
+ @Override
public DynamicSet<T> get() {
return new DynamicSet<>(find(injector, type));
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java
new file mode 100644
index 0000000000..2f615c12d6
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/AcceptsDelete.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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.restapi;
+
+
+/**
+ * Optional interface for {@link RestCollection}.
+ * <p>
+ * Collections that implement this interface can accept a {@code DELETE} directly
+ * on the collection itself.
+ */
+public interface AcceptsDelete<P extends RestResource> {
+ /**
+ * Handle deletion of a child resource by DELETE on the collection.
+ *
+ * @param parent parent collection handle.
+ * @param id id of the resource being created (optional).
+ * @return a view to perform the deletion.
+ * @throws RestApiException the view cannot be constructed.
+ */
+ <I> RestModifyView<P, I> delete(P parent, IdString id)
+ throws RestApiException;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
index 852c56efa0..18f356b202 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -14,11 +14,17 @@
package com.google.gerrit.extensions.restapi;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
/**
* Wrapper around a non-JSON result from a {@link RestView}.
@@ -34,13 +40,7 @@ public abstract class BinaryResult implements Closeable {
/** Produce a UTF-8 encoded result from a string. */
public static BinaryResult create(String data) {
- try {
- return create(data.getBytes("UTF-8"))
- .setContentType("text/plain")
- .setCharacterEncoding("UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("JVM does not support UTF-8", e);
- }
+ return new StringResult(data);
}
/** Produce an {@code application/octet-stream} result from a byte array. */
@@ -144,7 +144,30 @@ public abstract class BinaryResult implements Closeable {
*/
public abstract void writeTo(OutputStream os) throws IOException;
+ /**
+ * Return a copy of the result as a String.
+ * <p>
+ * The default version of this method copies the result into a temporary byte
+ * array and then tries to decode it using the configured encoding.
+ *
+ * @return string version of the result.
+ * @throws IOException if the data cannot be produced or could not be
+ * decoded to a String.
+ */
+ public String asString() throws IOException {
+ long len = getContentLength();
+ ByteArrayOutputStream buf;
+ if (0 < len) {
+ buf = new ByteArrayOutputStream((int) len);
+ } else {
+ buf = new ByteArrayOutputStream();
+ }
+ writeTo(buf);
+ return decode(buf.toByteArray(), getCharacterEncoding());
+ }
+
/** Close the result and release any resources it holds. */
+ @Override
public void close() throws IOException {
}
@@ -160,6 +183,25 @@ public abstract class BinaryResult implements Closeable {
getContentType());
}
+ private static String decode(byte[] data, String enc) {
+ try {
+ Charset cs = enc != null
+ ? Charset.forName(enc)
+ : StandardCharsets.UTF_8;
+ return cs.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ .decode(ByteBuffer.wrap(data))
+ .toString();
+ } catch (UnsupportedCharsetException | CharacterCodingException e) {
+ // Fallback to ISO-8850-1 style encoding.
+ StringBuilder r = new StringBuilder(data.length);
+ for (byte b : data)
+ r.append((char) (b & 0xff));
+ return r.toString();
+ }
+ }
+
private static class Array extends BinaryResult {
private final byte[] data;
@@ -172,6 +214,27 @@ public abstract class BinaryResult implements Closeable {
public void writeTo(OutputStream os) throws IOException {
os.write(data);
}
+
+ @Override
+ public String asString() {
+ return decode(data, getCharacterEncoding());
+ }
+ }
+
+ private static class StringResult extends Array {
+ private final String str;
+
+ StringResult(String str) {
+ super(str.getBytes(StandardCharsets.UTF_8));
+ setContentType("text/plain");
+ setCharacterEncoding("UTF-8");
+ this.str = str;
+ }
+
+ @Override
+ public String asString() {
+ return str;
+ }
}
private static class Stream extends BinaryResult {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
index 3aa1781fd8..d71732bf96 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
@@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit;
public class CacheControl {
public enum Type {
+ @SuppressWarnings("hiding")
NONE, PUBLIC, PRIVATE
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java
new file mode 100644
index 0000000000..f95161d00e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2015 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.restapi;
+
+/**
+ * A view which may change, although the underlying resource did not change
+ */
+public interface ETagView<R extends RestResource> extends RestReadView<R> {
+ public String getETag(R rsrc);
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
index 76942e6569..611812ae6a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
@@ -22,19 +22,17 @@ public class ResourceNotFoundException extends RestApiException {
public ResourceNotFoundException() {
}
- /** @param id portion of the resource URI that does not exist. */
- public ResourceNotFoundException(String id) {
- super(id);
+ public ResourceNotFoundException(String msg) {
+ super(msg);
}
- /** @param id portion of the resource URI that does not exist. */
- public ResourceNotFoundException(String id, Throwable cause) {
- super(id, cause);
+ public ResourceNotFoundException(String msg, Throwable cause) {
+ super(msg, cause);
}
/** @param id portion of the resource URI that does not exist. */
public ResourceNotFoundException(IdString id) {
- super(id.get());
+ super("Not found: " + id.get());
}
@SuppressWarnings("unchecked")
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java
index 314c89819a..43450764bc 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.restapi;
+import java.util.concurrent.TimeUnit;
+
/** Special return value to mean specific HTTP status codes in a REST API. */
public abstract class Response<T> {
@SuppressWarnings({"rawtypes"})
@@ -24,6 +26,11 @@ public abstract class Response<T> {
return new Impl<>(200, value);
}
+ public static <T> Response<T> withMustRevalidate(T value) {
+ return ok(value).caching(
+ CacheControl.PRIVATE(0, TimeUnit.SECONDS).setMustRevalidate());
+ }
+
/** HTTP 201 Created: typically used when a new resource is made. */
public static <T> Response<T> created(T value) {
return new Impl<>(201, value);
@@ -48,10 +55,12 @@ public abstract class Response<T> {
return obj;
}
+ public abstract boolean isNone();
public abstract int statusCode();
public abstract T value();
public abstract CacheControl caching();
public abstract Response<T> caching(CacheControl c);
+ @Override
public abstract String toString();
private static final class Impl<T> extends Response<T> {
@@ -65,6 +74,11 @@ public abstract class Response<T> {
}
@Override
+ public boolean isNone() {
+ return false;
+ }
+
+ @Override
public int statusCode() {
return statusCode;
}
@@ -96,10 +110,16 @@ public abstract class Response<T> {
}
@Override
+ public boolean isNone() {
+ return true;
+ }
+
+ @Override
public int statusCode() {
return 204;
}
+ @Override
public Object value() {
throw new UnsupportedOperationException();
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/BranchWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/BranchWebLink.java
new file mode 100644
index 0000000000..2a11012428
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/BranchWebLink.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2014 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.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+
+@ExtensionPoint
+public interface BranchWebLink extends WebLink {
+
+ /**
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo}
+ * describing a link from a branch to an external service.
+ *
+ * <p>In order for the web link to be visible
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo#url}
+ * and {@link com.google.gerrit.extensions.common.WebLinkInfo#name}
+ * must be set.<p>
+ *
+ * @param projectName Name of the project
+ * @param branchName Name of the branch
+ * @return WebLinkInfo that links to branch in external service,
+ * null if there should be no link.
+ */
+ WebLinkInfo getBranchWebLink(String projectName, String branchName);
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/DiffWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/DiffWebLink.java
new file mode 100644
index 0000000000..ad535194ad
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/DiffWebLink.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 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.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
+
+@ExtensionPoint
+public interface DiffWebLink extends WebLink {
+
+ /**
+ * {@link com.google.gerrit.extensions.common.DiffWebLinkInfo}
+ * describing a link from a file diff to an external service.
+ *
+ * <p>In order for the web link to be visible
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo#url}
+ * and {@link com.google.gerrit.extensions.common.WebLinkInfo#name}
+ * must be set.<p>
+ *
+ * @param projectName Name of the project
+ * @param changeId ID of the change
+ * @param patchSetIdA Patch set ID of side A, <code>null</code> if no base
+ * patch set was selected
+ * @param revisionA Name of the revision of side A (e.g. branch or commit ID)
+ * @param fileNameA Name of the file of side A
+ * @param patchSetIdB Patch set ID of side B
+ * @param revisionB Name of the revision of side B (e.g. branch or commit ID)
+ * @param fileNameB Name of the file of side B
+ * @return WebLinkInfo that links to file diff in external service,
+ * null if there should be no link.
+ */
+ DiffWebLinkInfo getDiffLink(String projectName, int changeId,
+ Integer patchSetIdA, String revisionA, String fileNameA,
+ int patchSetIdB, String revisionB, String fileNameB);
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/FileWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/FileWebLink.java
new file mode 100644
index 0000000000..9136b36da0
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/FileWebLink.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2014 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.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+
+@ExtensionPoint
+public interface FileWebLink extends WebLink {
+
+ /**
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo}
+ * describing a link from a file to an external service.
+ *
+ * <p>In order for the web link to be visible
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo#url}
+ * and {@link com.google.gerrit.extensions.common.WebLinkInfo#name}
+ * must be set.<p>
+ *
+ * @param projectName Name of the project
+ * @param revision Name of the revision (e.g. branch or commit ID)
+ * @param fileName Name of the file
+ * @return WebLinkInfo that links to project in external service,
+ * null if there should be no link.
+ */
+ WebLinkInfo getFileWebLink(String projectName, String revision, String fileName);
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
index 89a4f33595..4619a06a66 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
@@ -16,6 +16,9 @@ package com.google.gerrit.extensions.webui;
/** Configures a web UI plugin written using JavaScript. */
public class JavaScriptPlugin extends WebUiPlugin {
+ public static final String INIT_JS = "init.js";
+ public static final String STATIC_INIT_JS = "static/" + INIT_JS;
+
private final String fileName;
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
index b6086f2cb6..ad7484974e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
@@ -11,19 +11,28 @@
// 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.webui;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.WebLinkInfo;
@ExtensionPoint
-public interface PatchSetWebLink extends WebLink {
+public interface PatchSetWebLink extends WebLink{
/**
- * URL to patch set in external service.
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo}
+ * describing a link from a patch set to an external service.
+ *
+ * <p>In order for the web link to be visible
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo#url}
+ * and {@link com.google.gerrit.extensions.common.WebLinkInfo#name}
+ * must be set.<p>
*
* @param projectName Name of the project
* @param commit Commit of the patch set
- * @return url to patch set in external service.
+ * @return WebLinkInfo that links to patch set in external service,
+ * null if there should be no link.
*/
- String getPatchSetUrl(final String projectName, final String commit);
+ WebLinkInfo getPatchSetWebLink(final String projectName, final String commit);
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java
index 61e9982011..2f8e8023ad 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java
@@ -15,15 +15,23 @@
package com.google.gerrit.extensions.webui;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.WebLinkInfo;
@ExtensionPoint
public interface ProjectWebLink extends WebLink {
/**
- * URL to project in external service.
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo}
+ * describing a link from a project to an external service.
+ *
+ * <p>In order for the web link to be visible
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo#url}
+ * and {@link com.google.gerrit.extensions.common.WebLinkInfo#name}
+ * must be set.<p>
*
* @param projectName Name of the project
- * @return url to project in external service.
+ * @return WebLinkInfo that links to project in external service,
+ * null if there should be no link.
*/
- String getProjectUrl(String projectName);
+ WebLinkInfo getProjectWeblink(String projectName);
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java
index e5a1f7e28d..ead7c3123a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java
@@ -15,6 +15,7 @@
package com.google.gerrit.extensions.webui;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.client.GerritTopMenu;
import java.util.List;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java
index 19d9ab7f8d..e497f7ddb4 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java
@@ -11,14 +11,35 @@
// 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.webui;
-public interface WebLink {
+/**
+ * Marks that the implementor has a method that provides
+ * a weblinkInfo
+ *
+ */
+public interface WebLink {
/**
- * The link-name displayed in UI.
- *
- * @return name of link.
+ * Class that holds target defaults for WebLink anchors.
*/
- String getLinkName();
+ public static class Target {
+ /**
+ * Opens the link in a new window or tab
+ */
+ public static final String BLANK = "_blank";
+ /**
+ * Opens the link in the frame it was clicked.
+ */
+ public static final String SELF = "_self";
+ /**
+ * Opens link in parent frame.
+ */
+ public static final String PARENT = "_parent";
+ /**
+ * Opens link in the full body of the window.
+ */
+ public static final String TOP = "_top";
+ }
}
diff --git a/gerrit-gwtdebug/BUCK b/gerrit-gwtdebug/BUCK
index a926773a1a..bf05af0174 100644
--- a/gerrit-gwtdebug/BUCK
+++ b/gerrit-gwtdebug/BUCK
@@ -1,11 +1,16 @@
java_library(
name = 'gwtdebug',
- srcs = ['src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java'],
+ srcs = glob(['src/main/java/**/*.java']),
deps = [
+ '//gerrit-pgm:pgm',
+ '//gerrit-pgm:util',
+ '//gerrit-util-cli:cli',
'//lib/gwt:dev',
'//lib/jetty:server',
'//lib/jetty:servlet',
- '//lib/jetty:webapp',
+ '//lib/jetty:servlets',
+ '//lib/log:api',
+ '//lib/log:log4j',
],
visibility = ['//tools/eclipse:classpath'],
)
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
deleted file mode 100644
index 09a7fb6aac..0000000000
--- a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
+++ /dev/null
@@ -1,524 +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.gerrit.gwtdebug;
-
-import com.google.gwt.core.ext.ServletContainer;
-import com.google.gwt.core.ext.ServletContainerLauncher;
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.RequestLog;
-import org.eclipse.jetty.server.Response;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.WebAppClassLoader;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-
-public class GerritDebugLauncher extends ServletContainerLauncher {
-
- private final static boolean __escape = true;
-
- /**
- * Log jetty requests/responses to TreeLogger.
- */
- public static class JettyRequestLogger extends AbstractLifeCycle implements
- RequestLog {
-
- private final TreeLogger logger;
-
- public JettyRequestLogger(TreeLogger logger) {
- this.logger = logger;
- }
-
- /**
- * Log an HTTP request/response to TreeLogger.
- */
- public void log(Request request, Response response) {
- int status = response.getStatus();
- if (status < 0) {
- // Copied from NCSARequestLog
- status = 404;
- }
- TreeLogger.Type logStatus, logHeaders;
- if (status >= 500) {
- logStatus = TreeLogger.ERROR;
- logHeaders = TreeLogger.INFO;
- } else if (status >= 400) {
- logStatus = TreeLogger.WARN;
- logHeaders = TreeLogger.INFO;
- } else {
- logStatus = TreeLogger.INFO;
- logHeaders = TreeLogger.DEBUG;
- }
- String userString = request.getRemoteUser();
- if (userString == null) {
- userString = "";
- } else {
- userString += "@";
- }
- String bytesString = "";
- if (response.getContentCount() > 0) {
- bytesString = " " + response.getContentCount() + " bytes";
- }
- if (logger.isLoggable(logStatus)) {
- TreeLogger branch =
- logger.branch(logStatus, String.valueOf(status) + " - "
- + request.getMethod() + ' ' + request.getUri() + " ("
- + userString + request.getRemoteHost() + ')' + bytesString);
- if (branch.isLoggable(logHeaders)) {
- // Request headers
- TreeLogger headers = branch.branch(logHeaders, "Request headers");
- for (HttpField f : request.getHttpFields()) {
- headers.log(logHeaders, f.getName() + ": " + f.getValue());
- }
- // Response headers
- headers = branch.branch(logHeaders, "Response headers");
- for (HttpField f : response.getHttpFields()) {
- headers.log(logHeaders, f.getName() + ": " + f.getValue());
- }
- }
- }
- }
- }
-
- /**
- * An adapter for the Jetty logging system to GWT's TreeLogger. This
- * implementation class is only public to allow {@link Log} to instantiate it.
- *
- * The weird static data / default construction setup is a game we play with
- * {@link Log}'s static initializer to prevent the initial log message from
- * going to stderr.
- */
- public static class JettyTreeLogger implements Logger {
- private final TreeLogger logger;
-
- public JettyTreeLogger(TreeLogger logger) {
- if (logger == null) {
- throw new NullPointerException();
- }
- this.logger = logger;
- }
-
- public void debug(String msg, Object arg0, Object arg1) {
- logger.log(TreeLogger.SPAM, format(msg, arg0, arg1));
- }
-
- public void debug(String msg, Throwable th) {
- logger.log(TreeLogger.SPAM, msg, th);
- }
-
- public Logger getLogger(String name) {
- return this;
- }
-
- public void info(String msg, Object arg0, Object arg1) {
- logger.log(TreeLogger.INFO, format(msg, arg0, arg1));
- }
-
- public boolean isDebugEnabled() {
- return logger.isLoggable(TreeLogger.SPAM);
- }
-
- public void setDebugEnabled(boolean enabled) {
- // ignored
- }
-
- public void warn(String msg, Object arg0, Object arg1) {
- logger.log(TreeLogger.WARN, format(msg, arg0, arg1));
- }
-
- public void warn(String msg, Throwable th) {
- logger.log(TreeLogger.WARN, msg, th);
- }
-
- public void debug(String msg, long value) {
- // ignored
- }
-
- @Override
- public void debug(String msg, Object... args) {
- // ignored
- }
-
- @Override
- public void debug(Throwable thrown) {
- // ignored
- }
-
- @Override
- public void warn(String msg, Object... args) {
- logger.log(TreeLogger.WARN, format(msg, args));
- }
-
- @Override
- public void warn(Throwable thrown) {
- logger.log(TreeLogger.WARN, thrown.getMessage(), thrown);
- }
-
- @Override
- public void info(String msg, Object... args) {
- logger.log(TreeLogger.INFO, format(msg, args));
- }
-
- @Override
- public void info(Throwable thrown) {
- logger.log(TreeLogger.INFO, thrown.getMessage(), thrown);
- }
-
- @Override
- public void info(String msg, Throwable thrown) {
- logger.log(TreeLogger.INFO, msg, thrown);
- }
-
- @Override
- public void ignore(Throwable ignored) {
- }
-
- @Override
- public String getName() {
- return this.getClass().getName();
- }
-
- /**
- * Copied from org.mortbay.log.StdErrLog.
- */
- private String format(String msg, Object arg0, Object arg1) {
- int i0 = msg.indexOf("{}");
- int i1 = i0 < 0 ? -1 : msg.indexOf("{}", i0 + 2);
-
- if (arg1 != null && i1 >= 0) {
- msg = msg.substring(0, i1) + arg1 + msg.substring(i1 + 2);
- }
- if (arg0 != null && i0 >= 0) {
- msg = msg.substring(0, i0) + arg0 + msg.substring(i0 + 2);
- }
- return msg;
- }
-
- private String format(String msg, Object... args) {
- StringBuilder builder = new StringBuilder();
- if (msg == null) {
- msg = "";
- for (int i = 0; i < args.length; i++) {
- msg += "{} ";
- }
- }
- String braces = "{}";
- int start = 0;
- for (Object arg : args) {
- int bracesIndex = msg.indexOf(braces,start);
- if (bracesIndex < 0) {
- escape(builder, msg.substring(start));
- builder.append(" ");
- builder.append(arg);
- start = msg.length();
- } else {
- escape(builder, msg.substring(start, bracesIndex));
- builder.append(String.valueOf(arg));
- start = bracesIndex + braces.length();
- }
- }
- escape(builder, msg.substring(start));
- return builder.toString();
- }
-
- private void escape(StringBuilder builder, String string) {
- if (__escape) {
- for (int i = 0; i < string.length(); ++i) {
- char c = string.charAt(i);
- if (Character.isISOControl(c)) {
- if (c == '\n') {
- builder.append('|');
- } else if (c == '\r') {
- builder.append('<');
- } else {
- builder.append('?');
- }
- } else {
- builder.append(c);
- }
- }
- } else {
- builder.append(string);
- }
- }
- }
-
- /**
- * The resulting {@link ServletContainer} this is launched.
- */
- protected static class JettyServletContainer extends ServletContainer {
- private final int actualPort;
- private final File appRootDir;
- private final TreeLogger logger;
- private final Server server;
- private final WebAppContext wac;
-
- public JettyServletContainer(TreeLogger logger, Server server,
- WebAppContext wac, int actualPort, File appRootDir) {
- this.logger = logger;
- this.server = server;
- this.wac = wac;
- this.actualPort = actualPort;
- this.appRootDir = appRootDir;
- }
-
- @Override
- public int getPort() {
- return actualPort;
- }
-
- @Override
- public void refresh() throws UnableToCompleteException {
- String msg =
- "Reloading web app to reflect changes in "
- + appRootDir.getAbsolutePath();
- TreeLogger branch = logger.branch(TreeLogger.INFO, msg);
- // Temporarily log Jetty on the branch.
- Log.setLog(new JettyTreeLogger(branch));
- try {
- wac.stop();
- wac.start();
- branch.log(TreeLogger.INFO, "Reload completed successfully");
- } catch (Exception e) {
- branch.log(TreeLogger.ERROR, "Unable to restart embedded Jetty server",
- e);
- throw new UnableToCompleteException();
- } finally {
- // Reset the top-level logger.
- Log.setLog(new JettyTreeLogger(logger));
- }
- }
-
- @Override
- public void stop() throws UnableToCompleteException {
- TreeLogger branch =
- logger.branch(TreeLogger.INFO, "Stopping Jetty server");
- // Temporarily log Jetty on the branch.
- Log.setLog(new JettyTreeLogger(branch));
- try {
- server.stop();
- server.setStopAtShutdown(false);
- branch.log(TreeLogger.INFO, "Stopped successfully");
- } catch (Exception e) {
- branch.log(TreeLogger.ERROR, "Unable to stop embedded Jetty server", e);
- throw new UnableToCompleteException();
- } finally {
- // Reset the top-level logger.
- Log.setLog(new JettyTreeLogger(logger));
- }
- }
- }
-
- /**
- * A {@link WebAppContext} tailored to GWT hosted mode. Features hot-reload
- * with a new {@link WebAppClassLoader} to pick up disk changes. The default
- * Jetty {@code WebAppContext} will create new instances of servlets, but it
- * will not create a brand new {@link ClassLoader}. By creating a new {@code
- * ClassLoader} each time, we re-read updated classes from disk.
- *
- * Also provides special class filtering to isolate the web app from the GWT
- * hosting environment.
- */
- protected final class MyWebAppContext extends WebAppContext {
- /**
- * Parent ClassLoader for the Jetty web app, which can only load JVM
- * classes. We would just use {@code null} for the parent ClassLoader
- * except this makes Jetty unhappy.
- */
- private final ClassLoader bootStrapOnlyClassLoader =
- new ClassLoader(null) {};
-
- private final ClassLoader systemClassLoader =
- Thread.currentThread().getContextClassLoader();
-
- private MyWebAppContext(String webApp, String contextPath) {
- super(webApp, contextPath);
-
- // Prevent file locking on Windows; pick up file changes.
- getInitParams().put(
- "org.mortbay.jetty.servlet.Default.useFileMappedBuffer", "false");
-
- // Since the parent class loader is bootstrap-only, prefer it first.
- setParentLoaderPriority(true);
- }
-
- @Override
- protected void doStart() throws Exception {
- setClassLoader(new MyLoader(this));
- super.doStart();
- }
-
- @Override
- protected void doStop() throws Exception {
- super.doStop();
- setClassLoader(null);
- }
-
- private class MyLoader extends WebAppClassLoader {
- MyWebAppContext ctx;
- MyLoader(MyWebAppContext ctx) throws IOException {
- super(bootStrapOnlyClassLoader, MyWebAppContext.this);
- this.ctx = ctx;
- final URLClassLoader scl = (URLClassLoader) systemClassLoader;
- final URL[] urls = scl.getURLs();
- for (URL u : urls) {
- if ("file".equals(u.getProtocol())) {
- addClassPath(u.getPath());
- }
- }
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- // For system path, always prefer the outside world.
- if (ctx.isSystemClass(name.replace('/', '.'))) {
- try {
- return systemClassLoader.loadClass(name);
- } catch (ClassNotFoundException e) {
- }
- }
- return super.findClass(name);
- }
- }
- }
-
- static {
- Log.getLog();
-
- /*
- * Make JDT the default Ant compiler so that JSP compilation just works
- * out-of-the-box. If we don't set this, it's very, very difficult to make
- * JSP compilation work.
- */
- String antJavaC =
- System.getProperty("build.compiler",
- "org.eclipse.jdt.core.JDTCompilerAdapter");
- System.setProperty("build.compiler", antJavaC);
-
- System.setProperty("Gerrit.GwtDevMode", "" + true);
- }
-
- private String bindAddress = null;
-
- @Override
- public void setBindAddress(String bindAddress) {
- this.bindAddress = bindAddress;
- }
-
- @Override
- public ServletContainer start(TreeLogger logger, int port, File warDir)
- throws Exception {
- TreeLogger branch =
- logger.branch(TreeLogger.INFO, "Starting Jetty on port " + port, null);
- checkStartParams(branch, port, warDir);
-
- // Setup our branch logger during startup.
- Log.setLog(new JettyTreeLogger(branch));
-
- // Turn off XML validation.
- System.setProperty("org.mortbay.xml.XmlParser.Validating", "false");
-
- Server server = new Server();
- HttpConfiguration config = defaultConfig();
- ServerConnector connector = new ServerConnector(server,
- new HttpConnectionFactory(config));
- if (bindAddress != null) {
- connector.setHost(bindAddress);
- }
- connector.setPort(port);
-
- // Don't share ports with an existing process.
- connector.setReuseAddress(false);
-
- // Linux keeps the port blocked after shutdown if we don't disable this.
- connector.setSoLingerTime(0);
-
-
- server.addConnector(connector);
-
- File top;
- String root = System.getProperty("gerrit.source_root");
- if (root != null) {
- top = new File(root);
- } else {
- // Under Maven warDir is "$top/gerrit-gwtui/target/gwt-hosted-mode"
- top = warDir.getParentFile().getParentFile().getParentFile();
- }
-
- File app = new File(top, "gerrit-war/src/main/webapp");
- File webxml = new File(app, "WEB-INF/web.xml");
-
- // Jetty won't start unless this directory exists.
- if (!warDir.exists() && !warDir.mkdirs())
- logger.branch(TreeLogger.ERROR, "Cannot create "+warDir, null);
-
- // Create a new web app in the war directory.
- //
- WebAppContext wac =
- new MyWebAppContext(warDir.getAbsolutePath(), "/");
- wac.setDescriptor(webxml.getAbsolutePath());
-
- RequestLogHandler logHandler = new RequestLogHandler();
- logHandler.setRequestLog(new JettyRequestLogger(logger));
- logHandler.setHandler(wac);
- server.setHandler(logHandler);
- server.start();
- server.setStopAtShutdown(true);
-
- // Now that we're started, log to the top level logger.
- Log.setLog(new JettyTreeLogger(logger));
-
- return new JettyServletContainer(logger, server, wac,
- connector.getLocalPort(), warDir);
- }
-
- protected HttpConfiguration defaultConfig() {
- HttpConfiguration config = new HttpConfiguration();
- config.setRequestHeaderSize(16386);
- config.setSendServerVersion(false);
- config.setSendDateHeader(true);
- return config;
- }
-
- private void checkStartParams(TreeLogger logger, int port, File appRootDir) {
- if (logger == null) {
- throw new NullPointerException("logger cannot be null");
- }
-
- if (port < 0 || port > 65535) {
- throw new IllegalArgumentException(
- "port must be either 0 (for auto) or less than 65536");
- }
-
- if (appRootDir == null) {
- throw new NullPointerException("app root direcotry cannot be null");
- }
- }
-}
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritGwtDebugLauncher.java b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritGwtDebugLauncher.java
new file mode 100644
index 0000000000..4282534a32
--- /dev/null
+++ b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritGwtDebugLauncher.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2014 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.gwtdebug;
+
+import com.google.gerrit.pgm.Daemon;
+import com.google.gwt.dev.codeserver.CodeServer;
+import com.google.gwt.dev.codeserver.Options;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class GerritGwtDebugLauncher {
+ private static final Logger log = LoggerFactory.getLogger(GerritGwtDebugLauncher.class);
+
+ public static void main(String[] argv) throws Exception {
+ GerritGwtDebugLauncher launcher = new GerritGwtDebugLauncher();
+ launcher.mainImpl(argv);
+ }
+
+ private int mainImpl(String[] argv) {
+ List<String> sdmLauncherOptions = new ArrayList<>();
+ List<String> daemonLauncherOptions = new ArrayList<>();
+
+ // Separator between Daemon and Codeserver parameters is "--"
+ boolean daemonArgumentSeparator = false;
+ int i = 0;
+ for (; i < argv.length; i++) {
+ if (!argv[i].equals("--")) {
+ sdmLauncherOptions.add(argv[i]);
+ } else {
+ daemonArgumentSeparator = true;
+ break;
+ }
+ }
+ if (daemonArgumentSeparator) {
+ ++i;
+ for (; i < argv.length; i++) {
+ daemonLauncherOptions.add(argv[i]);
+ }
+ }
+
+ Options options = new Options();
+ if (!options.parseArgs(sdmLauncherOptions.toArray(
+ new String[sdmLauncherOptions.size()]))) {
+ log.error("Failed to parse codeserver arguments");
+ return 1;
+ }
+
+ CodeServer.main(options);
+
+ try {
+ int r = new Daemon().main(daemonLauncherOptions.toArray(
+ new String[daemonLauncherOptions.size()]));
+ if (r != 0) {
+ log.error("Daemon exited with return code: " + r);
+ return 1;
+ }
+ } catch (Exception e) {
+ log.error("Cannot start daemon", e);
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java b/gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java
new file mode 100644
index 0000000000..8072d75e1d
--- /dev/null
+++ b/gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2011 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.gwt.dev.codeserver;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.codeserver.CompileDir.PolicyFile;
+import com.google.gwt.dev.codeserver.Pages.ErrorPage;
+import com.google.gwt.dev.json.JsonObject;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.GzipFilter;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The web server for Super Dev Mode, also known as the code server. The URLs handled include:
+ * <ul>
+ * <li>HTML pages for the front page and module pages</li>
+ * <li>JavaScript that implementing the bookmarklets</li>
+ * <li>The web API for recompiling a GWT app</li>
+ * <li>The output files and log files from the GWT compiler</li>
+ * <li>Java source code (for source-level debugging)</li>
+ * </ul>
+ *
+ * <p>EXPERIMENTAL. There is no authentication, encryption, or XSS protection, so this server is
+ * only safe to run on localhost.</p>
+ */
+// This file was copied from GWT project and adjusted to run against
+// Jetty 9.2.2. The original diff can be found here:
+// https://gwt-review.googlesource.com/#/c/7857/13/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
+public class WebServer {
+
+ private static final Pattern SAFE_DIRECTORY =
+ Pattern.compile("([a-zA-Z0-9_-]+\\.)*[a-zA-Z0-9_-]+"); // no extension needed
+
+ private static final Pattern SAFE_FILENAME =
+ Pattern.compile("([a-zA-Z0-9_-]+\\.)+[a-zA-Z0-9_-]+"); // an extension is required
+
+ private static final Pattern SAFE_MODULE_PATH =
+ Pattern.compile("/(" + SAFE_DIRECTORY + ")/$");
+
+ static final Pattern SAFE_DIRECTORY_PATH =
+ Pattern.compile("/(" + SAFE_DIRECTORY + "/)+$");
+
+ /* visible for testing */
+ static final Pattern SAFE_FILE_PATH =
+ Pattern.compile("/(" + SAFE_DIRECTORY + "/)+" + SAFE_FILENAME + "$");
+
+ static final Pattern STRONG_NAME = Pattern.compile("[\\dA-F]{32}");
+
+ private static final Pattern CACHE_JS_FILE = Pattern.compile("/(" + STRONG_NAME + ").cache.js$");
+
+ private static final MimeTypes MIME_TYPES = new MimeTypes();
+
+ private static final String TIME_IN_THE_PAST = "Fri, 01 Jan 1990 00:00:00 GMT";
+
+ private final SourceHandler handler;
+ private final JsonExporter jsonExporter;
+ private final OutboxTable outboxes;
+ private final JobRunner runner;
+ private final JobEventTable eventTable;
+
+ private final String bindAddress;
+ private final int port;
+
+ private Server server;
+
+ WebServer(SourceHandler handler, JsonExporter jsonExporter, OutboxTable outboxes,
+ JobRunner runner, JobEventTable eventTable, String bindAddress, int port) {
+ this.handler = handler;
+ this.jsonExporter = jsonExporter;
+ this.outboxes = outboxes;
+ this.runner = runner;
+ this.eventTable = eventTable;
+ this.bindAddress = bindAddress;
+ this.port = port;
+ }
+
+ @SuppressWarnings("serial")
+ void start(final TreeLogger logger) throws UnableToCompleteException {
+
+ Server newServer = new Server();
+ ServerConnector connector = new ServerConnector(newServer);
+ connector.setHost(bindAddress);
+ connector.setPort(port);
+ connector.setReuseAddress(false);
+ connector.setSoLingerTime(0);
+
+ newServer.addConnector(connector);
+
+ ServletContextHandler newHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ newHandler.setContextPath("/");
+ newHandler.addServlet(new ServletHolder(new HttpServlet() {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ handleRequest(request.getPathInfo(), request, response, logger);
+ }
+ }), "/*");
+ newHandler.addFilter(GzipFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+ newServer.setHandler(newHandler);
+ try {
+ newServer.start();
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR, "cannot start web server", e);
+ throw new UnableToCompleteException();
+ }
+ this.server = newServer;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void stop() throws Exception {
+ server.stop();
+ server = null;
+ }
+
+ /**
+ * Returns the location of the compiler output. (Changes after every recompile.)
+ * @param outputModuleName the module name that the GWT compiler used in its output.
+ */
+ public File getCurrentWarDir(String outputModuleName) {
+ return outboxes.findByOutputModuleName(outputModuleName).getWarDir();
+ }
+
+ private void handleRequest(String target, HttpServletRequest request,
+ HttpServletResponse response, TreeLogger parentLogger)
+ throws IOException {
+
+ if (request.getMethod().equalsIgnoreCase("get")) {
+
+ TreeLogger logger = parentLogger.branch(Type.TRACE, "GET " + target);
+
+ Response page = doGet(target, request, logger);
+ if (page == null) {
+ logger.log(Type.WARN, "not handled: " + target);
+ return;
+ }
+
+ setHandled(request);
+ if (!target.endsWith(".cache.js")) {
+ // Make sure IE9 doesn't cache any pages.
+ // (Nearly all pages may change on server restart.)
+ response.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", TIME_IN_THE_PAST);
+ response.setDateHeader("Date", new Date().getTime());
+ }
+ page.send(request, response, logger);
+ }
+ }
+
+ /**
+ * Returns the page that should be sent in response to a GET request, or null for no response.
+ */
+ private Response doGet(String target, HttpServletRequest request, TreeLogger logger)
+ throws IOException {
+
+ if (target.equals("/")) {
+ JsonObject json = jsonExporter.exportFrontPageVars();
+ return Pages.newHtmlPage("config", json, "frontpage.html");
+ }
+
+ if (target.equals("/dev_mode_on.js")) {
+ JsonObject json = jsonExporter.exportDevModeOnVars();
+ return Responses.newJavascriptResponse("__gwt_codeserver_config", json,
+ "dev_mode_on.js");
+ }
+
+ // Recompile on request from the bookmarklet.
+ // This is a GET because a bookmarklet can call it from a different origin (JSONP).
+ if (target.startsWith("/recompile/")) {
+ String moduleName = target.substring("/recompile/".length());
+ Outbox box = outboxes.findByOutputModuleName(moduleName);
+ if (box == null) {
+ return new ErrorPage("No such module: " + moduleName);
+ }
+
+ // We are passing properties from an unauthenticated GET request directly to the compiler.
+ // This should be safe, but only because these are binding properties. For each binding
+ // property, you can only choose from a set of predefined values. So all an attacker can do is
+ // cause a spurious recompile, resulting in an unexpected permutation being loaded later.
+ //
+ // It would be unsafe to allow a configuration property to be changed.
+ Job job = box.makeJob(getBindingProperties(request), logger);
+ runner.submit(job);
+ Job.Result result = job.waitForResult();
+ JsonObject json = jsonExporter.exportRecompileResponse(result);
+ return Responses.newJsonResponse(json);
+ }
+
+ if (target.startsWith("/log/")) {
+ String moduleName = target.substring("/log/".length());
+ Outbox box = outboxes.findByOutputModuleName(moduleName);
+ if (box == null) {
+ return new ErrorPage("No such module: " + moduleName);
+ } else if (box.containsStubCompile()) {
+ return new ErrorPage("This module hasn't been compiled yet.");
+ } else {
+ return makeLogPage(box);
+ }
+ }
+
+ if (target.equals("/favicon.ico")) {
+ InputStream faviconStream = getClass().getResourceAsStream("favicon.ico");
+ if (faviconStream == null) {
+ return new ErrorPage("icon not found");
+ }
+ // IE8 will not load the favicon in an img tag with the default MIME type,
+ // so use "image/x-icon" instead.
+ return Responses.newBinaryStreamResponse("image/x-icon", faviconStream);
+ }
+
+ if (target.equals("/policies/")) {
+ return makePolicyIndexPage();
+ }
+
+ if (target.equals("/progress")) {
+ // TODO: return a list of progress objects here, one for each job.
+ JobEvent event = eventTable.getCompilingJobEvent();
+
+ JsonObject json;
+ if (event == null) {
+ json = new JsonObject();
+ json.put("status", "idle");
+ } else {
+ json = jsonExporter.exportProgressResponse(event);
+ }
+ return Responses.newJsonResponse(json);
+ }
+
+ Matcher matcher = SAFE_MODULE_PATH.matcher(target);
+ if (matcher.matches()) {
+ return makeModulePage(matcher.group(1));
+ }
+
+ matcher = SAFE_DIRECTORY_PATH.matcher(target);
+ if (matcher.matches() && SourceHandler.isSourceMapRequest(target)) {
+ return handler.handle(target, request, logger);
+ }
+
+ matcher = SAFE_FILE_PATH.matcher(target);
+ if (matcher.matches()) {
+ if (SourceHandler.isSourceMapRequest(target)) {
+ return handler.handle(target, request, logger);
+ }
+ if (target.startsWith("/policies/")) {
+ return makePolicyFilePage(target);
+ }
+ return makeCompilerOutputPage(target);
+ }
+
+ logger.log(TreeLogger.WARN, "ignored get request: " + target);
+ return null; // not handled
+ }
+
+ /**
+ * Returns a file that the compiler wrote to its war directory.
+ */
+ private Response makeCompilerOutputPage(String target) {
+
+ int secondSlash = target.indexOf('/', 1);
+ String moduleName = target.substring(1, secondSlash);
+ Outbox box = outboxes.findByOutputModuleName(moduleName);
+ if (box == null) {
+ return new ErrorPage("No such module: " + moduleName);
+ }
+
+ final String contentEncoding;
+ File file = box.getOutputFile(target);
+ if (!file.isFile()) {
+ // perhaps it's compressed
+ file = box.getOutputFile(target + ".gz");
+ if (!file.isFile()) {
+ return new ErrorPage("not found: " + file.toString());
+ }
+ contentEncoding = "gzip";
+ } else {
+ contentEncoding = null;
+ }
+
+ final String sourceMapUrl;
+ Matcher match = CACHE_JS_FILE.matcher(target);
+ if (match.matches()) {
+ String strongName = match.group(1);
+ String template = SourceHandler.sourceMapLocationTemplate(moduleName);
+ sourceMapUrl = template.replace("__HASH__", strongName);
+ } else {
+ sourceMapUrl = null;
+ }
+
+ String mimeType = guessMimeType(target);
+ final Response barePage = Responses.newFileResponse(mimeType, file);
+
+ // Wrap the response to send the extra headers.
+ return new Response() {
+ @Override
+ public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger)
+ throws IOException {
+ // TODO: why do we need this? Looks like Ray added it a long time ago.
+ response.setHeader("Access-Control-Allow-Origin", "*");
+
+ if (sourceMapUrl != null) {
+ response.setHeader("X-SourceMap", sourceMapUrl);
+ response.setHeader("SourceMap", sourceMapUrl);
+ }
+
+ if (contentEncoding != null) {
+ if (!request.getHeader("Accept-Encoding").contains("gzip")) {
+ response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+ logger.log(TreeLogger.WARN, "client doesn't accept gzip; bailing");
+ return;
+ }
+ response.setHeader("Content-Encoding", "gzip");
+ }
+
+ barePage.send(request, response, logger);
+ }
+ };
+ }
+
+ private Response makeModulePage(String moduleName) {
+ Outbox box = outboxes.findByOutputModuleName(moduleName);
+ if (box == null) {
+ return new ErrorPage("No such module: " + moduleName);
+ }
+
+ JsonObject json = jsonExporter.exportModulePageVars(box);
+ return Pages.newHtmlPage("config", json, "modulepage.html");
+ }
+
+ private Response makePolicyIndexPage() {
+
+ return new Response() {
+
+ @Override
+ public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger)
+ throws IOException {
+ response.setContentType("text/html");
+
+ HtmlWriter out = new HtmlWriter(response.getWriter());
+
+ out.startTag("html").nl();
+ out.startTag("head").nl();
+ out.startTag("title").text("Policy Files").endTag("title").nl();
+ out.endTag("head");
+ out.startTag("body");
+
+ out.startTag("h1").text("Policy Files").endTag("h1").nl();
+
+ for (Outbox box : outboxes.getOutboxes()) {
+ List<PolicyFile> policies = box.readRpcPolicyManifest();
+ if (!policies.isEmpty()) {
+ out.startTag("h2").text(box.getOutputModuleName()).endTag("h2").nl();
+
+ out.startTag("table").nl();
+ for (PolicyFile policy : policies) {
+
+ out.startTag("tr");
+
+ out.startTag("td");
+
+ out.startTag("a", "href=", policy.getServiceSourceUrl());
+ out.text(policy.getServiceName());
+ out.endTag("a");
+
+ out.endTag("td");
+
+ out.startTag("td");
+
+ out.startTag("a", "href=", policy.getUrl());
+ out.text(policy.getName());
+ out.endTag("a");
+
+ out.endTag("td");
+
+ out.endTag("tr").nl();
+ }
+ out.endTag("table").nl();
+ }
+ }
+
+ out.endTag("body").nl();
+ out.endTag("html").nl();
+ }
+ };
+ }
+
+ private Response makePolicyFilePage(String target) {
+
+ int secondSlash = target.indexOf('/', 1);
+ if (secondSlash < 1) {
+ return new ErrorPage("invalid URL for policy file: " + target);
+ }
+
+ String rest = target.substring(secondSlash + 1);
+ if (rest.contains("/") || !rest.endsWith(".gwt.rpc")) {
+ return new ErrorPage("invalid name for policy file: " + rest);
+ }
+
+ File fileToSend = outboxes.findPolicyFile(rest);
+ if (fileToSend == null) {
+ return new ErrorPage("Policy file not found: " + rest);
+ }
+
+ return Responses.newFileResponse("text/plain", fileToSend);
+ }
+
+ /**
+ * Sends the log file as html with errors highlighted in red.
+ */
+ private Response makeLogPage(final Outbox box) {
+ final File file = box.getCompileLog();
+ if (!file.isFile()) {
+ return new ErrorPage("log file not found");
+ }
+
+ return new Response() {
+
+ @Override
+ public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger)
+ throws IOException {
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("text/html");
+ response.setHeader("Content-Style-Type", "text/css");
+
+ HtmlWriter out = new HtmlWriter(response.getWriter());
+ out.startTag("html").nl();
+ out.startTag("head").nl();
+ out.startTag("title").text(box.getOutputModuleName() + " compile log").endTag("title").nl();
+ out.startTag("style").nl();
+ out.text(".error { color: red; font-weight: bold; }").nl();
+ out.endTag("style").nl();
+ out.endTag("head").nl();
+ out.startTag("body").nl();
+ sendLogAsHtml(reader, out);
+ out.endTag("body").nl();
+ out.endTag("html").nl();
+ }
+ };
+ }
+
+ private static final Pattern ERROR_PATTERN = Pattern.compile("\\[ERROR\\]");
+
+ /**
+ * Copies in to out line by line, escaping each line for html characters and highlighting
+ * error lines. Closes <code>in</code> when done.
+ */
+ private static void sendLogAsHtml(BufferedReader in, HtmlWriter out) throws IOException {
+ try {
+ out.startTag("pre").nl();
+ String line = in.readLine();
+ while (line != null) {
+ Matcher m = ERROR_PATTERN.matcher(line);
+ boolean error = m.find();
+ if (error) {
+ out.startTag("span", "class=", "error");
+ }
+ out.text(line);
+ if (error) {
+ out.endTag("span");
+ }
+ out.nl(); // the readLine doesn't include the newline.
+ line = in.readLine();
+ }
+ out.endTag("pre").nl();
+ } finally {
+ in.close();
+ }
+ }
+
+ /* visible for testing */
+ static String guessMimeType(String filename) {
+ String mimeType = MIME_TYPES.getMimeByExtension(filename);
+ return mimeType != null ? mimeType : "";
+ }
+
+ /**
+ * Returns the binding properties from the web page where dev mode is being used. (As passed in
+ * by dev_mode_on.js in a JSONP request to "/recompile".)
+ */
+ private Map<String, String> getBindingProperties(HttpServletRequest request) {
+ Map<String, String> result = new HashMap<>();
+ for (Object key : request.getParameterMap().keySet()) {
+ String propName = (String) key;
+ if (!propName.equals("_callback")) {
+ result.put(propName, request.getParameter(propName));
+ }
+ }
+ return result;
+ }
+
+ private static void setHandled(HttpServletRequest request) {
+ Request baseRequest = (request instanceof Request) ? (Request) request :
+ HttpConnection.getCurrentConnection().getHttpChannel().getRequest();
+ baseRequest.setHandled(true);
+ }
+}
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
index e06bf17068..b266e12625 100644
--- a/gerrit-gwtexpui/BUCK
+++ b/gerrit-gwtexpui/BUCK
@@ -8,10 +8,10 @@ gwt_module(
SRC + 'clippy/client/clippy.css',
SRC + 'clippy/client/clippy.swf',
],
+ provided_deps = ['//lib/gwt:user'],
deps = [
':SafeHtml',
':UserAgent',
- '//lib/gwt:user',
'//lib:LICENSE-clippy',
],
visibility = ['PUBLIC'],
@@ -21,7 +21,7 @@ java_library(
name = 'CSS',
srcs = glob([SRC + 'css/rebind/*.java']),
resources = [SRC + 'css/CSS.gwt.xml'],
- deps = ['//lib/gwt:dev'],
+ provided_deps = ['//lib/gwt:dev'],
visibility = ['PUBLIC'],
)
@@ -33,10 +33,10 @@ gwt_module(
SRC + 'globalkey/client/KeyConstants.properties',
SRC + 'globalkey/client/key.css',
],
+ provided_deps = ['//lib/gwt:user'],
deps = [
':SafeHtml',
':UserAgent',
- '//lib/gwt:user',
],
visibility = ['PUBLIC'],
)
@@ -53,7 +53,7 @@ gwt_module(
srcs = glob([SRC + 'progress/client/*.java']),
gwt_xml = SRC + 'progress/Progress.gwt.xml',
resources = [SRC + 'progress/client/progress.css'],
- deps = ['//lib/gwt:user'],
+ provided_deps = ['//lib/gwt:user'],
visibility = ['PUBLIC'],
)
@@ -62,7 +62,7 @@ gwt_module(
srcs = glob([SRC + 'safehtml/client/*.java']),
gwt_xml = SRC + 'safehtml/SafeHtml.gwt.xml',
resources = [SRC + 'safehtml/client/safehtml.css'],
- deps = ['//lib/gwt:user'],
+ provided_deps = ['//lib/gwt:user'],
visibility = ['PUBLIC'],
)
@@ -84,7 +84,7 @@ gwt_module(
name = 'UserAgent',
srcs = glob([SRC + 'user/client/*.java']),
gwt_xml = SRC + 'user/User.gwt.xml',
- deps = ['//lib/gwt:user'],
+ provided_deps = ['//lib/gwt:user'],
visibility = ['PUBLIC'],
)
@@ -94,3 +94,17 @@ java_library(
provided_deps = ['//lib:servlet-api-3_1'],
visibility = ['PUBLIC'],
)
+
+java_library(
+ name = 'client-src-lib',
+ srcs = [],
+ resources = glob(
+ [SRC + n for n in [
+ 'clippy/**/*',
+ 'globalkey/**/*',
+ 'safehtml/**/*',
+ 'user/**/*',
+ ]]
+ ),
+ visibility = ['PUBLIC'],
+)
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
index a0392f82ba..e34814f6ae 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
@@ -160,10 +160,12 @@ public class CopyableLabel extends Composite implements HasText {
}
}
+ @Override
public String getText() {
return text;
}
+ @Override
public void setText(final String newText) {
text = newText;
visibleLen = newText.length();
@@ -195,6 +197,7 @@ public class CopyableLabel extends Composite implements HasText {
@Override
public void onKeyUp(final KeyUpEvent event) {
Scheduler.get().scheduleDeferred(new Command() {
+ @Override
public void execute() {
hideTextBox();
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java
deleted file mode 100644
index 7ae7fd0c4a..0000000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java
+++ /dev/null
@@ -1,24 +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.gwtexpui.globalkey.client;
-
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.user.client.ui.FlowPanel;
-
-public class NpFlowPanel extends FlowPanel {
- public NpFlowPanel() {
- addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
- }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
index 5bb3c1ed9a..eb87e7f20e 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
@@ -14,9 +14,10 @@
package com.google.gwtexpui.linker.server;
+import static java.util.regex.Pattern.compile;
+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static java.util.regex.Pattern.compile;
import javax.servlet.http.HttpServletRequest;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/AttMap.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/AttMap.java
index b08b29f0b9..6eaa7fd733 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/AttMap.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/AttMap.java
@@ -107,11 +107,13 @@ class AttMap {
}
private static class AnyTag implements Tag {
+ @Override
public void assertSafe(String name, String value) {
}
}
private static class AnchorTag implements Tag {
+ @Override
public void assertSafe(String name, String value) {
if ("href".equals(name)) {
assertNotJavascriptUrl(value);
@@ -120,6 +122,7 @@ class AttMap {
}
private static class FormTag implements Tag {
+ @Override
public void assertSafe(String name, String value) {
if ("action".equals(name)) {
assertNotJavascriptUrl(value);
@@ -128,6 +131,7 @@ class AttMap {
}
private static class SrcTag implements Tag {
+ @Override
public void assertSafe(String name, String value) {
if ("src".equals(name)) {
assertNotJavascriptUrl(value);
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/Buffer.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/Buffer.java
index d79c580623..12389b4531 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/Buffer.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/Buffer.java
@@ -29,5 +29,6 @@ interface Buffer {
void append(String v);
+ @Override
String toString();
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferDirect.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferDirect.java
index a1801ad038..83abd5d6b3 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferDirect.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferDirect.java
@@ -21,30 +21,37 @@ final class BufferDirect implements Buffer {
return strbuf.length() == 0;
}
+ @Override
public void append(final boolean v) {
strbuf.append(v);
}
+ @Override
public void append(final char v) {
strbuf.append(v);
}
+ @Override
public void append(final int v) {
strbuf.append(v);
}
+ @Override
public void append(final long v) {
strbuf.append(v);
}
+ @Override
public void append(final float v) {
strbuf.append(v);
}
+ @Override
public void append(final double v) {
strbuf.append(v);
}
+ @Override
public void append(final String v) {
strbuf.append(v);
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferSealElement.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferSealElement.java
index 6b5346d8d2..e3aed550b9 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferSealElement.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/BufferSealElement.java
@@ -21,30 +21,37 @@ final class BufferSealElement implements Buffer {
shb = safeHtmlBuilder;
}
+ @Override
public void append(final boolean v) {
shb.sealElement().append(v);
}
+ @Override
public void append(final char v) {
shb.sealElement().append(v);
}
+ @Override
public void append(final double v) {
shb.sealElement().append(v);
}
+ @Override
public void append(final float v) {
shb.sealElement().append(v);
}
+ @Override
public void append(final int v) {
shb.sealElement().append(v);
}
+ @Override
public void append(final long v) {
shb.sealElement().append(v);
}
+ @Override
public void append(final String v) {
shb.sealElement().append(v);
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
index 216add1b23..1ca688b4ba 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
@@ -41,6 +41,7 @@ public abstract class HighlightSuggestOracle extends SuggestOracle {
@Override
public final void requestSuggestions(final Request request, final Callback cb) {
onRequestSuggestions(request, new Callback() {
+ @Override
public void onSuggestionsReady(final Request request,
final Response response) {
final String qpat = getQueryPattern(request.getQuery());
@@ -99,10 +100,12 @@ public abstract class HighlightSuggestOracle extends SuggestOracle {
private static native String sgi(String inString, String pat, String newHtml)
/*-{ return inString.replace(RegExp(pat, 'gi'), newHtml); }-*/;
+ @Override
public String getDisplayString() {
return displayString;
}
+ @Override
public String getReplacementString() {
return suggestion.getReplacementString();
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
index a71120adfd..b8f0800373 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
@@ -44,26 +44,32 @@ public abstract class SafeHtml
@Override
public SafeHtmlCss css() {
return new SafeHtmlCss() {
+ @Override
public String wikiList() {
return "wikiList";
}
+ @Override
public String wikiPreFormat() {
return "wikiPreFormat";
}
+ @Override
public String wikiQuote() {
return "wikiQuote";
}
+ @Override
public boolean ensureInjected() {
return false;
}
+ @Override
public String getName() {
return null;
}
+ @Override
public String getText() {
return null;
}
@@ -335,5 +341,6 @@ public abstract class SafeHtml
}
/** @return a clean HTML string safe for inclusion in any context. */
+ @Override
public abstract String asString();
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
index 75337ac485..69da38d396 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
@@ -402,7 +402,7 @@ public class SafeHtmlBuilder extends SafeHtml {
return isElementName(name);
}
- private static abstract class Impl {
+ private abstract static class Impl {
abstract void escapeStr(SafeHtmlBuilder b, String in);
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
index 54bfcd0021..a438caf24d 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
@@ -49,12 +49,15 @@ import javax.servlet.http.HttpServletResponse;
* </pre>
*/
public class CacheControlFilter implements Filter {
+ @Override
public void init(final FilterConfig config) {
}
+ @Override
public void destroy() {
}
+ @Override
public void doFilter(final ServletRequest sreq, final ServletResponse srsp,
final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest) sreq;
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
index bc20a9d88b..182eac3a85 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
@@ -14,9 +14,10 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
public class RawFindReplaceTest {
@Test
public void testFindReplace() {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
index 75c37459bc..f89c62bba0 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_LinkifyTest {
@Test
public void testLinkify_SimpleHttp1() {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
index 71b55a1f4e..4fa625433c 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
@@ -14,16 +14,16 @@
package com.google.gwtexpui.safehtml.client;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-
public class SafeHtml_ReplaceTest {
@Test
public void testReplaceEmpty() {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
index 045555a9e7..ea91ee3207 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyListTest {
private static final String BEGIN_LIST = "<ul class=\"wikiList\">";
private static final String END_LIST = "</ul>";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
index 605185ec43..57399dc44c 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyPreformatTest {
private static final String B = "<span class=\"wikiPreFormat\">";
private static final String E = "</span><br />";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
index d6fba26de4..f6b6b91123 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyQuoteTest {
private static final String B = "<blockquote class=\"wikiQuote\">";
private static final String E = "</blockquote>";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
index 00b29dee42..3c261d0e95 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
@@ -14,11 +14,11 @@
package com.google.gwtexpui.safehtml.client;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
+import org.junit.Test;
+
public class SafeHtml_WikifyTest {
@Test
public void testWikify_OneLine1() {
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
index f4c2358870..2eb068401d 100644
--- a/gerrit-gwtui-common/BUCK
+++ b/gerrit-gwtui-common/BUCK
@@ -1,9 +1,11 @@
SRC = 'src/main/java/com/google/gerrit/'
+DIFFY = glob(['src/main/resources/com/google/gerrit/client/diffy*.png'])
gwt_module(
name = 'client',
srcs = glob([SRC + 'client/**/*.java']),
gwt_xml = SRC + 'GerritGwtUICommon.gwt.xml',
+ resources = glob(['src/main/**/*']),
deps = ['//lib/gwt:user'],
visibility = ['PUBLIC'],
)
@@ -28,3 +30,25 @@ java_library(
resources = glob(['src/main/**/*']),
visibility = ['PUBLIC'],
)
+
+prebuilt_jar(
+ name = 'diffy_logo',
+ binary_jar = ':diffy_image_files_ln',
+ deps = [
+ '//lib:LICENSE-diffy',
+ '//lib:LICENSE-CC-BY3.0',
+ ],
+ visibility = ['PUBLIC'],
+)
+
+genrule(
+ name = 'diffy_image_files_ln',
+ cmd = 'ln -s $(location :diffy_image_files) $OUT',
+ deps = [':diffy_image_files'],
+ out = 'diffy_images.jar',
+)
+
+java_library(
+ name = 'diffy_image_files',
+ resources = DIFFY,
+)
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
new file mode 100644
index 0000000000..c4e21676d0
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
@@ -0,0 +1,107 @@
+// 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.client;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface Resources extends ClientBundle {
+ @Source("arrowRight.png")
+ public ImageResource arrowRight();
+
+ @Source("arrowUp.png")
+ public ImageResource arrowUp();
+
+ @Source("arrowDown.png")
+ public ImageResource arrowDown();
+
+ @Source("editText.png")
+ public ImageResource edit();
+
+ @Source("mediaFloppy.png")
+ public ImageResource save();
+
+ @Source("starOpen.png")
+ public ImageResource starOpen();
+
+ @Source("starFilled.png")
+ public ImageResource starFilled();
+
+ @Source("greenCheck.png")
+ public ImageResource greenCheck();
+
+ @Source("redNot.png")
+ public ImageResource redNot();
+
+ @Source("editUndo.png")
+ public ImageResource editUndo();
+
+ @Source("downloadIcon.png")
+ public ImageResource downloadIcon();
+
+ @Source("queryIcon.png")
+ public ImageResource queryIcon();
+
+ @Source("addFileComment.png")
+ public ImageResource addFileComment();
+
+ @Source("diffy26.png")
+ public ImageResource gerritAvatar26();
+
+ @Source("draftComments.png")
+ public ImageResource draftComments();
+
+ @Source("readOnly.png")
+ public ImageResource readOnly();
+
+ @Source("gear.png")
+ public ImageResource gear();
+
+ @Source("info.png")
+ public ImageResource info();
+
+ @Source("warning.png")
+ public ImageResource warning();
+
+ @Source("listAdd.png")
+ public ImageResource listAdd();
+
+ @Source("merge.png")
+ public ImageResource merge();
+
+ @Source("deleteNormal.png")
+ public ImageResource deleteNormal();
+
+ @Source("deleteHover.png")
+ public ImageResource deleteHover();
+
+ @Source("undoNormal.png")
+ public ImageResource undoNormal();
+
+ @Source("goPrev.png")
+ public ImageResource goPrev();
+
+ @Source("goNext.png")
+ public ImageResource goNext();
+
+ @Source("goUp.png")
+ public ImageResource goUp();
+
+ @Source("sideBySideDiff.png")
+ public ImageResource sideBySideDiff();
+
+ @Source("unifiedDiff.png")
+ public ImageResource unifiedDiff();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/addFileComment.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/addFileComment.png
index 4ae3ae8ce0..4ae3ae8ce0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/addFileComment.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/addFileComment.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowDown.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowDown.png
index ba67de7e33..ba67de7e33 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowDown.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowDown.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowRight.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowRight.png
new file mode 100644
index 0000000000..8549f5d386
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowRight.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowUp.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowUp.png
index 5674d6c372..5674d6c372 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowUp.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/arrowUp.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteHover.png
index 9fde3fa521..9fde3fa521 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteHover.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteNormal.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteNormal.png
new file mode 100644
index 0000000000..47a1195789
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/deleteNormal.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diffy100.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/diffy100.png
index 4be4541a05..4be4541a05 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diffy100.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/diffy100.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diffy26.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/diffy26.png
index 88b59d87b8..88b59d87b8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diffy26.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/diffy26.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/downloadIcon.png
index 1a6520e852..1a6520e852 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/downloadIcon.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/draftComments.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/draftComments.png
index 276912a90c..276912a90c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/draftComments.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/draftComments.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editText.png
index 2927275f56..2927275f56 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editText.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editUndo.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editUndo.png
new file mode 100644
index 0000000000..4790e10758
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/editUndo.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/gear.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/gear.png
index 2f84e4734f..2f84e4734f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/gear.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/gear.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-next.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goNext.png
index 872c1979e0..872c1979e0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-next.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goNext.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-prev.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goPrev.png
index d68f29b34c..d68f29b34c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-prev.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goPrev.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-up.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goUp.png
index f75bed4478..f75bed4478 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-up.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/goUp.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/greenCheck.png
index 207c0e7e34..207c0e7e34 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/greenCheck.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/info.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/info.png
index 8851b99ba1..8851b99ba1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/info.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/info.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/listAdd.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/listAdd.png
index 1aa7f095c6..1aa7f095c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/listAdd.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/listAdd.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/mediaFloppy.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/mediaFloppy.png
new file mode 100644
index 0000000000..f1d7a19859
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/mediaFloppy.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/merge.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/merge.png
index 9c892dbb1e..9c892dbb1e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/merge.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/merge.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/queryIcon.png
index 5ebf2cbcc4..5ebf2cbcc4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/queryIcon.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/readOnly.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/readOnly.png
index 62e89f9a7e..62e89f9a7e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/readOnly.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/readOnly.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/redNot.png
index 99834fdcf3..99834fdcf3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/redNot.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/sideBySideDiff.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/sideBySideDiff.png
new file mode 100755
index 0000000000..ee70080bd6
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/sideBySideDiff.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/starFilled.png
index db1e24e8de..db1e24e8de 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/starFilled.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_open.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/starOpen.png
index 6c955de010..6c955de010 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_open.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/starOpen.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/undoNormal.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/undoNormal.png
new file mode 100644
index 0000000000..b780f7560c
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/undoNormal.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/unifiedDiff.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/unifiedDiff.png
new file mode 100755
index 0000000000..ec5f97ae12
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/unifiedDiff.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/warning.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/warning.png
index 81e9ed29a5..81e9ed29a5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/warning.png
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/warning.png
Binary files differ
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 89b7ef7fc4..aad5e0b5fd 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -2,7 +2,7 @@ include_defs('//gerrit-gwtui/gwt.defs')
include_defs('//tools/gwt-constants.defs')
from multiprocessing import cpu_count
-DEPS = [
+DEPS = GWT_COMMON_DEPS + [
'//gerrit-gwtexpui:CSS',
'//lib:gwtjsonrpc',
]
@@ -15,7 +15,7 @@ genrule(
' gerrit_ui/gerrit_ui.nocache.js' +
' gerrit_ui/dbg_gerrit_ui.nocache.js;' +
'unzip -qo $(location :ui_opt);' +
- 'mkdir -p $(dirname $OUT);' +
+ 'mkdir -p \$(dirname $OUT);' +
'zip -qr $OUT .',
deps = [
':ui_dbg',
@@ -37,6 +37,17 @@ gwt_binary(
)
gwt_binary(
+ name = 'ui_soyc',
+ modules = [MODULE],
+ module_deps = [':ui_module'],
+ deps = DEPS + [':ui_dbg'],
+ local_workers = cpu_count(),
+ strict = True,
+ experimental_args = GWT_COMPILER_ARGS + ['-compileReport'],
+ vm_args = GWT_JVM_ARGS,
+)
+
+gwt_binary(
name = 'ui_dbg',
modules = [MODULE],
style = 'PRETTY',
@@ -59,16 +70,14 @@ gwt_user_agent_permutations(
visibility = ['//:'],
)
-DIFFY = glob(['src/main/java/com/google/gerrit/client/diffy*.png'])
-
gwt_module(
name = 'ui_module',
srcs = glob(['src/main/java/**/*.java']),
gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
- resources = glob(['src/main/java/**/*'], excludes = DIFFY),
+ resources = glob(['src/main/java/**/*']),
deps = [
- ':diffy_logo',
':freebie_application_icon_set',
+ '//gerrit-gwtui-common:diffy_logo',
'//gerrit-gwtexpui:Clippy',
'//gerrit-gwtexpui:GlobalKey',
'//gerrit-gwtexpui:Progress',
@@ -94,15 +103,6 @@ gwt_module(
],
)
-prebuilt_jar(
- name = 'diffy_logo',
- binary_jar = ':diffy_image_files_ln',
- deps = [
- '//lib:LICENSE-diffy',
- '//lib:LICENSE-CC-BY3.0',
- ],
-)
-
java_library(
name = 'freebie_application_icon_set',
deps = [
@@ -111,18 +111,6 @@ java_library(
],
)
-genrule(
- name = 'diffy_image_files_ln',
- cmd = 'ln -s $(location :diffy_image_files) $OUT',
- deps = [':diffy_image_files'],
- out = 'diffy_images.jar',
-)
-
-java_library(
- name = 'diffy_image_files',
- resources = DIFFY,
-)
-
java_test(
name = 'ui_tests',
srcs = glob(['src/test/java/**/*.java']),
diff --git a/gerrit-gwtui/gwt.defs b/gerrit-gwtui/gwt.defs
index 0e10116bea..cd206c0737 100644
--- a/gerrit-gwtui/gwt.defs
+++ b/gerrit-gwtui/gwt.defs
@@ -56,7 +56,7 @@ def gwt_user_agent_permutations(
genrule(
name = '%s_gwtxml_gen' % gwt_name,
cmd = 'cd $TMP;' +
- ('mkdir -p $(dirname %s);' % gwt) +
+ ('mkdir -p \$(dirname %s);' % gwt) +
('echo "%s">%s;' % (xml, gwt)) +
'zip -qr $OUT .',
out = jar,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
index b8102ec503..fd717eee11 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
@@ -39,7 +39,6 @@
<add-linker name='xsiframe'/>
<set-property name='gwt.logging.logLevel' value='SEVERE'/>
- <set-property name='gwt.logging.popupHandler' value='DISABLED'/>
<entry-point class='com.google.gerrit.client.Gerrit'/>
</module>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
index e85633fbe7..ab3ff5dffc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
@@ -25,8 +25,8 @@ import com.google.gwtexpui.user.client.AutoCenterDialogBox;
public class ConfirmationDialog extends AutoCenterDialogBox {
-
private Button cancelButton;
+ private Button okButton;
public ConfirmationDialog(final String dialogTitle, final SafeHtml message,
final ConfirmationCallback callback) {
@@ -36,7 +36,7 @@ public class ConfirmationDialog extends AutoCenterDialogBox {
final FlowPanel buttons = new FlowPanel();
- final Button okButton = new Button();
+ okButton = new Button();
okButton.setText(Gerrit.C.confirmationDialogOk());
okButton.addClickHandler(new ClickHandler() {
@Override
@@ -76,4 +76,11 @@ public class ConfirmationDialog extends AutoCenterDialogBox {
GlobalKey.dialog(this);
cancelButton.setFocus(true);
}
+
+ public void setCancelVisible(boolean visible) {
+ cancelButton.setVisible(visible);
+ if (!visible) {
+ okButton.setFocus(true);
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffWebLinkInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffWebLinkInfo.java
new file mode 100644
index 0000000000..bcf4256e36
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/DiffWebLinkInfo.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2014 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.client;
+
+public class DiffWebLinkInfo extends WebLinkInfo {
+ public final native boolean showOnSideBySideDiffView()
+ /*-{ return this.show_on_side_by_side_diff_view || false; }-*/;
+
+ public final native boolean showOnUnifiedDiffView()
+ /*-{ return this.show_on_unified_diff_view || false; }-*/;
+
+ protected DiffWebLinkInfo() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 2ad9a5e1ad..e2bf142830 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -56,7 +56,6 @@ import com.google.gerrit.client.admin.AccountGroupScreen;
import com.google.gerrit.client.admin.CreateGroupScreen;
import com.google.gerrit.client.admin.CreateProjectScreen;
import com.google.gerrit.client.admin.GroupListScreen;
-import com.google.gerrit.client.admin.MyGroupsListScreen;
import com.google.gerrit.client.admin.PluginListScreen;
import com.google.gerrit.client.admin.ProjectAccessScreen;
import com.google.gerrit.client.admin.ProjectBranchesScreen;
@@ -65,29 +64,26 @@ import com.google.gerrit.client.admin.ProjectInfoScreen;
import com.google.gerrit.client.admin.ProjectListScreen;
import com.google.gerrit.client.admin.ProjectScreen;
import com.google.gerrit.client.api.ExtensionScreen;
-import com.google.gerrit.client.change.ChangeScreen2;
+import com.google.gerrit.client.change.ChangeScreen;
+import com.google.gerrit.client.change.FileTable;
import com.google.gerrit.client.changes.AccountDashboardScreen;
-import com.google.gerrit.client.changes.ChangeScreen;
import com.google.gerrit.client.changes.CustomDashboardScreen;
-import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.ProjectDashboardScreen;
-import com.google.gerrit.client.changes.PublishCommentScreen;
import com.google.gerrit.client.changes.QueryScreen;
import com.google.gerrit.client.dashboards.DashboardInfo;
import com.google.gerrit.client.dashboards.DashboardList;
import com.google.gerrit.client.diff.DisplaySide;
-import com.google.gerrit.client.diff.SideBySide2;
+import com.google.gerrit.client.diff.SideBySide;
import com.google.gerrit.client.documentation.DocScreen;
+import com.google.gerrit.client.editor.EditScreen;
import com.google.gerrit.client.groups.GroupApi;
import com.google.gerrit.client.groups.GroupInfo;
-import com.google.gerrit.client.patches.PatchScreen;
+import com.google.gerrit.client.patches.UnifiedPatchScreen;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
@@ -97,19 +93,11 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.http.client.URL;
-import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Window;
import com.google.gwtorm.client.KeyUtil;
public class Dispatcher {
- public static final String COOKIE_CS2 = "gerrit_cs2";
- public static boolean changeScreen2;
-
- public static String toPatchSideBySide(final Patch.Key id) {
- return toPatch("", null, id);
- }
-
- public static String toPatchSideBySide(PatchSet.Id diffBase, Patch.Key id) {
+ public static String toSideBySide(PatchSet.Id diffBase, Patch.Key id) {
return toPatch("", diffBase, id);
}
@@ -128,18 +116,22 @@ public class Dispatcher {
return toPatch("unified", diffBase, revision, fileName, null, 0);
}
- public static String toPatchUnified(final Patch.Key id) {
- return toPatch("unified", null, id);
- }
-
- public static String toPatchUnified(PatchSet.Id diffBase, Patch.Key id) {
+ public static String toUnified(PatchSet.Id diffBase, Patch.Key id) {
return toPatch("unified", diffBase, id);
}
- private static String toPatch(String type, PatchSet.Id diffBase, Patch.Key id) {
+ public static String toPatch(String type, PatchSet.Id diffBase, Patch.Key id) {
return toPatch(type, diffBase, id.getParentKey(), id.get(), null, 0);
}
+ public static String toEditScreen(PatchSet.Id revision, String fileName) {
+ return toEditScreen(revision, fileName, 0);
+ }
+
+ public static String toEditScreen(PatchSet.Id revision, String fileName, int line) {
+ return toPatch("edit", null, revision, fileName, null, line);
+ }
+
private static String toPatch(String type, PatchSet.Id diffBase,
PatchSet.Id revision, String fileName, DisplaySide side, int line) {
Change.Id c = revision.getParentKey();
@@ -148,8 +140,9 @@ public class Dispatcher {
if (diffBase != null) {
p.append(diffBase.get()).append("..");
}
- p.append(revision.get()).append("/").append(KeyUtil.encode(fileName));
- if (type != null && !type.isEmpty()) {
+ p.append(revision.getId()).append("/").append(KeyUtil.encode(fileName));
+ if (type != null && !type.isEmpty()
+ && (!"sidebyside".equals(type) || preferUnified())) {
p.append(",").append(type);
}
if (side == DisplaySide.A && line > 0) {
@@ -160,19 +153,6 @@ public class Dispatcher {
return p.toString();
}
- public static String toPatch(final PatchScreen.Type type, final Patch.Key id) {
- if (type == PatchScreen.Type.SIDE_BY_SIDE) {
- return toPatchSideBySide(id);
- } else {
- return toPatchUnified(id);
- }
- }
-
- public static String toPublish(PatchSet.Id ps) {
- Change.Id c = ps.getParentKey();
- return "/c/" + c + "/" + ps.get() + ",publish";
- }
-
public static String toGroup(final AccountGroup.Id id) {
return ADMIN_GROUPS + id.toString();
}
@@ -239,7 +219,7 @@ public class Dispatcher {
if (defaultScreenToken != null && !MINE.equals(defaultScreenToken)) {
select(defaultScreenToken);
} else {
- Gerrit.display(token, mine(token));
+ Gerrit.display(token, mine());
}
} else if (matchPrefix("/dashboard/", token)) {
@@ -248,23 +228,20 @@ public class Dispatcher {
} else if (matchPrefix(PROJECTS, token)) {
projects(token);
- } else if (matchExact(SETTINGS, token) //
- || matchPrefix("/settings/", token) //
- || matchExact("register", token) //
- || matchExact(REGISTER, token) //
- || matchPrefix("/register/", token) //
- || matchPrefix("/VE/", token) || matchPrefix("VE,", token) //
+ } else if (matchExact(SETTINGS, token)
+ || matchPrefix("/settings/", token)
+ || matchExact(MY_GROUPS, token)
+ || matchExact("register", token)
+ || matchExact(REGISTER, token)
+ || matchPrefix("/register/", token)
+ || matchPrefix("/VE/", token) || matchPrefix("VE,", token)
|| matchPrefix("/SignInFailure,", token)) {
settings(token);
} else if (matchPrefix("/admin/", token)) {
admin(token);
- } else if (matchExact(MY_GROUPS, token)) {
- Gerrit.display(token, new MyGroupsListScreen());
-
} else if (/* DEPRECATED URL */matchPrefix("/c2/", token)) {
- changeScreen2 = true;
change(token);
} else if (/* LEGACY URL */matchPrefix("all,", token)) {
redirectFromLegacyToken(token, legacyAll(token));
@@ -307,7 +284,7 @@ public class Dispatcher {
}
if (matchExact("mine,drafts", token)) {
- return toChangeQuery("is:draft");
+ return toChangeQuery("owner:self is:draft");
}
if (matchExact("mine,comments", token)) {
@@ -373,11 +350,11 @@ public class Dispatcher {
private static String legacyPatch(String token) {
if (/* LEGACY URL */matchPrefix("patch,sidebyside,", token)) {
- return toPatchSideBySide(Patch.Key.parse(skip(token)));
+ return toPatch("", null, Patch.Key.parse(skip(token)));
}
if (/* LEGACY URL */matchPrefix("patch,unified,", token)) {
- return toPatchUnified(Patch.Key.parse(skip(token)));
+ return toPatch("unified", null, Patch.Key.parse(skip(token)));
}
return null;
@@ -432,7 +409,7 @@ public class Dispatcher {
Gerrit.display(token, screen);
}
- private static Screen mine(final String token) {
+ private static Screen mine() {
if (Gerrit.isSignedIn()) {
return new AccountDashboardScreen(Gerrit.getUserAccount().getId());
@@ -522,6 +499,11 @@ public class Dispatcher {
if (0 <= c) {
panel = rest.substring(c + 1);
rest = rest.substring(0, c);
+ int at = panel.lastIndexOf('@');
+ if (at > 0) {
+ rest += panel.substring(at);
+ panel = panel.substring(0, at);
+ }
}
Change.Id id;
@@ -535,10 +517,14 @@ public class Dispatcher {
}
if (rest.isEmpty()) {
- Gerrit.display(token, panel== null
- ? (isChangeScreen2()
- ? new ChangeScreen2(id, null, null, false)
- : new ChangeScreen(id))
+ FileTable.Mode mode = FileTable.Mode.REVIEW;
+ if (panel != null
+ && (panel.equals("edit") || panel.startsWith("edit/"))) {
+ mode = FileTable.Mode.EDIT;
+ panel = null;
+ }
+ Gerrit.display(token, panel == null
+ ? new ChangeScreen(id, null, null, false, mode)
: new NotFoundScreen());
return;
}
@@ -553,16 +539,14 @@ public class Dispatcher {
rest = "";
}
- PatchSet.Id base;
+ PatchSet.Id base = null;
PatchSet.Id ps;
int dotdot = psIdStr.indexOf("..");
if (1 <= dotdot) {
base = new PatchSet.Id(id, Integer.parseInt(psIdStr.substring(0, dotdot)));
- ps = new PatchSet.Id(id, Integer.parseInt(psIdStr.substring(dotdot + 2)));
- } else {
- base = null;
- ps = new PatchSet.Id(id, Integer.parseInt(psIdStr));
+ psIdStr = psIdStr.substring(dotdot + 2);
}
+ ps = toPsId(id, psIdStr);
if (!rest.isEmpty()) {
DisplaySide side = DisplaySide.B;
@@ -578,25 +562,27 @@ public class Dispatcher {
rest = rest.substring(0, at);
}
Patch.Key p = new Patch.Key(ps, KeyUtil.decode(rest));
- patch(token, base, p, side, line, 0,
- null, null, null, panel);
+ patch(token, base, p, side, line, panel);
} else {
if (panel == null) {
- Gerrit.display(token, isChangeScreen2()
- ? new ChangeScreen2(id,
+ Gerrit.display(token,
+ new ChangeScreen(id,
base != null
? String.valueOf(base.get())
: null,
- String.valueOf(ps.get()), false)
- : new ChangeScreen(id));
- } else if ("publish".equals(panel)) {
- publish(ps);
+ String.valueOf(ps.get()), false, FileTable.Mode.REVIEW));
} else {
Gerrit.display(token, new NotFoundScreen());
}
}
}
+ private static PatchSet.Id toPsId(Change.Id id, String psIdStr) {
+ return new PatchSet.Id(id, psIdStr.equals("edit")
+ ? 0
+ : Integer.parseInt(psIdStr));
+ }
+
private static void extension(final String token) {
ExtensionScreen view = new ExtensionScreen(skip(token));
if (view.isFound()) {
@@ -606,132 +592,70 @@ public class Dispatcher {
}
}
- public static boolean isChangeScreen2() {
- if (!Gerrit.getConfig().getNewFeatures()) {
- return false;
- } else if (changeScreen2) {
- return true;
+ private static void patch(String token,
+ PatchSet.Id baseId,
+ Patch.Key id,
+ DisplaySide side,
+ int line,
+ String panelType) {
+ String panel = panelType;
+ if (panel == null) {
+ int c = token.lastIndexOf(',');
+ panel = 0 <= c ? token.substring(c + 1) : "";
}
- AccountGeneralPreferences.ChangeScreen ui = null;
- if (Gerrit.isSignedIn()) {
- ui = Gerrit.getUserAccount()
- .getGeneralPreferences()
- .getChangeScreen();
- }
- String v = Cookies.getCookie(Dispatcher.COOKIE_CS2);
- if (v != null) {
- changeScreen2 = "1".equals(v);
- return changeScreen2;
- }
- if (ui == null) {
- ui = Gerrit.getConfig().getChangeScreen();
- }
- return ui == AccountGeneralPreferences.ChangeScreen.CHANGE_SCREEN2;
- }
-
- private static void publish(final PatchSet.Id ps) {
- String token = toPublish(ps);
- new AsyncSplit(token) {
- public void onSuccess() {
- Gerrit.display(token, select());
- }
-
- private Screen select() {
- return new PublishCommentScreen(ps);
+ if ("".equals(panel) || /* DEPRECATED URL */"cm".equals(panel)) {
+ if (preferUnified()) {
+ unified(token, baseId, id);
+ } else {
+ codemirror(token, baseId, id, side, line, false);
}
- }.onSuccess();
+ } else if ("sidebyside".equals(panel)) {
+ codemirror(token, null, id, side, line, false);
+ } else if ("unified".equals(panel)) {
+ unified(token, baseId, id);
+ } else if ("edit".equals(panel)) {
+ codemirror(token, null, id, side, line, true);
+ } else {
+ Gerrit.display(token, new NotFoundScreen());
+ }
}
- public static void patch(String token, PatchSet.Id base, Patch.Key id,
- int patchIndex, PatchSetDetail patchSetDetail,
- PatchTable patchTable, PatchScreen.TopView topView) {
- patch(token, base, id, null, 0, patchIndex,
- patchSetDetail, patchTable, topView, null);
+ private static boolean preferUnified() {
+ return Gerrit.isSignedIn()
+ && DiffView.UNIFIED_DIFF.equals(Gerrit.getUserAccount()
+ .getGeneralPreferences()
+ .getDiffView());
}
- public static void patch(String token, final PatchSet.Id baseId, final Patch.Key id,
- final DisplaySide side, final int line,
- final int patchIndex, final PatchSetDetail patchSetDetail,
- final PatchTable patchTable, final PatchScreen.TopView topView,
- final String panelType) {
- final PatchScreen.TopView top = topView == null ?
- Gerrit.getPatchScreenTopView() : topView;
-
+ private static void unified(final String token,
+ final PatchSet.Id baseId,
+ final Patch.Key id) {
GWT.runAsync(new AsyncSplit(token) {
+ @Override
public void onSuccess() {
- Gerrit.display(token, select());
+ UnifiedPatchScreen.TopView top = Gerrit.getPatchScreenTopView();
+ Gerrit.display(token, new UnifiedPatchScreen(id, top, baseId));
}
+ });
+ }
- private Screen select() {
- if (id != null) {
- String panel = panelType;
- if (panel == null) {
- int c = token.lastIndexOf(',');
- panel = 0 <= c ? token.substring(c + 1) : "";
- }
-
- if ("".equals(panel)) {
- if (isChangeScreen2()) {
- if (Gerrit.isSignedIn()
- && DiffView.UNIFIED_DIFF.equals(Gerrit.getUserAccount()
- .getGeneralPreferences().getDiffView())) {
- return new PatchScreen.Unified(id, patchIndex, patchSetDetail,
- patchTable, top, baseId);
- }
- return new SideBySide2(baseId, id.getParentKey(), id.get(),
- side, line);
- }
- return new PatchScreen.SideBySide( //
- id, //
- patchIndex, //
- patchSetDetail, //
- patchTable, //
- top, //
- baseId //
- );
- } else if ("unified".equals(panel)) {
- return new PatchScreen.Unified( //
- id, //
- patchIndex, //
- patchSetDetail, //
- patchTable, //
- top, //
- baseId //
- );
- } else if ("cm".equals(panel) && Gerrit.getConfig().getNewFeatures()) {
- if (Gerrit.isSignedIn()
- && DiffView.UNIFIED_DIFF.equals(Gerrit.getUserAccount()
- .getGeneralPreferences().getDiffView())) {
- return new PatchScreen.Unified( //
- id, //
- patchIndex, //
- patchSetDetail, //
- patchTable, //
- top, //
- baseId //
- );
- }
- return new SideBySide2(baseId, id.getParentKey(), id.get(),
- side, line);
- } else if ("".equals(panel) || "sidebyside".equals(panel)) {
- return new PatchScreen.SideBySide(//
- id, //
- patchIndex,//
- patchSetDetail,//
- patchTable,//
- top,//
- baseId);//
- }
- }
-
- return new NotFoundScreen();
+ private static void codemirror(final String token, final PatchSet.Id baseId,
+ final Patch.Key id, final DisplaySide side, final int line,
+ final boolean edit) {
+ GWT.runAsync(new AsyncSplit(token) {
+ @Override
+ public void onSuccess() {
+ Gerrit.display(token, edit
+ ? new EditScreen(baseId, id, line)
+ : new SideBySide(baseId, id.getParentKey(), id.get(), side, line));
}
});
}
private static void settings(String token) {
GWT.runAsync(new AsyncSplit(token) {
+ @Override
public void onSuccess() {
Gerrit.display(token, select());
}
@@ -765,7 +689,8 @@ public class Dispatcher {
return new MyPasswordScreen();
}
- if (matchExact(SETTINGS_MYGROUPS, token)) {
+ if (matchExact(MY_GROUPS, token)
+ || matchExact(SETTINGS_MYGROUPS, token)) {
return new MyGroupsScreen();
}
@@ -799,6 +724,7 @@ public class Dispatcher {
private static void admin(String token) {
GWT.runAsync(new AsyncSplit(token) {
+ @Override
public void onSuccess() {
if (matchExact(ADMIN_GROUPS, token)
|| matchExact("/admin/groups", token)) {
@@ -920,6 +846,11 @@ public class Dispatcher {
return new NotFoundScreen();
}
+ int q = rest.lastIndexOf('?');
+ if (q > 0 && rest.lastIndexOf(',', q) > 0) {
+ c = rest.substring(0, q - 1).lastIndexOf(',');
+ }
+
Project.NameKey k = Project.NameKey.parse(rest.substring(0, c));
String panel = rest.substring(c + 1);
@@ -927,7 +858,8 @@ public class Dispatcher {
return new ProjectInfoScreen(k);
}
- if (ProjectScreen.BRANCH.equals(panel)) {
+ if (ProjectScreen.BRANCH.equals(panel)
+ || matchPrefix(ProjectScreen.BRANCH, panel)) {
return new ProjectBranchesScreen(k);
}
@@ -963,7 +895,7 @@ public class Dispatcher {
return token.substring(prefixlen);
}
- private static abstract class AsyncSplit implements RunAsyncCallback {
+ private abstract static class AsyncSplit implements RunAsyncCallback {
private final boolean isReloadUi;
protected final String token;
@@ -972,6 +904,7 @@ public class Dispatcher {
this.token = token;
}
+ @Override
public final void onFailure(Throwable reason) {
if (!isReloadUi
&& "HTTP download failed with status 404".equals(reason.getMessage())) {
@@ -988,6 +921,7 @@ public class Dispatcher {
private static void docSearch(final String token) {
GWT.runAsync(new AsyncSplit(token) {
+ @Override
public void onSuccess() {
Gerrit.display(token, new DocScreen(skip(token)));
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 27a4b53bf8..296f93f94e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -31,14 +31,13 @@ import com.google.gerrit.client.config.ConfigServerApi;
import com.google.gerrit.client.extensions.TopMenu;
import com.google.gerrit.client.extensions.TopMenuItem;
import com.google.gerrit.client.extensions.TopMenuList;
-import com.google.gerrit.client.patches.PatchScreen;
+import com.google.gerrit.client.patches.UnifiedPatchScreen;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.LinkMenuBar;
import com.google.gerrit.client.ui.LinkMenuItem;
import com.google.gerrit.client.ui.MorphingTabPanel;
-import com.google.gerrit.client.ui.PatchLink;
import com.google.gerrit.client.ui.ProjectLinkMenuItem;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
@@ -46,11 +45,12 @@ import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.GitwebConfig;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.common.data.SystemInfoService;
-import com.google.gerrit.extensions.webui.GerritTopMenu;
+import com.google.gerrit.extensions.client.GerritTopMenu;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
@@ -102,6 +102,7 @@ public class Gerrit implements EntryPoint {
public static final SystemInfoService SYSTEM_SVC;
public static final EventBus EVENT_BUS = GWT.create(SimpleEventBus.class);
public static Themer THEMER = GWT.create(Themer.class);
+ public static final String PROJECT_NAME_MENU_VAR = "${projectName}";
private static String myHost;
private static GerritConfig myConfig;
@@ -110,6 +111,7 @@ public class Gerrit implements EntryPoint {
private static String defaultScreenToken;
private static AccountDiffPreference myAccountDiffPref;
private static String xGerritAuth;
+ private static boolean isNoteDbEnabled;
private static Map<String, LinkMenuBar> menuBars;
@@ -122,7 +124,7 @@ public class Gerrit implements EntryPoint {
private static SearchPanel searchPanel;
private static final Dispatcher dispatcher = new Dispatcher();
private static ViewSite<Screen> body;
- private static PatchScreen patchScreen;
+ private static UnifiedPatchScreen patchScreen;
private static String lastChangeListToken;
private static String lastViewToken;
@@ -136,7 +138,7 @@ public class Gerrit implements EntryPoint {
Window.Location.reload();
}
- public static PatchScreen.TopView getPatchScreenTopView() {
+ public static UnifiedPatchScreen.TopView getPatchScreenTopView() {
if (patchScreen == null) {
return null;
}
@@ -202,8 +204,8 @@ public class Gerrit implements EntryPoint {
*/
public static void updateMenus(Screen view) {
LinkMenuBar diffBar = menuBars.get(GerritTopMenu.DIFFERENCES.menuName);
- if (view instanceof PatchScreen) {
- patchScreen = (PatchScreen) view;
+ if (view instanceof UnifiedPatchScreen) {
+ patchScreen = (UnifiedPatchScreen) view;
menuLeft.setVisible(diffBar, true);
menuLeft.selectTab(menuLeft.getWidgetIndex(diffBar));
} else {
@@ -330,6 +332,10 @@ public class Gerrit implements EntryPoint {
Location.assign(loginRedirect(token));
}
+ public static boolean isNoteDbEnabled() {
+ return isNoteDbEnabled;
+ }
+
public static String loginRedirect(String token) {
if (token == null) {
token = "";
@@ -427,6 +433,7 @@ public class Gerrit implements EntryPoint {
Document.get().getElementById("gerrit_hostpagedata").removeFromParent();
myConfig = result.config;
myTheme = result.theme;
+ isNoteDbEnabled = result.isNoteDbEnabled;
if (result.account != null) {
myAccount = result.account;
xGerritAuth = result.xGerritAuth;
@@ -464,14 +471,17 @@ public class Gerrit implements EntryPoint {
btmmenu.add(new InlineHTML(M.poweredBy(vs)));
- final String reportBugText = getConfig().getReportBugText();
- Anchor a = new Anchor(
- reportBugText == null ? C.reportBug() : reportBugText,
- getConfig().getReportBugUrl());
- a.setTarget("_blank");
- a.setStyleName("");
- btmmenu.add(new InlineLabel(" | "));
- btmmenu.add(a);
+ String reportBugUrl = getConfig().getReportBugUrl();
+ if (reportBugUrl != null) {
+ String reportBugText = getConfig().getReportBugText();
+ Anchor a = new Anchor(
+ reportBugText == null ? C.reportBug() : reportBugText,
+ reportBugUrl);
+ a.setTarget("_blank");
+ a.setStyleName("");
+ btmmenu.add(new InlineLabel(" | "));
+ btmmenu.add(a);
+ }
btmmenu.add(new InlineLabel(" | "));
btmmenu.add(new InlineLabel(C.keyHelp()));
}
@@ -626,12 +636,10 @@ public class Gerrit implements EntryPoint {
LinkMenuBar diffBar = new LinkMenuBar();
menuBars.put(GerritTopMenu.DIFFERENCES.menuName, diffBar);
menuLeft.addInvisible(diffBar, C.menuDiff());
- addDiffLink(diffBar, CC.patchTableDiffSideBySide(), PatchScreen.Type.SIDE_BY_SIDE);
- addDiffLink(diffBar, CC.patchTableDiffUnified(), PatchScreen.Type.UNIFIED);
- addDiffLink(diffBar, C.menuDiffCommit(), PatchScreen.TopView.COMMIT);
- addDiffLink(diffBar, C.menuDiffPreferences(), PatchScreen.TopView.PREFERENCES);
- addDiffLink(diffBar, C.menuDiffPatchSets(), PatchScreen.TopView.PATCH_SETS);
- addDiffLink(diffBar, C.menuDiffFiles(), PatchScreen.TopView.FILES);
+ addDiffLink(diffBar, C.menuDiffCommit(), UnifiedPatchScreen.TopView.COMMIT);
+ addDiffLink(diffBar, C.menuDiffPreferences(), UnifiedPatchScreen.TopView.PREFERENCES);
+ addDiffLink(diffBar, C.menuDiffPatchSets(), UnifiedPatchScreen.TopView.PATCH_SETS);
+ addDiffLink(diffBar, C.menuDiffFiles(), UnifiedPatchScreen.TopView.FILES);
final LinkMenuBar projectsBar = new LinkMenuBar();
menuBars.put(GerritTopMenu.PROJECTS.menuName, projectsBar);
@@ -642,6 +650,7 @@ public class Gerrit implements EntryPoint {
final LinkMenuItem dashboardsMenuItem =
new ProjectLinkMenuItem(C.menuProjectsDashboards(),
ProjectScreen.DASHBOARDS) {
+ @Override
protected boolean match(String token) {
return super.match(token) ||
(!getTargetHistoryToken().isEmpty() && ("/admin" + token).startsWith(getTargetHistoryToken()));
@@ -703,6 +712,7 @@ public class Gerrit implements EntryPoint {
case OPENID:
menuRight.addItem(C.menuRegister(), new Command() {
+ @Override
public void execute() {
String t = History.getToken();
if (t == null) {
@@ -712,6 +722,7 @@ public class Gerrit implements EntryPoint {
}
});
menuRight.addItem(C.menuSignIn(), new Command() {
+ @Override
public void execute() {
doSignIn(History.getToken());
}
@@ -729,6 +740,7 @@ public class Gerrit implements EntryPoint {
case OPENID_SSO:
menuRight.addItem(C.menuSignIn(), new Command() {
+ @Override
public void execute() {
doSignIn(History.getToken());
}
@@ -751,6 +763,7 @@ public class Gerrit implements EntryPoint {
menuRight.add(anchor(registerText, cfg.getRegisterUrl()));
}
menuRight.addItem(C.menuSignIn(), new Command() {
+ @Override
public void execute() {
doSignIn(History.getToken());
}
@@ -763,17 +776,20 @@ public class Gerrit implements EntryPoint {
}
}
ConfigServerApi.topMenus(new GerritCallback<TopMenuList>() {
+ @Override
public void onSuccess(TopMenuList result) {
List<TopMenu> topMenuExtensions = Natives.asList(result);
for (TopMenu menu : topMenuExtensions) {
- LinkMenuBar existingBar = menuBars.get(menu.getName());
- LinkMenuBar bar = existingBar != null ? existingBar : new LinkMenuBar();
+ String name = menu.getName();
+ LinkMenuBar existingBar = menuBars.get(name);
+ LinkMenuBar bar =
+ existingBar != null ? existingBar : new LinkMenuBar();
for (TopMenuItem item : Natives.asList(menu.getItems())) {
- addExtensionLink(bar, item);
+ addMenuLink(bar, item);
}
if (existingBar == null) {
- menuBars.put(menu.getName(), bar);
- menuLeft.add(bar, menu.getName());
+ menuBars.put(name, bar);
+ menuLeft.add(bar, name);
}
}
}
@@ -880,7 +896,7 @@ public class Gerrit implements EntryPoint {
}
private static void addDiffLink(final LinkMenuBar m, final String text,
- final PatchScreen.TopView tv) {
+ final UnifiedPatchScreen.TopView tv) {
m.addItem(new LinkMenuItem(text, "") {
@Override
public void go() {
@@ -892,21 +908,45 @@ public class Gerrit implements EntryPoint {
});
}
- private static void addDiffLink(final LinkMenuBar m, final String text,
- final PatchScreen.Type type) {
- m.addItem(new LinkMenuItem(text, "") {
+ private static LinkMenuItem addProjectLink(LinkMenuBar m, TopMenuItem item) {
+ LinkMenuItem i = new ProjectLinkMenuItem(item.getName(), item.getUrl()) {
@Override
- public void go() {
- if (patchScreen != null) {
- patchScreen.setTopView(PatchScreen.TopView.MAIN);
- if (type == patchScreen.getPatchScreenType()) {
- AnchorElement.as(getElement()).blur();
- } else {
- new PatchLink("", type, patchScreen).go();
+ protected void onScreenLoad(Project.NameKey project) {
+ String p =
+ panel.replace(PROJECT_NAME_MENU_VAR,
+ URL.encodeQueryString(project.get()));
+ if (!panel.startsWith("/x/") && !isAbsolute(panel)) {
+ UrlBuilder builder = new UrlBuilder();
+ builder.setProtocol(Location.getProtocol());
+ builder.setHost(Location.getHost());
+ String port = Location.getPort();
+ if (port != null && !port.isEmpty()) {
+ builder.setPort(Integer.parseInt(port));
}
+ builder.setPath(Location.getPath());
+ p = builder.buildString() + p;
}
+ getElement().setPropertyString("href", p);
}
- });
+
+ @Override
+ public void go() {
+ String href = getElement().getPropertyString("href");
+ if (href.startsWith("#")) {
+ super.go();
+ } else {
+ Window.open(href, getElement().getPropertyString("target"), "");
+ }
+ }
+ };
+ if (item.getTarget() != null && !item.getTarget().isEmpty()) {
+ i.getElement().setAttribute("target", item.getTarget());
+ }
+ if (item.getId() != null) {
+ i.getElement().setAttribute("id", item.getId());
+ }
+ m.addItem(i);
+ return i;
}
private static void addDocLink(final LinkMenuBar m, final String text,
@@ -916,6 +956,14 @@ public class Gerrit implements EntryPoint {
m.add(atag);
}
+ private static void addMenuLink(LinkMenuBar m, TopMenuItem item) {
+ if (item.getUrl().contains(PROJECT_NAME_MENU_VAR)) {
+ addProjectLink(m, item);
+ } else {
+ addExtensionLink(m, item);
+ }
+ }
+
private static void addExtensionLink(LinkMenuBar m, TopMenuItem item) {
if (item.getUrl().startsWith("#")
&& (item.getTarget() == null || item.getTarget().isEmpty())) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 5ba520c4c5..6bbc8f16d9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -23,7 +23,7 @@ public interface GerritConstants extends Constants {
String loadingPlugins();
String signInDialogTitle();
- String signInDialogClose();
+ String signInDialogGoAnonymous();
String linkIdentityDialogTitle();
String registerDialogTitle();
@@ -36,6 +36,9 @@ public interface GerritConstants extends Constants {
String confirmationDialogOk();
String confirmationDialogCancel();
+ String branchCreationDialogTitle();
+ String branchCreationConfirmationMessage();
+
String branchDeletionDialogTitle();
String branchDeletionConfirmationMessage();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 58a6d08f80..05de983845 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -4,7 +4,7 @@ reportBug = Report Bug
loadingPlugins = Loading plugins ...
signInDialogTitle = Code Review - Sign In
-signInDialogClose = Close
+signInDialogGoAnonymous = Go Anonymous
linkIdentityDialogTitle = Code Review - Link Identity
registerDialogTitle = Code Review - Register New Account
@@ -17,6 +17,9 @@ warnTitle = Code Review - Warning
confirmationDialogOk = OK
confirmationDialogCancel = Cancel
+branchCreationDialogTitle = Branch Creation
+branchCreationConfirmationMessage = The following branch was successfully created:
+
branchDeletionDialogTitle = Branch Deletion
branchDeletionConfirmationMessage = Do you really want to delete the following branches?
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 42a8d74003..2844b5e898 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -22,22 +22,16 @@ public interface GerritCss extends CssResource {
String accountDashboard();
String accountInfoBlock();
String accountLinkPanel();
- String accountName();
String accountPassword();
String accountUsername();
String activeRow();
String addBranch();
String addMemberTextBox();
- String addReviewer();
String addSshKeyPanel();
String addWatchPanel();
- String approvalTable();
- String approvalhint();
- String approvalrole();
- String approvalscore();
String avatarInfoPanel();
- String blockHeader();
String bottomheader();
+ String branchTablePrevNextLinks();
String cAPPROVAL();
String cLastUpdate();
String cOWNER();
@@ -45,44 +39,32 @@ public interface GerritCss extends CssResource {
String cSUBJECT();
String cSTATUS();
String cellsNextToFileComment();
- String changeComments();
- String changeInfoBlock();
- String changeInfoTopicPanel();
- String changeScreen();
String changeScreenDescription();
String changeScreenStarIcon();
String changeSize();
String changeTable();
String changeTablePrevNextLinks();
String changeTypeCell();
- String changeid();
- String closedstate();
String commentCell();
String commentEditorPanel();
String commentHolder();
String commentHolderLeftmost();
String commentPanel();
String commentPanelAuthorCell();
- String commentPanelBorder();
String commentPanelButtons();
String commentPanelContent();
String commentPanelDateCell();
String commentPanelHeader();
String commentPanelLast();
- String commentPanelMenuBar();
String commentPanelMessage();
String commentPanelSummary();
String commentPanelSummaryCell();
String commentedActionDialog();
String commentedActionMessage();
- String complexHeader();
- String content();
String contributorAgreementAlreadySubmitted();
String contributorAgreementButton();
String contributorAgreementLegal();
String contributorAgreementShortDescription();
- String coverMessage();
- String createGroupLink();
String createProjectPanel();
String dataCell();
String dataCellHidden();
@@ -93,7 +75,6 @@ public interface GerritCss extends CssResource {
String diffTextCONTEXT();
String diffTextDELETE();
String diffTextFileHeader();
- String diffTextForBinaryInSideBySide();
String diffTextHunkHeader();
String diffTextINSERT();
String diffTextNoLF();
@@ -108,7 +89,6 @@ public interface GerritCss extends CssResource {
String downloadLinkHeader();
String downloadLinkHeaderGap();
String downloadLinkList();
- String downloadLinkListCell();
String downloadLink_Active();
String drafts();
String editHeadButton();
@@ -117,23 +97,18 @@ public interface GerritCss extends CssResource {
String errorDialogButtons();
String errorDialogErrorType();
String errorDialogGlass();
- String errorDialogText();
String errorDialogTitle();
String loadingPluginsDialog();
String fileColumnHeader();
String fileCommentBorder();
String fileLine();
- String fileLineCONTEXT();
String fileLineDELETE();
String fileLineINSERT();
- String fileLineMode();
- String fileLineNone();
String filePathCell();
String gerritBody();
String gerritTopMenu();
String greenCheckClass();
String groupDescriptionPanel();
- String groupExternalNameFilterTextBox();
String groupIncludesTable();
String groupMembersTable();
String groupName();
@@ -142,25 +117,21 @@ public interface GerritCss extends CssResource {
String groupOptionsPanel();
String groupOwnerPanel();
String groupOwnerTextBox();
- String groupTypeSelectListBox();
String groupUUIDPanel();
String header();
- String hyperlink();
String iconCell();
String iconCellOfFileCommentRow();
String iconHeader();
String identityUntrustedExternalId();
String infoBlock();
- String infoTable();
String inputFieldTypeHint();
- String labelList();
String labelNotApplicable();
String leftMostCell();
- String lineHeader();
String lineNumber();
String link();
String linkMenuBar();
String linkMenuItemNotLast();
+ String linkPanel();
String maxObjectSizeLimitEffectiveLabel();
String menuBarUserName();
String menuBarUserNameAvatar();
@@ -168,62 +139,40 @@ public interface GerritCss extends CssResource {
String menuBarUserNamePanel();
String menuItem();
String menuScreenMenuBar();
- String missingApproval();
- String missingApprovalList();
- String monospace();
String needsReview();
String negscore();
- String noLineLineNumber();
String noborder();
- String notVotable();
- String outdated();
- String parentsTable();
String patchBrowserPopup();
String patchBrowserPopupBody();
String patchCellReverseDiff();
- String patchComments();
String patchContentTable();
String patchHistoryTable();
String patchHistoryTablePatchSetHeader();
String patchNoDifference();
- String patchScreenDisplayControls();
String patchSetActions();
- String patchSetInfoBlock();
- String patchSetLink();
- String patchSetRevision();
- String patchSetUserIdentity();
String patchSizeCell();
String pluginProjectConfigInheritedValue();
String pluginsTable();
String posscore();
String projectActions();
- String projectAdminLabelRangeLine();
- String projectAdminLabelValue();
String projectFilterLabel();
String projectFilterPanel();
String projectNameColumn();
- String publishCommentsScreen();
+ String rebaseContentPanel();
+ String rebaseSuggestBox();
String registerScreenExplain();
String registerScreenNextLinks();
String registerScreenSection();
- String removeReviewer();
- String removeReviewerCell();
String reviewedPanelBottom();
String rightBorder();
- String rightmost();
String rpcStatus();
String screen();
String screenHeader();
- String screenNoHeader();
String searchPanel();
String suggestBoxPopup();
String sectionHeader();
- String selectPatchSetOldVersion();
String sideBySideScreenLinkTable();
- String sideBySideScreenSideBySideTable();
- String sideBySideTableBinaryHeader();
String singleLine();
- String skipLine();
String smallHeading();
String sourceFilePath();
String specialBranchDataCell();
@@ -245,7 +194,6 @@ public interface GerritCss extends CssResource {
String unifiedTable();
String unifiedTableHeader();
String userInfoPopup();
- String useridentity();
String usernameField();
String watchedProjectFilter();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
index 2b78fa465e..01be2f2381 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
@@ -14,71 +14,12 @@
package com.google.gerrit.client;
-import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
-import com.google.gwt.resources.client.ImageResource;
-public interface GerritResources extends ClientBundle {
+public interface GerritResources extends Resources {
@Source("gerrit.css")
GerritCss css();
@Source("gwt_override.css")
CssResource gwt_override();
-
- @Source("arrowRight.gif")
- public ImageResource arrowRight();
-
- @Source("arrowUp.png")
- public ImageResource arrowUp();
-
- @Source("arrowDown.png")
- public ImageResource arrowDown();
-
- @Source("editText.png")
- public ImageResource edit();
-
- @Source("starOpen.gif")
- public ImageResource starOpen();
-
- @Source("starFilled.gif")
- public ImageResource starFilled();
-
- @Source("greenCheck.png")
- public ImageResource greenCheck();
-
- @Source("redNot.png")
- public ImageResource redNot();
-
- @Source("downloadIcon.png")
- public ImageResource downloadIcon();
-
- @Source("queryIcon.png")
- public ImageResource queryIcon();
-
- @Source("addFileComment.png")
- public ImageResource addFileComment();
-
- @Source("diffy26.png")
- public ImageResource gerritAvatar26();
-
- @Source("draftComments.png")
- public ImageResource draftComments();
-
- @Source("readOnly.png")
- public ImageResource readOnly();
-
- @Source("gear.png")
- public ImageResource gear();
-
- @Source("info.png")
- public ImageResource info();
-
- @Source("warning.png")
- public ImageResource warning();
-
- @Source("listAdd.png")
- public ImageResource listAdd();
-
- @Source("merge.png")
- public ImageResource merge();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
index 45d9a9194a..e38cf7d6e7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
@@ -69,7 +69,7 @@ public class JumpKeys {
jumps.add(new KeyCommand(0, 'd', Gerrit.C.jumpMineDrafts()) {
@Override
public void onKeyPress(final KeyPressEvent event) {
- Gerrit.display(PageLinks.toChangeQuery("is:draft"));
+ Gerrit.display(PageLinks.toChangeQuery("owner:self is:draft"));
}
});
jumps.add(new KeyCommand(0, 'c', Gerrit.C.jumpMineDraftComments()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java
index b6ea6367c2..054cdb3219 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java
@@ -67,7 +67,7 @@ class MessageOfTheDayBar extends Composite {
}
@UiHandler("dismiss")
- void onDismiss(ClickEvent e) {
+ void onDismiss(@SuppressWarnings("unused") ClickEvent e) {
removeFromParent();
for (HostPageData.Message m : motd) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java
index 6a417ca958..83c32cd5eb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java
@@ -19,23 +19,25 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.AutoCenterDialogBox;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
/** A dialog box telling the user they are not signed in. */
-public class NotSignedInDialog extends AutoCenterDialogBox implements CloseHandler<PopupPanel> {
-
+public class NotSignedInDialog extends PluginSafePopupPanel implements CloseHandler<PopupPanel> {
private Button signin;
- private boolean buttonClicked = false;
+ private boolean buttonClicked;
public NotSignedInDialog() {
super(/* auto hide */false, /* modal */true);
setGlassEnabled(true);
- setText(Gerrit.C.notSignedInTitle());
+ getGlassElement().addClassName(Gerrit.RESOURCES.css().errorDialogGlass());
+ addStyleName(Gerrit.RESOURCES.css().errorDialog());
final FlowPanel buttons = new FlowPanel();
signin = new Button();
@@ -52,7 +54,7 @@ public class NotSignedInDialog extends AutoCenterDialogBox implements CloseHandl
final Button close = new Button();
close.getElement().getStyle().setProperty("marginLeft", "200px");
- close.setText(Gerrit.C.signInDialogClose());
+ close.setText(Gerrit.C.signInDialogGoAnonymous());
close.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
@@ -63,13 +65,18 @@ public class NotSignedInDialog extends AutoCenterDialogBox implements CloseHandl
});
buttons.add(close);
- final FlowPanel center = new FlowPanel();
+ Label title = new Label(Gerrit.C.notSignedInTitle());
+ title.setStyleName(Gerrit.RESOURCES.css().errorDialogTitle());
+
+ FlowPanel center = new FlowPanel();
+ center.add(title);
center.add(new HTML(Gerrit.C.notSignedInBody()));
center.add(buttons);
add(center);
- center.setWidth("400px");
-
+ int l = Window.getScrollLeft() + 20;
+ int t = Window.getScrollTop() + 20;
+ setPopupPosition(l, t);
addCloseHandler(this);
}
@@ -84,7 +91,7 @@ public class NotSignedInDialog extends AutoCenterDialogBox implements CloseHandl
@Override
public void center() {
- super.center();
+ show();
GlobalKey.dialog(this);
signin.setFocus(true);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
index f86e1fee29..d5805ef538 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
@@ -16,6 +16,7 @@ package com.google.gerrit.client;
import com.google.gerrit.client.changes.QueryScreen;
import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.RemoteSuggestOracle;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -54,8 +55,9 @@ class SearchPanel extends Composite {
}
});
- final SuggestBox suggestBox =
- new SuggestBox(new SearchSuggestOracle(), searchBox, suggestionDisplay);
+ final SuggestBox suggestBox = new SuggestBox(
+ new RemoteSuggestOracle(new SearchSuggestOracle()),
+ searchBox, suggestionDisplay);
searchBox.setStyleName("gwt-TextBox");
searchBox.setVisibleLength(70);
searchBox.setHintText(Gerrit.C.searchHint());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index e92e613ae1..0e1c375d36 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -124,6 +124,10 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
suggestions.add("delta:");
suggestions.add("size:");
+ if (Gerrit.isNoteDbEnabled()) {
+ suggestions.add("hashtag:");
+ }
+
suggestions.add("AND");
suggestions.add("OR");
suggestions.add("NOT");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java
index 64b9cb8ffc..45731f777d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java
@@ -15,12 +15,34 @@
package com.google.gerrit.client;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Image;
public class WebLinkInfo extends JavaScriptObject {
public final native String name() /*-{ return this.name; }-*/;
+ public final native String imageUrl() /*-{ return this.image_url; }-*/;
public final native String url() /*-{ return this.url; }-*/;
+ public final native String target() /*-{ return this.target; }-*/;
protected WebLinkInfo() {
}
+
+ public final Anchor toAnchor() {
+ Anchor a = new Anchor();
+ a.setHref(url());
+ if (target() != null && !target().isEmpty()) {
+ a.setTarget(target());
+ }
+ if (imageUrl() != null && !imageUrl().isEmpty()) {
+ Image img = new Image();
+ img.setAltText(name());
+ img.setUrl(imageUrl());
+ img.setTitle(name());
+ a.getElement().appendChild(img.getElement());
+ } else {
+ a.setText("(" + name() + ")");
+ }
+ return a;
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index 8906da4bb6..59d65f6fdf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -33,6 +33,15 @@ public class AccountApi {
return new RestApi("/accounts/").view("self");
}
+ public static void suggest(String query, int limit,
+ AsyncCallback<JsArray<AccountInfo>> cb) {
+ new RestApi("/accounts/")
+ .addParameter("q", query)
+ .addParameter("n", limit)
+ .background()
+ .get(cb);
+ }
+
public static void putDiffPreferences(DiffPreferences in,
AsyncCallback<DiffPreferences> cb) {
self().view("preferences.diff").put(in, cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index f52eb31de3..4c3cc29a0d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -25,8 +25,6 @@ public interface AccountConstants extends Constants {
String registeredOn();
String accountId();
- String commentVisibilityLabel();
- String changeScreenLabel();
String diffViewLabel();
String maximumPageSizeFieldLabel();
String dateFormatLabel();
@@ -34,7 +32,6 @@ public interface AccountConstants extends Constants {
String showSiteHeader();
String useFlashClipboard();
String copySelfOnEmails();
- String reversePatchSetOrder();
String reviewCategoryLabel();
String messageShowInReviewCategoryNone();
String messageShowInReviewCategoryName();
@@ -45,15 +42,13 @@ public interface AccountConstants extends Constants {
String showRelativeDateInChangeTable();
String showSizeBarInChangeTable();
String showLegacycidInChangeTable();
+ String muteCommonPathPrefixes();
String myMenu();
String myMenuInfo();
String myMenuName();
String myMenuUrl();
String myMenuReset();
- String changeScreenOldUi();
- String changeScreenNewUi();
-
String tabAccountSummary();
String tabPreferences();
String tabWatchedProjects();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index 5d48bb8d55..36cb765f49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -8,8 +8,6 @@ accountId = Account ID
showSiteHeader = Show Site Header
useFlashClipboard = Use Flash Clipboard Widget
copySelfOnEmails = CC Me On Comments I Write
-reversePatchSetOrder = Display Patch Sets In Reverse Order (deprecated: Old Change Screen)
-
reviewCategoryLabel = Display In Review Category
messageShowInReviewCategoryNone = None (default)
messageShowInReviewCategoryName = Show Name
@@ -18,15 +16,14 @@ messageShowInReviewCategoryUsername = Show Username
messageShowInReviewCategoryAbbrev = Show Abbreviated Name
maximumPageSizeFieldLabel = Maximum Page Size:
-commentVisibilityLabel = Comment Visibility (deprecated: Old Change Screen):
-changeScreenLabel = Change View:
-diffViewLabel = Diff View (New Change Screen):
+diffViewLabel = Diff View:
dateFormatLabel = Date/Time Format:
contextWholeFile = Whole File
buttonSaveChanges = Save Changes
showRelativeDateInChangeTable = Show Relative Dates In Changes Table
-showSizeBarInChangeTable = Show Change Sizes As Colored Bars In Changes Table
+showSizeBarInChangeTable = Show Change Sizes As Colored Bars
showLegacycidInChangeTable = Show Change Number In Changes Table
+muteCommonPathPrefixes = Mute Common Path Prefixes In File List
myMenu = My Menu
myMenuInfo = \
Menu items for the 'My' top level menu. \
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java
index 3ac626cc53..11273740b4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java
@@ -56,7 +56,7 @@ public class AccountInfo extends JavaScriptObject {
}
public static class AvatarInfo extends JavaScriptObject {
- public final static int DEFAULT_SIZE = 26;
+ public static final int DEFAULT_SIZE = 26;
public final native String url() /*-{ return this.url }-*/;
public final native int height() /*-{ return this.height || 0 }-*/;
public final native int width() /*-{ return this.width || 0 }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
index 66f676e390..b5fe70f631 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
@@ -194,6 +194,7 @@ class ContactPanelShort extends Composite {
haveEmails = false;
Util.ACCOUNT_SVC.myAccount(new GerritCallback<Account>() {
+ @Override
public void onSuccess(final Account result) {
if (!isAttached()) {
return;
@@ -359,6 +360,7 @@ class ContactPanelShort extends Composite {
Util.ACCOUNT_SEC.updateContact(newName, newEmail, info,
new GerritCallback<Account>() {
+ @Override
public void onSuccess(final Account result) {
registerNewEmail.setEnabled(true);
onSaveSuccess(result);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
index 8a4666b412..292e0b927d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
@@ -14,8 +14,8 @@
package com.google.gerrit.client.account;
+import com.google.gerrit.extensions.client.Theme;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gwt.core.client.JavaScriptObject;
@@ -35,6 +35,7 @@ public class DiffPreferences extends JavaScriptObject {
p.showWhitespaceErrors(in.isShowWhitespaceErrors());
p.syntaxHighlighting(in.isSyntaxHighlighting());
p.hideTopMenu(in.isHideTopMenu());
+ p.autoHideDiffTableHeader(in.isAutoHideDiffTableHeader());
p.hideLineNumbers(in.isHideLineNumbers());
p.expandAllComments(in.isExpandAllComments());
p.manualReview(in.isManualReview());
@@ -55,6 +56,7 @@ public class DiffPreferences extends JavaScriptObject {
p.setShowWhitespaceErrors(showWhitespaceErrors());
p.setSyntaxHighlighting(syntaxHighlighting());
p.setHideTopMenu(hideTopMenu());
+ p.setAutoHideDiffTableHeader(autoHideDiffTableHeader());
p.setHideLineNumbers(hideLineNumbers());
p.setExpandAllComments(expandAllComments());
p.setManualReview(manualReview());
@@ -82,6 +84,7 @@ public class DiffPreferences extends JavaScriptObject {
public final native void showWhitespaceErrors(boolean s) /*-{ this.show_whitespace_errors = s }-*/;
public final native void syntaxHighlighting(boolean s) /*-{ this.syntax_highlighting = s }-*/;
public final native void hideTopMenu(boolean s) /*-{ this.hide_top_menu = s }-*/;
+ public final native void autoHideDiffTableHeader(boolean s) /*-{ this.auto_hide_diff_table_header = s }-*/;
public final native void hideLineNumbers(boolean s) /*-{ this.hide_line_numbers = s }-*/;
public final native void expandAllComments(boolean e) /*-{ this.expand_all_comments = e }-*/;
public final native void manualReview(boolean r) /*-{ this.manual_review = r }-*/;
@@ -110,6 +113,7 @@ public class DiffPreferences extends JavaScriptObject {
public final native boolean showWhitespaceErrors() /*-{ return this.show_whitespace_errors || false }-*/;
public final native boolean syntaxHighlighting() /*-{ return this.syntax_highlighting || false }-*/;
public final native boolean hideTopMenu() /*-{ return this.hide_top_menu || false }-*/;
+ public final native boolean autoHideDiffTableHeader() /*-{ return this.auto_hide_diff_table_header || false }-*/;
public final native boolean hideLineNumbers() /*-{ return this.hide_line_numbers || false }-*/;
public final native boolean expandAllComments() /*-{ return this.expand_all_comments || false }-*/;
public final native boolean manualReview() /*-{ return this.manual_review || false }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
index 3bd2e77844..0908f6bcf8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
@@ -40,6 +40,7 @@ public class MyAgreementsScreen extends SettingsScreen {
protected void onLoad() {
super.onLoad();
Util.ACCOUNT_SVC.myAgreements(new ScreenLoadCallback<AgreementInfo>(this) {
+ @Override
public void preDisplay(final AgreementInfo result) {
agreements.display(result);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
index 1f4d7ed20f..1191696ce6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
@@ -77,6 +77,7 @@ public class MyIdentitiesScreen extends SettingsScreen {
super.onLoad();
Util.ACCOUNT_SEC
.myExternalIds(new ScreenLoadCallback<List<AccountExternalId>>(this) {
+ @Override
public void preDisplay(final List<AccountExternalId> result) {
identites.display(result);
}
@@ -127,6 +128,7 @@ public class MyIdentitiesScreen extends SettingsScreen {
deleteIdentity.setEnabled(false);
Util.ACCOUNT_SEC.deleteExternalIds(keys,
new GerritCallback<Set<AccountExternalId.Key>>() {
+ @Override
public void onSuccess(final Set<AccountExternalId.Key> removed) {
for (int row = 1; row < table.getRowCount();) {
final AccountExternalId k = getRowItem(row);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index db1acbfb9f..803ac55197 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -17,7 +17,6 @@ package com.google.gerrit.client.account;
import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DEFAULT_PAGESIZE;
import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.PAGESIZE_CHOICES;
-import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.StringListPanel;
import com.google.gerrit.client.config.ConfigServerApi;
@@ -27,7 +26,6 @@ import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -49,16 +47,14 @@ public class MyPreferencesScreen extends SettingsScreen {
private CheckBox showSiteHeader;
private CheckBox useFlashClipboard;
private CheckBox copySelfOnEmails;
- private CheckBox reversePatchSetOrder;
private CheckBox relativeDateInChangeTable;
private CheckBox sizeBarInChangeTable;
private CheckBox legacycidInChangeTable;
+ private CheckBox muteCommonPathPrefixes;
private ListBox maximumPageSize;
private ListBox dateFormat;
private ListBox timeFormat;
private ListBox reviewCategoryStrategy;
- private ListBox commentVisibilityStrategy;
- private ListBox changeScreen;
private ListBox diffView;
private StringListPanel myMenus;
private Button save;
@@ -70,7 +66,6 @@ public class MyPreferencesScreen extends SettingsScreen {
showSiteHeader = new CheckBox(Util.C.showSiteHeader());
useFlashClipboard = new CheckBox(Util.C.useFlashClipboard());
copySelfOnEmails = new CheckBox(Util.C.copySelfOnEmails());
- reversePatchSetOrder = new CheckBox(Util.C.reversePatchSetOrder());
maximumPageSize = new ListBox();
for (final short v : PAGESIZE_CHOICES) {
maximumPageSize.addItem(Util.M.rowsPerPage(v), String.valueOf(v));
@@ -93,32 +88,6 @@ public class MyPreferencesScreen extends SettingsScreen {
Util.C.messageShowInReviewCategoryAbbrev(),
AccountGeneralPreferences.ReviewCategoryStrategy.ABBREV.name());
- commentVisibilityStrategy = new ListBox();
- commentVisibilityStrategy.addItem(
- com.google.gerrit.client.changes.Util.C.messageCollapseAll(),
- AccountGeneralPreferences.CommentVisibilityStrategy.COLLAPSE_ALL.name());
- commentVisibilityStrategy.addItem(
- com.google.gerrit.client.changes.Util.C.messageExpandMostRecent(),
- AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_MOST_RECENT.name());
- commentVisibilityStrategy.addItem(
- com.google.gerrit.client.changes.Util.C.messageExpandRecent(),
- AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_RECENT.name());
- commentVisibilityStrategy.addItem(
- com.google.gerrit.client.changes.Util.C.messageExpandAll(),
- AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_ALL.name());
-
- changeScreen = new ListBox();
- changeScreen.addItem(
- Util.M.changeScreenServerDefault(
- getLabel(Gerrit.getConfig().getChangeScreen())),
- "");
- changeScreen.addItem(
- Util.C.changeScreenOldUi(),
- AccountGeneralPreferences.ChangeScreen.OLD_UI.name());
- changeScreen.addItem(
- Util.C.changeScreenNewUi(),
- AccountGeneralPreferences.ChangeScreen.CHANGE_SCREEN2.name());
-
diffView = new ListBox();
diffView.addItem(
com.google.gerrit.client.changes.Util.C.sideBySide(),
@@ -164,8 +133,9 @@ public class MyPreferencesScreen extends SettingsScreen {
relativeDateInChangeTable = new CheckBox(Util.C.showRelativeDateInChangeTable());
sizeBarInChangeTable = new CheckBox(Util.C.showSizeBarInChangeTable());
legacycidInChangeTable = new CheckBox(Util.C.showLegacycidInChangeTable());
+ muteCommonPathPrefixes = new CheckBox(Util.C.muteCommonPathPrefixes());
- final Grid formGrid = new Grid(13, 2);
+ final Grid formGrid = new Grid(11, 2);
int row = 0;
formGrid.setText(row, labelIdx, "");
@@ -180,10 +150,6 @@ public class MyPreferencesScreen extends SettingsScreen {
formGrid.setWidget(row, fieldIdx, copySelfOnEmails);
row++;
- formGrid.setText(row, labelIdx, "");
- formGrid.setWidget(row, fieldIdx, reversePatchSetOrder);
- row++;
-
formGrid.setText(row, labelIdx, Util.C.reviewCategoryLabel());
formGrid.setWidget(row, fieldIdx, reviewCategoryStrategy);
row++;
@@ -196,32 +162,25 @@ public class MyPreferencesScreen extends SettingsScreen {
formGrid.setWidget(row, fieldIdx, dateTimePanel);
row++;
- if (Gerrit.getConfig().getNewFeatures()) {
- formGrid.setText(row, labelIdx, "");
- formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
- row++;
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
+ row++;
- formGrid.setText(row, labelIdx, "");
- formGrid.setWidget(row, fieldIdx, sizeBarInChangeTable);
- row++;
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, sizeBarInChangeTable);
+ row++;
- formGrid.setText(row, labelIdx, "");
- formGrid.setWidget(row, fieldIdx, legacycidInChangeTable);
- row++;
- }
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, legacycidInChangeTable);
+ row++;
- formGrid.setText(row, labelIdx, Util.C.commentVisibilityLabel());
- formGrid.setWidget(row, fieldIdx, commentVisibilityStrategy);
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, muteCommonPathPrefixes);
row++;
- if (Gerrit.getConfig().getNewFeatures()) {
- formGrid.setText(row, labelIdx, Util.C.changeScreenLabel());
- formGrid.setWidget(row, fieldIdx, changeScreen);
- row++;
+ formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
+ formGrid.setWidget(row, fieldIdx, diffView);
- formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
- formGrid.setWidget(row, fieldIdx, diffView);
- }
add(formGrid);
save = new Button(Util.C.buttonSaveChanges());
@@ -242,17 +201,15 @@ public class MyPreferencesScreen extends SettingsScreen {
e.listenTo(showSiteHeader);
e.listenTo(useFlashClipboard);
e.listenTo(copySelfOnEmails);
- e.listenTo(reversePatchSetOrder);
e.listenTo(maximumPageSize);
e.listenTo(dateFormat);
e.listenTo(timeFormat);
e.listenTo(relativeDateInChangeTable);
e.listenTo(sizeBarInChangeTable);
e.listenTo(legacycidInChangeTable);
- e.listenTo(reviewCategoryStrategy);
- e.listenTo(commentVisibilityStrategy);
- e.listenTo(changeScreen);
+ e.listenTo(muteCommonPathPrefixes);
e.listenTo(diffView);
+ e.listenTo(reviewCategoryStrategy);
}
@Override
@@ -271,16 +228,14 @@ public class MyPreferencesScreen extends SettingsScreen {
showSiteHeader.setEnabled(on);
useFlashClipboard.setEnabled(on);
copySelfOnEmails.setEnabled(on);
- reversePatchSetOrder.setEnabled(on);
maximumPageSize.setEnabled(on);
dateFormat.setEnabled(on);
timeFormat.setEnabled(on);
relativeDateInChangeTable.setEnabled(on);
sizeBarInChangeTable.setEnabled(on);
legacycidInChangeTable.setEnabled(on);
+ muteCommonPathPrefixes.setEnabled(on);
reviewCategoryStrategy.setEnabled(on);
- commentVisibilityStrategy.setEnabled(on);
- changeScreen.setEnabled(on);
diffView.setEnabled(on);
}
@@ -288,7 +243,6 @@ public class MyPreferencesScreen extends SettingsScreen {
showSiteHeader.setValue(p.showSiteHeader());
useFlashClipboard.setValue(p.useFlashClipboard());
copySelfOnEmails.setValue(p.copySelfOnEmail());
- reversePatchSetOrder.setValue(p.reversePatchSetOrder());
setListBox(maximumPageSize, DEFAULT_PAGESIZE, p.changesPerPage());
setListBox(dateFormat, AccountGeneralPreferences.DateFormat.STD, //
p.dateFormat());
@@ -297,15 +251,10 @@ public class MyPreferencesScreen extends SettingsScreen {
relativeDateInChangeTable.setValue(p.relativeDateInChangeTable());
sizeBarInChangeTable.setValue(p.sizeBarInChangeTable());
legacycidInChangeTable.setValue(p.legacycidInChangeTable());
+ muteCommonPathPrefixes.setValue(p.muteCommonPathPrefixes());
setListBox(reviewCategoryStrategy,
AccountGeneralPreferences.ReviewCategoryStrategy.NONE,
p.reviewCategoryStrategy());
- setListBox(commentVisibilityStrategy,
- AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_RECENT,
- p.commentVisibilityStrategy());
- setListBox(changeScreen,
- null,
- p.changeScreen());
setListBox(diffView,
AccountGeneralPreferences.DiffView.SIDE_BY_SIDE,
p.diffView());
@@ -376,7 +325,6 @@ public class MyPreferencesScreen extends SettingsScreen {
p.setShowSiteHeader(showSiteHeader.getValue());
p.setUseFlashClipboard(useFlashClipboard.getValue());
p.setCopySelfOnEmails(copySelfOnEmails.getValue());
- p.setReversePatchSetOrder(reversePatchSetOrder.getValue());
p.setMaximumPageSize(getListBox(maximumPageSize, DEFAULT_PAGESIZE));
p.setDateFormat(getListBox(dateFormat,
AccountGeneralPreferences.DateFormat.STD,
@@ -387,18 +335,13 @@ public class MyPreferencesScreen extends SettingsScreen {
p.setRelativeDateInChangeTable(relativeDateInChangeTable.getValue());
p.setSizeBarInChangeTable(sizeBarInChangeTable.getValue());
p.setLegacycidInChangeTable(legacycidInChangeTable.getValue());
+ p.setMuteCommonPathPrefixes(muteCommonPathPrefixes.getValue());
p.setReviewCategoryStrategy(getListBox(reviewCategoryStrategy,
ReviewCategoryStrategy.NONE,
ReviewCategoryStrategy.values()));
- p.setCommentVisibilityStrategy(getListBox(commentVisibilityStrategy,
- CommentVisibilityStrategy.EXPAND_RECENT,
- CommentVisibilityStrategy.values()));
p.setDiffView(getListBox(diffView,
AccountGeneralPreferences.DiffView.SIDE_BY_SIDE,
AccountGeneralPreferences.DiffView.values()));
- p.setChangeScreen(getListBox(changeScreen,
- null,
- AccountGeneralPreferences.ChangeScreen.values()));
enable(false);
save.setEnabled(false);
@@ -409,12 +352,11 @@ public class MyPreferencesScreen extends SettingsScreen {
}
AccountApi.self().view("preferences")
- .post(Preferences.create(p, items), new GerritCallback<Preferences>() {
+ .put(Preferences.create(p, items), new GerritCallback<Preferences>() {
@Override
public void onSuccess(Preferences prefs) {
Gerrit.getUserAccount().setGeneralPreferences(p);
Gerrit.applyUserPreferences();
- Dispatcher.changeScreen2 = false;
enable(true);
display(prefs);
Gerrit.refreshMenuBar();
@@ -429,20 +371,6 @@ public class MyPreferencesScreen extends SettingsScreen {
});
}
- private static String getLabel(AccountGeneralPreferences.ChangeScreen ui) {
- if (ui == null) {
- return "";
- }
- switch (ui) {
- case OLD_UI:
- return Util.C.changeScreenOldUi();
- case CHANGE_SCREEN2:
- return Util.C.changeScreenNewUi();
- default:
- return ui.name();
- }
- }
-
private class MyMenuPanel extends StringListPanel {
MyMenuPanel(Button save) {
super(Util.C.myMenu(), Arrays.asList(Util.C.myMenuName(),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
index a528912ea9..a8bf3e5003 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
@@ -20,6 +20,7 @@ import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.client.ui.ProjectListPopup;
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccountProjectWatchInfo;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -34,21 +35,16 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.SuggestBox;
-import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
-import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import java.util.List;
public class MyWatchedProjectsScreen extends SettingsScreen {
private Button addNew;
- private HintTextBox nameBox;
- private SuggestBox nameTxt;
+ private RemoteSuggestBox nameBox;
private HintTextBox filterTxt;
private MyWatchesTable watchesTab;
private Button browse;
private Button delSel;
- private boolean submitOnSelection;
private Grid grid;
private ProjectListPopup projectsPopup;
@@ -62,7 +58,7 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
grid.setText(0, 0, Util.C.watchedProjectName());
final HorizontalPanel hp = new HorizontalPanel();
- hp.add(nameTxt);
+ hp.add(nameBox);
hp.add(browse);
grid.setWidget(0, 1, hp);
@@ -108,32 +104,13 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
}
protected void createWidgets() {
- nameBox = new HintTextBox();
- nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), nameBox);
+ nameBox = new RemoteSuggestBox(new ProjectNameSuggestOracle());
nameBox.setVisibleLength(50);
nameBox.setHintText(Util.C.defaultProjectName());
- nameBox.addKeyPressHandler(new KeyPressHandler() {
+ nameBox.addSelectionHandler(new SelectionHandler<String>() {
@Override
- public void onKeyPress(KeyPressEvent event) {
- submitOnSelection = false;
-
- if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- if (((DefaultSuggestionDisplay) nameTxt.getSuggestionDisplay())
- .isSuggestionListShowing()) {
- submitOnSelection = true;
- } else {
- doAddNew();
- }
- }
- }
- });
- nameTxt.addSelectionHandler(new SelectionHandler<Suggestion>() {
- @Override
- public void onSelection(SelectionEvent<Suggestion> event) {
- if (submitOnSelection) {
- submitOnSelection = false;
- doAddNew();
- }
+ public void onSelection(SelectionEvent<String> event) {
+ doAddNew();
}
});
@@ -196,7 +173,7 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
}
protected void doAddNew() {
- final String projectName = nameTxt.getText().trim();
+ final String projectName = nameBox.getText().trim();
if ("".equals(projectName)) {
return;
}
@@ -213,12 +190,13 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
Util.ACCOUNT_SVC.addProjectWatch(projectName, filter,
new GerritCallback<AccountProjectWatchInfo>() {
+ @Override
public void onSuccess(final AccountProjectWatchInfo result) {
addNew.setEnabled(true);
nameBox.setEnabled(true);
filterTxt.setEnabled(true);
- nameTxt.setText("");
+ nameBox.setText("");
watchesTab.insertWatch(result);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
index 197dcf8bc7..67f5b4a218 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
@@ -67,6 +67,7 @@ public class MyWatchesTable extends FancyFlexTable<AccountProjectWatchInfo> {
if (!ids.isEmpty()) {
Util.ACCOUNT_SVC.deleteProjectWatches(ids,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
remove(ids);
}
@@ -165,6 +166,7 @@ public class MyWatchesTable extends FancyFlexTable<AccountProjectWatchInfo> {
cbox.setEnabled(false);
Util.ACCOUNT_SVC.updateProjectWatch(info.getWatch(),
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
cbox.setEnabled(true);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
index 275937eed5..95ea317cdb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
@@ -32,7 +32,6 @@ import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
-import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
@@ -43,6 +42,7 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
+import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
import java.util.HashSet;
@@ -79,6 +79,7 @@ public class NewAgreementScreen extends AccountScreen {
protected void onLoad() {
super.onLoad();
Util.ACCOUNT_SVC.myAgreements(new GerritCallback<AgreementInfo>() {
+ @Override
public void onSuccess(AgreementInfo result) {
if (isAttached()) {
mySigned = new HashSet<>(result.accepted);
@@ -88,6 +89,7 @@ public class NewAgreementScreen extends AccountScreen {
});
Gerrit.SYSTEM_SVC
.contributorAgreements(new GerritCallback<List<ContributorAgreement>>() {
+ @Override
public void onSuccess(final List<ContributorAgreement> result) {
if (isAttached()) {
available = result;
@@ -224,6 +226,7 @@ public class NewAgreementScreen extends AccountScreen {
private void doEnterAgreement() {
Util.ACCOUNT_SEC.enterAgreement(current.getName(),
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
Gerrit.display(nextToken);
}
@@ -247,10 +250,12 @@ public class NewAgreementScreen extends AccountScreen {
}
final RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url);
rb.setCallback(new RequestCallback() {
+ @Override
public void onError(Request request, Throwable exception) {
new ErrorDialog(exception).center();
}
+ @Override
public void onResponseReceived(Request request, Response response) {
final String ct = response.getHeader("Content-Type");
if (response.getStatusCode() == 200 && ct != null
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
index 25036d3806..e9bd5f1e2e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
@@ -16,13 +16,11 @@ package com.google.gerrit.client.account;
import com.google.gerrit.client.extensions.TopMenuItem;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
@@ -43,14 +41,12 @@ public class Preferences extends JavaScriptObject {
p.copySelfOnEmail(in.isCopySelfOnEmails());
p.dateFormat(in.getDateFormat());
p.timeFormat(in.getTimeFormat());
- p.reversePatchSetOrder(in.isReversePatchSetOrder());
p.relativeDateInChangeTable(in.isRelativeDateInChangeTable());
p.sizeBarInChangeTable(in.isSizeBarInChangeTable());
p.legacycidInChangeTable(in.isLegacycidInChangeTable());
- p.commentVisibilityStrategy(in.getCommentVisibilityStrategy());
+ p.muteCommonPathPrefixes(in.isMuteCommonPathPrefixes());
p.reviewCategoryStrategy(in.getReviewCategoryStrategy());
p.diffView(in.getDiffView());
- p.changeScreen(in.getChangeScreen());
p.setMyMenus(myMenus);
return p;
}
@@ -98,9 +94,6 @@ public class Preferences extends JavaScriptObject {
private final native String timeFormatRaw()
/*-{ return this.time_format }-*/;
- public final native boolean reversePatchSetOrder()
- /*-{ return this.reverse_patch_set_order || false }-*/;
-
public final native boolean relativeDateInChangeTable()
/*-{ return this.relative_date_in_change_table || false }-*/;
@@ -110,6 +103,9 @@ public class Preferences extends JavaScriptObject {
public final native boolean legacycidInChangeTable()
/*-{ return this.legacycid_in_change_table || false }-*/;
+ public final native boolean muteCommonPathPrefixes()
+ /*-{ return this.mute_common_path_prefixes || false }-*/;
+
public final ReviewCategoryStrategy reviewCategoryStrategy() {
String s = reviewCategeoryStrategyRaw();
return s != null ? ReviewCategoryStrategy.valueOf(s) : ReviewCategoryStrategy.NONE;
@@ -117,13 +113,6 @@ public class Preferences extends JavaScriptObject {
private final native String reviewCategeoryStrategyRaw()
/*-{ return this.review_category_strategy }-*/;
- public final CommentVisibilityStrategy commentVisibilityStrategy() {
- String s = commentVisibilityStrategyRaw();
- return s != null ? CommentVisibilityStrategy.valueOf(s) : null;
- }
- private final native String commentVisibilityStrategyRaw()
- /*-{ return this.comment_visibility_strategy }-*/;
-
public final DiffView diffView() {
String s = diffViewRaw();
return s != null ? DiffView.valueOf(s) : null;
@@ -131,13 +120,6 @@ public class Preferences extends JavaScriptObject {
private final native String diffViewRaw()
/*-{ return this.diff_view }-*/;
- public final ChangeScreen changeScreen() {
- String s = changeScreenRaw();
- return s != null ? ChangeScreen.valueOf(s) : null;
- }
- private final native String changeScreenRaw()
- /*-{ return this.change_screen }-*/;
-
public final native JsArray<TopMenuItem> my()
/*-{ return this.my; }-*/;
@@ -177,9 +159,6 @@ public class Preferences extends JavaScriptObject {
private final native void timeFormatRaw(String f)
/*-{ this.time_format = f }-*/;
- public final native void reversePatchSetOrder(boolean r)
- /*-{ this.reverse_patch_set_order = r }-*/;
-
public final native void relativeDateInChangeTable(boolean d)
/*-{ this.relative_date_in_change_table = d }-*/;
@@ -189,30 +168,21 @@ public class Preferences extends JavaScriptObject {
public final native void legacycidInChangeTable(boolean s)
/*-{ this.legacycid_in_change_table = s }-*/;
+ public final native void muteCommonPathPrefixes(boolean s)
+ /*-{ this.mute_common_path_prefixes = s }-*/;
+
public final void reviewCategoryStrategy(ReviewCategoryStrategy s) {
reviewCategoryStrategyRaw(s != null ? s.toString() : null);
}
private final native void reviewCategoryStrategyRaw(String s)
/*-{ this.review_category_strategy = s }-*/;
- public final void commentVisibilityStrategy(CommentVisibilityStrategy s) {
- commentVisibilityStrategyRaw(s != null ? s.toString() : null);
- }
- private final native void commentVisibilityStrategyRaw(String s)
- /*-{ this.comment_visibility_strategy = s }-*/;
-
public final void diffView(DiffView d) {
diffViewRaw(d != null ? d.toString() : null);
}
private final native void diffViewRaw(String d)
/*-{ this.diff_view = d }-*/;
- public final void changeScreen(ChangeScreen s) {
- changeScreenRaw(s != null ? s.toString() : null);
- }
- private final native void changeScreenRaw(String s)
- /*-{ this.change_screen = s }-*/;
-
final void setMyMenus(List<TopMenuItem> myMenus) {
initMy();
for (TopMenuItem n : myMenus) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
index fb1ed8ba8a..0cfc098433 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
@@ -159,6 +159,7 @@ class SshPanel extends Composite {
if (txt != null && txt.length() > 0) {
addNew.setEnabled(false);
AccountApi.addSshKey("self", txt, new GerritCallback<SshKeyInfo>() {
+ @Override
public void onSuccess(final SshKeyInfo k) {
addNew.setEnabled(true);
addTxt.setText("");
@@ -198,6 +199,7 @@ class SshPanel extends Composite {
super.onLoad();
refreshSshKeys();
Gerrit.SYSTEM_SVC.daemonHostKeys(new GerritCallback<List<SshHostKey>>() {
+ @Override
public void onSuccess(final List<SshHostKey> result) {
serverKeys.clear();
for (final SshHostKey keyInfo : result) {
@@ -272,6 +274,7 @@ class SshPanel extends Composite {
deleteKey.setEnabled(false);
AccountApi.deleteSshKeys("self", sequenceNumbers,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
final SshKeyInfo k = getRowItem(row);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
index bf4d75c09b..9975887f09 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -106,20 +106,17 @@ class UsernameField extends Composite {
return;
}
+ enableUI(false);
+
String newName = userNameTxt.getText();
if ("".equals(newName)) {
newName = null;
}
- if (newName != null && !newName.matches(Account.USER_NAME_PATTERN)) {
- invalidUserName();
- return;
- }
-
- enableUI(false);
-
final String newUserName = newName;
+
Util.ACCOUNT_SEC.changeUserName(newUserName,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
Gerrit.getUserAccount().setUserName(newUserName);
userNameLbl.setText(newUserName);
@@ -131,8 +128,8 @@ class UsernameField extends Composite {
@Override
public void onFailure(final Throwable caught) {
enableUI(true);
- if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
- invalidUserName();
+ if (caught instanceof InvalidUserNameException) {
+ new ErrorDialog(Util.C.invalidUserName()).center();
} else {
super.onFailure(caught);
}
@@ -140,10 +137,6 @@ class UsernameField extends Composite {
});
}
- private void invalidUserName() {
- new ErrorDialog(Util.C.invalidUserName()).center();
- }
-
private void enableUI(final boolean on) {
userNameTxt.setEnabled(on);
setUserName.setEnabled(on);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
index ab94a75c39..2e2d31449e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
@@ -16,9 +16,11 @@ package com.google.gerrit.client.actions;
import com.google.gerrit.client.api.ActionContext;
import com.google.gerrit.client.api.ChangeGlue;
+import com.google.gerrit.client.api.EditGlue;
import com.google.gerrit.client.api.ProjectGlue;
import com.google.gerrit.client.api.RevisionGlue;
import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.projects.BranchInfo;
import com.google.gerrit.reviewdb.client.Project;
@@ -31,30 +33,37 @@ public class ActionButton extends Button implements ClickHandler {
private final Project.NameKey project;
private final BranchInfo branch;
private final ChangeInfo change;
+ private final EditInfo edit;
private final RevisionInfo revision;
private final ActionInfo action;
private ActionContext ctx;
public ActionButton(Project.NameKey project, ActionInfo action) {
- this(project, null, null, null, action);
+ this(project, null, null, null, null, action);
}
public ActionButton(Project.NameKey project, BranchInfo branch,
ActionInfo action) {
- this(project, branch, null, null, action);
+ this(project, branch, null, null, null, action);
}
public ActionButton(ChangeInfo change, ActionInfo action) {
- this(change, null, action);
+ this(null, null, change, null, null, action);
}
public ActionButton(ChangeInfo change, RevisionInfo revision,
ActionInfo action) {
- this(null, null, change, revision, action);
+ this(null, null, change, null, revision, action);
+ }
+
+ public ActionButton(ChangeInfo change, EditInfo edit,
+ ActionInfo action) {
+ this(null, null, change, edit, null, action);
}
private ActionButton(Project.NameKey project, BranchInfo branch,
- ChangeInfo change, RevisionInfo revision, ActionInfo action) {
+ ChangeInfo change, EditInfo edit, RevisionInfo revision,
+ ActionInfo action) {
super(new SafeHtmlBuilder()
.openDiv()
.append(action.label())
@@ -67,6 +76,7 @@ public class ActionButton extends Button implements ClickHandler {
this.project = project;
this.branch = branch;
this.change = change;
+ this.edit = edit;
this.revision = revision;
this.action = action;
}
@@ -81,6 +91,8 @@ public class ActionButton extends Button implements ClickHandler {
if (revision != null) {
RevisionGlue.onAction(change, revision, action, this);
+ } else if (edit != null) {
+ EditGlue.onAction(change, edit, action, this);
} else if (change != null) {
ChangeGlue.onAction(change, action, this);
} else if (branch != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 3a0d2f7b4a..157748f8e2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -93,7 +93,7 @@ public class AccessSectionEditor extends Composite implements
public AccessSectionEditor(ProjectAccess access) {
projectAccess = access;
- permissionSelector = new ValueListBox<String>(
+ permissionSelector = new ValueListBox<>(
new PermissionNameRenderer(access.getCapabilities()));
permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
@@ -109,17 +109,17 @@ public class AccessSectionEditor extends Composite implements
}
@UiHandler("deleteSection")
- void onDeleteHover(MouseOverEvent event) {
+ void onDeleteHover(@SuppressWarnings("unused") MouseOverEvent event) {
normal.addClassName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deleteSection")
- void onDeleteNonHover(MouseOutEvent event) {
+ void onDeleteNonHover(@SuppressWarnings("unused") MouseOutEvent event) {
normal.removeClassName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deleteSection")
- void onDeleteSection(ClickEvent event) {
+ void onDeleteSection(@SuppressWarnings("unused") ClickEvent event) {
isDeleted = true;
if (name.isVisible()
@@ -139,7 +139,7 @@ public class AccessSectionEditor extends Composite implements
}
@UiHandler("undoDelete")
- void onUndoDelete(ClickEvent event) {
+ void onUndoDelete(@SuppressWarnings("unused") ClickEvent event) {
isDeleted = false;
deleted.getStyle().setDisplay(Display.NONE);
normal.getStyle().setDisplay(Display.BLOCK);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
index 2cacf35225..cd7268678c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -21,14 +21,13 @@ import com.google.gerrit.client.groups.GroupInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.OnEditEnabler;
-import com.google.gerrit.client.ui.RPCSuggestOracle;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.globalkey.client.NpTextArea;
@@ -40,8 +39,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
private NpTextBox groupNameTxt;
private Button saveName;
- private NpTextBox ownerTxtBox;
- private SuggestBox ownerTxt;
+ private RemoteSuggestBox ownerTxt;
private Button saveOwner;
private NpTextArea descTxt;
@@ -66,7 +64,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
private void enableForm(final boolean canModify) {
groupNameTxt.setEnabled(canModify);
- ownerTxtBox.setEnabled(canModify);
+ ownerTxt.setEnabled(canModify);
descTxt.setEnabled(canModify);
visibleToAllCheckBox.setEnabled(canModify);
}
@@ -96,6 +94,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
final String newName = groupNameTxt.getText().trim();
GroupApi.renameGroup(getGroupUUID(), newName,
new GerritCallback<com.google.gerrit.client.VoidResult>() {
+ @Override
public void onSuccess(final com.google.gerrit.client.VoidResult result) {
saveName.setEnabled(false);
setPageTitle(Util.M.group(newName));
@@ -116,12 +115,10 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
ownerPanel.setStyleName(Gerrit.RESOURCES.css().groupOwnerPanel());
ownerPanel.add(new SmallHeading(Util.C.headingOwner()));
- ownerTxtBox = new NpTextBox();
- ownerTxtBox.setVisibleLength(60);
final AccountGroupSuggestOracle accountGroupOracle = new AccountGroupSuggestOracle();
- ownerTxt = new SuggestBox(new RPCSuggestOracle(
- accountGroupOracle), ownerTxtBox);
+ ownerTxt = new RemoteSuggestBox(accountGroupOracle);
ownerTxt.setStyleName(Gerrit.RESOURCES.css().groupOwnerTextBox());
+ ownerTxt.setVisibleLength(60);
ownerPanel.add(ownerTxt);
saveOwner = new Button(Util.C.buttonChangeGroupOwner());
@@ -135,6 +132,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
String ownerId = ownerUuid != null ? ownerUuid.get() : newOwner;
GroupApi.setGroupOwner(getGroupUUID(), ownerId,
new GerritCallback<GroupInfo>() {
+ @Override
public void onSuccess(final GroupInfo result) {
updateOwnerGroup(result);
saveOwner.setEnabled(false);
@@ -165,6 +163,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
final String txt = descTxt.getText().trim();
GroupApi.setGroupDescription(getGroupUUID(), txt,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
saveDesc.setEnabled(false);
}
@@ -193,6 +192,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
public void onClick(final ClickEvent event) {
GroupApi.setGroupOptions(getGroupUUID(),
visibleToAllCheckBox.getValue(), new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
saveGroupOptions.setEnabled(false);
}
@@ -223,6 +223,6 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
saveGroupOptions.setVisible(canModify);
new OnEditEnabler(saveDesc, descTxt);
new OnEditEnabler(saveName, groupNameTxt);
- new OnEditEnabler(saveOwner, ownerTxtBox);
+ new OnEditEnabler(saveOwner, ownerTxt.getTextBox());
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index df74a41c6e..cf3e9404ee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -24,6 +24,7 @@ import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.AccountLinkPanel;
+import com.google.gerrit.client.ui.AccountSuggestOracle;
import com.google.gerrit.client.ui.AddMemberBox;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.Hyperlink;
@@ -80,7 +81,10 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
private void initMemberList() {
- addMemberBox = new AddMemberBox();
+ addMemberBox = new AddMemberBox(
+ Util.C.buttonAddGroupMember(),
+ Util.C.defaultAccountName(),
+ new AccountSuggestOracle());
addMemberBox.addClickHandler(new ClickHandler() {
@Override
@@ -172,6 +176,7 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
addMemberBox.setEnabled(false);
GroupApi.addMember(getGroupUUID(), nameEmail,
new GerritCallback<AccountInfo>() {
+ @Override
public void onSuccess(final AccountInfo memberInfo) {
addMemberBox.setEnabled(true);
addMemberBox.setText("");
@@ -200,6 +205,7 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
addIncludeBox.setEnabled(false);
GroupApi.addIncludedGroup(getGroupUUID(), uuid.get(),
new GerritCallback<GroupInfo>() {
+ @Override
public void onSuccess(final GroupInfo result) {
addIncludeBox.setEnabled(true);
addIncludeBox.setText("");
@@ -248,6 +254,7 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
if (!ids.isEmpty()) {
GroupApi.removeMembers(getGroupUUID(), ids,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
final AccountInfo i = getRowItem(row);
@@ -353,6 +360,7 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
if (!ids.isEmpty()) {
GroupApi.removeIncludedGroups(getGroupUUID(), ids,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(final VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
final GroupInfo i = getRowItem(row);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index a92b736374..86f543a8cc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -41,6 +41,7 @@ public interface AdminConstants extends Constants {
String useContentMerge();
String useContributorAgreements();
String useSignedOffBy();
+ String createNewChangeForAllNotInTarget();
String requireChangeID();
String headingMaxObjectSizeLimit();
String headingGroupOptions();
@@ -135,9 +136,14 @@ public interface AdminConstants extends Constants {
String sectionTypeSection();
Map<String, String> sectionNames();
- String pagedProjectListPrev();
- String pagedProjectListNext();
+ String pagedListPrev();
+ String pagedListNext();
- String pagedGroupListPrev();
- String pagedGroupListNext();
+ String buttonCreate();
+ String buttonCreateDescription();
+ String buttonCreateChange();
+ String buttonCreateChangeDescription();
+ String buttonEditConfig();
+ String buttonEditConfigDescription();
+ String editConfigMessage();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index ef35e00718..4446354662 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -23,6 +23,7 @@ projectRepoBrowser = Repository Browser
useContentMerge = Automatically resolve conflicts
useContributorAgreements = Require a valid contributor agreement to upload
useSignedOffBy = Require <code>Signed-off-by</code> in commit message
+createNewChangeForAllNotInTarget = Create a new change for every commit not in the target branch
requireChangeID = Require <code>Change-Id</code> in commit message
headingMaxObjectSizeLimit = Maximum Git object size limit
headingGroupOptions = Group Options
@@ -99,11 +100,8 @@ noGroupSelected = (No group selected)
errorNoMatchingGroups = No Matching Groups
errorNoGitRepository = No Git Repository
-pagedProjectListPrev = &#x21e6;Prev
-pagedProjectListNext = Next&#x21e8;
-
-pagedGroupListPrev = &#x21e6;Prev
-pagedGroupListNext = Next&#x21e8;
+pagedListPrev = &#x21e6;Prev
+pagedListNext = Next&#x21e8;
addPermission = Add Permission ...
@@ -112,6 +110,7 @@ permissionNames = \
abandon, \
create, \
deleteDrafts, \
+ editHashtags, \
editTopicName, \
forgeAuthor, \
forgeCommitter, \
@@ -132,6 +131,7 @@ permissionNames = \
abandon = Abandon
create = Create Reference
deleteDrafts = Delete Drafts
+editHashtags = Edit Hashtags
editTopicName = Edit Topic Name
forgeAuthor = Forge Author Identity
forgeCommitter = Forge Committer Identity
@@ -162,3 +162,11 @@ sectionTypeSection = Section:
sectionNames = \
GLOBAL_CAPABILITIES
GLOBAL_CAPABILITIES = Global Capabilities
+
+buttonCreate = Create
+buttonCreateDescription = Insert the description of the change.
+buttonCreateChange = Create Change
+buttonCreateChangeDescription = Create change directly in the browser.
+buttonEditConfig = Edit Config
+buttonEditConfigDescription = Creates a change to edit the project configuration in the browser.
+editConfigMessage = Edit Project Config
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java
index cd366f35f6..f1ac27f496 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java
@@ -24,9 +24,6 @@ public interface AdminResources extends ClientBundle {
@Source("admin.css")
AdminCss css();
- @Source("editText.png")
- public ImageResource editText();
-
@Source("deleteNormal.png")
public ImageResource deleteNormal();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
new file mode 100644
index 0000000000..1ffd6f0b64
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2014 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.client.admin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.CreateChangeDialog;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+class CreateChangeAction {
+ static void call(final Button b, final String project) {
+ // TODO Replace CreateChangeDialog with a nicer looking display.
+ b.setEnabled(false);
+ new CreateChangeDialog(new Project.NameKey(project)) {
+ {
+ sendButton.setText(Util.C.buttonCreate());
+ message.setText(Util.C.buttonCreateDescription());
+ }
+
+ @Override
+ public void onSend() {
+ ChangeApi.createChange(project, getDestinationBranch(),
+ message.getText(), null,
+ new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ sent = true;
+ hide();
+ Gerrit.display(PageLinks.toChange(result.legacy_id()));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ enableButtons(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ super.onClose(event);
+ b.setEnabled(true);
+ }
+
+ }.center();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
index 69dff5c6e1..a2ba5cd61a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
@@ -128,6 +128,7 @@ public class CreateGroupScreen extends Screen {
addNew.setEnabled(false);
GroupApi.createGroup(newName, new GerritCallback<GroupInfo>() {
+ @Override
public void onSuccess(final GroupInfo result) {
History.newItem(Dispatcher.toGroup(result.getGroupId(),
AccountGroupScreen.MEMBERS));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
index 0379cc0e87..3132531a0e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
@@ -26,11 +26,11 @@ import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.projects.ProjectInfo;
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.ProjectListPopup;
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
import com.google.gerrit.client.ui.ProjectsTable;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.ProjectUtil;
@@ -49,7 +49,6 @@ import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
@@ -58,8 +57,7 @@ public class CreateProjectScreen extends Screen {
private NpTextBox project;
private Button create;
private Button browse;
- private HintTextBox parent;
- private SuggestBox suggestParent;
+ private RemoteSuggestBox parent;
private CheckBox emptyCommit;
private CheckBox permissionsOnly;
private ProjectsTable suggestedParentsTab;
@@ -102,8 +100,8 @@ public class CreateProjectScreen extends Screen {
@Override
protected void onMovePointerTo(String projectName) {
// prevent user input from being overwritten by simply poping up
- if (!projectsPopup.isPoppingUp() || "".equals(suggestParent.getText())) {
- suggestParent.setText(projectName);
+ if (!projectsPopup.isPoppingUp() || "".equals(parent.getText())) {
+ parent.setText(projectName);
}
}
};
@@ -191,9 +189,7 @@ public class CreateProjectScreen extends Screen {
}
private void initParentBox() {
- parent = new HintTextBox();
- suggestParent =
- new SuggestBox(new ProjectNameSuggestOracle(), parent);
+ parent = new RemoteSuggestBox(new ProjectNameSuggestOracle());
parent.setVisibleLength(50);
}
@@ -210,7 +206,7 @@ public class CreateProjectScreen extends Screen {
@Override
public void onClick(ClickEvent event) {
- suggestParent.setText(getRowItem(row).name());
+ parent.setText(getRowItem(row).name());
}
});
@@ -240,14 +236,14 @@ public class CreateProjectScreen extends Screen {
grid.setText(0, 0, Util.C.columnProjectName() + ":");
grid.setWidget(0, 1, project);
grid.setText(1, 0, Util.C.headingParentProjectName() + ":");
- grid.setWidget(1, 1, suggestParent);
+ grid.setWidget(1, 1, parent);
grid.setWidget(1, 2, browse);
fp.add(grid);
}
private void doCreateProject() {
final String projectName = project.getText().trim();
- final String parentName = suggestParent.getText().trim();
+ final String parentName = parent.getText().trim();
if ("".equals(projectName)) {
project.setFocus(true);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
new file mode 100644
index 0000000000..86a31ee593
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2015 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.client.admin;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gwt.user.client.ui.Button;
+
+public class EditConfigAction {
+ static void call(final Button b, final String project) {
+ b.setEnabled(false);
+
+ ChangeApi.createChange(project, RefNames.REFS_CONFIG,
+ Util.C.editConfigMessage(), null, new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ Gerrit.display(Dispatcher.toEditScreen(
+ new PatchSet.Id(result.legacy_id(), 1), "project.config"));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ b.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index 6579f83e96..c5f757d18e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -19,10 +19,8 @@ import static com.google.gerrit.common.PageLinks.ADMIN_GROUPS;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.groups.GroupMap;
import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.AccountScreen;
-import com.google.gerrit.client.ui.FilteredUserInterface;
import com.google.gerrit.client.ui.Hyperlink;
-import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
+import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -33,20 +31,24 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwtexpui.globalkey.client.NpTextBox;
-public class GroupListScreen extends AccountScreen implements FilteredUserInterface {
+public class GroupListScreen extends Screen {
private Hyperlink prev;
private Hyperlink next;
private GroupTable groups;
private NpTextBox filterTxt;
- private String subname = "";
- private int startPosition;
private int pageSize;
+ private String match = "";
+ private int start;
+ private Query query;
+
public GroupListScreen() {
+ setRequiresSignIn(true);
configurePageSize();
}
public GroupListScreen(String params) {
+ this();
for (String kvPair : params.split("[,;&]")) {
String[] kv = kvPair.split("=", 2);
if (kv.length != 2 || kv[0].isEmpty()) {
@@ -54,14 +56,13 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
}
if ("filter".equals(kv[0])) {
- subname = URL.decodeQueryString(kv[1]);
+ match = URL.decodeQueryString(kv[1]);
}
if ("skip".equals(kv[0]) && URL.decodeQueryString(kv[1]).matches("^[\\d]+")) {
- startPosition = Integer.parseInt(URL.decodeQueryString(kv[1]));
+ start = Integer.parseInt(URL.decodeQueryString(kv[1]));
}
}
- configurePageSize();
}
private void configurePageSize() {
@@ -78,42 +79,7 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
@Override
protected void onLoad() {
super.onLoad();
- display();
- refresh(false, false);
- }
-
- private void refresh(final boolean open, final boolean filterModified) {
- if (filterModified){
- startPosition = 0;
- }
- setToken(getTokenForScreen(subname, startPosition));
- // Retrieve one more group than page size to determine if there are more
- // groups to display
- GroupMap.match(subname, pageSize + 1, startPosition,
- new IgnoreOutdatedFilterResultsCallbackWrapper<GroupMap>(this,
- new GerritCallback<GroupMap>() {
- @Override
- public void onSuccess(GroupMap result) {
- if (open && result.values().length() > 0) {
- Gerrit.display(PageLinks.toGroup(
- result.values().get(0).getGroupUUID()));
- } else {
- if (result.size() <= pageSize) {
- groups.display(result, subname);
- next.setVisible(false);
- } else {
- groups.displaySubset(result, 0, result.size() - 1, subname);
- setupNavigationLink(next, subname, startPosition + pageSize);
- }
- if (startPosition > 0) {
- setupNavigationLink(prev, subname, startPosition - pageSize);
- } else {
- prev.setVisible(false);
- }
- groups.finishDisplay();
- }
- }
- }));
+ query = new Query(match).start(start).run();
}
private void setupNavigationLink(Hyperlink link, String filter, int skip) {
@@ -138,20 +104,15 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
}
@Override
- public String getCurrentFilter() {
- return subname;
- }
-
- @Override
protected void onInitUI() {
super.onInitUI();
setPageTitle(Util.C.groupListTitle());
initPageHeader();
- prev = new Hyperlink(Util.C.pagedGroupListPrev(), true, "");
+ prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedGroupListNext(), true, "");
+ next = new Hyperlink(Util.C.pagedListNext(), true, "");
next.setVisible(false);
groups = new GroupTable(PageLinks.ADMIN_GROUPS);
@@ -171,16 +132,20 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
hp.add(filterLabel);
filterTxt = new NpTextBox();
- filterTxt.setValue(subname);
+ filterTxt.setValue(match);
filterTxt.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
- boolean enterPressed =
- event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER;
- boolean filterModified = !filterTxt.getValue().equals(subname);
- if (enterPressed || filterModified) {
- subname = filterTxt.getValue();
- refresh(enterPressed, filterModified);
+ Query q = new Query(filterTxt.getValue())
+ .open(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
+ if (match.equals(q.qMatch)) {
+ q.start(start);
+ }
+ if (q.open || !match.equals(q.qMatch)) {
+ if (query == null) {
+ q.run();
+ }
+ query = q;
}
}
});
@@ -191,8 +156,8 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
@Override
public void onShowView() {
super.onShowView();
- if (subname != null) {
- filterTxt.setCursorPos(subname.length());
+ if (match != null) {
+ filterTxt.setCursorPos(match.length());
}
filterTxt.setFocus(true);
}
@@ -202,4 +167,73 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
super.registerKeys();
groups.setRegisterKeys(true);
}
+
+ private class Query {
+ private final String qMatch;
+ private int qStart;
+ private boolean open;
+
+ Query(String match) {
+ this.qMatch = match;
+ }
+
+ Query start(int start) {
+ this.qStart = start;
+ return this;
+ }
+
+ Query open(boolean open) {
+ this.open = open;
+ return this;
+ }
+
+ Query run() {
+ int limit = open ? 1 : pageSize + 1;
+ GroupMap.match(qMatch, limit, qStart,
+ new GerritCallback<GroupMap>() {
+ @Override
+ public void onSuccess(GroupMap result) {
+ if (!isAttached()) {
+ // View has been disposed.
+ } else if (query == Query.this) {
+ query = null;
+ showMap(result);
+ } else {
+ query.run();
+ }
+ }
+ });
+ return this;
+ }
+
+ private void showMap(GroupMap result) {
+ if (open && !result.isEmpty()) {
+ Gerrit.display(PageLinks.toGroup(
+ result.values().get(0).getGroupUUID()));
+ return;
+ }
+
+ setToken(getTokenForScreen(qMatch, qStart));
+ GroupListScreen.this.match = qMatch;
+ GroupListScreen.this.start = qStart;
+
+ if (result.size() <= pageSize) {
+ groups.display(result, qMatch);
+ next.setVisible(false);
+ } else {
+ groups.displaySubset(result, 0, result.size() - 1, qMatch);
+ setupNavigationLink(next, qMatch, qStart + pageSize);
+ }
+
+ if (qStart > 0) {
+ setupNavigationLink(prev, qMatch, qStart - pageSize);
+ } else {
+ prev.setVisible(false);
+ }
+
+ if (!isCurrentView()) {
+ display();
+ }
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
index 4ddc9a835e..c1082bc999 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
@@ -15,15 +15,10 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
-import com.google.gerrit.client.ui.RPCSuggestOracle;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.editor.client.LeafValueEditor;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
@@ -33,67 +28,36 @@ import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.SuggestBox;
-import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
-import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
public class GroupReferenceBox extends Composite implements
LeafValueEditor<GroupReference>, HasSelectionHandlers<GroupReference>,
HasCloseHandlers<GroupReferenceBox>, Focusable {
- private final DefaultSuggestionDisplay suggestions;
- private final NpTextBox textBox;
private final AccountGroupSuggestOracle oracle;
- private final SuggestBox suggestBox;
-
- private boolean submitOnSelection;
+ private final RemoteSuggestBox suggestBox;
public GroupReferenceBox() {
- suggestions = new DefaultSuggestionDisplay();
- textBox = new NpTextBox();
oracle = new AccountGroupSuggestOracle();
- suggestBox = new SuggestBox( //
- new RPCSuggestOracle(oracle), //
- textBox, //
- suggestions);
+ suggestBox = new RemoteSuggestBox(oracle);
initWidget(suggestBox);
- textBox.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- submitOnSelection = false;
-
- if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- if (suggestions.isSuggestionListShowing()) {
- submitOnSelection = true;
- } else {
- SelectionEvent.fire(GroupReferenceBox.this, getValue());
- }
- }
- }
- });
- suggestBox.addKeyUpHandler(new KeyUpHandler() {
+ suggestBox.addSelectionHandler(new SelectionHandler<String>() {
@Override
- public void onKeyUp(KeyUpEvent event) {
- if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
- suggestBox.setText("");
- CloseEvent.fire(GroupReferenceBox.this, GroupReferenceBox.this);
- }
+ public void onSelection(SelectionEvent<String> event) {
+ SelectionEvent.fire(GroupReferenceBox.this,
+ toValue(event.getSelectedItem()));
}
});
- suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ suggestBox.addCloseHandler(new CloseHandler<RemoteSuggestBox>(){
@Override
- public void onSelection(SelectionEvent<Suggestion> event) {
- if (submitOnSelection) {
- submitOnSelection = false;
- SelectionEvent.fire(GroupReferenceBox.this, getValue());
- }
+ public void onClose(CloseEvent<RemoteSuggestBox> event) {
+ suggestBox.setText("");
+ CloseEvent.fire(GroupReferenceBox.this, GroupReferenceBox.this);
}
});
}
public void setVisibleLength(int len) {
- textBox.setVisibleLength(len);
+ suggestBox.setVisibleLength(len);
}
@Override
@@ -110,12 +74,14 @@ public class GroupReferenceBox extends Composite implements
@Override
public GroupReference getValue() {
- String name = suggestBox.getText();
+ return toValue(suggestBox.getText());
+ }
+
+ private GroupReference toValue(String name) {
if (name != null && !name.isEmpty()) {
return new GroupReference(oracle.getUUID(name), name);
- } else {
- return null;
}
+ return null;
}
@Override
@@ -133,6 +99,7 @@ public class GroupReferenceBox extends Composite implements
suggestBox.setTabIndex(index);
}
+ @Override
public void setFocus(boolean focused) {
suggestBox.setFocus(focused);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/MyGroupsListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/MyGroupsListScreen.java
deleted file mode 100644
index cabe2f5c89..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/MyGroupsListScreen.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2014 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.client.admin;
-
-import com.google.gerrit.client.groups.GroupList;
-import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.AccountScreen;
-
-public class MyGroupsListScreen extends AccountScreen {
- private GroupTable groups;
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- groups = new GroupTable();
- add(groups);
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- GroupList.my(new ScreenLoadCallback<GroupList>(this) {
- @Override
- protected void preDisplay(GroupList result) {
- groups.display(result);
- groups.finishDisplay();
- }});
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index b3d856458f..b9baccc76c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -145,31 +145,31 @@ public class PermissionEditor extends Composite implements Editor<Permission>,
}
@UiHandler("deletePermission")
- void onDeleteHover(MouseOverEvent event) {
+ void onDeleteHover(@SuppressWarnings("unused") MouseOverEvent event) {
addStyleName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deletePermission")
- void onDeleteNonHover(MouseOutEvent event) {
+ void onDeleteNonHover(@SuppressWarnings("unused") MouseOutEvent event) {
removeStyleName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deletePermission")
- void onDeletePermission(ClickEvent event) {
+ void onDeletePermission(@SuppressWarnings("unused") ClickEvent event) {
isDeleted = true;
normal.getStyle().setDisplay(Display.NONE);
deleted.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("undoDelete")
- void onUndoDelete(ClickEvent event) {
+ void onUndoDelete(@SuppressWarnings("unused") ClickEvent event) {
isDeleted = false;
deleted.getStyle().setDisplay(Display.NONE);
normal.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("beginAddRule")
- void onBeginAddRule(ClickEvent event) {
+ void onBeginAddRule(@SuppressWarnings("unused") ClickEvent event) {
beginAddRule();
}
@@ -186,7 +186,7 @@ public class PermissionEditor extends Composite implements Editor<Permission>,
}
@UiHandler("addRule")
- void onAddGroupByClick(ClickEvent event) {
+ void onAddGroupByClick(@SuppressWarnings("unused") ClickEvent event) {
GroupReference ref = groupToAdd.getValue();
if (ref != null) {
addGroup(ref);
@@ -204,12 +204,13 @@ public class PermissionEditor extends Composite implements Editor<Permission>,
}
@UiHandler("groupToAdd")
- void onAbortAddGroup(CloseEvent<GroupReferenceBox> event) {
+ void onAbortAddGroup(
+ @SuppressWarnings("unused") CloseEvent<GroupReferenceBox> event) {
hideAddGroup();
}
@UiHandler("hideAddGroup")
- void hideAddGroup(ClickEvent event) {
+ void hideAddGroup(@SuppressWarnings("unused") ClickEvent event) {
hideAddGroup();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml
index 25995d9210..ada070d53c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml
@@ -39,6 +39,7 @@ limitations under the License.
}
.header {
+ position: relative;
padding-left: 5px;
padding-right: 5px;
padding-bottom: 1px;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
index 9e719ead68..f66307ce14 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
@@ -169,6 +169,10 @@ public class PermissionRuleEditor extends Composite implements
deleteRule.removeFromParent();
deleteRule = null;
}
+
+ if (name.equals(GlobalCapability.BATCH_CHANGES_LIMIT)) {
+ min.setEnabled(false);
+ }
}
boolean isDeleted() {
@@ -176,14 +180,14 @@ public class PermissionRuleEditor extends Composite implements
}
@UiHandler("deleteRule")
- void onDeleteRule(ClickEvent event) {
+ void onDeleteRule(@SuppressWarnings("unused") ClickEvent event) {
isDeleted = true;
normal.getStyle().setDisplay(Display.NONE);
deleted.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("undoDelete")
- void onUndoDelete(ClickEvent event) {
+ void onUndoDelete(@SuppressWarnings("unused") ClickEvent event) {
isDeleted = false;
deleted.getStyle().setDisplay(Display.NONE);
normal.getStyle().setDisplay(Display.BLOCK);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
index 8981e8282b..55bc655237 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -84,7 +84,7 @@ public class ProjectAccessEditor extends Composite implements
}
@UiHandler("addSection")
- void onAddSection(ClickEvent event) {
+ void onAddSection(@SuppressWarnings("unused") ClickEvent event) {
int index = local.getList().size();
local.getList().add(new AccessSection("refs/heads/*"));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 9c6cc1d1a7..b86d0a8397 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -159,7 +159,7 @@ public class ProjectAccessScreen extends ProjectScreen {
}
@UiHandler("edit")
- void onEdit(ClickEvent event) {
+ void onEdit(@SuppressWarnings("unused") ClickEvent event) {
resetEditors();
edit.setEnabled(false);
@@ -184,12 +184,12 @@ public class ProjectAccessScreen extends ProjectScreen {
}
@UiHandler(value={"cancel1", "cancel2"})
- void onCancel(ClickEvent event) {
+ void onCancel(@SuppressWarnings("unused") ClickEvent event) {
Gerrit.display(PageLinks.toProjectAcceess(getProjectKey()));
}
@UiHandler("commit")
- void onCommit(ClickEvent event) {
+ void onCommit(@SuppressWarnings("unused") ClickEvent event) {
final ProjectAccess access = driver.flush();
if (driver.hasErrors()) {
@@ -267,7 +267,7 @@ public class ProjectAccessScreen extends ProjectScreen {
}
@UiHandler("review")
- void onReview(ClickEvent event) {
+ void onReview(@SuppressWarnings("unused") ClickEvent event) {
final ProjectAccess access = driver.flush();
if (driver.hasErrors()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 577bf1d585..c5f7225b04 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -14,12 +14,15 @@
package com.google.gerrit.client.admin;
+import static com.google.gerrit.client.ui.Util.highlight;
+
import com.google.gerrit.client.ConfirmationCallback;
import com.google.gerrit.client.ConfirmationDialog;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.access.AccessMap;
import com.google.gerrit.client.access.ProjectAccessInfo;
import com.google.gerrit.client.actions.ActionButton;
@@ -30,10 +33,12 @@ import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -45,36 +50,101 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ProjectBranchesScreen extends ProjectScreen {
+ private Hyperlink prev;
+ private Hyperlink next;
private BranchesTable branchTable;
private Button delBranch;
private Button addBranch;
private HintTextBox nameTxtBox;
private HintTextBox irevTxtBox;
private FlowPanel addPanel;
+ private int pageSize;
+ private int start;
+ private NpTextBox filterTxt;
+ private String match;
+ private Query query;
public ProjectBranchesScreen(final Project.NameKey toShow) {
super(toShow);
+ configurePageSize();
+ }
+
+ private void configurePageSize() {
+ if (Gerrit.isSignedIn()) {
+ AccountGeneralPreferences p =
+ Gerrit.getUserAccount().getGeneralPreferences();
+ short m = p.getMaximumPageSize();
+ pageSize = 0 < m ? m : AccountGeneralPreferences.DEFAULT_PAGESIZE;
+ } else {
+ pageSize = AccountGeneralPreferences.DEFAULT_PAGESIZE;
+ }
+ }
+
+ private void parseToken() {
+ String token = getToken();
+
+ for (String kvPair : token.split("[,;&/?]")) {
+ String[] kv = kvPair.split("=", 2);
+ if (kv.length != 2 || kv[0].isEmpty()) {
+ continue;
+ }
+
+ if ("filter".equals(kv[0])) {
+ match = URL.decodeQueryString(kv[1]);
+ }
+
+ if ("skip".equals(kv[0])
+ && URL.decodeQueryString(kv[1]).matches("^[\\d]+")) {
+ start = Integer.parseInt(URL.decodeQueryString(kv[1]));
+ }
+ }
+ }
+
+ private void setupNavigationLink(Hyperlink link, String filter, int skip) {
+ link.setTargetHistoryToken(getTokenForScreen(filter, skip));
+ link.setVisible(true);
+ }
+
+ private String getTokenForScreen(String filter, int skip) {
+ String token = PageLinks.toProjectBranches(getProjectKey());
+ if (filter != null && !filter.isEmpty()) {
+ token += "?filter=" + URL.encodeQueryString(filter);
+ }
+ if (skip > 0) {
+ if (token.contains("?filter=")) {
+ token += ",";
+ } else {
+ token += "?";
+ }
+ token += "skip=" + skip;
+ }
+ return token;
}
@Override
@@ -88,28 +158,10 @@ public class ProjectBranchesScreen extends ProjectScreen {
addPanel.setVisible(result.canAddRefs());
}
});
- refreshBranches();
+ query = new Query(match).start(start).run();
savedPanel = BRANCH;
}
- private void refreshBranches() {
- ProjectApi.getBranches(getProjectKey(),
- new ScreenLoadCallback<JsArray<BranchInfo>>(this) {
- @Override
- public void preDisplay(final JsArray<BranchInfo> result) {
- Set<String> checkedRefs = branchTable.getCheckedRefs();
- display(Natives.asList(result));
- branchTable.setChecked(checkedRefs);
- updateForm();
- }
- });
- }
-
- private void display(final List<BranchInfo> branches) {
- branchTable.display(branches);
- delBranch.setVisible(branchTable.hasBranchCanDelete());
- }
-
private void updateForm() {
branchTable.updateDeleteButton();
addBranch.setEnabled(true);
@@ -120,6 +172,13 @@ public class ProjectBranchesScreen extends ProjectScreen {
@Override
protected void onInitUI() {
super.onInitUI();
+ initPageHeader();
+
+ prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
+ prev.setVisible(false);
+
+ next = new Hyperlink(Util.C.pagedListNext(), true, "");
+ next.setVisible(false);
addPanel = new FlowPanel();
@@ -174,12 +233,41 @@ public class ProjectBranchesScreen extends ProjectScreen {
branchTable.deleteChecked();
}
});
-
+ HorizontalPanel buttons = new HorizontalPanel();
+ buttons.setStyleName(Gerrit.RESOURCES.css().branchTablePrevNextLinks());
+ buttons.add(delBranch);
+ buttons.add(prev);
+ buttons.add(next);
add(branchTable);
- add(delBranch);
+ add(buttons);
add(addPanel);
}
+ private void initPageHeader() {
+ parseToken();
+ HorizontalPanel hp = new HorizontalPanel();
+ hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
+ Label filterLabel = new Label(Util.C.projectFilter());
+ filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
+ hp.add(filterLabel);
+ filterTxt = new NpTextBox();
+ filterTxt.setValue(match);
+ filterTxt.addKeyUpHandler(new KeyUpHandler() {
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ Query q = new Query(filterTxt.getValue());
+ if (match.equals(q.qMatch)) {
+ q.start(start);
+ } else if (query == null) {
+ q.run();
+ query = q;
+ }
+ }
+ });
+ hp.add(filterTxt);
+ add(hp);
+ }
+
private void doAddNewBranch() {
final String branchName = nameTxtBox.getText().trim();
if ("".equals(branchName)) {
@@ -205,28 +293,49 @@ public class ProjectBranchesScreen extends ProjectScreen {
new GerritCallback<BranchInfo>() {
@Override
public void onSuccess(BranchInfo branch) {
- addBranch.setEnabled(true);
+ showAddedBranch(branch);
nameTxtBox.setText("");
irevTxtBox.setText("");
- branchTable.insert(branch);
- delBranch.setVisible(branchTable.hasBranchCanDelete());
+ query = new Query(match).start(start).run();
}
+ @Override
+ public void onFailure(Throwable caught) {
+ addBranch.setEnabled(true);
+ selectAllAndFocus(nameTxtBox);
+ new ErrorDialog(caught.getMessage()).center();
+ }
+ });
+ }
+
+ void showAddedBranch(BranchInfo branch) {
+ SafeHtmlBuilder b = new SafeHtmlBuilder();
+ b.openElement("b");
+ b.append(Gerrit.C.branchCreationConfirmationMessage());
+ b.closeElement("b");
+
+ b.openElement("p");
+ b.append(branch.ref());
+ b.closeElement("p");
+
+ ConfirmationDialog confirmationDialog =
+ new ConfirmationDialog(Gerrit.C.branchCreationDialogTitle(),
+ b.toSafeHtml(), new ConfirmationCallback() {
@Override
- public void onFailure(Throwable caught) {
- addBranch.setEnabled(true);
- selectAllAndFocus(nameTxtBox);
- new ErrorDialog(caught.getMessage()).center();
+ public void onOk() {
+ //do nothing
}
});
+ confirmationDialog.center();
+ confirmationDialog.setCancelVisible(false);
}
- private static void selectAllAndFocus(final TextBox textBox) {
+ private static void selectAllAndFocus(TextBox textBox) {
textBox.selectAll();
textBox.setFocus(true);
}
- private class BranchesTable extends FancyFlexTable<BranchInfo> {
+ private class BranchesTable extends NavigationTable<BranchInfo> {
private ValueChangeHandler<Boolean> updateDeleteHandler;
boolean canDelete;
@@ -239,9 +348,7 @@ public class ProjectBranchesScreen extends ProjectScreen {
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
- if (Gerrit.getGitwebLink() != null) {
- fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
- }
+ fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
updateDeleteHandler = new ValueChangeHandler<Boolean>() {
@Override
@@ -317,34 +424,30 @@ public class ProjectBranchesScreen extends ProjectScreen {
private void deleteBranches(final Set<String> branches) {
ProjectApi.deleteBranches(getProjectKey(), branches,
new GerritCallback<VoidResult>() {
+ @Override
public void onSuccess(VoidResult result) {
- for (int row = 1; row < table.getRowCount();) {
- BranchInfo k = getRowItem(row);
- if (k != null && branches.contains(k.ref())) {
- table.removeRow(row);
- } else {
- row++;
- }
- }
- updateDeleteButton();
- delBranch.setVisible(branchTable.hasBranchCanDelete());
+ query = new Query(match).start(start).run();
}
@Override
public void onFailure(Throwable caught) {
- refreshBranches();
+ query = new Query(match).start(start).run();
super.onFailure(caught);
}
});
}
void display(List<BranchInfo> branches) {
+ displaySubset(branches, 0, branches.size());
+ }
+
+ void displaySubset(List<BranchInfo> branches, int fromIndex, int toIndex) {
canDelete = false;
while (1 < table.getRowCount())
table.removeRow(table.getRowCount() - 1);
- for (final BranchInfo k : branches) {
+ for (BranchInfo k : branches.subList(fromIndex, toIndex)) {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
@@ -352,21 +455,6 @@ public class ProjectBranchesScreen extends ProjectScreen {
}
}
- void insert(BranchInfo info) {
- Comparator<BranchInfo> c = new Comparator<BranchInfo>() {
- @Override
- public int compare(BranchInfo a, BranchInfo b) {
- return a.ref().compareTo(b.ref());
- }
- };
- int insertPos = getInsertRow(c, info);
- if (insertPos >= 0) {
- table.insertRow(insertPos);
- applyDataRowStyle(insertPos);
- populate(insertPos, info);
- }
- }
-
void populate(int row, BranchInfo k) {
final GitwebLink c = Gerrit.getGitwebLink();
@@ -379,7 +467,7 @@ public class ProjectBranchesScreen extends ProjectScreen {
table.setText(row, 1, "");
}
- table.setText(row, 2, k.getShortName());
+ table.setWidget(row, 2, new InlineHTML(highlight(k.getShortName(), match)));
if (k.revision() != null) {
if ("HEAD".equals(k.getShortName())) {
@@ -396,6 +484,11 @@ public class ProjectBranchesScreen extends ProjectScreen {
actionsPanel.add(new Anchor(c.getLinkName(), false,
c.toBranch(new Branch.NameKey(getProjectKey(), k.ref()))));
}
+ if (k.web_links() != null) {
+ for (WebLinkInfo webLink : Natives.asList(k.web_links())) {
+ actionsPanel.add(webLink.toAnchor());
+ }
+ }
if (k.actions() != null) {
k.actions().copyKeysIntoChildren("id");
for (ActionInfo a : Natives.asList(k.actions().values())) {
@@ -416,9 +509,7 @@ public class ProjectBranchesScreen extends ProjectScreen {
fmt.addStyleName(row, 1, iconCellStyle);
fmt.addStyleName(row, 2, dataCellStyle);
fmt.addStyleName(row, 3, dataCellStyle);
- if (c != null) {
- fmt.addStyleName(row, 4, dataCellStyle);
- }
+ fmt.addStyleName(row, 4, dataCellStyle);
setRowItem(row, k);
}
@@ -527,5 +618,89 @@ public class ProjectBranchesScreen extends ProjectScreen {
}
delBranch.setEnabled(on);
}
+
+ @Override
+ protected void onOpenRow(int row) {
+ if (row > 0) {
+ movePointerTo(row);
+ }
+ }
+
+ @Override
+ protected Object getRowItemKey(BranchInfo item) {
+ return item.ref();
+ }
+ }
+
+ @Override
+ public void onShowView() {
+ super.onShowView();
+ if (match != null) {
+ filterTxt.setCursorPos(match.length());
+ }
+ filterTxt.setFocus(true);
+ }
+
+ private class Query {
+ private String qMatch;
+ private int qStart;
+
+ Query(String match) {
+ this.qMatch = match;
+ }
+
+ Query start(int start) {
+ this.qStart = start;
+ return this;
+ }
+
+ Query run() {
+ // Retrieve one more branch than page size to determine if there are more
+ // branches to display
+ ProjectApi.getBranches(getProjectKey(), pageSize + 1, qStart, qMatch,
+ new ScreenLoadCallback<JsArray<BranchInfo>>(ProjectBranchesScreen.this) {
+ @Override
+ public void preDisplay(JsArray<BranchInfo> result) {
+ if (!isAttached()) {
+ // View has been disposed.
+ } else if (query == Query.this) {
+ query = null;
+ showList(result);
+ } else {
+ query.run();
+ }
+ }
+ });
+ return this;
+ }
+
+ void showList(JsArray<BranchInfo> result) {
+ setToken(getTokenForScreen(qMatch, qStart));
+ ProjectBranchesScreen.this.match = qMatch;
+ ProjectBranchesScreen.this.start = qStart;
+
+ if (result.length() <= pageSize) {
+ branchTable.display(Natives.asList(result));
+ next.setVisible(false);
+ } else {
+ branchTable.displaySubset(Natives.asList(result), 0,
+ result.length() - 1);
+ setupNavigationLink(next, qMatch, qStart + pageSize);
+ }
+ if (qStart > 0) {
+ setupNavigationLink(prev, qMatch, qStart - pageSize);
+ } else {
+ prev.setVisible(false);
+ }
+
+ delBranch.setVisible(branchTable.hasBranchCanDelete());
+ Set<String> checkedRefs = branchTable.getCheckedRefs();
+ branchTable.setChecked(checkedRefs);
+ updateForm();
+
+ if (!isCurrentView()) {
+ display();
+ }
+ }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 24ee27defe..6cb92951a2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -35,9 +35,9 @@ import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.NpIntTextBox;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -79,6 +79,7 @@ public class ProjectInfoScreen extends ProjectScreen {
private ListBox submitType;
private ListBox state;
private ListBox contentMerge;
+ private ListBox newChangeForAllNotInTarget;
private NpTextBox maxObjectSizeLimit;
private Label effectiveMaxObjectSizeLimit;
private Map<String, Map<String, HasEnabled>> pluginConfigWidgets;
@@ -102,6 +103,7 @@ public class ProjectInfoScreen extends ProjectScreen {
Resources.I.style().ensureInjected();
saveProject = new Button(Util.C.buttonSaveChanges());
+ saveProject.setStyleName("");
saveProject.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
@@ -157,6 +159,7 @@ public class ProjectInfoScreen extends ProjectScreen {
state.setEnabled(isOwner);
submitType.setEnabled(isOwner);
setEnabledForUseContentMerge();
+ newChangeForAllNotInTarget.setEnabled(isOwner);
descTxt.setEnabled(isOwner);
contributorAgreements.setEnabled(isOwner);
signedOffBy.setEnabled(isOwner);
@@ -213,6 +216,10 @@ public class ProjectInfoScreen extends ProjectScreen {
saveEnabler.listenTo(contentMerge);
grid.add(Util.C.useContentMerge(), contentMerge);
+ newChangeForAllNotInTarget = newInheritedBooleanBox();
+ saveEnabler.listenTo(newChangeForAllNotInTarget);
+ grid.add(Util.C.createNewChangeForAllNotInTarget(), newChangeForAllNotInTarget);
+
requireChangeID = newInheritedBooleanBox();
saveEnabler.listenTo(requireChangeID);
grid.addHtml(Util.C.requireChangeID(), requireChangeID);
@@ -338,6 +345,7 @@ public class ProjectInfoScreen extends ProjectScreen {
setBool(contributorAgreements, result.use_contributor_agreements());
setBool(signedOffBy, result.use_signed_off_by());
setBool(contentMerge, result.use_content_merge());
+ setBool(newChangeForAllNotInTarget, result.create_new_change_for_all_not_in_target());
setBool(requireChangeID, result.require_change_id());
setSubmitType(result.submit_type());
setState(result.state());
@@ -547,9 +555,13 @@ public class ProjectInfoScreen extends ProjectScreen {
private void initProjectActions(ConfigInfo info) {
actionsGrid.clear(true);
actionsGrid.removeAllRows();
+ boolean showCreateChange = Gerrit.isSignedIn();
NativeMap<ActionInfo> actions = info.actions();
- if (actions == null || actions.isEmpty()) {
+ if (actions == null) {
+ actions = NativeMap.create().cast();
+ }
+ if (actions.isEmpty() && !showCreateChange) {
return;
}
actions.copyKeysIntoChildren("id");
@@ -558,10 +570,47 @@ public class ProjectInfoScreen extends ProjectScreen {
actionsPanel.setStyleName(Gerrit.RESOURCES.css().projectActions());
actionsPanel.setVisible(true);
actionsGrid.add(Util.C.headingCommands(), actionsPanel);
+
for (String id : actions.keySet()) {
actionsPanel.add(new ActionButton(getProjectKey(),
actions.get(id)));
}
+
+ // TODO: The user should have create permission on the branch referred to by
+ // HEAD. This would have to happen on the server side.
+ if (showCreateChange) {
+ actionsPanel.add(createChangeAction());
+ }
+
+ if (isOwner) {
+ actionsPanel.add(createEditConfigAction());
+ }
+ }
+
+ private Button createChangeAction() {
+ final Button createChange = new Button(Util.C.buttonCreateChange());
+ createChange.setStyleName("");
+ createChange.setTitle(Util.C.buttonCreateChangeDescription());
+ createChange.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ CreateChangeAction.call(createChange, getProjectKey().get());
+ }
+ });
+ return createChange;
+ }
+
+ private Button createEditConfigAction() {
+ final Button editConfig = new Button(Util.C.buttonEditConfig());
+ editConfig.setStyleName("");
+ editConfig.setTitle(Util.C.buttonEditConfigDescription());
+ editConfig.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ EditConfigAction.call(editConfig, getProjectKey().get());
+ }
+ });
+ return editConfig;
}
private void doSave() {
@@ -569,7 +618,7 @@ public class ProjectInfoScreen extends ProjectScreen {
saveProject.setEnabled(false);
ProjectApi.setConfig(getProjectKey(), descTxt.getText().trim(),
getBool(contributorAgreements), getBool(contentMerge),
- getBool(signedOffBy), getBool(requireChangeID),
+ getBool(signedOffBy), getBool(newChangeForAllNotInTarget), getBool(requireChangeID),
maxObjectSizeLimit.getText().trim(),
SubmitType.valueOf(submitType.getValue(submitType.getSelectedIndex())),
ProjectState.valueOf(state.getValue(state.getSelectedIndex())),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index 3bd05e0e14..828352c6fb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -24,10 +24,8 @@ import com.google.gerrit.client.projects.ProjectInfo;
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.ui.FilteredUserInterface;
import com.google.gerrit.client.ui.HighlightingInlineHyperlink;
import com.google.gerrit.client.ui.Hyperlink;
-import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
import com.google.gerrit.client.ui.ProjectSearchLink;
import com.google.gerrit.client.ui.ProjectsTable;
import com.google.gerrit.client.ui.Screen;
@@ -47,15 +45,17 @@ import com.google.gwtexpui.globalkey.client.NpTextBox;
import java.util.List;
-public class ProjectListScreen extends Screen implements FilteredUserInterface {
+public class ProjectListScreen extends Screen {
private Hyperlink prev;
private Hyperlink next;
private ProjectsTable projects;
private NpTextBox filterTxt;
- private String subname = "";
- private int startPosition;
private int pageSize;
+ private String match = "";
+ private int start;
+ private Query query;
+
public ProjectListScreen() {
configurePageSize();
}
@@ -68,11 +68,11 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
}
if ("filter".equals(kv[0])) {
- subname = URL.decodeQueryString(kv[1]);
+ match = URL.decodeQueryString(kv[1]);
}
if ("skip".equals(kv[0]) && URL.decodeQueryString(kv[1]).matches("^[\\d]+")) {
- startPosition = Integer.parseInt(URL.decodeQueryString(kv[1]));
+ start = Integer.parseInt(URL.decodeQueryString(kv[1]));
}
}
configurePageSize();
@@ -92,41 +92,7 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
@Override
protected void onLoad() {
super.onLoad();
- display();
- refresh(false, false);
- }
-
- private void refresh(final boolean open, final boolean filterModified) {
- if (filterModified){
- startPosition = 0;
- }
- setToken(getTokenForScreen(subname, startPosition));
- // Retrieve one more project than page size to determine if there are more
- // projects to display
- ProjectMap.match(subname, pageSize + 1, startPosition,
- new IgnoreOutdatedFilterResultsCallbackWrapper<ProjectMap>(this,
- new GerritCallback<ProjectMap>() {
- @Override
- public void onSuccess(ProjectMap result) {
- if (open && result.values().length() > 0) {
- Gerrit.display(PageLinks.toProject(
- result.values().get(0).name_key()));
- } else {
- if (result.size() <= pageSize) {
- projects.display(result);
- next.setVisible(false);
- } else {
- projects.displaySubset(result, 0, result.size() - 1);
- setupNavigationLink(next, subname, startPosition + pageSize);
- }
- if (startPosition > 0) {
- setupNavigationLink(prev, subname, startPosition - pageSize);
- } else {
- prev.setVisible(false);
- }
- }
- }
- }));
+ query = new Query(match).start(start).run();
}
private void setupNavigationLink(Hyperlink link, String filter, int skip) {
@@ -151,20 +117,15 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
}
@Override
- public String getCurrentFilter() {
- return subname;
- }
-
- @Override
protected void onInitUI() {
super.onInitUI();
setPageTitle(Util.C.projectListTitle());
initPageHeader();
- prev = new Hyperlink(Util.C.pagedProjectListPrev(), true, "");
+ prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedProjectListNext(), true, "");
+ next = new Hyperlink(Util.C.pagedListNext(), true, "");
next.setVisible(false);
projects = new ProjectsTable() {
@@ -215,7 +176,7 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
FlowPanel fp = new FlowPanel();
fp.add(new ProjectSearchLink(k.name_key()));
- fp.add(new HighlightingInlineHyperlink(k.name(), link(k), subname));
+ fp.add(new HighlightingInlineHyperlink(k.name(), link(k), match));
table.setWidget(row, ProjectsTable.C_NAME, fp);
table.setText(row, ProjectsTable.C_DESCRIPTION, k.description());
addWebLinks(row, k);
@@ -236,12 +197,10 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
a.setHref(gitWebLink.toProject(k.name_key()));
p.add(a);
}
-
- for (WebLinkInfo weblink : webLinks) {
- Anchor a = new Anchor();
- a.setText("(" + weblink.name() + ")");
- a.setHref(weblink.url());
- p.add(a);
+ if (webLinks != null) {
+ for (WebLinkInfo weblink : webLinks) {
+ p.add(weblink.toAnchor());
+ }
}
}
}
@@ -263,16 +222,20 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
hp.add(filterLabel);
filterTxt = new NpTextBox();
- filterTxt.setValue(subname);
+ filterTxt.setValue(match);
filterTxt.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
- boolean enterPressed =
- event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER;
- boolean filterModified = !filterTxt.getValue().equals(subname);
- if (enterPressed || filterModified) {
- subname = filterTxt.getValue();
- refresh(enterPressed, filterModified);
+ Query q = new Query(filterTxt.getValue())
+ .open(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
+ if (match.equals(q.qMatch)) {
+ q.start(start);
+ }
+ if (q.open || !match.equals(q.qMatch)) {
+ if (query == null) {
+ q.run();
+ }
+ query = q;
}
}
});
@@ -283,8 +246,8 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
@Override
public void onShowView() {
super.onShowView();
- if (subname != null) {
- filterTxt.setCursorPos(subname.length());
+ if (match != null) {
+ filterTxt.setCursorPos(match.length());
}
filterTxt.setFocus(true);
}
@@ -294,4 +257,72 @@ public class ProjectListScreen extends Screen implements FilteredUserInterface {
super.registerKeys();
projects.setRegisterKeys(true);
}
+
+ private class Query {
+ private final String qMatch;
+ private int qStart;
+ private boolean open;
+
+ Query(String match) {
+ this.qMatch = match;
+ }
+
+ Query start(int start) {
+ this.qStart = start;
+ return this;
+ }
+
+ Query open(boolean open) {
+ this.open = open;
+ return this;
+ }
+
+ Query run() {
+ int limit = open ? 1 : pageSize + 1;
+ ProjectMap.match(qMatch, limit, qStart,
+ new GerritCallback<ProjectMap>() {
+ @Override
+ public void onSuccess(ProjectMap result) {
+ if (!isAttached()) {
+ // View has been disposed.
+ } else if (query == Query.this) {
+ query = null;
+ showMap(result);
+ } else {
+ query.run();
+ }
+ }
+ });
+ return this;
+ }
+
+ private void showMap(ProjectMap result) {
+ if (open && !result.isEmpty()) {
+ Gerrit.display(PageLinks.toProject(result.values().get(0).name_key()));
+ return;
+ }
+
+ setToken(getTokenForScreen(qMatch, qStart));
+ ProjectListScreen.this.match = qMatch;
+ ProjectListScreen.this.start = qStart;
+
+ if (result.size() <= pageSize) {
+ projects.display(result);
+ next.setVisible(false);
+ } else {
+ projects.displaySubset(result, 0, result.size() - 1);
+ setupNavigationLink(next, qMatch, qStart + pageSize);
+ }
+
+ if (qStart > 0) {
+ setupNavigationLink(prev, qMatch, qStart - pageSize);
+ } else {
+ prev.setVisible(false);
+ }
+
+ if (!isCurrentView()) {
+ display();
+ }
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
index 0cf5d48881..e8e88cc81d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
@@ -27,16 +27,19 @@ import java.text.ParseException;
public class RefPatternBox extends ValueBox<String> {
private static final Renderer<String> RENDERER = new Renderer<String>() {
+ @Override
public String render(String ref) {
return ref;
}
+ @Override
public void render(String ref, Appendable dst) throws IOException {
dst.append(render(ref));
}
};
private static final Parser<String> PARSER = new Parser<String>() {
+ @Override
public String parse(CharSequence text) throws ParseException {
String ref = text.toString();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 5b8fe386f7..81286eaf94 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -15,8 +15,8 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.common.data.ProjectAdminService;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gwt.core.client.GWT;
import com.google.gwtjsonrpc.client.JsonUtil;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java
index a7463b42f1..60b11c2606 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java
@@ -86,6 +86,7 @@ public class ValueEditor<T> extends Composite implements HasEditorErrors<T>,
editPanel.setVisible(true);
}
+ @Override
public ValueBoxEditor<T> asEditor() {
if (editProxy == null) {
editProxy = new EditorProxy();
@@ -133,6 +134,7 @@ public class ValueEditor<T> extends Composite implements HasEditorErrors<T>,
startHandlers.enabled = enabled;
}
+ @Override
public void showErrors(List<EditorError> errors) {
StringBuilder buf = new StringBuilder();
for (EditorError error : errors) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml
index 9862848064..e5f66499df 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml
@@ -18,7 +18,7 @@ limitations under the License.
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
>
-<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
+<ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
<ui:style>
.panel {
position: relative;
@@ -49,7 +49,7 @@ limitations under the License.
<g:HTMLPanel stylePrimaryName='{style.panel}'>
<g:Image
ui:field='editIcon'
- resource='{res.editText}'
+ resource='{ico.edit}'
stylePrimaryName='{style.editIcon}'
title='Edit'>
<ui:attribute name='title'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
index 7490d82619..2b40954eec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
@@ -17,6 +17,7 @@ package com.google.gerrit.client.api;
import com.google.gerrit.client.actions.ActionButton;
import com.google.gerrit.client.actions.ActionInfo;
import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.projects.BranchInfo;
import com.google.gerrit.client.rpc.GerritCallback;
@@ -39,6 +40,7 @@ public class ActionContext extends JavaScriptObject {
go: Gerrit.go,
refresh: Gerrit.refresh,
refreshMenuBar: Gerrit.refreshMenuBar,
+ isSignedIn: Gerrit.isSignedIn,
showError: Gerrit.showError,
br: function(){return doc.createElement('br')},
@@ -137,6 +139,7 @@ public class ActionContext extends JavaScriptObject {
final native void set(ActionInfo a) /*-{ this.action=a; }-*/;
final native void set(ChangeInfo c) /*-{ this.change=c; }-*/;
+ final native void set(EditInfo e) /*-{ this.edit=e; }-*/;
final native void set(Project.NameKey p) /*-{ this.project=p; }-*/;
final native void set(BranchInfo b) /*-{ this.branch=b }-*/;
final native void set(RevisionInfo r) /*-{ this.revision=r; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
index 8da896e36b..b52efe8565 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
@@ -16,6 +16,7 @@ package com.google.gerrit.client.api;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountInfo;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.History;
@@ -41,6 +42,7 @@ public class ApiGlue {
plugins: {},
screens: {},
change_actions: {},
+ edit_actions: {},
revision_actions: {},
project_actions: {},
branch_actions: {},
@@ -64,13 +66,16 @@ public class ApiGlue {
go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
refreshMenuBar: @com.google.gerrit.client.api.ApiGlue::refreshMenuBar(),
+ isSignedIn: @com.google.gerrit.client.api.ApiGlue::isSignedIn(),
showError: @com.google.gerrit.client.api.ApiGlue::showError(Ljava/lang/String;),
+ getCurrentUser: @com.google.gerrit.client.api.ApiGlue::getCurrentUser(),
on: function (e,f){(this.events[e] || (this.events[e]=[])).push(f)},
onAction: function (t,n,c){this._onAction(this.getPluginName(),t,n,c)},
_onAction: function (p,t,n,c) {
var i = p+'~'+n;
if ('change' == t) this.change_actions[i]=c;
+ else if ('edit' == t) this.edit_actions[i]=c;
else if ('revision' == t) this.revision_actions[i]=c;
else if ('project' == t) this.project_actions[i]=c;
else if ('branch' == t) this.branch_actions[i]=c;
@@ -192,10 +197,18 @@ public class ApiGlue {
Gerrit.display(History.getToken());
}
+ private static final AccountInfo getCurrentUser() {
+ return Gerrit.getUserAccountInfo();
+ }
+
private static final void refreshMenuBar() {
Gerrit.refreshMenuBar();
}
+ private static final boolean isSignedIn() {
+ return Gerrit.isSignedIn();
+ }
+
private static final void showError(String message) {
new ErrorDialog(message).center();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/EditGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/EditGlue.java
new file mode 100644
index 0000000000..ebcafb8d84
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/EditGlue.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 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.client.api;
+
+import com.google.gerrit.client.actions.ActionButton;
+import com.google.gerrit.client.actions.ActionInfo;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class EditGlue {
+ public static void onAction(
+ ChangeInfo change,
+ EditInfo edit,
+ ActionInfo action,
+ ActionButton button) {
+ RestApi api = ChangeApi.edit(
+ change.legacy_id().get())
+ .view(action.id());
+
+ JavaScriptObject f = get(action.id());
+ if (f != null) {
+ ActionContext c = ActionContext.create(api);
+ c.set(action);
+ c.set(change);
+ c.set(edit);
+ c.button(button);
+ ApiGlue.invoke(f, c);
+ } else {
+ DefaultActions.invoke(change, action, api);
+ }
+ }
+
+ private static final native JavaScriptObject get(String id) /*-{
+ return $wnd.Gerrit.edit_actions[id];
+ }-*/;
+
+ private EditGlue() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
index 7ef022a62b..931a04de10 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
@@ -50,9 +50,11 @@ final class Plugin extends JavaScriptObject {
var G = $wnd.Gerrit;
@com.google.gerrit.client.api.Plugin::TYPE.prototype = {
getPluginName: function(){return this.name},
+ getCurrentUser: @com.google.gerrit.client.api.ApiGlue::getCurrentUser(),
go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
refreshMenuBar: @com.google.gerrit.client.api.ApiGlue::refreshMenuBar(),
+ isSignedIn: @com.google.gerrit.client.api.ApiGlue::isSignedIn(),
showError: @com.google.gerrit.client.api.ApiGlue::showError(Ljava/lang/String;),
on: function(e,f){G.on(e,f)},
onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
index bd90cd37b2..67b8a980a9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
@@ -81,6 +81,7 @@ class PluginName {
/** Extracts URL from the stack frame. */
static class PluginNameMoz extends PluginName {
+ @Override
String findCallerUrl() {
return getUrl(makeException());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowRight.gif b/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowRight.gif
deleted file mode 100644
index d9e63a5754..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowRight.gif
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java
index 60fbee406e..54b83f1937 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java
@@ -30,6 +30,7 @@ class AbandonAction extends ActionMessageBox {
this.id = id;
}
+ @Override
void send(String message) {
ChangeApi.abandon(id.get(), message, new GerritCallback<ChangeInfo>() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
index 45f122c593..c560f6d905 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
@@ -98,7 +98,7 @@ abstract class ActionMessageBox extends Composite {
}
@UiHandler("send")
- void onSend(ClickEvent e) {
+ void onSend(@SuppressWarnings("unused") ClickEvent e) {
send(message.getValue().trim());
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
index 906efcad25..edf11058e3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -38,15 +38,12 @@ class Actions extends Composite {
private static final String[] CORE = {
"abandon", "restore", "revert", "topic",
"cherrypick", "submit", "rebase", "message",
- "publish", "/"};
+ "publish", "followup", "/"};
interface Binder extends UiBinder<FlowPanel, Actions> {}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField Button cherrypick;
- @UiField Button deleteChange;
- @UiField Button deleteRevision;
- @UiField Button publish;
@UiField Button rebase;
@UiField Button revert;
@UiField Button submit;
@@ -57,12 +54,17 @@ class Actions extends Composite {
@UiField Button restore;
private RestoreAction restoreAction;
+ @UiField Button followUp;
+ private FollowUpAction followUpAction;
+
private Change.Id changeId;
private ChangeInfo changeInfo;
private String revision;
private String project;
private String subject;
private String message;
+ private String branch;
+ private String key;
private boolean canSubmit;
Actions() {
@@ -80,6 +82,8 @@ class Actions extends Composite {
project = info.project();
subject = commit.subject();
message = commit.message();
+ branch = info.branch();
+ key = info.change_id();
changeInfo = info;
initChangeActions(info, hasUser);
@@ -93,10 +97,10 @@ class Actions extends Composite {
actions.copyKeysIntoChildren("id");
if (hasUser) {
- a2b(actions, "/", deleteChange);
a2b(actions, "abandon", abandon);
a2b(actions, "restore", restore);
a2b(actions, "revert", revert);
+ a2b(actions, "followup", followUp);
for (String id : filterNonCore(actions)) {
add(new ActionButton(info, actions.get(id)));
}
@@ -116,15 +120,18 @@ class Actions extends Composite {
if (canSubmit) {
ActionInfo action = actions.get("submit");
submit.setTitle(action.title());
+ submit.setEnabled(action.enabled());
submit.setHTML(new SafeHtmlBuilder()
.openDiv()
.append(action.label())
.closeDiv());
}
- a2b(actions, "/", deleteRevision);
a2b(actions, "cherrypick", cherrypick);
- a2b(actions, "publish", publish);
a2b(actions, "rebase", rebase);
+ if (rebase.isVisible()) {
+ // it is the rebase button in RebaseDialog that the server wants to disable
+ rebase.setEnabled(true);
+ }
for (String id : filterNonCore(actions)) {
add(new ActionButton(info, revInfo, actions.get(id)));
}
@@ -147,35 +154,25 @@ class Actions extends Composite {
submit.setVisible(canSubmit);
}
- boolean isSubmitEnabled() {
- return submit.isVisible() && submit.isEnabled();
+ @UiHandler("followUp")
+ void onFollowUp(@SuppressWarnings("unused") ClickEvent e) {
+ if (followUpAction == null) {
+ followUpAction = new FollowUpAction(followUp, project,
+ branch, key);
+ }
+ followUpAction.show();
}
@UiHandler("abandon")
- void onAbandon(ClickEvent e) {
+ void onAbandon(@SuppressWarnings("unused") ClickEvent e) {
if (abandonAction == null) {
abandonAction = new AbandonAction(abandon, changeId);
}
abandonAction.show();
}
- @UiHandler("publish")
- void onPublish(ClickEvent e) {
- DraftActions.publish(changeId, revision);
- }
-
- @UiHandler("deleteRevision")
- void onDeleteRevision(ClickEvent e) {
- DraftActions.delete(changeId, revision);
- }
-
- @UiHandler("deleteChange")
- void onDeleteChange(ClickEvent e) {
- DraftActions.delete(changeId);
- }
-
@UiHandler("restore")
- void onRestore(ClickEvent e) {
+ void onRestore(@SuppressWarnings("unused") ClickEvent e) {
if (restoreAction == null) {
restoreAction = new RestoreAction(restore, changeId);
}
@@ -183,29 +180,40 @@ class Actions extends Composite {
}
@UiHandler("rebase")
- void onRebase(ClickEvent e) {
- RebaseAction.call(changeId, revision);
+ void onRebase(@SuppressWarnings("unused") ClickEvent e) {
+ boolean enabled = true;
+ RevisionInfo revInfo = changeInfo.revision(revision);
+ if (revInfo.has_actions()) {
+ NativeMap<ActionInfo> actions = revInfo.actions();
+ if (actions.containsKey("rebase")) {
+ enabled = actions.get("rebase").enabled();
+ }
+ }
+ RebaseAction.call(rebase, project, changeInfo.branch(), changeId, revision,
+ enabled);
}
@UiHandler("submit")
- void onSubmit(ClickEvent e) {
+ void onSubmit(@SuppressWarnings("unused") ClickEvent e) {
SubmitAction.call(changeInfo, changeInfo.revision(revision));
}
@UiHandler("cherrypick")
- void onCherryPick(ClickEvent e) {
+ void onCherryPick(@SuppressWarnings("unused") ClickEvent e) {
CherryPickAction.call(cherrypick, changeInfo, revision, project, message);
}
@UiHandler("revert")
- void onRevert(ClickEvent e) {
- RevertAction.call(revert, changeId, revision, project, subject);
+ void onRevert(@SuppressWarnings("unused") ClickEvent e) {
+ RevertAction.call(revert, changeId, revision, subject);
}
private static void a2b(NativeMap<ActionInfo> actions, String a, Button b) {
if (actions.containsKey(a)) {
b.setVisible(true);
- b.setTitle(actions.get(a).title());
+ ActionInfo actionInfo = actions.get(a);
+ b.setTitle(actionInfo.title());
+ b.setEnabled(actionInfo.enabled());
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
index cb2b37f36c..40d732a452 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
@@ -18,6 +18,8 @@ limitations under the License.
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:style>
+ @def BUTTON_HEIGHT 14px;
+
#change_actions {
padding-top: 2px;
padding-bottom: 20px;
@@ -25,40 +27,24 @@ limitations under the License.
#change_actions button {
margin: 6px 3px 0 0;
- border-color: rgba(0, 0, 0, 0.1);
text-align: center;
font-size: 8pt;
font-weight: bold;
- border: 1px solid;
+ border: 2px solid;
cursor: pointer;
- color: #444;
+ color: rgba(0, 0, 0, 0.15);
background-color: #f5f5f5;
- background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
-webkit-border-radius: 2px;
-webkit-box-sizing: content-box;
}
#change_actions button div {
- color: #444;
- height: 10px;
+ color: #444;
min-width: 54px;
- line-height: 10px;
white-space: nowrap;
+ height: BUTTON_HEIGHT;
+ line-height: BUTTON_HEIGHT;
}
- #change_actions button {
- color: #444;
- background-color: #f5f5f5;
- background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
- }
- #change_actions button div {color: #444;}
-
- #change_actions button.red {
- color: #d14836;
- background-color: #d14836;
- background-image: -webkit-linear-gradient(top, #d14836, #d14836);
- }
- #change_actions button.red div {color: #fff;}
-
#change_actions button.submit {
float: right;
background-color: #4d90fe;
@@ -83,22 +69,15 @@ limitations under the License.
<g:Button ui:field='revert' styleName='' visible='false'>
<div><ui:msg>Revert</ui:msg></div>
</g:Button>
- <g:Button ui:field='deleteChange' styleName='' visible='false'>
- <div><ui:msg>Delete Change</ui:msg></div>
- </g:Button>
- <g:Button ui:field='deleteRevision' styleName='' visible='false'>
- <div><ui:msg>Delete Revision</ui:msg></div>
- </g:Button>
- <g:Button ui:field='publish' styleName='' visible='false'>
- <div><ui:msg>Publish</ui:msg></div>
- </g:Button>
-
- <g:Button ui:field='abandon' styleName='{style.red}' visible='false'>
+ <g:Button ui:field='abandon' styleName='' visible='false'>
<div><ui:msg>Abandon</ui:msg></div>
</g:Button>
- <g:Button ui:field='restore' styleName='{style.red}' visible='false'>
+ <g:Button ui:field='restore' styleName='' visible='false'>
<div><ui:msg>Restore</ui:msg></div>
</g:Button>
+ <g:Button ui:field='followUp' styleName='' visible='false'>
+ <div><ui:msg>Follow-Up</ui:msg></div>
+ </g:Button>
<g:Button ui:field='submit' styleName='{style.submit}' visible='false'/>
</g:FlowPanel>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
new file mode 100644
index 0000000000..f74ebf67e5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
@@ -0,0 +1,74 @@
+//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.client.change;
+
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+class AddFileAction {
+ private final Change.Id changeId;
+ private final RevisionInfo revision;
+ private final ChangeScreen.Style style;
+ private final Widget addButton;
+ private final FileTable files;
+
+ private AddFileBox addBox;
+ private PopupPanel popup;
+
+ AddFileAction(Change.Id changeId, RevisionInfo revision,
+ ChangeScreen.Style style, Widget addButton, FileTable files) {
+ this.changeId = changeId;
+ this.revision = revision;
+ this.style = style;
+ this.addButton = addButton;
+ this.files = files;
+ }
+
+ public void onEdit() {
+ if (popup != null) {
+ popup.hide();
+ return;
+ }
+
+ files.unregisterKeys();
+ if (addBox == null) {
+ addBox = new AddFileBox(changeId, revision, files);
+ }
+ addBox.clearPath();
+
+ final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+ p.setStyleName(style.replyBox());
+ p.addAutoHidePartner(addButton.getElement());
+ p.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (popup == p) {
+ popup = null;
+ }
+ }
+ });
+ p.add(addBox);
+ p.showRelativeTo(addButton);
+ GlobalKey.dialog(p);
+ addBox.setFocus(true);
+ popup = p;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
new file mode 100644
index 0000000000..7245e47f91
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
@@ -0,0 +1,109 @@
+//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.client.change;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+class AddFileBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, AddFileBox> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ private final Change.Id changeId;
+ private final RevisionInfo revision;
+ private final FileTable fileTable;
+
+ @UiField Button open;
+ @UiField Button cancel;
+
+ @UiField(provided = true)
+ RemoteSuggestBox path;
+
+ AddFileBox(Change.Id changeId, RevisionInfo revision, FileTable files) {
+ this.changeId = changeId;
+ this.revision = revision;
+ this.fileTable = files;
+
+ path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision));
+ path.addSelectionHandler(new SelectionHandler<String>() {
+ @Override
+ public void onSelection(SelectionEvent<String> event) {
+ open(event.getSelectedItem());
+ }
+ });
+ path.addCloseHandler(new CloseHandler<RemoteSuggestBox>() {
+ @Override
+ public void onClose(CloseEvent<RemoteSuggestBox> event) {
+ hide();
+ fileTable.registerKeys();
+ }
+ });
+
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ void setFocus(boolean focus) {
+ path.setFocus(focus);
+ }
+
+ void clearPath() {
+ path.setText("");
+ }
+
+ @UiHandler("open")
+ void onOpen(@SuppressWarnings("unused") ClickEvent e) {
+ open(path.getText());
+ }
+
+ private void open(String path) {
+ hide();
+ Gerrit.display(Dispatcher.toEditScreen(
+ new PatchSet.Id(changeId, revision._number()),
+ path));
+ }
+
+ @UiHandler("cancel")
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
+ hide();
+ fileTable.registerKeys();
+ }
+
+ private void hide() {
+ for (Widget w = getParent(); w != null; w = w.getParent()) {
+ if (w instanceof PopupPanel) {
+ ((PopupPanel) w).hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.ui.xml
index 9df4bbccb9..d8236e6827 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.ui.xml
@@ -16,30 +16,22 @@ limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
- xmlns:g='urn:import:com.google.gwt.user.client.ui'
- xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'>
+ xmlns:u='urn:import:com.google.gerrit.client.ui'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
<ui:style>
- .commitMessage {
- background-color: white;
- font-family: monospace;
- }
.cancel { float: right; }
</ui:style>
<g:HTMLPanel>
<div class='{res.style.section}'>
- <c:NpTextArea
- visibleLines='30'
- characterWidth='78'
- styleName='{style.commitMessage}'
- ui:field='message'/>
+ <ui:msg>Path: <u:RemoteSuggestBox ui:field='path' visibleLength='86'/></ui:msg>
</div>
<div class='{res.style.section}'>
- <g:Button ui:field='save'
- title='Create new patch set with updated commit message'
+ <g:Button ui:field='open'
+ title='Open file in editor'
styleName='{res.style.button}'>
<ui:attribute name='title'/>
- <div><ui:msg>Save</ui:msg></div>
+ <div><ui:msg>Open</ui:msg></div>
</g:Button>
<g:Button ui:field='cancel'
styleName='{res.style.button}'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
index 11a18249e0..be6879eba2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
@@ -21,6 +21,10 @@ interface ChangeConstants extends Constants {
String nextChange();
String openChange();
String reviewedFileTitle();
+ String editMessage();
+ String editFileInline();
+ String removeFileInline();
+ String restoreFileInline();
String openLastFile();
String openCommitMessage();
@@ -43,4 +47,10 @@ interface ChangeConstants extends Constants {
String sameTopicTooltip();
String noChanges();
String indirectAncestor();
+ String merged();
+ String abandoned();
+
+ String deleteChangeEdit();
+ String deleteDraftChange();
+ String deleteDraftRevision();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties
index 289e9b4870..682cd186f5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties
@@ -2,6 +2,10 @@ previousChange = Previous related change
nextChange = Next related change
openChange = Open related change
reviewedFileTitle = Mark file as reviewed (Shortcut: r)
+editMessage = Edit commit message
+editFileInline = Edit file inline
+removeFileInline = Remove file inline
+restoreFileInline = Restore file inline
openLastFile = Open last file
openCommitMessage = Open commit message
@@ -24,3 +28,11 @@ sameTopic = Same Topic
sameTopicTooltip = Changes with the same topic
noChanges = No Changes
indirectAncestor = Indirect ancestor
+merged = Merged
+abandoned = Abandoned
+
+deleteChangeEdit = Delete Change Edit?\n\
+ \n\
+ All changes made in the edit revision will be lost.
+deleteDraftChange = Delete Draft Change?
+deleteDraftRevision = Delete Draft Revision?
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java
index 595a6c95f9..8d5b72ca38 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java
@@ -17,7 +17,7 @@ package com.google.gerrit.client.change;
import com.google.gwt.i18n.client.Messages;
public interface ChangeMessages extends Messages {
- String patchSets(int currentlyViewedPatchSet, int currentPatchSet);
+ String patchSets(String currentlyViewedPatchSet, int currentPatchSet);
String changeWithNoRevisions(int changeId);
String relatedChanges(int count);
String relatedChanges(String count);
@@ -27,4 +27,5 @@ public interface ChangeMessages extends Messages {
String cherryPicks(String count);
String sameTopic(int count);
String sameTopic(String count);
+ String editPatchSet(int patchSet);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties
index 5fddd8ea40..6e095fb1d0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties
@@ -4,3 +4,4 @@ relatedChanges = Related Changes ({0})
conflictingChanges = Conflicts With ({0})
cherryPicks = Cherry-Picks ({0})
sameTopic = Same Topic ({0})
+editPatchSet = edit:{0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 69cd3fcda4..4d7dac206c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -24,6 +24,8 @@ import com.google.gerrit.client.api.ChangeGlue;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.changes.ChangeList;
@@ -50,8 +52,8 @@ import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.client.ui.UserActivityMonitor;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.extensions.common.ListChangesOption;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -75,6 +77,7 @@ import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
@@ -86,15 +89,18 @@ import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import com.google.gwtorm.client.KeyUtil;
+import net.codemirror.lib.CodeMirror;
+
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
-public class ChangeScreen2 extends Screen {
- interface Binder extends UiBinder<HTMLPanel, ChangeScreen2> {}
+public class ChangeScreen extends Screen {
+ interface Binder extends UiBinder<HTMLPanel, ChangeScreen> {}
private static final Binder uiBinder = GWT.create(Binder.class);
interface Style extends CssResource {
@@ -107,14 +113,15 @@ public class ChangeScreen2 extends Screen {
String label_need();
String replyBox();
String selected();
+ String hashtagName();
}
- static ChangeScreen2 get(NativeEvent in) {
+ static ChangeScreen get(NativeEvent in) {
Element e = in.getEventTarget().cast();
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
EventListener l = DOM.getEventListener(e);
- if (l instanceof ChangeScreen2) {
- return (ChangeScreen2) l;
+ if (l instanceof ChangeScreen) {
+ return (ChangeScreen) l;
}
}
return null;
@@ -125,6 +132,7 @@ public class ChangeScreen2 extends Screen {
private String revision;
private ChangeInfo changeInfo;
private CommentLinkProcessor commentLinkProcessor;
+ private EditInfo edit;
private KeyCommandSet keysNavigation;
private KeyCommandSet keysAction;
@@ -134,6 +142,7 @@ public class ChangeScreen2 extends Screen {
private UpdateAvailableBar updateAvailable;
private boolean openReplyBox;
private boolean loaded;
+ private FileTable.Mode fileTableMode;
@UiField HTMLPanel headerLine;
@UiField Style style;
@@ -142,6 +151,8 @@ public class ChangeScreen2 extends Screen {
@UiField Element ccText;
@UiField Reviewers reviewers;
+ @UiField Hashtags hashtags;
+ @UiField Element hashtagTableRow;
@UiField FlowPanel ownerPanel;
@UiField InlineHyperlink ownerLink;
@UiField Element statusText;
@@ -169,23 +180,37 @@ public class ChangeScreen2 extends Screen {
@UiField Element patchSetsText;
@UiField Button download;
@UiField Button reply;
+ @UiField Button publishEdit;
+ @UiField Button rebaseEdit;
+ @UiField Button deleteEdit;
+ @UiField Button publish;
+ @UiField Button deleteChange;
+ @UiField Button deleteRevision;
@UiField Button openAll;
+ @UiField Button editMode;
+ @UiField Button reviewMode;
+ @UiField Button addFile;
+ @UiField Button deleteFile;
+ @UiField Button renameFile;
@UiField Button expandAll;
@UiField Button collapseAll;
- @UiField Button editMessage;
@UiField QuickApprove quickApprove;
private ReplyAction replyAction;
- private EditMessageAction editMessageAction;
private IncludedInAction includedInAction;
private PatchSetsAction patchSetsAction;
private DownloadAction downloadAction;
+ private AddFileAction addFileAction;
+ private DeleteFileAction deleteFileAction;
+ private RenameFileAction renameFileAction;
- public ChangeScreen2(Change.Id changeId, String base, String revision, boolean openReplyBox) {
+ public ChangeScreen(Change.Id changeId, String base, String revision,
+ boolean openReplyBox, FileTable.Mode mode) {
this.changeId = changeId;
this.base = normalize(base);
this.revision = normalize(revision);
this.openReplyBox = openReplyBox;
+ this.fileTableMode = mode;
add(uiBinder.createAndBindUi(this));
}
@@ -196,21 +221,35 @@ public class ChangeScreen2 extends Screen {
@Override
protected void onLoad() {
super.onLoad();
- loadChangeInfo(true, new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo info) {
- info.init();
- loadConfigInfo(info, base);
- }
- });
+ CallbackGroup group = new CallbackGroup();
+ if (Gerrit.isSignedIn()) {
+ ChangeApi.editWithFiles(changeId.get(), group.add(
+ new AsyncCallback<EditInfo>() {
+ @Override
+ public void onSuccess(EditInfo result) {
+ edit = result;
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+ }
+ loadChangeInfo(true, group.addFinal(
+ new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo info) {
+ info.init();
+ loadConfigInfo(info, base);
+ }
+ }));
}
void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) {
RestApi call = ChangeApi.detail(changeId.get());
ChangeList.addOptions(call, EnumSet.of(
ListChangesOption.CURRENT_ACTIONS,
- ListChangesOption.ALL_REVISIONS,
- ListChangesOption.WEB_LINKS));
+ ListChangesOption.ALL_REVISIONS));
if (!fg) {
call.background();
}
@@ -239,8 +278,9 @@ public class ChangeScreen2 extends Screen {
setHeaderVisible(false);
Resources.I.style().ensureInjected();
star.setVisible(Gerrit.isSignedIn());
- labels.init(style, statusText);
+ labels.init(style);
reviewers.init(style, ccText);
+ hashtags.init(style);
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
@@ -306,6 +346,17 @@ public class ChangeScreen2 extends Screen {
}
}
+ private void initReplyButton(ChangeInfo info, String revision) {
+ if (!info.revision(revision).is_edit()) {
+ reply.setTitle(Gerrit.getConfig().getReplyTitle());
+ reply.setHTML(new SafeHtmlBuilder()
+ .openDiv()
+ .append(Gerrit.getConfig().getReplyLabel())
+ .closeDiv());
+ reply.setVisible(true);
+ }
+ }
+
private void gotoSibling(final int offset) {
if (offset > 0 && changeInfo.current_revision().equals(revision)) {
return;
@@ -339,6 +390,19 @@ public class ChangeScreen2 extends Screen {
}
}
+ private void initChangeAction(ChangeInfo info) {
+ if (info.status() == Status.DRAFT) {
+ NativeMap<ActionInfo> actions = info.has_actions()
+ ? info.actions()
+ : NativeMap.<ActionInfo> create();
+ actions.copyKeysIntoChildren("id");
+ if (actions.containsKey("/")) {
+ deleteChange.setVisible(true);
+ deleteChange.setTitle(actions.get("/").title());
+ }
+ }
+ }
+
private void initRevisionsAction(ChangeInfo info, String revision) {
int currentPatchSet;
if (info.current_revision() != null
@@ -350,12 +414,37 @@ public class ChangeScreen2 extends Screen {
currentPatchSet = revList.get(revList.length() - 1)._number();
}
- int currentlyViewedPatchSet = info.revision(revision)._number();
+ String currentlyViewedPatchSet;
+ if (info.revision(revision).id().equals("edit")) {
+ currentlyViewedPatchSet =
+ Resources.M.editPatchSet(RevisionInfo.findEditParent(info.revisions()
+ .values()));
+ currentPatchSet = info.revisions().values().length() - 1;
+ } else {
+ currentlyViewedPatchSet = info.revision(revision).id();
+ }
patchSetsText.setInnerText(Resources.M.patchSets(
currentlyViewedPatchSet, currentPatchSet));
patchSetsAction = new PatchSetsAction(
- info.legacy_id(), revision,
+ info.legacy_id(), revision, edit,
style, headerLine, patchSets);
+
+ RevisionInfo revInfo = info.revision(revision);
+ if (revInfo.draft()) {
+ NativeMap<ActionInfo> actions = revInfo.has_actions()
+ ? revInfo.actions()
+ : NativeMap.<ActionInfo> create();
+ actions.copyKeysIntoChildren("id");
+
+ if (actions.containsKey("publish")) {
+ publish.setVisible(true);
+ publish.setTitle(actions.get("publish").title());
+ }
+ if (actions.containsKey("/")) {
+ deleteRevision.setVisible(true);
+ deleteRevision.setTitle(actions.get("/").title());
+ }
+ }
}
private void initDownloadAction(ChangeInfo info, String revision) {
@@ -392,23 +481,85 @@ public class ChangeScreen2 extends Screen {
null)));
}
- private void initEditMessageAction(ChangeInfo info, String revision) {
- NativeMap<ActionInfo> actions = info.revision(revision).actions();
- if (actions != null && actions.containsKey("message")) {
- editMessage.setVisible(true);
- editMessageAction = new EditMessageAction(
- info.legacy_id(),
- revision,
- info.revision(revision).commit().message(),
- style,
- editMessage,
- reply);
- keysAction.add(new KeyCommand(0, 'e', Util.C.keyEditMessage()) {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- editMessageAction.onEdit();
+ private void initEditMode(ChangeInfo info, String revision) {
+ if (Gerrit.isSignedIn() && info.status().isOpen()) {
+ RevisionInfo rev = info.revision(revision);
+ if (isEditModeEnabled(info, rev)) {
+ editMode.setVisible(fileTableMode == FileTable.Mode.REVIEW);
+ addFile.setVisible(!editMode.isVisible());
+ deleteFile.setVisible(!editMode.isVisible());
+ renameFile.setVisible(!editMode.isVisible());
+ reviewMode.setVisible(!editMode.isVisible());
+ addFileAction = new AddFileAction(
+ changeId, info.revision(revision),
+ style, addFile, files);
+ deleteFileAction = new DeleteFileAction(
+ changeId, info.revision(revision),
+ style, addFile);
+ renameFileAction = new RenameFileAction(
+ changeId, info.revision(revision),
+ style, addFile);
+ } else {
+ editMode.setVisible(false);
+ addFile.setVisible(false);
+ reviewMode.setVisible(false);
+ }
+
+ if (rev.is_edit()) {
+ if (info.hasEditBasedOnCurrentPatchSet()) {
+ publishEdit.setVisible(true);
+ } else {
+ rebaseEdit.setVisible(true);
}
- });
+ deleteEdit.setVisible(true);
+ }
+ }
+ }
+
+ private boolean isEditModeEnabled(ChangeInfo info, RevisionInfo rev) {
+ if (rev.is_edit()) {
+ return true;
+ }
+ if (edit == null) {
+ return revision.equals(info.current_revision());
+ }
+ return rev._number() == RevisionInfo.findEditParent(
+ info.revisions().values());
+ }
+
+ @UiHandler("publishEdit")
+ void onPublishEdit(@SuppressWarnings("unused") ClickEvent e) {
+ EditActions.publishEdit(changeId);
+ }
+
+ @UiHandler("rebaseEdit")
+ void onRebaseEdit(@SuppressWarnings("unused") ClickEvent e) {
+ EditActions.rebaseEdit(changeId);
+ }
+
+ @UiHandler("deleteEdit")
+ void onDeleteEdit(@SuppressWarnings("unused") ClickEvent e) {
+ if (Window.confirm(Resources.C.deleteChangeEdit())) {
+ EditActions.deleteEdit(changeId);
+ }
+ }
+
+ @UiHandler("publish")
+ void onPublish(@SuppressWarnings("unused") ClickEvent e) {
+ DraftActions.publish(changeId, revision);
+ }
+
+ @UiHandler("deleteRevision")
+ void onDeleteRevision(@SuppressWarnings("unused") ClickEvent e) {
+ if (Window.confirm(Resources.C.deleteDraftRevision())) {
+ DraftActions.delete(changeId, revision);
+ }
+ }
+
+ @UiHandler("deleteChange")
+ void onDeleteChange(@SuppressWarnings("unused") ClickEvent e) {
+ if (Window.confirm(Resources.C.deleteDraftChange())) {
+ DraftActions.delete(changeId);
}
}
@@ -438,16 +589,19 @@ public class ChangeScreen2 extends Screen {
}
ChangeGlue.fireShowChange(changeInfo, changeInfo.revision(revision));
+ CodeMirror.preload();
startPoller();
- if (NewChangeScreenBar.show()) {
- add(new NewChangeScreenBar(changeId));
- }
}
private void scrollToPath(String token) {
int s = token.indexOf('/');
try {
- if (s < 0 || !changeId.equals(Change.Id.parse(token.substring(0, s)))) {
+ String c = token.substring(0, s);
+ int editIndex = c.indexOf(",edit");
+ if (editIndex > 0) {
+ c = c.substring(0, editIndex);
+ }
+ if (s < 0 || !changeId.equals(Change.Id.parse(c))) {
return; // Unrelated URL, do not scroll.
}
} catch (IllegalArgumentException e) {
@@ -477,22 +631,22 @@ public class ChangeScreen2 extends Screen {
}
@UiHandler("includedIn")
- void onIncludedIn(ClickEvent e) {
+ void onIncludedIn(@SuppressWarnings("unused") ClickEvent e) {
includedInAction.show();
}
@UiHandler("download")
- void onDownload(ClickEvent e) {
+ void onDownload(@SuppressWarnings("unused") ClickEvent e) {
downloadAction.show();
}
@UiHandler("patchSets")
- void onPatchSets(ClickEvent e) {
+ void onPatchSets(@SuppressWarnings("unused") ClickEvent e) {
patchSetsAction.show();
}
@UiHandler("reply")
- void onReply(ClickEvent e) {
+ void onReply(@SuppressWarnings("unused") ClickEvent e) {
onReply();
}
@@ -510,18 +664,58 @@ public class ChangeScreen2 extends Screen {
}
}
- @UiHandler("editMessage")
- void onEditMessage(ClickEvent e) {
- editMessageAction.onEdit();
- }
-
@UiHandler("openAll")
- void onOpenAll(ClickEvent e) {
+ void onOpenAll(@SuppressWarnings("unused") ClickEvent e) {
files.openAll();
}
+ @UiHandler("editMode")
+ void onEditMode(@SuppressWarnings("unused") ClickEvent e) {
+ fileTableMode = FileTable.Mode.EDIT;
+ refreshFileTable();
+ editMode.setVisible(false);
+ addFile.setVisible(true);
+ deleteFile.setVisible(true);
+ renameFile.setVisible(true);
+ reviewMode.setVisible(true);
+ }
+
+ @UiHandler("reviewMode")
+ void onReviewMode(@SuppressWarnings("unused") ClickEvent e) {
+ fileTableMode = FileTable.Mode.REVIEW;
+ refreshFileTable();
+ editMode.setVisible(true);
+ addFile.setVisible(false);
+ deleteFile.setVisible(false);
+ renameFile.setVisible(false);
+ reviewMode.setVisible(false);
+ }
+
+ @UiHandler("addFile")
+ void onAddFile(@SuppressWarnings("unused") ClickEvent e) {
+ addFileAction.onEdit();
+ }
+
+ @UiHandler("deleteFile")
+ void onDeleteFile(@SuppressWarnings("unused") ClickEvent e) {
+ deleteFileAction.onDelete();
+ }
+
+ @UiHandler("renameFile")
+ void onRenameFile(@SuppressWarnings("unused") ClickEvent e) {
+ renameFileAction.onRename();
+ }
+
+ private void refreshFileTable() {
+ int idx = diffBase.getSelectedIndex();
+ if (0 <= idx) {
+ String n = diffBase.getValue(idx);
+ loadConfigInfo(changeInfo, !n.isEmpty() ? n : null);
+ }
+ }
+
@UiHandler("expandAll")
- void onExpandAll(ClickEvent e) {
+ void onExpandAll(@SuppressWarnings("unused") ClickEvent e) {
int n = history.getWidgetCount();
for (int i = 0; i < n; i++) {
((Message) history.getWidget(i)).setOpen(true);
@@ -531,7 +725,7 @@ public class ChangeScreen2 extends Screen {
}
@UiHandler("collapseAll")
- void onCollapseAll(ClickEvent e) {
+ void onCollapseAll(@SuppressWarnings("unused") ClickEvent e) {
int n = history.getWidgetCount();
for (int i = 0; i < n; i++) {
((Message) history.getWidget(i)).setOpen(false);
@@ -541,7 +735,7 @@ public class ChangeScreen2 extends Screen {
}
@UiHandler("diffBase")
- void onChangeRevision(ChangeEvent e) {
+ void onChangeRevision(@SuppressWarnings("unused") ChangeEvent e) {
int idx = diffBase.getSelectedIndex();
if (0 <= idx) {
String n = diffBase.getValue(idx);
@@ -551,11 +745,50 @@ public class ChangeScreen2 extends Screen {
private void loadConfigInfo(final ChangeInfo info, final String base) {
info.revisions().copyKeysIntoChildren("name");
+ if (edit != null) {
+ edit.set_name(edit.commit().commit());
+ info.set_edit(edit);
+ if (edit.has_files()) {
+ edit.files().copyKeysIntoChildren("path");
+ }
+ info.revisions().put(edit.name(), RevisionInfo.fromEdit(edit));
+ JsArray<RevisionInfo> list = info.revisions().values();
+
+ // Edit is converted to a regular revision (with number = 0) and
+ // added to the list of revisions. Additionally under certain
+ // circumstances change edit is assigned to be the current revision
+ // and is selected to be shown on the change screen.
+ // We have two different strategies to assign edit to the current ps:
+ // 1. revision == null: no revision is selected, so use the edit only
+ // if it is based on the latest patch set
+ // 2. edit was selected explicitly from ps drop down:
+ // use the edit regardless of which patch set it is based on
+ if (revision == null) {
+ RevisionInfo.sortRevisionInfoByNumber(list);
+ RevisionInfo rev = list.get(list.length() - 1);
+ if (rev.is_edit()) {
+ info.set_current_revision(rev.name());
+ }
+ } else if (revision.equals("edit") || revision.equals("0")) {
+ for (int i = 0; i < list.length(); i++) {
+ RevisionInfo r = list.get(i);
+ if (r.is_edit()) {
+ info.set_current_revision(r.name());
+ break;
+ }
+ }
+ }
+ }
final RevisionInfo rev = resolveRevisionToDisplay(info);
final RevisionInfo b = resolveRevisionOrPatchSetId(info, base, null);
CallbackGroup group = new CallbackGroup();
- loadDiff(b, rev, myLastReply(info), group);
+ Timestamp lastReply = myLastReply(info);
+ if (rev.is_edit()) {
+ loadFileList(b, rev, lastReply, group, null, null);
+ } else {
+ loadDiff(b, rev, lastReply, group);
+ }
loadCommit(rev, group);
if (loaded) {
@@ -594,24 +827,9 @@ public class ChangeScreen2 extends Screen {
final Timestamp myLastReply, CallbackGroup group) {
final List<NativeMap<JsArray<CommentInfo>>> comments = loadComments(rev, group);
final List<NativeMap<JsArray<CommentInfo>>> drafts = loadDrafts(rev, group);
- DiffApi.list(changeId.get(),
- base != null ? base.name() : null,
- rev.name(),
- group.add(new AsyncCallback<NativeMap<FileInfo>>() {
- @Override
- public void onSuccess(NativeMap<FileInfo> m) {
- files.setRevisions(
- base != null ? new PatchSet.Id(changeId, base._number()) : null,
- new PatchSet.Id(changeId, rev._number()));
- files.setValue(m, myLastReply, comments.get(0), drafts.get(0));
- }
-
- @Override
- public void onFailure(Throwable caught) {
- }
- }));
+ loadFileList(base, rev, myLastReply, group, comments, drafts);
- if (Gerrit.isSignedIn()) {
+ if (Gerrit.isSignedIn() && fileTableMode == FileTable.Mode.REVIEW) {
ChangeApi.revision(changeId.get(), rev.name())
.view("files")
.addParameterTrue("reviewed")
@@ -628,6 +846,31 @@ public class ChangeScreen2 extends Screen {
}
}
+ private void loadFileList(final RevisionInfo base, final RevisionInfo rev,
+ final Timestamp myLastReply, CallbackGroup group,
+ final List<NativeMap<JsArray<CommentInfo>>> comments,
+ final List<NativeMap<JsArray<CommentInfo>>> drafts) {
+ DiffApi.list(changeId.get(),
+ base != null ? base.name() : null,
+ rev.name(),
+ group.add(new AsyncCallback<NativeMap<FileInfo>>() {
+ @Override
+ public void onSuccess(NativeMap<FileInfo> m) {
+ files.set(
+ base != null ? new PatchSet.Id(changeId, base._number()) : null,
+ new PatchSet.Id(changeId, rev._number()),
+ style, reply, fileTableMode, edit != null);
+ files.setValue(m, myLastReply,
+ comments != null ? comments.get(0) : null,
+ drafts != null ? drafts.get(0) : null);
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+ }
+
private List<NativeMap<JsArray<CommentInfo>>> loadComments(
RevisionInfo rev, CallbackGroup group) {
final int id = rev._number();
@@ -671,18 +914,21 @@ public class ChangeScreen2 extends Screen {
}
private void loadCommit(final RevisionInfo rev, CallbackGroup group) {
- ChangeApi.revision(changeId.get(), rev.name())
- .view("commit")
- .get(group.add(new AsyncCallback<CommitInfo>() {
- @Override
- public void onSuccess(CommitInfo info) {
- rev.set_commit(info);
- }
+ if (rev.is_edit()) {
+ return;
+ }
- @Override
- public void onFailure(Throwable caught) {
- }
- }));
+ ChangeApi.commitWithLinks(changeId.get(), rev.name(),
+ group.add(new AsyncCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(CommitInfo info) {
+ rev.set_commit(info);
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
}
private void loadSubmitType(final Change.Status status, final boolean canSubmit) {
@@ -769,28 +1015,59 @@ public class ChangeScreen2 extends Screen {
return revOrId != null ? info.revision(revOrId) : null;
}
+ private boolean isSubmittable(ChangeInfo info) {
+ boolean canSubmit = info.status().isOpen();
+ if (canSubmit && info.status() == Change.Status.NEW) {
+ for (String name : info.labels()) {
+ LabelInfo label = info.label(name);
+ switch (label.status()) {
+ case NEED:
+ statusText.setInnerText("Needs " + name);
+ canSubmit = false;
+ break;
+ case REJECT:
+ case IMPOSSIBLE:
+ if (label.blocking()) {
+ statusText.setInnerText("Not " + name);
+ canSubmit = false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return canSubmit;
+ }
+
private void renderChangeInfo(ChangeInfo info) {
changeInfo = info;
lastDisplayedUpdate = info.updated();
- boolean current = info.status().isOpen()
- && revision.equals(info.current_revision());
+ RevisionInfo revisionInfo = info.revision(revision);
+ boolean current = revision.equals(info.current_revision())
+ && !revisionInfo.is_edit();
- if (!current && info.status() == Change.Status.NEW) {
+ if (revisionInfo.is_edit()) {
+ statusText.setInnerText(Util.C.changeEdit());
+ } else if (!current) {
statusText.setInnerText(Util.C.notCurrent());
labels.setVisible(false);
} else {
statusText.setInnerText(Util.toLongString(info.status()));
}
- boolean canSubmit = labels.set(info, current);
+ labels.set(info);
renderOwner(info);
renderActionTextDate(info);
renderDiffBaseListBox(info);
+ initReplyButton(info, revision);
initIncludedInAction(info);
+ initChangeAction(info);
initRevisionsAction(info, revision);
initDownloadAction(info, revision);
initProjectLinks(info);
initBranchLink(info);
+ initEditMode(info, revision);
actions.display(info, revision);
star.setValue(info.starred());
@@ -800,15 +1077,25 @@ public class ChangeScreen2 extends Screen {
commit.set(commentLinkProcessor, info, revision);
related.set(info, revision);
reviewers.set(info);
+ if (Gerrit.isNoteDbEnabled()) {
+ hashtags.set(info);
+ } else {
+ setVisible(hashtagTableRow, false);
+ }
if (Gerrit.isSignedIn()) {
- initEditMessageAction(info, revision);
replyAction = new ReplyAction(info, revision,
style, commentLinkProcessor, reply, quickApprove);
if (topic.canEdit()) {
keysAction.add(new KeyCommand(0, 't', Util.C.keyEditTopic()) {
@Override
public void onKeyPress(KeyPressEvent event) {
+ // In Firefox this event is mistakenly called when F5 is pressed so
+ // differentiate F5 from 't' by checking the charCode(F5=0, t=116).
+ if (event.getNativeEvent().getCharCode() == 0) {
+ Window.Location.reload();
+ return;
+ }
topic.onEdit();
}
});
@@ -816,9 +1103,9 @@ public class ChangeScreen2 extends Screen {
}
history.set(commentLinkProcessor, replyAction, changeId, info);
- if (current) {
+ if (current && info.status().isOpen()) {
quickApprove.set(info, revision, replyAction);
- loadSubmitType(info.status(), canSubmit);
+ loadSubmitType(info.status(), isSubmittable(info));
} else {
quickApprove.setVisible(false);
setVisible(strategy, false);
@@ -882,7 +1169,7 @@ public class ChangeScreen2 extends Screen {
for (int i = list.length() - 1; i >= 0; i--) {
RevisionInfo r = list.get(i);
diffBase.addItem(
- r._number() + ": " + r.name().substring(0, 6),
+ r.id() + ": " + r.name().substring(0, 6),
r.name());
if (r.name().equals(revision)) {
SelectElement.as(diffBase.getElement()).getOptions()
@@ -924,6 +1211,7 @@ public class ChangeScreen2 extends Screen {
Gerrit.display(PageLinks.toChange(changeId));
}
+ @Override
void onIgnore(Timestamp newTime) {
lastDisplayedUpdate = newTime;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
index 2b09ef9e55..6f8d3aa205 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
@@ -21,12 +21,14 @@ limitations under the License.
xmlns:x='urn:import:com.google.gerrit.client.ui'>
<ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
<ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
- <ui:style type='com.google.gerrit.client.change.ChangeScreen2.Style'>
+ <ui:style type='com.google.gerrit.client.change.ChangeScreen.Style'>
@eval textColor com.google.gerrit.client.Gerrit.getTheme().textColor;
@eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
@def COMMIT_WIDTH 560px;
- @def HEADER_HEIGHT 29px;
+ @def HEADER_HEIGHT 30px;
+
+ @def BUTTON_HEIGHT 14px;
.cs2 {
margin-bottom: 1em;
@@ -56,13 +58,6 @@ limitations under the License.
text-overflow: ellipsis;
white-space: nowrap;
}
- .subjectButtons {
- position: absolute;
- top: 0;
- right: 3px;
- height: HEADER_HEIGHT;
- line-height: HEADER_HEIGHT;
- }
.infoLine {
position: absolute;
@@ -75,8 +70,6 @@ limitations under the License.
.infoLineHeaderButtons {
display: inline-block;
height: HEADER_HEIGHT;
- line-height: HEADER_HEIGHT;
- vertical-align: top;
}
.statusRight {
position: absolute;
@@ -119,8 +112,6 @@ limitations under the License.
cursor: pointer;
height: 25px;
border: none;
- border-left: 2px solid #fff;
- border-right: 2px solid #fff;
background-color: trimColor;
margin: 0 0 0 -2px;
padding-left: 2px;
@@ -236,39 +227,57 @@ limitations under the License.
.label_need {color: #000;}
.label_may {color: #777;}
+ .hashtagName {
+ display: inline-block;
+ margin-bottom: 2px;
+ padding: 1px 3px 0px 3px;
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ background: #E2F5FF;
+ border: 1px solid #579FDA;
+ white-space: nowrap;
+ }
+
+ .hashtagName button {
+ cursor: pointer;
+ padding: 0;
+ margin: 0 0 0 5px;
+ border: 0;
+ background-color: transparent;
+ white-space: nowrap;
+ }
+
.headerButtons button {
- margin: 6px 3px 0 0;
- border-color: rgba(0, 0, 0, 0.1);
+ margin: 5.286px 3px 0 0;
text-align: center;
font-size: 8pt;
font-weight: bold;
- border: 1px solid;
cursor: pointer;
- color: #444;
+ border: 2px solid;
+ color: rgba(0, 0, 0, 0.15);
background-color: #f5f5f5;
- background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
-webkit-border-radius: 2px;
-webkit-box-sizing: content-box;
}
.headerButtons button div {
color: #444;
- height: 10px;
min-width: 54px;
- line-height: 10px;
white-space: nowrap;
+ height: BUTTON_HEIGHT;
+ line-height: BUTTON_HEIGHT;
}
- button.quickApprove {
+ button.highlight {
background-color: #4d90fe;
- background-image: -webkit-linear-gradient(top, #4d90fe, #4d90fe);
}
- button.quickApprove div { color: #fff; }
+ button.highlight div { color: #fff; }
.sectionHeader {
position: relative;
background-color: trimColor;
font-weight: bold;
color: textColor;
- height: 18px;
+ height: 20px;
+ line-height: 20px;
margin: 0 -5px;
padding: 5px 5px;
}
@@ -295,8 +304,12 @@ limitations under the License.
}
.diffBase select {
margin: 0;
- border: 1px solid #bbb;
- font-size: smaller;
+ border: 2px solid rgba(0, 0, 0, 0.15);
+ height: 20px;
+ font-size: 8pt;
+ font-weight: bold;
+ border-radius: 2px;
+ background-color: #f5f5f5
}
.replyBox {
@@ -321,30 +334,42 @@ limitations under the License.
</g:Anchor> - <span ui:field='statusText' class='{style.statusText}'/></ui:msg>
</span>
</div>
- <div class='{style.subjectButtons} {style.headerButtons}'>
- <g:Button ui:field='editMessage'
- styleName=''
- visible='false'
- title='Edit commit message (Shortcut: e)'>
- <ui:attribute name='title'/>
- <div><ui:msg>Edit Message</ui:msg></div>
- </g:Button>
- </div>
</div>
<div class='{style.infoLine}'>
<div class='{style.headerButtons} {style.infoLineHeaderButtons}'>
<g:Button ui:field='reply'
styleName=''
- title='Reply and score (Shortcut: a)'>
+ title=''
+ visible='false'>
<ui:attribute name='title'/>
- <div><ui:msg>Reply&#8230;</ui:msg></div>
</g:Button>
<c:QuickApprove ui:field='quickApprove'
- styleName='{style.quickApprove}'
+ styleName='{style.highlight}'
title='Apply score with one click'>
<ui:attribute name='title'/>
</c:QuickApprove>
+ <g:Button ui:field='publishEdit'
+ styleName='{style.highlight}' visible='false'>
+ <div><ui:msg>Publish Edit</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='rebaseEdit'
+ styleName='{style.highlight}' visible='false'>
+ <div><ui:msg>Rebase Edit</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='deleteEdit' styleName='' visible='false'>
+ <div><ui:msg>Delete Edit</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='publish'
+ styleName='{style.highlight}' visible='false'>
+ <div><ui:msg>Publish</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='deleteChange' styleName='' visible='false'>
+ <div><ui:msg>Delete Change</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='deleteRevision' styleName='' visible='false'>
+ <div><ui:msg>Delete Revision</ui:msg></div>
+ </g:Button>
</div>
</div>
@@ -428,7 +453,7 @@ limitations under the License.
class='{style.notMergeable}'
style='display: none'
aria-hidden='true'
- title='The change cannot be merged due to a path conflict. Rebase the change locally and upload the rebased commit for review.'>
+ title='The change cannot be merged due to a path conflict. Rebase the change and upload the rebased commit for review.'>
<ui:attribute name='title'/>
<ui:msg>Cannot Merge</ui:msg>
</div>
@@ -438,6 +463,11 @@ limitations under the License.
<th ui:field='actionText'/>
<td ui:field='actionDate'/>
</tr>
+ <tr ui:field='hashtagTableRow'>
+ <td colspan='2'>
+ <c:Hashtags ui:field='hashtags'/>
+ </td>
+ </tr>
<tr><td colspan='2'><c:Actions ui:field='actions'/></td></tr>
</table>
<hr/>
@@ -450,8 +480,29 @@ limitations under the License.
</tr>
</table>
- <div class='{style.sectionHeader}'>
+ <div class='{style.sectionHeader} {style.headerButtons}'>
<ui:msg>Files</ui:msg>
+ <g:Button ui:field='addFile'
+ title='Add file to this change'
+ styleName=''
+ visible='false'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Add&#8230;</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='deleteFile'
+ title='Delete file from the repository'
+ styleName=''
+ visible='false'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Delete&#8230;</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='renameFile'
+ title='Rename file in the repository'
+ styleName=''
+ visible='false'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Rename&#8230;</ui:msg></div>
+ </g:Button>
<div class='{style.headerButtons}'>
<g:Button ui:field='openAll'
styleName=''
@@ -462,6 +513,20 @@ limitations under the License.
<div class='{style.diffBase}'>
<ui:msg>Diff against: <g:ListBox ui:field='diffBase' styleName=''/></ui:msg>
</div>
+ <g:Button ui:field='editMode'
+ styleName=''
+ visible='false'
+ title='Switch file table to edit mode'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Edit</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='reviewMode'
+ styleName=''
+ visible='false'
+ title='Done with edit mode'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Done Editing</ui:msg></div>
+ </g:Button>
</div>
</div>
<c:FileTable ui:field='files'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java
index 9f64fa05c7..bcd8f6be49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java
@@ -33,7 +33,7 @@ class CherryPickAction {
String project, final String commitMessage) {
// TODO Replace CherryPickDialog with a nicer looking display.
b.setEnabled(false);
- new CherryPickDialog(b, new Project.NameKey(project)) {
+ new CherryPickDialog(new Project.NameKey(project)) {
{
sendButton.setText(Util.C.buttonCherryPickChangeSend());
if (info.status() == Change.Status.MERGED) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 4572cf8091..93db1a7afe 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -19,20 +19,21 @@ import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
import com.google.gerrit.client.WebLinkInfo;
+import com.google.gerrit.client.actions.ActionInfo;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.common.PageLinks;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
-import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
@@ -47,6 +48,7 @@ import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -66,8 +68,8 @@ class CommitBox extends Composite {
@UiField FlowPanel committerPanel;
@UiField Image mergeCommit;
@UiField CopyableLabel commitName;
- @UiField TableCellElement webLinkCell;
- @UiField Element parents;
+ @UiField FlowPanel webLinkPanel;
+ @UiField TableRowElement firstParent;
@UiField FlowPanel parentCommits;
@UiField FlowPanel parentWebLinks;
@UiField InlineHyperlink authorNameEmail;
@@ -78,6 +80,7 @@ class CommitBox extends Composite {
@UiField HTML text;
@UiField ScrollPanel scroll;
@UiField Button more;
+ @UiField Element parentNotCurrentText;
private boolean expanded;
CommitBox() {
@@ -90,7 +93,7 @@ class CommitBox extends Composite {
}
@UiHandler("more")
- void onMore(ClickEvent e) {
+ void onMore(@SuppressWarnings("unused") ClickEvent e) {
if (expanded) {
removeStyleName(style.expanded());
addStyleName(style.collapsed());
@@ -122,48 +125,95 @@ class CommitBox extends Composite {
if (revInfo.commit().parents().length() > 1) {
mergeCommit.setVisible(true);
}
+
setParents(change.project(), revInfo.commit().parents());
+
+ // display the orange ball if parent has moved on (not current)
+ boolean parentNotCurrent = false;
+ if (revInfo.has_actions()) {
+ NativeMap<ActionInfo> actions = revInfo.actions();
+ if (actions.containsKey("rebase")) {
+ parentNotCurrent = actions.get("rebase").enabled();
+ }
+ }
+ UIObject.setVisible(parentNotCurrentText, parentNotCurrent);
+ parentNotCurrentText.setInnerText(parentNotCurrent ? "\u25CF" : "");
}
private void setWebLinks(ChangeInfo change, String revision,
RevisionInfo revInfo) {
GitwebLink gw = Gerrit.getGitwebLink();
if (gw != null && gw.canLink(revInfo)) {
- addWebLink(gw.toRevision(change.project(), revision), gw.getLinkName());
+ toAnchor(gw.toRevision(change.project(), revision),
+ gw.getLinkName());
}
- JsArray<WebLinkInfo> links = revInfo.web_links();
+ JsArray<WebLinkInfo> links = revInfo.commit().web_links();
if (links != null) {
for (WebLinkInfo link : Natives.asList(links)) {
- addWebLink(link.url(), parenthesize(link.name()));
+ webLinkPanel.add(link.toAnchor());
}
}
}
- private void addWebLink(String href, String name) {
- AnchorElement a = DOM.createAnchor().cast();
+ private void toAnchor(String href, String name) {
+ Anchor a = new Anchor();
a.setHref(href);
- a.setInnerText(name);
- webLinkCell.appendChild(a);
+ a.setText(name);
+ webLinkPanel.add(a);
}
private void setParents(String project, JsArray<CommitInfo> commits) {
- setVisible(parents, true);
+ setVisible(firstParent, true);
+ TableRowElement next = firstParent;
+ TableRowElement previous = null;
for (CommitInfo c : Natives.asList(commits)) {
- CopyableLabel copyLabel = new CopyableLabel(c.commit());
- copyLabel.setStyleName(style.clippy());
- parentCommits.add(copyLabel);
-
- GitwebLink gw = Gerrit.getGitwebLink();
- if (gw != null) {
- Anchor a =
- new Anchor(gw.getLinkName(), gw.toRevision(project, c.commit()));
- a.setStyleName(style.parentWebLink());
- parentWebLinks.add(a);
+ if (next == firstParent) {
+ CopyableLabel copyLabel = getCommitLabel(c);
+ parentCommits.add(copyLabel);
+ addLinks(project, c, parentWebLinks);
+ } else {
+ next.appendChild(DOM.createTD());
+ Element td1 = DOM.createTD();
+ td1.appendChild(getCommitLabel(c).getElement());
+ next.appendChild(td1);
+ FlowPanel linksPanel = new FlowPanel();
+ linksPanel.addStyleName(style.parentWebLink());
+ addLinks(project, c, linksPanel);
+ Element td2 = DOM.createTD();
+ td2.appendChild(linksPanel.getElement());
+ next.appendChild(td2);
+ previous.getParentElement().insertAfter(next, previous);
}
+ previous = next;
+ next = DOM.createTR().cast();
}
}
+ private void addLinks(String project, CommitInfo c, FlowPanel panel) {
+ GitwebLink gw = Gerrit.getGitwebLink();
+ if (gw != null) {
+ Anchor a =
+ new Anchor(gw.getLinkName(), gw.toRevision(project, c.commit()));
+ a.setStyleName(style.parentWebLink());
+ panel.add(a);
+ }
+ JsArray<WebLinkInfo> links = c.web_links();
+ if (links != null) {
+ for (WebLinkInfo link : Natives.asList(links)) {
+ panel.add(link.toAnchor());
+ }
+ }
+ }
+
+ private CopyableLabel getCommitLabel(CommitInfo c) {
+ CopyableLabel copyLabel;
+ copyLabel = new CopyableLabel(c.commit());
+ copyLabel.setTitle(c.subject());
+ copyLabel.setStyleName(style.clippy());
+ return copyLabel;
+ }
+
private static void formatLink(GitPerson person, FlowPanel p,
InlineHyperlink name, Element date, ChangeInfo change) {
// only try to fetch the avatar image for author and committer if an avatar
@@ -186,14 +236,6 @@ class CommitBox extends Composite {
date.setInnerText(FormatUtil.mediumFormat(person.date()));
}
- private static String parenthesize(String str) {
- return new StringBuilder()
- .append("(")
- .append(str)
- .append(")")
- .toString();
- }
-
private static String renderName(GitPerson person) {
return person.name() + " <" + person.email() + ">";
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
index 7d0bb00d52..93312fa874 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
@@ -20,7 +20,7 @@ limitations under the License.
xmlns:x='urn:import:com.google.gerrit.client.ui'
xmlns:clippy='urn:import:com.google.gwtexpui.clippy.client'>
<ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
- <ui:image field="toggle" src="more_less.png"/>
+ <ui:image field="toggle" src="moreLess.png"/>
<ui:style type='com.google.gerrit.client.change.CommitBox.Style'>
@eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
@@ -68,7 +68,7 @@ limitations under the License.
padding: 0;
width: 560px;
}
- .header th { width: 70px; }
+ .header th { width: 72px; }
.header td { white-space: nowrap; }
.date { width: 132px; }
@@ -81,12 +81,18 @@ limitations under the License.
right: -16px;
}
<!-- To make room for the copyableLabel from the adjacent column -->
- .webLinkCell a:first-child {
+ .webLinkPanel a:first-child {
margin-left:16px;
}
- .parentWebLink {
+ .webLinkPanel>a {
+ margin-left:2px;
+ }
+
+ .parentWebLink a:first-child {
margin-left:16px;
- display: block;
+ }
+ .parentWebLink>a {
+ margin-left:2px;
}
.commit {
@@ -100,6 +106,16 @@ limitations under the License.
height: 16px !important;
vertical-align: bottom;
}
+
+ .parent {
+ margin-right: 3px;
+ float: left;
+ }
+ .parentNotCurrent {
+ color: #FFA62F; <!-- orange -->
+ font-weight: bold;
+ }
+
</ui:style>
<g:HTMLPanel>
<g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'>
@@ -152,15 +168,25 @@ limitations under the License.
</g:Image>
</th>
<td><clippy:CopyableLabel styleName='{style.clippy}' ui:field='commitName'/></td>
- <td ui:field='webLinkCell' class='{style.webLinkCell}'></td>
+ <td>
+ <g:FlowPanel ui:field='webLinkPanel' styleName='{style.webLinkPanel}'/>
+ </td>
</tr>
- <tr ui:field='parents' style='display: none'>
- <th><ui:msg>Parent(s)</ui:msg></th>
+ <tr ui:field='firstParent' style='display: none'>
+ <th>
+ <div class='{style.parent}'>
+ <ui:msg>Parent(s)</ui:msg>
+ </div>
+ <div ui:field='parentNotCurrentText'
+ title='Not current - rebase possible'
+ class='{style.parentNotCurrent}'
+ style='display: none' aria-hidden='true'/>
+ </th>
<td>
<g:FlowPanel ui:field='parentCommits'/>
</td>
<td>
- <g:FlowPanel ui:field='parentWebLinks'/>
+ <g:FlowPanel ui:field='parentWebLinks' styleName='{style.parentWebLink}'/>
</td>
</tr>
<tr>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java
new file mode 100644
index 0000000000..ee94564368
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java
@@ -0,0 +1,71 @@
+//Copyright (C) 2015 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.client.change;
+
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+class DeleteFileAction {
+ private final Change.Id changeId;
+ private final RevisionInfo revision;
+ private final ChangeScreen.Style style;
+ private final Widget deleteButton;
+
+ private DeleteFileBox deleteBox;
+ private PopupPanel popup;
+
+ DeleteFileAction(Change.Id changeId, RevisionInfo revision,
+ ChangeScreen.Style style, Widget deleteButton) {
+ this.changeId = changeId;
+ this.revision = revision;
+ this.style = style;
+ this.deleteButton = deleteButton;
+ }
+
+ void onDelete() {
+ if (popup != null) {
+ popup.hide();
+ return;
+ }
+
+ if (deleteBox == null) {
+ deleteBox = new DeleteFileBox(changeId, revision);
+ }
+ deleteBox.clearPath();
+
+ final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+ p.setStyleName(style.replyBox());
+ p.addAutoHidePartner(deleteButton.getElement());
+ p.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (popup == p) {
+ popup = null;
+ }
+ }
+ });
+ p.add(deleteBox);
+ p.showRelativeTo(deleteButton);
+ GlobalKey.dialog(p);
+ deleteBox.setFocus(true);
+ popup = p;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.java
new file mode 100644
index 0000000000..e55b7edec3
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.java
@@ -0,0 +1,113 @@
+//Copyright (C) 2015 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.changes.ChangeEditApi;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+class DeleteFileBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, DeleteFileBox> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ private final Change.Id changeId;
+
+ @UiField Button delete;
+ @UiField Button cancel;
+
+ @UiField(provided = true)
+ RemoteSuggestBox path;
+
+ DeleteFileBox(Change.Id changeId, RevisionInfo revision) {
+ this.changeId = changeId;
+
+ path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision));
+ path.addSelectionHandler(new SelectionHandler<String>() {
+ @Override
+ public void onSelection(SelectionEvent<String> event) {
+ delete(event.getSelectedItem());
+ }
+ });
+ path.addCloseHandler(new CloseHandler<RemoteSuggestBox>() {
+ @Override
+ public void onClose(CloseEvent<RemoteSuggestBox> event) {
+ hide();
+ }
+ });
+
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ void setFocus(boolean focus) {
+ path.setFocus(focus);
+ }
+
+ void clearPath() {
+ path.setText("");
+ }
+
+ @UiHandler("delete")
+ void onDelete(@SuppressWarnings("unused") ClickEvent e) {
+ delete(path.getText());
+ }
+
+ private void delete(String path) {
+ hide();
+ ChangeEditApi.delete(changeId.get(), path,
+ new AsyncCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ Gerrit.display(PageLinks.toChangeInEditMode(changeId));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ }
+
+ @UiHandler("cancel")
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
+ hide();
+ }
+
+ private void hide() {
+ for (Widget w = getParent(); w != null; w = w.getParent()) {
+ if (w instanceof PopupPanel) {
+ ((PopupPanel) w).hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.ui.xml
new file mode 100644
index 0000000000..4e7b2baff2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileBox.ui.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:u='urn:import:com.google.gerrit.client.ui'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+ <ui:style>
+ .cancel { float: right; }
+ </ui:style>
+ <g:HTMLPanel>
+ <div class='{res.style.section}'>
+ <ui:msg>Path: <u:RemoteSuggestBox ui:field='path' visibleLength='86'/></ui:msg>
+ </div>
+ <div class='{res.style.section}'>
+ <g:Button ui:field='delete'
+ title='Delete file from the repository'
+ styleName='{res.style.button}'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Delete</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='cancel'
+ styleName='{res.style.button}'
+ addStyleNames='{style.cancel}'>
+ <div>Cancel</div>
+ </g:Button>
+ </div>
+ </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java
index 891cc10f21..b1bb4e0ac7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadAction.java
@@ -25,7 +25,7 @@ class DownloadAction extends RightSidePopdownAction {
DownloadAction(
ChangeInfo info,
String revision,
- ChangeScreen2.Style style,
+ ChangeScreen.Style style,
UIObject relativeTo,
Widget downloadButton) {
super(style, relativeTo, downloadButton);
@@ -34,6 +34,7 @@ class DownloadAction extends RightSidePopdownAction {
info.revision(revision)._number()));
}
+ @Override
Widget getWidget() {
return downloadBox;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
index 09335c1ded..49389f3569 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
@@ -18,12 +18,13 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.AccountApi;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.client.changes.ChangeInfo.FetchInfo;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -78,23 +79,38 @@ class DownloadBox extends VerticalPanel {
@Override
protected void onLoad() {
if (fetch == null) {
- RestApi call = ChangeApi.detail(change.legacy_id().get());
- ChangeList.addOptions(call, EnumSet.of(
- revision.equals(change.current_revision())
- ? ListChangesOption.CURRENT_REVISION
- : ListChangesOption.ALL_REVISIONS,
- ListChangesOption.DOWNLOAD_COMMANDS));
- call.get(new AsyncCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- fetch = result.revision(revision).fetch();
- renderScheme();
- }
+ if (psId.get() == 0) {
+ ChangeApi.editWithCommands(change.legacy_id().get()).get(
+ new AsyncCallback<EditInfo>() {
+ @Override
+ public void onSuccess(EditInfo result) {
+ fetch = result.fetch();
+ renderScheme();
+ }
- @Override
- public void onFailure(Throwable caught) {
- }
- });
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ } else {
+ RestApi call = ChangeApi.detail(change.legacy_id().get());
+ ChangeList.addOptions(call, EnumSet.of(
+ revision.equals(change.current_revision())
+ ? ListChangesOption.CURRENT_REVISION
+ : ListChangesOption.ALL_REVISIONS,
+ ListChangesOption.DOWNLOAD_COMMANDS));
+ call.get(new AsyncCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ fetch = result.revision(revision).fetch();
+ renderScheme();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ }
}
}
@@ -254,7 +270,7 @@ class DownloadBox extends VerticalPanel {
PreferenceInput in = PreferenceInput.create();
in.download_scheme(scheme);
AccountApi.self().view("preferences")
- .post(in, new AsyncCallback<JavaScriptObject>() {
+ .put(in, new AsyncCallback<JavaScriptObject>() {
@Override
public void onSuccess(JavaScriptObject result) {
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
index d0666c644e..634190a239 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
@@ -16,7 +16,6 @@ package com.google.gerrit.client.change;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
-import com.google.gerrit.client.changes.SubmitFailureDialog;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
@@ -40,10 +39,12 @@ public class DraftActions {
public static GerritCallback<JavaScriptObject> cs(
final Change.Id id) {
return new GerritCallback<JavaScriptObject>() {
+ @Override
public void onSuccess(JavaScriptObject result) {
Gerrit.display(PageLinks.toChange(id));
}
+ @Override
public void onFailure(Throwable err) {
if (SubmitFailureDialog.isConflict(err)) {
new SubmitFailureDialog(err.getMessage()).center();
@@ -57,10 +58,12 @@ public class DraftActions {
private static AsyncCallback<JavaScriptObject> mine() {
return new GerritCallback<JavaScriptObject>() {
+ @Override
public void onSuccess(JavaScriptObject result) {
Gerrit.display(PageLinks.MINE);
}
+ @Override
public void onFailure(Throwable err) {
if (SubmitFailureDialog.isConflict(err)) {
new SubmitFailureDialog(err.getMessage()).center();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java
new file mode 100644
index 0000000000..d11cf7e51e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2014 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class EditActions {
+
+ static void deleteEdit(Change.Id id) {
+ ChangeApi.deleteEdit(id.get(), cs(id));
+ }
+
+ static void publishEdit(Change.Id id) {
+ ChangeApi.publishEdit(id.get(), cs(id));
+ }
+
+ static void rebaseEdit(Change.Id id) {
+ ChangeApi.rebaseEdit(id.get(), cs(id));
+ }
+
+ public static GerritCallback<JavaScriptObject> cs(
+ final Change.Id id) {
+ return new GerritCallback<JavaScriptObject>() {
+ @Override
+ public void onSuccess(JavaScriptObject result) {
+ Gerrit.display(PageLinks.toChange(id));
+ }
+
+ @Override
+ public void onFailure(Throwable err) {
+ if (SubmitFailureDialog.isConflict(err)) {
+ new SubmitFailureDialog(err.getMessage()).center();
+ Gerrit.display(PageLinks.toChange(id));
+ } else {
+ super.onFailure(err);
+ }
+ }
+ };
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageAction.java
deleted file mode 100644
index a0f7c9d6d9..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageAction.java
+++ /dev/null
@@ -1,80 +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.client.change;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
-
-class EditMessageAction {
- private final Change.Id changeId;
- private final String revision;
- private final String originalMessage;
- private final ChangeScreen2.Style style;
- private final Widget editMessageButton;
- private final Widget replyButton;
-
- private EditMessageBox editBox;
- private PopupPanel popup;
-
- EditMessageAction(
- Change.Id changeId,
- String revision,
- String originalMessage,
- ChangeScreen2.Style style,
- Widget editButton,
- Widget replyButton) {
- this.changeId = changeId;
- this.revision = revision;
- this.originalMessage = originalMessage;
- this.style = style;
- this.editMessageButton = editButton;
- this.replyButton = replyButton;
- }
-
- void onEdit() {
- if (popup != null) {
- popup.hide();
- return;
- }
-
- if (editBox == null) {
- editBox = new EditMessageBox(
- changeId,
- revision,
- originalMessage);
- }
-
- final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
- p.setStyleName(style.replyBox());
- p.addAutoHidePartner(editMessageButton.getElement());
- p.addCloseHandler(new CloseHandler<PopupPanel>() {
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- if (popup == p) {
- popup = null;
- }
- }
- });
- p.add(editBox);
- p.showRelativeTo(replyButton);
- GlobalKey.dialog(p);
- popup = p;
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java
deleted file mode 100644
index 0fe91ca22a..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java
+++ /dev/null
@@ -1,108 +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.client.change;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeApi;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.TextBoxChangeListener;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
-
-class EditMessageBox extends Composite {
- interface Binder extends UiBinder<HTMLPanel, EditMessageBox> {}
- private static final Binder uiBinder = GWT.create(Binder.class);
-
- private final Change.Id changeId;
- private final String revision;
- private String originalMessage;
-
- @UiField NpTextArea message;
- @UiField Button save;
- @UiField Button cancel;
-
- EditMessageBox(
- Change.Id changeId,
- String revision,
- String msg) {
- this.changeId = changeId;
- this.revision = revision;
- this.originalMessage = msg.trim();
- initWidget(uiBinder.createAndBindUi(this));
- message.getElement().setAttribute("wrap", "off");
- message.setText("");
- new TextBoxChangeListener(message) {
- public void onTextChanged(String newText) {
- save.setEnabled(!newText.trim()
- .equals(originalMessage));
- }
- };
- }
-
- @Override
- protected void onLoad() {
- if (message.getText().isEmpty()) {
- message.setText(originalMessage);
- save.setEnabled(false);
- }
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- message.setFocus(true);
- }});
- }
-
- @UiHandler("save")
- void onSave(ClickEvent e) {
- save.setEnabled(false);
- ChangeApi.message(changeId.get(), revision, message.getText().trim(),
- new GerritCallback<JavaScriptObject>() {
- @Override
- public void onSuccess(JavaScriptObject msg) {
- Gerrit.display(PageLinks.toChange(changeId));
- hide();
- }
- });
- }
-
- @UiHandler("cancel")
- void onCancel(ClickEvent e) {
- message.setText("");
- hide();
- }
-
- private void hide() {
- for (Widget w = getParent(); w != null; w = w.getParent()) {
- if (w instanceof PopupPanel) {
- ((PopupPanel) w).hide();
- break;
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index e097006678..2947be8dad 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -16,7 +16,9 @@ package com.google.gerrit.client.change;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeEditApi;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.changes.ReviewInfo;
import com.google.gerrit.client.changes.Util;
@@ -27,6 +29,8 @@ import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -46,8 +50,11 @@ import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
+import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.progress.client.ProgressBar;
@@ -55,7 +62,7 @@ import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.sql.Timestamp;
-class FileTable extends FlowPanel {
+public class FileTable extends FlowPanel {
static final FileTableResources R = GWT
.create(FileTableResources.class);
@@ -80,20 +87,36 @@ class FileTable extends FlowPanel {
String deltaColumn2();
String inserted();
String deleted();
+ String removeButton();
}
+ public static enum Mode {
+ REVIEW,
+ EDIT
+ }
+
+ private static final String DELETE;
+ private static final String RESTORE;
private static final String REVIEWED;
private static final String OPEN;
private static final int C_PATH = 3;
private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
static {
+ DELETE = DOM.createUniqueId().replace('-', '_');
+ RESTORE = DOM.createUniqueId().replace('-', '_');
REVIEWED = DOM.createUniqueId().replace('-', '_');
OPEN = DOM.createUniqueId().replace('-', '_');
- init(REVIEWED, OPEN);
+ init(DELETE, RESTORE, REVIEWED, OPEN);
}
- private static final native void init(String r, String o) /*-{
+ private static final native void init(String d, String t, String r, String o) /*-{
+ $wnd[d] = $entry(function(e,i) {
+ @com.google.gerrit.client.change.FileTable::onDelete(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i)
+ });
+ $wnd[t] = $entry(function(e,i) {
+ @com.google.gerrit.client.change.FileTable::onRestore(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i)
+ });
$wnd[r] = $entry(function(e,i) {
@com.google.gerrit.client.change.FileTable::onReviewed(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i)
});
@@ -102,6 +125,24 @@ class FileTable extends FlowPanel {
});
}-*/;
+ private static void onDelete(NativeEvent e, int idx) {
+ MyTable t = getMyTable(e);
+ if (t != null) {
+ t.onDelete(idx);
+ }
+ }
+
+ private static boolean onRestore(NativeEvent e, int idx) {
+ MyTable t = getMyTable(e);
+ if (t != null) {
+ t.onRestore(idx);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ return true;
+ }
+
private static void onReviewed(NativeEvent e, int idx) {
MyTable t = getMyTable(e);
if (t != null) {
@@ -139,6 +180,10 @@ class FileTable extends FlowPanel {
private boolean register;
private JsArrayString reviewed;
private String scrollToPath;
+ private ChangeScreen.Style style;
+ private Widget replyButton;
+ private boolean editExists;
+ private Mode mode;
@Override
protected void onLoad() {
@@ -146,15 +191,20 @@ class FileTable extends FlowPanel {
R.css().ensureInjected();
}
- void setRevisions(PatchSet.Id base, PatchSet.Id curr) {
+ public void set(PatchSet.Id base, PatchSet.Id curr, ChangeScreen.Style style,
+ Widget replyButton, Mode mode, boolean editExists) {
this.base = base;
this.curr = curr;
+ this.style = style;
+ this.replyButton = replyButton;
+ this.mode = mode;
+ this.editExists = editExists;
}
void setValue(NativeMap<FileInfo> fileMap,
Timestamp myLastReply,
- NativeMap<JsArray<CommentInfo>> comments,
- NativeMap<JsArray<CommentInfo>> drafts) {
+ @Nullable NativeMap<JsArray<CommentInfo>> comments,
+ @Nullable NativeMap<JsArray<CommentInfo>> drafts) {
JsArray<FileInfo> list = fileMap.values();
FileInfo.sortFileInfoByPath(list);
@@ -174,6 +224,14 @@ class FileTable extends FlowPanel {
}
}
+ void unregisterKeys() {
+ register = false;
+
+ if (table != null) {
+ table.setRegisterKeys(false);
+ }
+ }
+
void registerKeys() {
register = true;
@@ -220,7 +278,9 @@ class FileTable extends FlowPanel {
private String url(FileInfo info) {
return info.binary()
? Dispatcher.toUnified(base, curr, info.path())
- : Dispatcher.toSideBySide(base, curr, info.path());
+ : mode == Mode.REVIEW
+ ? Dispatcher.toSideBySide(base, curr, info.path())
+ : Dispatcher.toEditScreen(curr, info.path());
}
private final class MyTable extends NavigationTable<FileInfo> {
@@ -262,6 +322,38 @@ class FileTable extends FlowPanel {
+ curr.toString());
}
+ void onDelete(int idx) {
+ String path = list.get(idx).path();
+ ChangeEditApi.delete(curr.getParentKey().get(), path,
+ new AsyncCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ Gerrit.display(PageLinks.toChangeInEditMode(
+ curr.getParentKey()));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ }
+
+ void onRestore(int idx) {
+ String path = list.get(idx).path();
+ ChangeEditApi.restore(curr.getParentKey().get(), path,
+ new AsyncCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ Gerrit.display(PageLinks.toChangeInEditMode(
+ curr.getParentKey()));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ }
+
void onReviewed(InputElement checkbox, int idx) {
setReviewed(list.get(idx), checkbox.isChecked());
}
@@ -352,12 +444,13 @@ class FileTable extends FlowPanel {
private final class DisplayCommand implements RepeatingCommand {
private final SafeHtmlBuilder sb = new SafeHtmlBuilder();
- private final MyTable table;
+ private final MyTable myTable;
private final JsArray<FileInfo> list;
private final Timestamp myLastReply;
private final NativeMap<JsArray<CommentInfo>> comments;
private final NativeMap<JsArray<CommentInfo>> drafts;
private final boolean hasUser;
+ private final boolean showChangeSizeBars;
private boolean attached;
private int row;
private double start;
@@ -370,17 +463,20 @@ class FileTable extends FlowPanel {
private DisplayCommand(NativeMap<FileInfo> map,
JsArray<FileInfo> list,
Timestamp myLastReply,
- NativeMap<JsArray<CommentInfo>> comments,
- NativeMap<JsArray<CommentInfo>> drafts) {
- this.table = new MyTable(map, list);
+ @Nullable NativeMap<JsArray<CommentInfo>> comments,
+ @Nullable NativeMap<JsArray<CommentInfo>> drafts) {
+ this.myTable = new MyTable(map, list);
this.list = list;
this.myLastReply = myLastReply;
this.comments = comments;
this.drafts = drafts;
this.hasUser = Gerrit.isSignedIn();
- table.addStyleName(R.css().table());
+ this.showChangeSizeBars = !hasUser
+ || Gerrit.getUserAccount().getGeneralPreferences().isSizeBarInChangeTable();
+ myTable.addStyleName(R.css().table());
}
+ @Override
public boolean execute() {
boolean attachedNow = isAttached();
if (!attached && attachedNow) {
@@ -408,9 +504,9 @@ class FileTable extends FlowPanel {
}
}
footer(sb);
- table.resetHtml(sb);
- table.finishDisplay();
- setTable(table);
+ myTable.resetHtml(sb);
+ myTable.finishDisplay();
+ setTable(myTable);
return false;
}
@@ -449,7 +545,11 @@ class FileTable extends FlowPanel {
private void header(SafeHtmlBuilder sb) {
sb.openTr().setStyleName(R.css().nohover());
sb.openTh().setStyleName(R.css().pointer()).closeTh();
- sb.openTh().setStyleName(R.css().reviewed()).closeTh();
+ if (mode == Mode.REVIEW) {
+ sb.openTh().setStyleName(R.css().reviewed()).closeTh();
+ } else {
+ sb.openTh().setStyleName(R.css().removeButton()).closeTh();
+ }
sb.openTh().setStyleName(R.css().status()).closeTh();
sb.openTh().append(Util.C.patchTableColumnName()).closeTh();
sb.openTh()
@@ -466,7 +566,11 @@ class FileTable extends FlowPanel {
private void render(SafeHtmlBuilder sb, FileInfo info) {
sb.openTr();
sb.openTd().setStyleName(R.css().pointer()).closeTd();
- columnReviewed(sb, info);
+ if (mode == Mode.REVIEW) {
+ columnReviewed(sb, info);
+ } else {
+ columnDeleteRestore(sb, info);
+ }
columnStatus(sb, info);
columnPath(sb, info);
columnComments(sb, info);
@@ -487,6 +591,32 @@ class FileTable extends FlowPanel {
sb.closeTd();
}
+ private void columnDeleteRestore(SafeHtmlBuilder sb, FileInfo info) {
+ sb.openTd().setStyleName(R.css().removeButton());
+ if (hasUser) {
+ if (!Patch.COMMIT_MSG.equals(info.path())) {
+ boolean editable = isEditable(info);
+ sb.openElement("button")
+ .setAttribute("title", editable
+ ? Resources.C.removeFileInline()
+ : Resources.C.restoreFileInline())
+ .setAttribute("onclick", (editable ? DELETE : RESTORE)
+ + "(event," + info._row() + ")")
+ .append(new ImageResourceRenderer().render(editable
+ ? Gerrit.RESOURCES.redNot()
+ : Gerrit.RESOURCES.editUndo()))
+ .closeElement("button");
+ }
+ }
+ sb.closeTd();
+ }
+
+ private boolean isEditable(FileInfo info) {
+ String status = info.status();
+ return status == null
+ || !ChangeType.DELETED.matches(status);
+ }
+
private void columnStatus(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().status());
if (!Patch.COMMIT_MSG.equals(info.path())
@@ -500,14 +630,20 @@ class FileTable extends FlowPanel {
private void columnPath(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd()
.setStyleName(R.css().pathColumn())
- .openAnchor()
- .setAttribute("href", "#" + url(info))
- .setAttribute("onclick", OPEN + "(event," + info._row() + ")");
+ .openAnchor();
String path = info.path();
+ if (mode == Mode.EDIT && !isEditable(info)) {
+ sb.setAttribute("onclick", RESTORE + "(event," + info._row() + ")");
+ } else {
+ sb.setAttribute("href", "#" + url(info))
+ .setAttribute("onclick", OPEN + "(event," + info._row() + ")");
+ }
+
if (Patch.COMMIT_MSG.equals(path)) {
sb.append(Util.C.commitMessage());
- } else {
+ } else if (!hasUser || Gerrit.getUserAccount().getGeneralPreferences()
+ .isMuteCommonPathPrefixes()) {
int commonPrefixLen = commonPrefix(path);
if (commonPrefixLen > 0) {
sb.openSpan().setStyleName(R.css().commonPrefix())
@@ -516,6 +652,8 @@ class FileTable extends FlowPanel {
}
sb.append(path.substring(commonPrefixLen));
lastPath = path;
+ } else {
+ sb.append(path);
}
sb.closeAnchor();
@@ -581,7 +719,10 @@ class FileTable extends FlowPanel {
}
private JsArray<CommentInfo> get(String p, NativeMap<JsArray<CommentInfo>> m) {
- JsArray<CommentInfo> r = m.get(p);
+ JsArray<CommentInfo> r = null;
+ if (m != null) {
+ r = m.get(p);
+ }
if (r == null) {
r = JsArray.createArray().cast();
}
@@ -591,14 +732,27 @@ class FileTable extends FlowPanel {
private void columnDelta1(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().deltaColumn1());
if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()) {
- sb.append(info.lines_inserted() + info.lines_deleted());
+ if (showChangeSizeBars) {
+ sb.append(info.lines_inserted() + info.lines_deleted());
+ } else if (!ChangeType.DELETED.matches(info.status())) {
+ if (ChangeType.ADDED.matches(info.status())) {
+ sb.append(info.lines_inserted())
+ .append(" lines");
+ } else {
+ sb.append("+")
+ .append(info.lines_inserted())
+ .append(", -")
+ .append(info.lines_deleted());
+ }
+ }
}
sb.closeTd();
}
private void columnDelta2(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().deltaColumn2());
- if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()
+ if (showChangeSizeBars
+ && !Patch.COMMIT_MSG.equals(info.path()) && !info.binary()
&& (info.lines_inserted() != 0 || info.lines_deleted() != 0)) {
int w = 80;
int t = inserted + deleted;
@@ -629,7 +783,11 @@ class FileTable extends FlowPanel {
private void footer(SafeHtmlBuilder sb) {
sb.openTr().setStyleName(R.css().nohover());
sb.openTh().setStyleName(R.css().pointer()).closeTh();
- sb.openTh().setStyleName(R.css().reviewed()).closeTh();
+ if (mode == Mode.REVIEW) {
+ sb.openTh().setStyleName(R.css().reviewed()).closeTh();
+ } else {
+ sb.openTh().setStyleName(R.css().removeButton()).closeTh();
+ }
sb.openTh().setStyleName(R.css().status()).closeTh();
sb.openTd().closeTd(); // path
sb.openTd().setAttribute("colspan", 3).closeTd(); // comments
@@ -641,26 +799,28 @@ class FileTable extends FlowPanel {
// delta2
sb.openTh().setStyleName(R.css().deltaColumn2());
- int w = 80;
- int t = inserted + deleted;
- int i = Math.max(1, (int) (((double) w) * inserted / t));
- int d = Math.max(1, (int) (((double) w) * deleted / t));
- if (i + d > w && i > d) {
- i = w - d;
- } else if (i + d > w && d > i) {
- d = w - i;
- }
- if (0 < inserted) {
- sb.openDiv()
- .setStyleName(R.css().inserted())
- .setAttribute("style", "width:" + i + "px")
- .closeDiv();
- }
- if (0 < deleted) {
- sb.openDiv()
- .setStyleName(R.css().deleted())
- .setAttribute("style", "width:" + d + "px")
+ if (showChangeSizeBars) {
+ int w = 80;
+ int t = inserted + deleted;
+ int i = Math.max(1, (int) (((double) w) * inserted / t));
+ int d = Math.max(1, (int) (((double) w) * deleted / t));
+ if (i + d > w && i > d) {
+ i = w - d;
+ } else if (i + d > w && d > i) {
+ d = w - i;
+ }
+ if (0 < inserted) {
+ sb.openDiv()
+ .setStyleName(R.css().inserted())
+ .setAttribute("style", "width:" + i + "px")
.closeDiv();
+ }
+ if (0 < deleted) {
+ sb.openDiv()
+ .setStyleName(R.css().deleted())
+ .setAttribute("style", "width:" + d + "px")
+ .closeDiv();
+ }
}
sb.closeTh();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
new file mode 100644
index 0000000000..5a7df72a9f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
@@ -0,0 +1,47 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.PageLinks;
+import com.google.gwt.user.client.ui.Button;
+
+class FollowUpAction extends ActionMessageBox {
+ private final String project;
+ private final String branch;
+ private final String base;
+
+ FollowUpAction(Button b, String project, String branch, String key) {
+ super(b);
+ this.project = project;
+ this.branch = branch;
+ this.base = project + "~" + branch + "~" + key;
+ }
+
+ @Override
+ void send(String message) {
+ ChangeApi.createChange(project, branch, message, base,
+ new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ Gerrit.display(PageLinks.toChange(result.legacy_id()));
+ hide();
+ }
+ });
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
new file mode 100644
index 0000000000..faba10dd70
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
@@ -0,0 +1,271 @@
+// Copyright (C) 2014 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.rpc.StatusCodeException;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import java.util.Iterator;
+
+public class Hashtags extends Composite {
+
+ interface Binder extends UiBinder<HTMLPanel, Hashtags> {}
+ private static final int VISIBLE_LENGTH = 55;
+ private static final Binder uiBinder = GWT.create(Binder.class);
+ private static final String REMOVE;
+ private static final String DATA_ID = "data-id";
+
+ static {
+ REMOVE = DOM.createUniqueId().replace('-', '_');
+ init(REMOVE);
+ }
+
+ private static final native void init(String r) /*-{
+ $wnd[r] = $entry(function(e) {
+ @com.google.gerrit.client.change.Hashtags::onRemove(Lcom/google/gwt/dom/client/NativeEvent;)(e)
+ });
+ }-*/;
+
+ private static void onRemove(NativeEvent event) {
+ String hashtags = getDataId(event);
+ if (hashtags != null) {
+ final ChangeScreen screen = ChangeScreen.get(event);
+ ChangeApi.hashtags(screen.getChangeId().get()).post(
+ PostInput.create(null, hashtags), new GerritCallback<JavaScriptObject>() {
+ @Override
+ public void onSuccess(JavaScriptObject result) {
+ if (screen.isCurrentView()) {
+ Gerrit.display(PageLinks.toChange(screen.getChangeId()));
+ }
+ }
+ });
+ }
+ }
+
+ private static String getDataId(NativeEvent event) {
+ Element e = event.getEventTarget().cast();
+ while (e != null) {
+ String v = e.getAttribute(DATA_ID);
+ if (!v.isEmpty()) {
+ return v;
+ }
+ e = e.getParentElement();
+ }
+ return null;
+ }
+
+ @UiField Element hashtagsText;
+ @UiField Button openForm;
+ @UiField Element form;
+ @UiField Element error;
+ @UiField NpTextBox hashtagTextBox;
+
+ private ChangeScreen.Style style;
+ private Change.Id changeId;
+
+ public Hashtags() {
+
+ initWidget(uiBinder.createAndBindUi(this));
+
+ hashtagTextBox.setVisibleLength(VISIBLE_LENGTH);
+ hashtagTextBox.addKeyDownHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent e) {
+ if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ onCancel(null);
+ } else if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ onAdd(null);
+ }
+ }
+ });
+ }
+
+ void init(ChangeScreen.Style style){
+ this.style = style;
+ }
+
+ void set(ChangeInfo info) {
+ this.changeId = info.legacy_id();
+ display(info);
+ openForm.setVisible(Gerrit.isSignedIn());
+ }
+
+ @UiHandler("openForm")
+ void onOpenForm(@SuppressWarnings("unused") ClickEvent e) {
+ onOpenForm();
+ }
+
+ void onOpenForm() {
+ UIObject.setVisible(form, true);
+ UIObject.setVisible(error, false);
+ openForm.setVisible(false);
+ hashtagTextBox.setFocus(true);
+ }
+
+ private void display(ChangeInfo info) {
+ hashtagsText.setInnerSafeHtml(formatHashtags(info));
+ }
+ private void display(JsArrayString hashtags) {
+ hashtagsText.setInnerSafeHtml(formatHashtags(hashtags));
+ }
+
+ private SafeHtmlBuilder formatHashtags(ChangeInfo info) {
+ if (info.hashtags() != null) {
+ return formatHashtags(info.hashtags());
+ }
+ return new SafeHtmlBuilder();
+ }
+
+ private SafeHtmlBuilder formatHashtags(JsArrayString hashtags) {
+ SafeHtmlBuilder html = new SafeHtmlBuilder();
+ Iterator<String> itr = Natives.asList(hashtags).iterator();
+ while (itr.hasNext()) {
+ String hashtagName = itr.next();
+ html.openSpan()
+ .setAttribute(DATA_ID, hashtagName)
+ .setStyleName(style.hashtagName())
+ .openAnchor()
+ .setAttribute("href",
+ "#" + PageLinks.toChangeQuery("hashtag:\"" + hashtagName + "\""))
+ .setAttribute("role", "listitem")
+ .append("#").append(hashtagName)
+ .closeAnchor()
+ .openElement("button")
+ .setAttribute("title", "Remove hashtag")
+ .setAttribute("onclick", REMOVE + "(event)")
+ .append("×")
+ .closeElement("button")
+ .closeSpan();
+ if (itr.hasNext()) {
+ html.append(' ');
+ }
+ }
+ return html;
+ }
+
+ @UiHandler("cancel")
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
+ openForm.setVisible(true);
+ UIObject.setVisible(form, false);
+ hashtagTextBox.setFocus(false);
+ }
+
+ @UiHandler("add")
+ void onAdd(@SuppressWarnings("unused") ClickEvent e) {
+ String hashtag = hashtagTextBox.getText();
+ if (!hashtag.isEmpty()) {
+ addHashtag(hashtag);
+ }
+ }
+
+ private void addHashtag(final String hashtags) {
+ ChangeApi.hashtags(changeId.get()).post(
+ PostInput.create(hashtags, null),
+ new GerritCallback<JsArrayString>() {
+ @Override
+ public void onSuccess(JsArrayString result) {
+ hashtagTextBox.setEnabled(true);
+ UIObject.setVisible(error, false);
+ error.setInnerText("");
+ hashtagTextBox.setText("");
+
+ if (result != null && result.length() > 0) {
+ updateHashtagList(result);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable err) {
+ UIObject.setVisible(error, true);
+ error.setInnerText(err instanceof StatusCodeException
+ ? ((StatusCodeException) err).getEncodedResponse()
+ : err.getMessage());
+ hashtagTextBox.setEnabled(true);
+ }
+ });
+ }
+
+ protected void updateHashtagList() {
+ ChangeApi.detail(changeId.get(),
+ new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ display(result);
+ }
+ });
+ }
+
+ protected void updateHashtagList(JsArrayString hashtags){
+ display(hashtags);
+ }
+
+ public static class PostInput extends JavaScriptObject {
+ public static PostInput create(String add, String remove) {
+ PostInput input = createObject().cast();
+ input.init(toJsArrayString(add), toJsArrayString(remove));
+ return input;
+ }
+ private static JsArrayString toJsArrayString(String commaSeparated){
+ if (commaSeparated == null || commaSeparated.equals("")) {
+ return null;
+ }
+ JsArrayString array = JsArrayString.createArray().cast();
+ for (String hashtag : commaSeparated.split(",")){
+ array.push(hashtag.trim());
+ }
+ return array;
+ }
+
+ private native void init(JsArrayString add, JsArrayString remove) /*-{
+ this.add = add;
+ this.remove = remove;
+ }-*/;
+
+ protected PostInput() {
+ }
+ }
+
+ public static class Result extends JavaScriptObject {
+ public final native JsArrayString hashtags() /*-{ return this.hashtags; }-*/;
+ public final native String error() /*-{ return this.error; }-*/;
+
+ protected Result() {
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.ui.xml
new file mode 100644
index 0000000000..dd06c77767
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.ui.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+ <ui:style>
+ button.openAdd {
+ margin: 3px 3px 0 0;
+ float: right;
+ color: rgba(0, 0, 0, 0.15);
+ background-color: #f5f5f5;
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ }
+ button.openAdd div {
+ width: auto;
+ color: #444;
+ }
+
+ .hashtagTextBox {
+ margin-bottom: 2px;
+ }
+
+ .error {
+ color: #D33D3D;
+ font-weight: bold;
+ }
+
+ .cancel {
+ float: right;
+ }
+ </ui:style>
+ <g:HTMLPanel>
+ <div>
+ <span ui:field='hashtagsText'/>
+ <g:Button ui:field='openForm'
+ title='Add hashtags to this change'
+ styleName='{res.style.button}'
+ addStyleNames='{style.openAdd}'
+ visible='false'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Add #...</ui:msg></div>
+ </g:Button>
+ </div>
+ <div ui:field='form' style='display: none' aria-hidden='true'>
+ <c:NpTextBox ui:field='hashtagTextBox' styleName='{style.hashtagTextBox}'/>
+ <div ui:field='error'
+ class='{style.error}'
+ style='display: none' aria-hidden='true'/>
+ <div>
+ <g:Button ui:field='add' styleName='{res.style.button}'>
+ <div>Add</div>
+ </g:Button>
+ <g:Button ui:field='cancel'
+ styleName='{res.style.button}'
+ addStyleNames='{style.cancel}'>
+ <div>Cancel</div>
+ </g:Button>
+ </div>
+ </div>
+ </g:HTMLPanel>
+ </ui:UiBinder> \ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java
index 36be692a30..7635d81a1b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java
@@ -65,7 +65,7 @@ class History extends FlowPanel {
}
add(ui);
}
- autoOpen(ChangeScreen2.myLastReply(info));
+ autoOpen(ChangeScreen.myLastReply(info));
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/IncludedInAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/IncludedInAction.java
index 58b286c422..479670fcdc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/IncludedInAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/IncludedInAction.java
@@ -23,13 +23,14 @@ class IncludedInAction extends RightSidePopdownAction {
IncludedInAction(
Change.Id changeId,
- ChangeScreen2.Style style,
+ ChangeScreen.Style style,
UIObject relativeTo,
Widget includedInButton) {
super(style, relativeTo, includedInButton);
this.includedInBox = new IncludedInBox(changeId);
}
+ @Override
Widget getWidget() {
return includedInBox;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
index 2680347d96..a416894cc4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -26,13 +26,11 @@ import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -66,7 +64,7 @@ class Labels extends Grid {
private static void onRemove(NativeEvent event) {
Integer user = getDataId(event);
if (user != null) {
- final ChangeScreen2 screen = ChangeScreen2.get(event);
+ final ChangeScreen screen = ChangeScreen.get(event);
ChangeApi.reviewer(screen.getChangeId().get(), user).delete(
new GerritCallback<JavaScriptObject>() {
@Override
@@ -91,19 +89,16 @@ class Labels extends Grid {
return null;
}
- private ChangeScreen2.Style style;
- private Element statusText;
+ private ChangeScreen.Style style;
- void init(ChangeScreen2.Style style, Element statusText) {
+ void init(ChangeScreen.Style style) {
this.style = style;
- this.statusText = statusText;
}
- boolean set(ChangeInfo info, boolean current) {
+ void set(ChangeInfo info) {
List<String> names = new ArrayList<>(info.labels());
Collections.sort(names);
- boolean canSubmit = info.status().isOpen();
resize(names.size(), 2);
for (int row = 0; row < names.size(); row++) {
@@ -115,30 +110,7 @@ class Labels extends Grid {
}
getCellFormatter().setStyleName(row, 0, style.labelName());
getCellFormatter().addStyleName(row, 0, getStyleForLabel(label));
-
- if (canSubmit && info.status() == Change.Status.NEW) {
- switch (label.status()) {
- case NEED:
- if (current) {
- statusText.setInnerText("Needs " + name);
- }
- canSubmit = false;
- break;
- case REJECT:
- case IMPOSSIBLE:
- if (label.blocking()) {
- if (current) {
- statusText.setInnerText("Not " + name);
- }
- canSubmit = false;
- }
- break;
- default:
- break;
- }
- }
}
- return canSubmit;
}
private Widget renderUsers(LabelInfo label) {
@@ -222,7 +194,7 @@ class Labels extends Grid {
}
}
- static SafeHtml formatUserList(ChangeScreen2.Style style,
+ static SafeHtml formatUserList(ChangeScreen.Style style,
Collection<? extends AccountInfo> in,
Set<Integer> removable,
Map<Integer, VotableInfo> votable) {
@@ -301,7 +273,7 @@ class Labels extends Grid {
html.openElement("button")
.setAttribute("title", Util.M.removeReviewer(name))
.setAttribute("onclick", REMOVE + "(event)")
- .append(new ImageResourceRenderer().render(Resources.I.remove_reviewer()))
+ .append("×")
.closeElement("button");
}
html.closeSpan();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java
index 6540638b16..8fa5a68e15 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java
@@ -19,7 +19,7 @@ import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.diff.DisplaySide;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.InlineHyperlink;
-import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
index e3799cad12..22d39a7dea 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
@@ -89,7 +89,7 @@ class Message extends Composite {
this.history = parent;
this.info = info;
- name.setInnerText(authorName(info));
+ setName(false);
date.setInnerText(FormatUtil.shortFormatDayTime(info.date()));
if (info.message() != null) {
String msg = info.message().trim();
@@ -129,6 +129,7 @@ class Message extends Composite {
commentList = Collections.emptyList();
}
}
+ setName(open);
UIObject.setVisible(summary, !open);
UIObject.setVisible(message, open);
@@ -140,6 +141,18 @@ class Message extends Composite {
}
}
+ private void setName(boolean open) {
+ name.setInnerText(open ? authorName(info) : elide(authorName(info), 20));
+ }
+
+ private static String elide(final String s, final int len) {
+ if (s == null || s.length() <= len || len <= 10) {
+ return s;
+ }
+ int i = (len - 3) / 2;
+ return s.substring(0, i) + "..." + s.substring(s.length() - i);
+ }
+
void autoOpen() {
if (commentList == null) {
autoOpen = true;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
index fe0c97bd2b..42cb39bfdc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
@@ -64,9 +64,8 @@ limitations under the License.
font-weight: bold;
}
.closed .name {
- width: 120px;
+ width: 150px;
overflow: hidden;
- text-overflow: ellipsis;
font-weight: normal;
}
@@ -74,7 +73,7 @@ limitations under the License.
color: #777;
position: absolute;
top: 0;
- left: 120px;
+ left: 150px;
width: 880px;
overflow: hidden;
text-overflow: ellipsis;
@@ -103,7 +102,7 @@ limitations under the License.
font-size: 18px;
}
.closed .reply {
- visibility: HIDDEN;
+ visibility: hidden;
}
.comment {
}
@@ -125,11 +124,13 @@ limitations under the License.
<div>&#x21a9;</div>
</g:Button>
</g:HTMLPanel>
- <div ui:field='message'
- aria-hidden='true'
- style='display: NONE'
- styleName='{style.comment}'/>
- <g:FlowPanel ui:field='comments' visible='false'/>
+ <div style='overflow: auto'>
+ <div ui:field='message'
+ aria-hidden='true'
+ style='display: NONE'
+ styleName='{style.comment}'/>
+ <g:FlowPanel ui:field='comments' visible='false'/>
+ </div>
</div>
</g:HTMLPanel>
</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java
deleted file mode 100644
index fa65514144..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java
+++ /dev/null
@@ -1,105 +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.client.change;
-
-import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountApi;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.Cookies;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.UIObject;
-
-import java.util.Date;
-
-/** Displays a welcome to the new change screen bar. */
-class NewChangeScreenBar extends Composite {
- interface Binder extends UiBinder<HTMLPanel, NewChangeScreenBar> {}
- private static final Binder uiBinder = GWT.create(Binder.class);
-
- static boolean show() {
- if (Gerrit.isSignedIn()) {
- return Gerrit.getUserAccount()
- .getGeneralPreferences()
- .getChangeScreen() == null;
- }
- return Cookies.getCookie(Dispatcher.COOKIE_CS2) == null;
- }
-
- private final Change.Id id;
-
- @UiField Element docs;
- @UiField Element settings;
- @UiField Anchor keepNew;
- @UiField Anchor keepOld;
-
- NewChangeScreenBar(Change.Id id) {
- this.id = id;
- initWidget(uiBinder.createAndBindUi(this));
- UIObject.setVisible(docs, Gerrit.getConfig().isDocumentationAvailable());
- UIObject.setVisible(settings, Gerrit.isSignedIn());
- }
-
- @UiHandler("keepOld")
- void onKeepOld(ClickEvent e) {
- save(ChangeScreen.OLD_UI);
- Gerrit.display(PageLinks.toChange(id));
- }
-
- @UiHandler("keepNew")
- void onKeepNew(ClickEvent e) {
- save(ChangeScreen.CHANGE_SCREEN2);
- }
-
- private void save(ChangeScreen sel) {
- removeFromParent();
- Dispatcher.changeScreen2 = sel == ChangeScreen.CHANGE_SCREEN2;
-
- if (Gerrit.isSignedIn()) {
- Gerrit.getUserAccount().getGeneralPreferences().setChangeScreen(sel);
-
- Prefs in = Prefs.createObject().cast();
- in.change_screen(sel.name());
- AccountApi.self().view("preferences").background().post(in,
- new AsyncCallback<JavaScriptObject>() {
- @Override public void onFailure(Throwable caught) {}
- @Override public void onSuccess(JavaScriptObject result) {}
- });
- } else {
- Cookies.setCookie(
- Dispatcher.COOKIE_CS2,
- Dispatcher.changeScreen2 ? "1" : "0",
- new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000));
- }
- }
-
- private static class Prefs extends JavaScriptObject {
- final native void change_screen(String n) /*-{ this.change_screen=n }-*/;
- protected Prefs() {
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml
deleted file mode 100644
index 92eff8dbe2..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<ui:UiBinder
- xmlns:ui='urn:ui:com.google.gwt.uibinder'
- xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
- xmlns:g='urn:import:com.google.gwt.user.client.ui'>
- <ui:style>
- .popup {
- position: fixed;
- top: 5px;
- left: 50%;
- margin-left: -200px;
- z-index: 201;
- padding-top: 5px;
- padding-bottom: 5px;
- padding-left: 12px;
- padding-right: 12px;
- text-align: center;
- background: #FFF1A8;
- border-radius: 10px;
- }
-
- @if user.agent safari {
- .popup {
- \-webkit-border-radius: 10px;
- }
- }
- @if user.agent gecko1_8 {
- .popup {
- \-moz-border-radius: 10px;
- }
- }
-
- a.action {
- color: #222;
- text-decoration: underline;
- display: inline-block;
- margin-left: 0.5em;
- }
- .welcome { font-weight: bold; }
- </ui:style>
- <g:HTMLPanel styleName='{style.popup}'>
- <div><ui:msg><span class='{style.welcome}'>Welcome to the new change screen!</span>
- <a ui:field='docs'
- class='{style.action}'
- href='Documentation/user-review-ui.html'
- target='_blank'>Learn more</a></ui:msg>
- </div>
- <div>
- <ui:msg>You can<g:Anchor ui:field='keepOld'
- styleName='{style.action}'
- href='javascript:;'
- title='Switch back to the old screen'><ui:attribute name='title'/>revert
- to the old screen</g:Anchor><span ui:field='settings'>&#160;in Settings &gt; Preferences</span>.
- <g:Anchor ui:field='keepNew'
- styleName='{style.action}'
- href='javascript:;'
- title='Keep the new change screen'>
- <ui:attribute name='title'/>
- Got it!
- </g:Anchor></ui:msg>
- </div>
- </g:HTMLPanel>
-</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java
index b29ca11f3b..3842ee6fe2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.change;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
@@ -24,13 +25,15 @@ class PatchSetsAction extends RightSidePopdownAction {
PatchSetsAction(
Change.Id changeId,
String revision,
- ChangeScreen2.Style style,
+ EditInfo edit,
+ ChangeScreen.Style style,
UIObject relativeTo,
Widget downloadButton) {
super(style, relativeTo, downloadButton);
- this.revisionBox = new PatchSetsBox(changeId, revision);
+ this.revisionBox = new PatchSetsBox(changeId, revision, edit);
}
+ @Override
Widget getWidget() {
return revisionBox;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
index 2fa721eec0..665eff50f1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
@@ -19,6 +19,7 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.rpc.NativeMap;
@@ -26,7 +27,7 @@ import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.ui.FancyFlexTableImpl;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
@@ -101,15 +102,17 @@ class PatchSetsBox extends Composite {
private final Change.Id changeId;
private final String revision;
+ private final EditInfo edit;
private boolean loaded;
private JsArray<RevisionInfo> revisions;
@UiField FlexTable table;
@UiField Style style;
- PatchSetsBox(Change.Id changeId, String revision) {
+ PatchSetsBox(Change.Id changeId, String revision, EditInfo edit) {
this.changeId = changeId;
this.revision = revision;
+ this.edit = edit;
initWidget(uiBinder.createAndBindUi(this));
}
@@ -124,6 +127,10 @@ class PatchSetsBox extends Composite {
call.get(new AsyncCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
+ if (edit != null) {
+ edit.set_name(edit.commit().commit());
+ result.revisions().put(edit.name(), RevisionInfo.fromEdit(edit));
+ }
render(result.revisions());
loaded = true;
}
@@ -189,7 +196,7 @@ class PatchSetsBox extends Composite {
.closeSpan()
.append(' ');
}
- sb.append(r._number());
+ sb.append(r.id());
sb.closeTd();
sb.openTd()
@@ -218,9 +225,7 @@ class PatchSetsBox extends Composite {
}
private String url(RevisionInfo r) {
- return PageLinks.toChange(
- changeId,
- String.valueOf(r._number()));
+ return PageLinks.toChange(changeId, r.id());
}
private void closeParent() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PathSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PathSuggestOracle.java
new file mode 100644
index 0000000000..7667559a14
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PathSuggestOracle.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2015 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.client.change;
+
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class PathSuggestOracle extends HighlightSuggestOracle {
+
+ private final Change.Id changeId;
+ private final RevisionInfo revision;
+
+ PathSuggestOracle(Change.Id changeId, RevisionInfo revision) {
+ this.changeId = changeId;
+ this.revision = revision;
+ }
+
+ @Override
+ protected void onRequestSuggestions(final Request req, final Callback cb) {
+ ChangeApi.revision(changeId.get(), revision.name())
+ .view("files")
+ .addParameter("q", req.getQuery())
+ .background()
+ .get(new AsyncCallback<JsArrayString>() {
+ @Override
+ public void onSuccess(JsArrayString result) {
+ List<Suggestion> r = new ArrayList<>();
+ for (String path : Natives.asList(result)) {
+ r.add(new PathSuggestion(path));
+ }
+ cb.onSuggestionsReady(req, new Response(r));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ List<Suggestion> none = Collections.emptyList();
+ cb.onSuggestionsReady(req, new Response(none));
+ }
+ });
+ }
+
+ private static class PathSuggestion implements Suggestion {
+ private final String path;
+
+ PathSuggestion(String path) {
+ this.path = path;
+ }
+
+ @Override
+ public String getDisplayString() {
+ return path;
+ }
+
+ @Override
+ public String getReplacementString() {
+ return path;
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
index 6e8673066b..6638dbebd9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
@@ -46,6 +46,10 @@ class QuickApprove extends Button implements ClickHandler {
setVisible(false);
return;
}
+ if (info.revision(commit).is_edit() || info.revision(commit).draft()) {
+ setVisible(false);
+ return;
+ }
String qName = null;
String qValueStr = null;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
index 2b1c250021..790198b014 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
@@ -18,16 +18,42 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.RebaseDialog;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
class RebaseAction {
- static void call(final Change.Id id, String revision) {
- ChangeApi.rebase(id.get(), revision,
- new GerritCallback<ChangeInfo>() {
- public void onSuccess(ChangeInfo result) {
- Gerrit.display(PageLinks.toChange(id));
- }
- });
+ static void call(final Button b, final String project, final String branch,
+ final Change.Id id, final String revision, final boolean enabled) {
+ b.setEnabled(false);
+
+ new RebaseDialog(project, branch, id, enabled) {
+ @Override
+ public void onSend() {
+ ChangeApi.rebase(id.get(), revision, getBase(), new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ sent = true;
+ hide();
+ Gerrit.display(PageLinks.toChange(id));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ enableButtons(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ super.onClose(event);
+ b.setEnabled(true);
+ }
+ }.center();
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index 5a0ba153c7..0c78a677d9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -22,7 +22,7 @@ import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
@@ -62,7 +62,7 @@ public class RelatedChanges extends TabPanel {
String tabPanel();
}
- private enum Tab {
+ enum Tab {
RELATED_CHANGES(Resources.C.relatedChanges(),
Resources.C.relatedChangesTooltip()) {
@Override
@@ -127,6 +127,8 @@ public class RelatedChanges extends TabPanel {
}
}
+ private static Tab savedTab;
+
private final List<RelatedChangesTab> tabs;
private int maxHeightWithHeader;
private int selectedTab;
@@ -155,7 +157,7 @@ public class RelatedChanges extends TabPanel {
});
for (Tab tabInfo : Tab.values()) {
- RelatedChangesTab panel = new RelatedChangesTab();
+ RelatedChangesTab panel = new RelatedChangesTab(tabInfo);
add(panel, tabInfo.defaultTitle);
tabs.add(panel);
@@ -167,6 +169,8 @@ public class RelatedChanges extends TabPanel {
}
getTab(Tab.RELATED_CHANGES).setShowIndirectAncestors(true);
getTab(Tab.CHERRY_PICKS).setShowBranches(true);
+ getTab(Tab.SAME_TOPIC).setShowBranches(true);
+ getTab(Tab.SAME_TOPIC).setShowProjects(true);
}
void set(final ChangeInfo info, final String revision) {
@@ -194,8 +198,6 @@ public class RelatedChanges extends TabPanel {
if (info.topic() != null && !"".equals(info.topic())) {
StringBuilder topicQuery = new StringBuilder();
topicQuery.append("status:open");
- topicQuery.append(" ").append(op("project", info.project()));
- topicQuery.append(" ").append(op("branch", info.branch()));
topicQuery.append(" ").append(op("topic", info.topic()));
topicQuery.append(" ").append(op("-change", info.legacy_id().get()));
ChangeList.query(topicQuery.toString(),
@@ -222,6 +224,10 @@ public class RelatedChanges extends TabPanel {
R.css().ensureInjected();
}
+ static void setSavedTab(Tab subject) {
+ savedTab = subject;
+ }
+
private RelatedChangesTab getTab(Tab tabInfo) {
return tabs.get(tabInfo.ordinal());
}
@@ -264,19 +270,23 @@ public class RelatedChanges extends TabPanel {
@Override
public void onSuccess(T result) {
- JsArray<ChangeAndCommit> changes = convert(result);
- if (changes.length() > 0) {
- setTabTitle(tabInfo, tabInfo.getTitle(changes.length()));
- getTab(tabInfo).setChanges(project, revision, changes);
+ if (isAttached()) {
+ JsArray<ChangeAndCommit> changes = convert(result);
+ if (changes.length() > 0) {
+ setTabTitle(tabInfo, tabInfo.getTitle(changes.length()));
+ getTab(tabInfo).setChanges(project, revision, changes);
+ }
+ onDone(changes.length() > 0);
}
- onDone(changes.length() > 0);
}
@Override
public void onFailure(Throwable err) {
- setTabTitle(tabInfo, tabInfo.getTitle(Resources.C.notAvailable()));
- getTab(tabInfo).setError(err.getMessage());
- onDone(true);
+ if (isAttached()) {
+ setTabTitle(tabInfo, tabInfo.getTitle(Resources.C.notAvailable()));
+ getTab(tabInfo).setError(err.getMessage());
+ onDone(true);
+ }
}
private void onDone(boolean enabled) {
@@ -293,6 +303,10 @@ public class RelatedChanges extends TabPanel {
}
}
}
+
+ if (tabInfo == savedTab && enabled) {
+ selectTab(savedTab.ordinal());
+ }
}
}
@@ -313,6 +327,7 @@ public class RelatedChanges extends TabPanel {
c.set_change_number(i.legacy_id().get());
c.set_revision_number(currentRevision._number());
c.set_branch(i.branch());
+ c.set_project(i.project());
arr.push(c);
}
}
@@ -334,6 +349,7 @@ public class RelatedChanges extends TabPanel {
public final native String id() /*-{ return this.change_id }-*/;
public final native CommitInfo commit() /*-{ return this.commit }-*/;
final native String branch() /*-{ return this.branch }-*/;
+ final native String project() /*-{ return this.project }-*/;
final native void set_id(String i)
/*-{ if(i)this.change_id=i; }-*/;
@@ -344,6 +360,9 @@ public class RelatedChanges extends TabPanel {
final native void set_branch(String b)
/*-{ if(b)this.branch=b; }-*/;
+ final native void set_project(String b)
+ /*-{ if(b)this.project=b; }-*/;
+
public final Change.Id legacy_id() {
return has_change_number() ? new Change.Id(_change_number()) : null;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
index e1ce99d014..96d0d1095a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
@@ -82,8 +82,10 @@ class RelatedChangesTab implements IsWidget {
}
private final SimplePanel panel;
+ private final RelatedChanges.Tab subject;
private boolean showBranches;
+ private boolean showProjects;
private boolean showIndirectAncestors;
private boolean registerKeys;
private int maxHeight;
@@ -91,8 +93,9 @@ class RelatedChangesTab implements IsWidget {
private String project;
private NavigationList view;
- RelatedChangesTab() {
+ RelatedChangesTab(RelatedChanges.Tab subject) {
panel = new SimplePanel();
+ this.subject = subject;
}
@Override
@@ -104,6 +107,10 @@ class RelatedChangesTab implements IsWidget {
this.showBranches = showBranches;
}
+ void setShowProjects(boolean showProjects) {
+ this.showProjects = showProjects;
+ }
+
void setShowIndirectAncestors(boolean showIndirectAncestors) {
this.showIndirectAncestors = showIndirectAncestors;
}
@@ -200,6 +207,7 @@ class RelatedChangesTab implements IsWidget {
return false;
}
+ @Override
public boolean execute() {
if (navList != view || !panel.isAttached()) {
// If the user navigated away, we aren't in the DOM anymore.
@@ -273,6 +281,9 @@ class RelatedChangesTab implements IsWidget {
if (url.startsWith("#")) {
sb.setAttribute("onclick", OPEN);
}
+ if (showProjects) {
+ sb.append(info.project()).append(": ");
+ }
if (showBranches) {
sb.append(info.branch()).append(": ");
}
@@ -311,7 +322,7 @@ class RelatedChangesTab implements IsWidget {
PatchSet.Id id = info.patch_set_id();
return "#" + PageLinks.toChange(
id.getParentKey(),
- String.valueOf(id.get()));
+ id.getId());
}
GitwebLink gw = Gerrit.getGitwebLink();
@@ -486,6 +497,7 @@ class RelatedChangesTab implements IsWidget {
movePointerTo(startRow + DOM.getChildIndex(body, row), false);
event.stopPropagation();
}
+ saveSelectedTab();
}
@Override
@@ -536,6 +548,12 @@ class RelatedChangesTab implements IsWidget {
}
}
}
+
+ saveSelectedTab();
+ }
+
+ private void saveSelectedTab() {
+ RelatedChanges.setSavedTab(subject);
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java
new file mode 100644
index 0000000000..1f11e6542f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java
@@ -0,0 +1,71 @@
+//Copyright (C) 2015 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.client.change;
+
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+class RenameFileAction {
+ private final Change.Id changeId;
+ private final RevisionInfo revision;
+ private final ChangeScreen.Style style;
+ private final Widget renameButton;
+
+ private RenameFileBox renameBox;
+ private PopupPanel popup;
+
+ RenameFileAction(Change.Id changeId, RevisionInfo revision,
+ ChangeScreen.Style style, Widget renameButton) {
+ this.changeId = changeId;
+ this.revision = revision;
+ this.style = style;
+ this.renameButton = renameButton;
+ }
+
+ void onRename() {
+ if (popup != null) {
+ popup.hide();
+ return;
+ }
+
+ if (renameBox == null) {
+ renameBox = new RenameFileBox(changeId, revision);
+ }
+ renameBox.clearPath();
+
+ final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+ p.setStyleName(style.replyBox());
+ p.addAutoHidePartner(renameButton.getElement());
+ p.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (popup == p) {
+ popup = null;
+ }
+ }
+ });
+ p.add(renameBox);
+ p.showRelativeTo(renameButton);
+ GlobalKey.dialog(p);
+ renameBox.setFocus(true);
+ popup = p;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.java
new file mode 100644
index 0000000000..77348f7f1c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.java
@@ -0,0 +1,107 @@
+//Copyright (C) 2015 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.changes.ChangeEditApi;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+class RenameFileBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, RenameFileBox> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ private final Change.Id changeId;
+
+ @UiField Button rename;
+ @UiField Button cancel;
+
+ @UiField(provided = true)
+ RemoteSuggestBox path;
+ @UiField NpTextBox newPath;
+
+ RenameFileBox(Change.Id changeId, RevisionInfo revision) {
+ this.changeId = changeId;
+
+ path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision));
+ path.addCloseHandler(new CloseHandler<RemoteSuggestBox>() {
+ @Override
+ public void onClose(CloseEvent<RemoteSuggestBox> event) {
+ hide();
+ }
+ });
+
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ void setFocus(boolean focus) {
+ path.setFocus(focus);
+ }
+
+ void clearPath() {
+ path.setText("");
+ }
+
+ @UiHandler("rename")
+ void onRename(@SuppressWarnings("unused") ClickEvent e) {
+ rename(path.getText(), newPath.getText());
+ }
+
+ private void rename(String path, String newPath) {
+ hide();
+ ChangeEditApi.rename(changeId.get(), path, newPath,
+ new AsyncCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ Gerrit.display(PageLinks.toChangeInEditMode(changeId));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ });
+ }
+
+ @UiHandler("cancel")
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
+ hide();
+ }
+
+ private void hide() {
+ for (Widget w = getParent(); w != null; w = w.getParent()) {
+ if (w instanceof PopupPanel) {
+ ((PopupPanel) w).hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.ui.xml
new file mode 100644
index 0000000000..27849ee0e4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileBox.ui.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
+ xmlns:u='urn:import:com.google.gerrit.client.ui'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+ <ui:style>
+ .cancel { float: right; }
+ </ui:style>
+ <g:HTMLPanel>
+ <div class='{res.style.section}'>
+ <ui:msg>Old: <u:RemoteSuggestBox ui:field='path' visibleLength='86'/></ui:msg>
+ </div>
+ <div class='{res.style.section}'>
+ <ui:msg>New: <c:NpTextBox ui:field='newPath' visibleLength='86'/></ui:msg>
+ </div>
+ <div class='{res.style.section}'>
+ <g:Button ui:field='rename'
+ title='Rename file in the repository'
+ styleName='{res.style.button}'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Rename</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='cancel'
+ styleName='{res.style.button}'
+ addStyleNames='{style.cancel}'>
+ <div>Cancel</div>
+ </g:Button>
+ </div>
+ </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
index b234fe3930..6e409790d8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
@@ -33,7 +33,7 @@ import com.google.gwtexpui.user.client.PluginSafePopupPanel;
class ReplyAction {
private final PatchSet.Id psId;
private final String revision;
- private final ChangeScreen2.Style style;
+ private final ChangeScreen.Style style;
private final CommentLinkProcessor clp;
private final Widget replyButton;
private final Widget quickApproveButton;
@@ -47,7 +47,7 @@ class ReplyAction {
ReplyAction(
ChangeInfo info,
String revision,
- ChangeScreen2.Style style,
+ ChangeScreen.Style style,
CommentLinkProcessor clp,
Widget replyButton,
Widget quickApproveButton) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index ddd650e8fb..f9054fe3da 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -76,8 +76,6 @@ import java.util.Set;
import java.util.TreeSet;
class ReplyBox extends Composite {
- private static final String CODE_REVIEW = "Code-Review";
-
interface Binder extends UiBinder<HTMLPanel, ReplyBox> {}
private static final Binder uiBinder = GWT.create(Binder.class);
@@ -92,7 +90,6 @@ class ReplyBox extends Composite {
private final String revision;
private ReviewInput in = ReviewInput.create();
private int labelHelpColumn;
- private Runnable lgtm;
@UiField Styles style;
@UiField TextArea message;
@@ -173,22 +170,8 @@ class ReplyBox extends Composite {
}}, 0);
}
- @UiHandler("message")
- void onMessageKey(KeyPressEvent event) {
- if (lgtm != null
- && event.getCharCode() == 'M'
- && message.getValue().equals("LGT")) {
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- lgtm.run();
- }
- });
- }
- }
-
@UiHandler("post")
- void onPost(ClickEvent e) {
+ void onPost(@SuppressWarnings("unused") ClickEvent e) {
postReview();
}
@@ -214,7 +197,7 @@ class ReplyBox extends Composite {
}
@UiHandler("cancel")
- void onCancel(ClickEvent e) {
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
message.setText("");
hide();
}
@@ -358,15 +341,6 @@ class ReplyBox extends Composite {
labelsTable.setWidget(row, 1 + i, b);
}
}
-
- if (CODE_REVIEW.equalsIgnoreCase(id) && !group.buttons.isEmpty()) {
- lgtm = new Runnable() {
- @Override
- public void run() {
- group.selectMax();
- }
- };
- }
}
private void renderCheckBox(int row, LabelAndValues lv) {
@@ -377,7 +351,6 @@ class ReplyBox extends Composite {
final String id = lv.info.name();
final CheckBox b = new CheckBox();
b.setText(id);
- b.setTitle(lv.info.value_text("+1"));
b.setEnabled(lv.permitted.contains((short) 1));
if (self != null && self.value() == 1) {
b.setValue(true);
@@ -391,14 +364,9 @@ class ReplyBox extends Composite {
b.setStyleName(style.label_name());
labelsTable.setWidget(row, 0, b);
- if (CODE_REVIEW.equalsIgnoreCase(id)) {
- lgtm = new Runnable() {
- @Override
- public void run() {
- b.setValue(true, true);
- }
- };
- }
+ CellFormatter fmt = labelsTable.getCellFormatter();
+ fmt.setStyleName(row, labelHelpColumn, style.label_help());
+ labelsTable.setText(row, labelHelpColumn, lv.info.value_text("+1"));
}
private static boolean isCheckBox(Set<Short> values) {
@@ -467,16 +435,6 @@ class ReplyBox extends Composite {
selected = b;
labelsTable.setText(row, labelHelpColumn, b.text);
}
-
- void selectMax() {
- for (int i = 0; i < buttons.size() - 1; i++) {
- buttons.get(i).setValue(false, false);
- }
-
- LabelRadioButton max = buttons.get(buttons.size() - 1);
- max.setValue(true, true);
- max.select();
- }
}
private class LabelRadioButton extends RadioButton implements
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
index 3c927fc0d3..a17d648629 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
@@ -63,12 +63,12 @@ limitations under the License.
<g:FlowPanel ui:field='comments'/>
</g:ScrollPanel>
<div class='{res.style.section}' style='position: relative'>
- <ui:msg><g:Button ui:field='post'
+ <g:Button ui:field='post'
title='Post reply (Shortcut: Ctrl-Enter)'
styleName='{res.style.button}'>
<ui:attribute name='title'/>
- <div>Post</div>
- </g:Button></ui:msg>
+ <div><ui:msg>Post</ui:msg></div>
+ </g:Button>
<g:Button ui:field='cancel'
title='Close reply form (Shortcut: Esc)'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
index 7ca3c5315e..4fc76c18ab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
@@ -17,16 +17,12 @@ package com.google.gerrit.client.change;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
-import com.google.gwt.resources.client.ImageResource;
public interface Resources extends ClientBundle {
public static final Resources I = GWT.create(Resources.class);
static final ChangeConstants C = GWT.create(ChangeConstants.class);
static final ChangeMessages M = GWT.create(ChangeMessages.class);
- @Source("star_open.png") ImageResource star_open();
- @Source("star_filled.png") ImageResource star_filled();
- @Source("remove_reviewer.png") ImageResource remove_reviewer();
@Source("common.css") Style style();
public interface Style extends CssResource {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java
index 215f39d6b2..5ec292b984 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java
@@ -30,6 +30,7 @@ class RestoreAction extends ActionMessageBox {
this.id = id;
}
+ @Override
void send(String message) {
ChangeApi.restore(id.get(), message, new GerritCallback<ChangeInfo>() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java
index 5f9f07604f..47d6cad3a9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java
@@ -19,17 +19,19 @@ import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.ActionDialog;
+import com.google.gerrit.client.ui.TextAreaActionDialog;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
class RevertAction {
- static void call(Button b, final Change.Id id, final String revision,
- String project, final String commitSubject) {
+ static void call(final Button b, final Change.Id id, final String revision,
+ final String commitSubject) {
// TODO Replace ActionDialog with a nicer looking display.
b.setEnabled(false);
- new ActionDialog(b, false,
+ new TextAreaActionDialog(
Util.C.revertChangeTitle(),
Util.C.headingRevertMessage()) {
{
@@ -56,6 +58,12 @@ class RevertAction {
}
});
}
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ super.onClose(event);
+ b.setEnabled(true);
+ }
}.center();
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
index b5a3023350..7af247a233 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
@@ -25,28 +25,32 @@ import com.google.gerrit.client.ui.SuggestAfterTypingNCharsOracle;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
-import com.google.gwt.user.client.ui.SuggestOracle;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** REST API based suggestion Oracle for reviewers. */
-public class RestReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
-
+public class ReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
private Change.Id changeId;
@Override
- protected void _onRequestSuggestions(final Request req, final Callback callback) {
- ChangeApi.suggestReviewers(changeId.get(), req.getQuery(),
- req.getLimit()).get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
+ protected void _onRequestSuggestions(final Request req, final Callback cb) {
+ ChangeApi.suggestReviewers(changeId.get(), req.getQuery(), req.getLimit())
+ .get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
@Override
public void onSuccess(JsArray<SuggestReviewerInfo> result) {
- final List<RestReviewerSuggestion> r =
- new ArrayList<>(result.length());
- for (final SuggestReviewerInfo reviewer : Natives.asList(result)) {
+ List<RestReviewerSuggestion> r = new ArrayList<>(result.length());
+ for (SuggestReviewerInfo reviewer : Natives.asList(result)) {
r.add(new RestReviewerSuggestion(reviewer));
}
- callback.onSuggestionsReady(req, new Response(r));
+ cb.onSuggestionsReady(req, new Response(r));
+ }
+
+ @Override
+ public void onFailure(Throwable err) {
+ List<Suggestion> r = Collections.emptyList();
+ cb.onSuggestionsReady(req, new Response(r));
}
});
}
@@ -55,13 +59,14 @@ public class RestReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
this.changeId = changeId;
}
- private static class RestReviewerSuggestion implements SuggestOracle.Suggestion {
+ private static class RestReviewerSuggestion implements Suggestion {
private final SuggestReviewerInfo reviewer;
RestReviewerSuggestion(final SuggestReviewerInfo reviewer) {
this.reviewer = reviewer;
}
+ @Override
public String getDisplayString() {
if (reviewer.account() != null) {
return FormatUtil.nameEmail(reviewer.account());
@@ -72,6 +77,7 @@ public class RestReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
+ ")";
}
+ @Override
public String getReplacementString() {
if (reviewer.account() != null) {
return FormatUtil.nameEmail(reviewer.account());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
index 87320b85e7..18e5e8789c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -27,16 +27,15 @@ import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.uibinder.client.UiBinder;
@@ -46,9 +45,6 @@ import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.SuggestBox;
-import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
-import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -69,53 +65,36 @@ public class Reviewers extends Composite {
@UiField Element form;
@UiField Element error;
@UiField(provided = true)
- SuggestBox suggestBox;
+ RemoteSuggestBox suggestBox;
- private ChangeScreen2.Style style;
+ private ChangeScreen.Style style;
private Element ccText;
- private RestReviewerSuggestOracle reviewerSuggestOracle;
- private HintTextBox nameTxtBox;
+ private ReviewerSuggestOracle reviewerSuggestOracle;
private Change.Id changeId;
- private boolean submitOnSelection;
Reviewers() {
- reviewerSuggestOracle = new RestReviewerSuggestOracle();
- nameTxtBox = new HintTextBox();
- suggestBox = new SuggestBox(reviewerSuggestOracle, nameTxtBox);
- initWidget(uiBinder.createAndBindUi(this));
-
- nameTxtBox.setVisibleLength(55);
- nameTxtBox.setHintText(Util.C.approvalTableAddReviewerHint());
- nameTxtBox.addKeyDownHandler(new KeyDownHandler() {
+ reviewerSuggestOracle = new ReviewerSuggestOracle();
+ suggestBox = new RemoteSuggestBox(reviewerSuggestOracle);
+ suggestBox.setVisibleLength(55);
+ suggestBox.setHintText(Util.C.approvalTableAddReviewerHint());
+ suggestBox.addCloseHandler(new CloseHandler<RemoteSuggestBox>() {
@Override
- public void onKeyDown(KeyDownEvent e) {
- submitOnSelection = false;
-
- if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
- onCancel(null);
- } else if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- if (((DefaultSuggestionDisplay) suggestBox.getSuggestionDisplay())
- .isSuggestionListShowing()) {
- submitOnSelection = true;
- } else {
- onAdd(null);
- }
- }
+ public void onClose(CloseEvent<RemoteSuggestBox> event) {
+ Reviewers.this.onCancel(null);
}
});
- suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ suggestBox.addSelectionHandler(new SelectionHandler<String>() {
@Override
- public void onSelection(SelectionEvent<Suggestion> event) {
- nameTxtBox.setFocus(true);
- if (submitOnSelection) {
- onAdd(null);
- }
+ public void onSelection(SelectionEvent<String> event) {
+ addReviewer(event.getSelectedItem(), false);
}
});
+
+ initWidget(uiBinder.createAndBindUi(this));
}
- void init(ChangeScreen2.Style style, Element ccText) {
+ void init(ChangeScreen.Style style, Element ccText) {
this.style = style;
this.ccText = ccText;
}
@@ -128,7 +107,7 @@ public class Reviewers extends Composite {
}
@UiHandler("openForm")
- void onOpenForm(ClickEvent e) {
+ void onOpenForm(@SuppressWarnings("unused") ClickEvent e) {
onOpenForm();
}
@@ -140,33 +119,33 @@ public class Reviewers extends Composite {
}
@UiHandler("add")
- void onAdd(ClickEvent e) {
- String reviewer = suggestBox.getText();
- if (!reviewer.isEmpty()) {
- addReviewer(reviewer, false);
- }
+ void onAdd(@SuppressWarnings("unused") ClickEvent e) {
+ addReviewer(suggestBox.getText(), false);
}
@UiHandler("addMe")
- void onAddMe(ClickEvent e) {
+ void onAddMe(@SuppressWarnings("unused") ClickEvent e) {
String accountId = String.valueOf(Gerrit.getUserAccountInfo()._account_id());
addReviewer(accountId, false);
}
@UiHandler("cancel")
- void onCancel(ClickEvent e) {
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
openForm.setVisible(true);
UIObject.setVisible(form, false);
suggestBox.setFocus(false);
}
private void addReviewer(final String reviewer, boolean confirmed) {
+ if (reviewer.isEmpty()) {
+ return;
+ }
+
ChangeApi.reviewers(changeId.get()).post(
PostInput.create(reviewer, confirmed),
new GerritCallback<PostResult>() {
+ @Override
public void onSuccess(PostResult result) {
- nameTxtBox.setEnabled(true);
-
if (result.confirm()) {
askForConfirmation(result.error());
} else if (result.error() != null) {
@@ -175,7 +154,7 @@ public class Reviewers extends Composite {
} else {
UIObject.setVisible(error, false);
error.setInnerText("");
- nameTxtBox.setText("");
+ suggestBox.setText("");
if (result.reviewers() != null
&& result.reviewers().length() > 0) {
@@ -202,7 +181,6 @@ public class Reviewers extends Composite {
error.setInnerText(err instanceof StatusCodeException
? ((StatusCodeException) err).getEncodedResponse()
: err.getMessage());
- nameTxtBox.setEnabled(true);
}
});
}
@@ -230,7 +208,6 @@ public class Reviewers extends Composite {
for (Integer i : r.keySet()) {
cc.remove(i);
}
- r.remove(info.owner()._account_id());
cc.remove(info.owner()._account_id());
Set<Integer> removable = new HashSet<>();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
index 024197d48d..9924c1d73d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
@@ -17,15 +17,16 @@ limitations under the License.
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
- xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:u='urn:import:com.google.gerrit.client.ui'>
<ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
<ui:style>
button.openAdd {
margin: 3px 3px 0 0;
float: right;
- color: #444;
+ color: rgba(0, 0, 0, 0.15);
background-color: #f5f5f5;
- background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+ background-image: none;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
@@ -64,7 +65,7 @@ limitations under the License.
</g:Button>
</div>
<div ui:field='form' style='display: none' aria-hidden='true'>
- <g:SuggestBox ui:field='suggestBox' styleName='{style.suggestBox}'/>
+ <u:RemoteSuggestBox ui:field='suggestBox' styleName='{style.suggestBox}'/>
<div ui:field='error'
class='{style.error}'
style='display: none' aria-hidden='true'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
index 34240641c3..1bf6f6c5a7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
@@ -26,13 +26,13 @@ import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.user.client.PluginSafePopupPanel;
abstract class RightSidePopdownAction {
- private final ChangeScreen2.Style style;
+ private final ChangeScreen.Style style;
private final Widget button;
private final UIObject relativeTo;
private PopupPanel popup;
RightSidePopdownAction(
- ChangeScreen2.Style style,
+ ChangeScreen.Style style,
UIObject relativeTo,
Widget button) {
this.style = style;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java
index fccca27058..0f19406d0c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java
@@ -14,13 +14,14 @@
package com.google.gerrit.client.change;
+import com.google.gerrit.client.Gerrit;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ToggleButton;
class StarIcon extends ToggleButton {
StarIcon() {
super(
- new Image(Resources.I.star_open()),
- new Image(Resources.I.star_filled()));
+ new Image(Gerrit.RESOURCES.starOpen()),
+ new Image(Gerrit.RESOURCES.starFilled()));
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
index 4ca992b028..09d34763b0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
@@ -19,7 +19,6 @@ import com.google.gerrit.client.api.ChangeGlue;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
-import com.google.gerrit.client.changes.SubmitFailureDialog;
import com.google.gerrit.client.changes.SubmitInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.PageLinks;
@@ -32,10 +31,12 @@ class SubmitAction {
ChangeApi.submit(
changeId.get(), revisionInfo.name(),
new GerritCallback<SubmitInfo>() {
+ @Override
public void onSuccess(SubmitInfo result) {
redisplay();
}
+ @Override
public void onFailure(Throwable err) {
if (SubmitFailureDialog.isConflict(err)) {
new SubmitFailureDialog(err.getMessage()).center();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitFailureDialog.java
index bd97790e85..c9ac2cb267 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitFailureDialog.java
@@ -12,19 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.client.changes;
+package com.google.gerrit.client.change;
import com.google.gerrit.client.ErrorDialog;
+import com.google.gerrit.client.changes.Util;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import com.google.gwtjsonrpc.client.RemoteJsonException;
-public class SubmitFailureDialog extends ErrorDialog {
- public static boolean isConflict(Throwable err) {
+class SubmitFailureDialog extends ErrorDialog {
+ static boolean isConflict(Throwable err) {
return err instanceof RemoteJsonException
&& 409 == ((RemoteJsonException) err).getCode();
}
- public SubmitFailureDialog(String msg) {
+ SubmitFailureDialog(String msg) {
super(new SafeHtmlBuilder().append(msg.trim()).wikify());
setText(Util.C.submitFailed());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
index 512203a22b..9f456789ef 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
@@ -27,6 +27,7 @@ import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
@@ -97,6 +98,7 @@ class Topic extends Composite {
void onEdit() {
if (canEdit) {
+ UIObject.setVisible(show, false);
UIObject.setVisible(form, true);
input.setText(text.getText());
@@ -105,9 +107,10 @@ class Topic extends Composite {
}
@UiHandler("cancel")
- void onCancel(ClickEvent e) {
+ void onCancel(@SuppressWarnings("unused") ClickEvent e) {
input.setFocus(false);
UIObject.setVisible(form, false);
+ UIObject.setVisible(show, true);
}
@UiHandler("input")
@@ -116,12 +119,13 @@ class Topic extends Composite {
onCancel(null);
} else if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
e.stopPropagation();
+ e.preventDefault();
onSave(null);
}
}
@UiHandler("save")
- void onSave(ClickEvent e) {
+ void onSave(@SuppressWarnings("unused") ClickEvent e) {
ChangeApi.topic(
psId.getParentKey().get(),
input.getValue().trim(),
@@ -135,4 +139,11 @@ class Topic extends Composite {
});
onCancel(null);
}
+
+ @UiHandler("save")
+ void onSaveKeyPress(KeyPressEvent e) {
+ if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ e.stopPropagation();
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java
index 2b6f418e31..cdbc6938c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java
@@ -61,12 +61,12 @@ abstract class UpdateAvailableBar extends Composite {
}
@UiHandler("show")
- void onShow(ClickEvent e) {
+ void onShow(@SuppressWarnings("unused") ClickEvent e) {
onShow();
}
@UiHandler("ignore")
- void onIgnore(ClickEvent e) {
+ void onIgnore(@SuppressWarnings("unused") ClickEvent e) {
onIgnore(updated);
removeFromParent();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateCheckTimer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateCheckTimer.java
index 9d2a4676d5..5820a50b25 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateCheckTimer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateCheckTimer.java
@@ -28,11 +28,11 @@ class UpdateCheckTimer extends Timer implements ValueChangeHandler<Boolean> {
private static final int POLL_PERIOD =
Gerrit.getConfig().getChangeUpdateDelay() * 1000;
- private final ChangeScreen2 screen;
+ private final ChangeScreen screen;
private int delay;
private boolean running;
- UpdateCheckTimer(ChangeScreen2 screen) {
+ UpdateCheckTimer(ChangeScreen screen) {
this.screen = screen;
this.delay = POLL_PERIOD;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
index ae00dc7752..bb7cb2780d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
@@ -29,11 +29,11 @@
.popup button,
.popup input[type='button'] {
margin: 0 3px 0 0;
- border-color: rgba(0, 0, 0, 0.1);
+ border-color: rgba(0, 0, 0, 0.15) !important;
text-align: center;
font-size: 11px;
font-weight: bold;
- border: 1px solid;
+ border: 2px solid;
cursor: pointer;
color: #fff;
background-color: #4d90fe;
@@ -53,8 +53,8 @@
width: 54px;
white-space: nowrap;
color: #fff;
- height: 10px;
- line-height: 10px;
+ height: 14px;
+ line-height: 14px;
}
.section {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
index 4b695d2265..2803db38ab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -13,12 +13,22 @@
* limitations under the License.
*/
-.pointer, .reviewed {
- width: 12px;
+.pointer, .reviewed, .removeButton {
padding: 0px;
vertical-align: top;
}
+.pointer {
+ width: 12px;
+}
+.reviewed, .removeButton {
+ height: 19px;
+ width: 20px;
+}
+.table th {
+ vertical-align: top;
+ text-align: left;
+}
.table tr {
vertical-align: top;
}
@@ -40,6 +50,7 @@
}
.pathColumn a {
color: #000;
+ cursor: pointer;
}
.commonPrefix {
color: #888;
@@ -85,3 +96,12 @@
background-color: #d44;
}
+.removeButton button {
+ cursor: pointer;
+ padding: 0;
+ margin: 0 0 0 5px;
+ border: 0;
+ background-color: transparent;
+ white-space: nowrap;
+}
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/more_less.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/moreLess.png
index 298514ffc8..298514ffc8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/more_less.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/moreLess.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png
deleted file mode 100644
index 5a3e6f0b16..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index f312d11419..d9e9878f27 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -19,7 +19,7 @@ import com.google.gerrit.client.NotFoundScreen;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -32,10 +32,10 @@ import java.util.EnumSet;
public class AccountDashboardScreen extends Screen implements ChangeListScreen {
private final Account.Id ownerId;
private final boolean mine;
- private ChangeTable2 table;
- private ChangeTable2.Section outgoing;
- private ChangeTable2.Section incoming;
- private ChangeTable2.Section closed;
+ private ChangeTable table;
+ private ChangeTable.Section outgoing;
+ private ChangeTable.Section incoming;
+ private ChangeTable.Section closed;
public AccountDashboardScreen(final Account.Id id) {
ownerId = id;
@@ -45,7 +45,7 @@ public class AccountDashboardScreen extends Screen implements ChangeListScreen {
@Override
protected void onInitUI() {
super.onInitUI();
- table = new ChangeTable2() {
+ table = new ChangeTable() {
{
keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadSearch()) {
@Override
@@ -57,9 +57,9 @@ public class AccountDashboardScreen extends Screen implements ChangeListScreen {
};
table.addStyleName(Gerrit.RESOURCES.css().accountDashboard());
- outgoing = new ChangeTable2.Section();
- incoming = new ChangeTable2.Section();
- closed = new ChangeTable2.Section();
+ outgoing = new ChangeTable.Section();
+ incoming = new ChangeTable.Section();
+ closed = new ChangeTable.Section();
outgoing.setTitleText(Util.C.outgoingReviews());
incoming.setTitleText(Util.C.incomingReviews());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
deleted file mode 100644
index b41a082175..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ /dev/null
@@ -1,394 +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.client.changes;
-
-import static com.google.gerrit.common.data.LabelValue.formatValue;
-
-import com.google.gerrit.client.ConfirmationCallback;
-import com.google.gerrit.client.ConfirmationDialog;
-import com.google.gerrit.client.ErrorDialog;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
-import com.google.gerrit.client.change.Reviewers.PostInput;
-import com.google.gerrit.client.change.Reviewers.PostResult;
-import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
-import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.ui.AccountLinkPanel;
-import com.google.gerrit.client.ui.AddMemberBox;
-import com.google.gerrit.client.ui.ReviewerSuggestOracle;
-import com.google.gerrit.common.data.ApprovalDetail;
-import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.PushButton;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/** Displays a table of {@link ApprovalDetail} objects for a change record. */
-public class ApprovalTable extends Composite {
- private final Grid table;
- private final Widget missing;
- private final Panel addReviewer;
- private final ReviewerSuggestOracle reviewerSuggestOracle;
- private final AddMemberBox addMemberBox;
- private ChangeInfo lastChange;
- private Map<Integer, Integer> rows;
-
- public ApprovalTable() {
- rows = new HashMap<>();
- table = new Grid(1, 3);
- table.addStyleName(Gerrit.RESOURCES.css().infoTable());
-
- missing = new Widget() {
- {
- setElement((Element)(DOM.createElement("ul")));
- }
- };
- missing.setStyleName(Gerrit.RESOURCES.css().missingApprovalList());
-
- addReviewer = new FlowPanel();
- addReviewer.setStyleName(Gerrit.RESOURCES.css().addReviewer());
- reviewerSuggestOracle = new ReviewerSuggestOracle();
- addMemberBox =
- new AddMemberBox(Util.C.approvalTableAddReviewer(),
- Util.C.approvalTableAddReviewerHint(), reviewerSuggestOracle);
- addMemberBox.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- doAddReviewer();
- }
- });
- addReviewer.add(addMemberBox);
- addReviewer.setVisible(false);
-
- final FlowPanel fp = new FlowPanel();
- fp.add(table);
- fp.add(missing);
- fp.add(addReviewer);
- initWidget(fp);
-
- setStyleName(Gerrit.RESOURCES.css().approvalTable());
- }
-
- /**
- * Sets the header row
- *
- * @param labels The list of labels to display in the header. This list does
- * not get resorted, so be sure that the list's elements are in the same
- * order as the list of labels passed to the {@code displayRow} method.
- */
- private void displayHeader(Collection<String> labels) {
- table.resizeColumns(2 + labels.size());
-
- final CellFormatter fmt = table.getCellFormatter();
- int col = 0;
-
- table.setText(0, col, Util.C.approvalTableReviewer());
- fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header());
- col++;
-
- table.clearCell(0, col);
- fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header());
- col++;
-
- for (String name : labels) {
- table.setText(0, col, name);
- fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header());
- col++;
- }
- fmt.addStyleName(0, col - 1, Gerrit.RESOURCES.css().rightmost());
- }
-
- void display(ChangeInfo change) {
- lastChange = change;
- reviewerSuggestOracle.setChange(change.legacy_id());
- Map<Integer, ApprovalDetail> byUser = new LinkedHashMap<>();
- Map<Integer, AccountInfo> accounts = new LinkedHashMap<>();
- List<String> missingLabels = initLabels(change, accounts, byUser);
-
- removeAllChildren(missing.getElement());
- for (String label : missingLabels) {
- addMissingLabel(Util.M.needApproval(label));
- }
-
- if (byUser.isEmpty()) {
- table.setVisible(false);
- } else {
- List<String> labels = new ArrayList<>(change.labels());
- Collections.sort(labels);
- displayHeader(labels);
- table.resizeRows(1 + byUser.size());
- int i = 1;
- for (ApprovalDetail ad : ApprovalDetail.sort(
- byUser.values(), change.owner()._account_id())) {
- displayRow(i++, ad, labels, accounts.get(ad.getAccount().get()));
- }
- table.setVisible(true);
- }
-
- if (change.status() != Change.Status.MERGED
- && !change.mergeable()) {
- addMissingLabel(Util.C.messageNeedsRebaseOrHasDependency());
- }
- missing.setVisible(missing.getElement().getChildCount() > 0);
- addReviewer.setVisible(Gerrit.isSignedIn());
- }
-
- private void removeAllChildren(Element el) {
- for (int i = DOM.getChildCount(el) - 1; i >= 0; i--) {
- el.removeChild(DOM.getChild(el, i));
- }
- }
-
- private void addMissingLabel(String text) {
- Element li = DOM.createElement("li");
- li.setClassName(Gerrit.RESOURCES.css().missingApproval());
- li.setInnerText(text);
- DOM.appendChild(missing.getElement(), li);
- }
-
- private Set<Integer> removableReviewers(ChangeInfo change) {
- Set<Integer> result =
- new HashSet<>(change.removable_reviewers().length());
- for (int i = 0; i < change.removable_reviewers().length(); i++) {
- result.add(change.removable_reviewers().get(i)._account_id());
- }
- return result;
- }
-
- private List<String> initLabels(ChangeInfo change,
- Map<Integer, AccountInfo> accounts,
- Map<Integer, ApprovalDetail> byUser) {
- Set<Integer> removableReviewers = removableReviewers(change);
- List<String> missing = new ArrayList<>();
- for (String name : change.labels()) {
- LabelInfo label = change.label(name);
-
- String min = null;
- String max = null;
- for (String v : label.values()) {
- if (min == null) {
- min = v;
- }
- if (v.startsWith("+")) {
- max = v;
- }
- }
-
- if (label.status() == SubmitRecord.Label.Status.NEED) {
- missing.add(name);
- }
-
- if (label.all() != null) {
- for (ApprovalInfo ai : Natives.asList(label.all())) {
- if (!accounts.containsKey(ai._account_id())) {
- accounts.put(ai._account_id(), ai);
- }
- int id = ai._account_id();
- ApprovalDetail ad = byUser.get(id);
- if (ad == null) {
- ad = new ApprovalDetail(new Account.Id(id));
- ad.setCanRemove(removableReviewers.contains(id));
- byUser.put(id, ad);
- }
- if (ai.has_value()) {
- ad.votable(name);
- ad.value(name, ai.value());
- String fv = formatValue(ai.value());
- if (fv.equals(max)) {
- ad.approved(name);
- } else if (ai.value() < 0 && fv.equals(min)) {
- ad.rejected(name);
- }
- }
- }
- }
- }
- return missing;
- }
-
- private void doAddReviewer() {
- String reviewer = addMemberBox.getText();
- if (!reviewer.isEmpty()) {
- addMemberBox.setEnabled(false);
- addReviewer(reviewer, false);
- }
- }
-
- private void addReviewer(final String reviewer, boolean confirmed) {
- ChangeApi.reviewers(lastChange.legacy_id().get()).post(
- PostInput.create(reviewer, confirmed),
- new GerritCallback<PostResult>() {
- public void onSuccess(PostResult result) {
- addMemberBox.setEnabled(true);
- addMemberBox.setText("");
- if (result.error() == null) {
- reload();
- } else if (result.confirm()) {
- askForConfirmation(result.error());
- } else {
- new ErrorDialog(new SafeHtmlBuilder().append(result.error()));
- }
- }
-
- private void askForConfirmation(String text) {
- String title = Util.C
- .approvalTableAddManyReviewersConfirmationDialogTitle();
- ConfirmationDialog confirmationDialog = new ConfirmationDialog(
- title, new SafeHtmlBuilder().append(text),
- new ConfirmationCallback() {
- @Override
- public void onOk() {
- addReviewer(reviewer, true);
- }
- });
- confirmationDialog.center();
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- addMemberBox.setEnabled(true);
- if (isNoSuchEntity(caught)) {
- new ErrorDialog(Util.M.reviewerNotFound(reviewer)).center();
- } else {
- super.onFailure(caught);
- }
- }
- });
- }
-
- /**
- * Sets the reviewer data for a row.
- *
- * @param row The number of the row on which to set the reviewer.
- * @param ad The details for this reviewer's approval.
- * @param labels The list of labels to show. This list does not get resorted,
- * so be sure that the list's elements are in the same order as the list
- * of labels passed to the {@code displayHeader} method.
- * @param account The account information for the approval.
- */
- private void displayRow(int row, final ApprovalDetail ad,
- List<String> labels, AccountInfo account) {
- final CellFormatter fmt = table.getCellFormatter();
- int col = 0;
-
- table.setWidget(row, col++, new AccountLinkPanel(account));
- rows.put(account._account_id(), row);
-
- if (ad.canRemove()) {
- final PushButton remove = new PushButton( //
- new Image(Util.R.removeReviewerNormal()), //
- new Image(Util.R.removeReviewerPressed()));
- remove.setTitle(Util.M.removeReviewer(account.name()));
- remove.setStyleName(Gerrit.RESOURCES.css().removeReviewer());
- remove.addStyleName(Gerrit.RESOURCES.css().link());
- remove.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- doRemove(ad, remove);
- }
- });
- table.setWidget(row, col, remove);
- } else {
- table.clearCell(row, col);
- }
- fmt.setStyleName(row, col++, Gerrit.RESOURCES.css().removeReviewerCell());
-
- for (String labelName : labels) {
- fmt.setStyleName(row, col, Gerrit.RESOURCES.css().approvalscore());
- if (!ad.canVote(labelName)) {
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().notVotable());
- fmt.getElement(row, col).setTitle(Gerrit.C.userCannotVoteToolTip());
- }
-
- if (ad.isRejected(labelName)) {
- table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot()));
-
- } else if (ad.isApproved(labelName)) {
- table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck()));
-
- } else {
- int v = ad.getValue(labelName);
- if (v == 0) {
- table.clearCell(row, col);
- col++;
- continue;
- }
- String vstr = String.valueOf(ad.getValue(labelName));
- if (v > 0) {
- vstr = "+" + vstr;
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore());
- } else {
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
- }
- table.setText(row, col, vstr);
- }
-
- col++;
- }
-
- fmt.addStyleName(row, col - 1, Gerrit.RESOURCES.css().rightmost());
- }
-
- private void reload() {
- ChangeApi.detail(lastChange.legacy_id().get(),
- new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- display(result);
- }
- });
- }
-
- private void doRemove(ApprovalDetail ad, final PushButton remove) {
- remove.setEnabled(false);
- ChangeApi.reviewer(lastChange.legacy_id().get(), ad.getAccount().get())
- .delete(new GerritCallback<JavaScriptObject>() {
- @Override
- public void onSuccess(JavaScriptObject result) {
- reload();
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- remove.setEnabled(true);
- super.onFailure(caught);
- }
- });
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 406ffd9c29..98595e7aff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -14,9 +14,14 @@
package com.google.gerrit.client.changes;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.client.changes.ChangeInfo.IncludedInInfo;
+import com.google.gerrit.client.rpc.CallbackGroup.Callback;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -33,6 +38,28 @@ public class ChangeApi {
call(id, "abandon").post(input, cb);
}
+ /** Create a new change.
+ *
+ * The new change is created as DRAFT unless the draft workflow is disabled
+ * by `change.allowDrafts = false` in the configuration, in which case the
+ * new change is created as NEW.
+ *
+ */
+ public static void createChange(String project, String branch,
+ String subject, String base, AsyncCallback<ChangeInfo> cb) {
+ CreateChangeInput input = CreateChangeInput.create();
+ input.project(emptyToNull(project));
+ input.branch(emptyToNull(branch));
+ input.subject(emptyToNull(subject));
+ input.base_change(emptyToNull(base));
+
+ if (Gerrit.getConfig().isAllowDraftChanges()) {
+ input.status(Change.Status.DRAFT.toString());
+ }
+
+ new RestApi("/changes/").post(input, cb);
+ }
+
/** Restore a previously abandoned change to be open again. */
public static void restore(int id, String msg, AsyncCallback<ChangeInfo> cb) {
Input input = Input.create();
@@ -68,6 +95,22 @@ public class ChangeApi {
return call(id, "detail");
}
+ public static void edit(int id, AsyncCallback<EditInfo> cb) {
+ edit(id).get(cb);
+ }
+
+ public static void editWithFiles(int id, AsyncCallback<EditInfo> cb) {
+ edit(id).addParameterTrue("list").get(cb);
+ }
+
+ public static RestApi edit(int id) {
+ return change(id).view("edit");
+ }
+
+ public static RestApi editWithCommands(int id) {
+ return edit(id).addParameterTrue("download-commands");
+ }
+
public static void includedIn(int id, AsyncCallback<IncludedInInfo> cb) {
call(id, "in").get(cb);
}
@@ -103,6 +146,13 @@ public class ChangeApi {
return change(id).view("reviewers").id(reviewer);
}
+ public static RestApi hashtags(int changeId) {
+ return change(changeId).view("hashtags");
+ }
+ public static RestApi hashtag(int changeId, String hashtag){
+ return change(changeId).view("hashtags").id(hashtag);
+ }
+
/** Submit a specific revision of a change. */
public static void cherrypick(int id, String commit, String destination, String message, AsyncCallback<ChangeInfo> cb) {
CherryPickInput cherryPickInput = CherryPickInput.create();
@@ -142,10 +192,28 @@ public class ChangeApi {
revision(id, commit).delete(cb);
}
- /** Rebase a revision onto the branch tip. */
- public static void rebase(int id, String commit, AsyncCallback<ChangeInfo> cb) {
+ /** Delete change edit. */
+ public static void deleteEdit(int id, AsyncCallback<JavaScriptObject> cb) {
+ edit(id).delete(cb);
+ }
+
+ /** Publish change edit. */
+ public static void publishEdit(int id, AsyncCallback<JavaScriptObject> cb) {
+ JavaScriptObject in = JavaScriptObject.createObject();
+ change(id).view("edit:publish").post(in, cb);
+ }
+
+ /** Rebase change edit on latest patch set. */
+ public static void rebaseEdit(int id, AsyncCallback<JavaScriptObject> cb) {
JavaScriptObject in = JavaScriptObject.createObject();
- call(id, commit, "rebase").post(in, cb);
+ change(id).view("edit:rebase").post(in, cb);
+ }
+
+ /** Rebase a revision onto the branch tip or another change. */
+ public static void rebase(int id, String commit, String base, AsyncCallback<ChangeInfo> cb) {
+ RebaseInput rebaseInput = RebaseInput.create();
+ rebaseInput.setBase(base);
+ call(id, commit, "rebase").post(rebaseInput, cb);
}
private static class Input extends JavaScriptObject {
@@ -160,6 +228,21 @@ public class ChangeApi {
}
}
+ private static class CreateChangeInput extends JavaScriptObject {
+ static CreateChangeInput create() {
+ return (CreateChangeInput) createObject();
+ }
+
+ public final native void branch(String b) /*-{ if(b)this.branch=b; }-*/;
+ public final native void project(String p) /*-{ if(p)this.project=p; }-*/;
+ public final native void subject(String s) /*-{ if(s)this.subject=s; }-*/;
+ public final native void base_change(String b) /*-{ if(b)this.base_change=b; }-*/;
+ public final native void status(String s) /*-{ if(s)this.status=s; }-*/;
+
+ protected CreateChangeInput() {
+ }
+ }
+
private static class CherryPickInput extends JavaScriptObject {
static CherryPickInput create() {
return (CherryPickInput) createObject();
@@ -171,6 +254,17 @@ public class ChangeApi {
}
}
+ private static class RebaseInput extends JavaScriptObject {
+ final native void setBase(String b) /*-{ this.base = b; }-*/;
+
+ static RebaseInput create() {
+ return (RebaseInput) createObject();
+ }
+
+ protected RebaseInput() {
+ }
+ }
+
private static class SubmitInput extends JavaScriptObject {
final native void wait_for_merge(boolean b) /*-{ this.wait_for_merge=b; }-*/;
@@ -198,4 +292,12 @@ public class ChangeApi {
public static String emptyToNull(String str) {
return str == null || str.isEmpty() ? null : str;
}
+
+ public static void commitWithLinks(int changeId, String revision,
+ Callback<CommitInfo> callback) {
+ revision(changeId, revision)
+ .view("commit")
+ .addParameterTrue("links")
+ .get(callback);
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
deleted file mode 100644
index 7fd5290c6b..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2012 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.client.changes;
-
-import com.google.gerrit.client.ui.ListenableValue;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.reviewdb.client.Change;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** A Cache to store common client side data by change */
-public class ChangeCache {
- private static Map<Change.Id, ChangeCache> caches = new HashMap<>();
-
- public static ChangeCache get(Change.Id chg) {
- ChangeCache cache = caches.get(chg);
- if (cache == null) {
- cache = new ChangeCache(chg);
- caches.put(chg, cache);
- }
- return cache;
- }
-
- private Change.Id changeId;
- private ChangeDetailCache detail;
- private ListenableValue<ChangeInfo> info;
-
- protected ChangeCache(Change.Id chg) {
- changeId = chg;
- }
-
- public Change.Id getChangeId() {
- return changeId;
- }
-
- public ChangeDetailCache getChangeDetailCache() {
- if (detail == null) {
- detail = new ChangeDetailCache(changeId);
- }
- return detail;
- }
-
- public ListenableValue<ChangeInfo> getChangeInfoCache() {
- if (info == null) {
- info = new ListenableValue<>();
- }
- return info;
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 03c58d2d7f..6531129f09 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -25,6 +25,7 @@ public interface ChangeConstants extends Constants {
String readyToSubmit();
String mergeConflict();
String notCurrent();
+ String changeEdit();
String myDashboardTitle();
String unknownDashboardTitle();
@@ -163,6 +164,12 @@ public interface ChangeConstants extends Constants {
String cherryPickCommitMessage();
String cherryPickTitle();
+ String buttonRebaseChangeSend();
+ String rebaseConfirmMessage();
+ String rebaseNotPossibleMessage();
+ String rebasePlaceholderMessage();
+ String rebaseTitle();
+
String buttonAbandonChangeBegin();
String buttonAbandonChangeSend();
String headingAbandonMessage();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index c904cac216..45415c5629 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -6,6 +6,7 @@ statusLongDraft = Draft
readyToSubmit = Ready to Submit
mergeConflict = Merge Conflict
notCurrent = Not Current
+changeEdit = Change Edit
starredHeading = Starred Changes
watchedHeading = Open Changes of Watched Projects
@@ -149,6 +150,12 @@ headingCherryPickBranch = Cherry Pick to Branch:
cherryPickCommitMessage = Cherry Pick Commit Message:
cherryPickTitle = Code Review - Cherry Pick Change to Another Branch
+buttonRebaseChangeSend = Rebase
+rebaseConfirmMessage = Change parent revision
+rebaseNotPossibleMessage = Change is already up to date
+rebasePlaceholderMessage = (subject, change number, or leave empty)
+rebaseTitle = Code Review - Rebase Change
+
buttonRestoreChangeBegin = Restore Change
restoreChangeTitle = Code Review - Restore Change
headingRestoreMessage = Restore Message:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
deleted file mode 100644
index 8f2642c385..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
+++ /dev/null
@@ -1,48 +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.client.changes;
-
-import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.common.data.AccountInfoCache;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwtexpui.globalkey.client.KeyCommandSet;
-
-public class ChangeDescriptionBlock extends Composite {
- private final ChangeInfoBlock infoBlock;
- private final CommitMessageBlock messageBlock;
-
- public ChangeDescriptionBlock(KeyCommandSet keysAction) {
- infoBlock = new ChangeInfoBlock();
- messageBlock = new CommitMessageBlock(keysAction);
-
- final HorizontalPanel hp = new HorizontalPanel();
- hp.add(infoBlock);
- hp.add(messageBlock);
- initWidget(hp);
- }
-
- public void display(ChangeDetail changeDetail, Boolean starred, Boolean canEditCommitMessage,
- PatchSetInfo info, AccountInfoCache acc,
- SubmitTypeRecord submitTypeRecord,
- CommentLinkProcessor commentLinkProcessor) {
- infoBlock.display(changeDetail, acc, submitTypeRecord);
- messageBlock.display(changeDetail.getChange().currentPatchSetId(), info.getRevId(), starred,
- canEditCommitMessage, info.getMessage(), commentLinkProcessor);
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
deleted file mode 100644
index f2c97c1ac9..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright (C) 2012 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.client.changes;
-
-import com.google.gerrit.client.actions.ActionInfo;
-import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
-import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
-import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
-import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
-import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.client.ui.ListenableValue;
-import com.google.gerrit.common.data.AccountInfo;
-import com.google.gerrit.common.data.AccountInfoCache;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.common.data.UiCommandDetail;
-import com.google.gerrit.extensions.common.ListChangesOption;
-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.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.PatchSetInfo.ParentInfo;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.UserIdentity;
-import com.google.gwt.core.client.JsArray;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-public class ChangeDetailCache extends ListenableValue<ChangeDetail> {
- public static class NewGerritCallback extends
- com.google.gerrit.client.rpc.GerritCallback<ChangeInfo> {
- @Override
- public void onSuccess(ChangeInfo detail) {
- setChangeDetail(reverse(detail));
- }
- }
-
- public static class IgnoreErrorCallback implements AsyncCallback<ChangeDetail> {
- @Override
- public void onSuccess(ChangeDetail info) {
- setChangeDetail(info);
- }
-
- @Override
- public void onFailure(Throwable caught) {
- }
- }
-
- public static ChangeDetail reverse(ChangeInfo info) {
- info.revisions().copyKeysIntoChildren("name");
- RevisionInfo rev = current(info);
-
- ChangeDetail r = new ChangeDetail();
- r.setAllowsAnonymous(rev.has_fetch() && rev.fetch().containsKey("http"));
- r.setCanAbandon(can(info.actions(), "abandon"));
- r.setCanEditCommitMessage(can(rev.actions(), "message"));
- r.setCanCherryPick(can(rev.actions(), "cherrypick"));
- r.setCanPublish(can(rev.actions(), "publish"));
- r.setCanRebase(can(rev.actions(), "rebase"));
- r.setCanRestore(can(info.actions(), "restore"));
- r.setCanRevert(can(info.actions(), "revert"));
- r.setCanDeleteDraft(can(info.actions(), "/"));
- r.setCanEditTopicName(can(info.actions(), "topic"));
- r.setCanSubmit(can(rev.actions(), "submit"));
- r.setCanEdit(true);
- r.setChange(toChange(info));
- r.setStarred(info.starred());
- r.setPatchSets(toPatchSets(info));
- r.setMessages(toMessages(info));
- r.setAccounts(users(info));
- r.setCurrentPatchSetId(new PatchSet.Id(info.legacy_id(), rev._number()));
- r.setCurrentPatchSetDetail(toPatchSetDetail(info));
- r.setSubmitRecords(new ArrayList<SubmitRecord>());
-
- // Obtained later in ChangeScreen.
- r.setSubmitTypeRecord(new SubmitTypeRecord());
- r.getSubmitTypeRecord().status = SubmitTypeRecord.Status.RULE_ERROR;
- r.setPatchSetsWithDraftComments(new HashSet<PatchSet.Id>());
- r.setDependsOn(new ArrayList<com.google.gerrit.common.data.ChangeInfo>());
- r.setNeededBy(new ArrayList<com.google.gerrit.common.data.ChangeInfo>());
- return r;
- }
-
- private static PatchSetDetail toPatchSetDetail(ChangeInfo info) {
- RevisionInfo rev = current(info);
- PatchSetDetail p = new PatchSetDetail();
- p.setPatchSet(toPatchSet(info, rev));
- p.setProject(info.project_name_key());
- p.setInfo(new PatchSetInfo(p.getPatchSet().getId()));
- p.getInfo().setRevId(rev.name());
- p.getInfo().setParents(new ArrayList<ParentInfo>());
- p.getInfo().setAuthor(toUser(rev.commit().author()));
- p.getInfo().setCommitter(toUser(rev.commit().committer()));
- p.getInfo().setSubject(rev.commit().subject());
- p.getInfo().setMessage(rev.commit().message());
- if (rev.commit().parents() != null) {
- for (CommitInfo c : Natives.asList(rev.commit().parents())) {
- p.getInfo().getParents().add(new ParentInfo(
- new RevId(c.commit()),
- c.subject()));
- }
- }
- p.setPatches(new ArrayList<Patch>());
- p.setCommands(new ArrayList<UiCommandDetail>());
-
- rev.files();
- return p;
- }
-
- private static UserIdentity toUser(GitPerson p) {
- UserIdentity u = new UserIdentity();
- u.setName(p.name());
- u.setEmail(p.email());
- u.setDate(p.date());
- return u;
- }
-
- public static AccountInfoCache users(ChangeInfo info) {
- Map<Integer, AccountInfo> r = new HashMap<>();
- add(r, info.owner());
- if (info.messages() != null) {
- for (MessageInfo m : Natives.asList(info.messages())) {
- add(r, m.author());
- }
- }
- return new AccountInfoCache(r.values());
- }
-
- private static void add(Map<Integer, AccountInfo> r,
- com.google.gerrit.client.account.AccountInfo user) {
- if (user != null && !r.containsKey(user._account_id())) {
- AccountInfo a = new AccountInfo(new Account.Id(user._account_id()));
- a.setPreferredEmail(user.email());
- a.setFullName(user.name());
- r.put(user._account_id(), a);
- }
- }
-
- private static boolean can(NativeMap<ActionInfo> m, String n) {
- return m != null && m.containsKey(n) && m.get(n).enabled();
- }
-
- private static List<ChangeMessage> toMessages(ChangeInfo info) {
- List<ChangeMessage> msgs = new ArrayList<>();
- for (MessageInfo m : Natives.asList(info.messages())) {
- ChangeMessage o = new ChangeMessage(
- new ChangeMessage.Key(
- info.legacy_id(),
- m.date().toString()),
- m.author() != null
- ? new Account.Id(m.author()._account_id())
- : null,
- m.date(),
- m._revisionNumber() > 0
- ? new PatchSet.Id(info.legacy_id(), m._revisionNumber())
- : null);
- o.setMessage(m.message());
- msgs.add(o);
- }
- return msgs;
- }
-
- private static List<PatchSet> toPatchSets(ChangeInfo info) {
- JsArray<RevisionInfo> all = info.revisions().values();
- RevisionInfo.sortRevisionInfoByNumber(all);
-
- List<PatchSet> r = new ArrayList<>(all.length());
- for (RevisionInfo rev : Natives.asList(all)) {
- r.add(toPatchSet(info, rev));
- }
- return r;
- }
-
- private static PatchSet toPatchSet(ChangeInfo info, RevisionInfo rev) {
- PatchSet p = new PatchSet(
- new PatchSet.Id(info.legacy_id(), rev._number()));
- p.setCreatedOn(rev.commit().committer().date());
- p.setDraft(rev.draft());
- p.setRevision(new RevId(rev.name()));
- return p;
- }
-
- public static Change toChange(ChangeInfo info) {
- RevisionInfo rev = current(info);
- PatchSetInfo p = new PatchSetInfo(
- new PatchSet.Id(
- info.legacy_id(),
- rev._number()));
- p.setSubject(info.subject());
- Change c = new Change(
- new Change.Key(info.change_id()),
- info.legacy_id(),
- new Account.Id(info.owner()._account_id()),
- new Branch.NameKey(
- info.project_name_key(),
- info.branch()),
- info.created());
- c.setTopic(info.topic());
- c.setStatus(info.status());
- c.setCurrentPatchSet(p);
- c.setLastUpdatedOn(info.updated());
- c.setMergeable(info.mergeable());
- return c;
- }
-
- private static RevisionInfo current(ChangeInfo info) {
- RevisionInfo rev = info.revision(info.current_revision());
- if (rev == null) {
- JsArray<RevisionInfo> all = info.revisions().values();
- RevisionInfo.sortRevisionInfoByNumber(all);
- rev = all.get(all.length() - 1);
- }
- return rev;
- }
-
- public static void setChangeDetail(ChangeDetail detail) {
- Change.Id chgId = detail.getChange().getId();
- ChangeCache.get(chgId).getChangeDetailCache().set(detail);
- StarredChanges.fireChangeStarEvent(chgId, detail.isStarred());
- }
-
- private final Change.Id changeId;
-
- public ChangeDetailCache(final Change.Id chg) {
- changeId = chg;
- }
-
- public void refresh() {
- RestApi call = ChangeApi.detail(changeId.get());
- ChangeList.addOptions(call, EnumSet.of(
- ListChangesOption.CURRENT_ACTIONS,
- ListChangesOption.ALL_REVISIONS,
- ListChangesOption.ALL_COMMITS));
- call.get(new NewGerritCallback());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
new file mode 100644
index 0000000000..a00e3291d1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
@@ -0,0 +1,111 @@
+// Copyright (C) 2014 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.client.changes;
+
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.editor.EditFileInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.HttpCallback;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/** REST API helpers to remotely edit a change. */
+public class ChangeEditApi {
+ /** Get file (or commit message) contents. */
+ public static void get(PatchSet.Id id, String path,
+ HttpCallback<NativeString> cb) {
+ RestApi api;
+ if (id.get() != 0) {
+ // Read from a published revision, when change edit doesn't
+ // exist for the caller, or is not currently active.
+ api = ChangeApi.revision(id).view("files").id(path).view("content");
+ } else if (Patch.COMMIT_MSG.equals(path)) {
+ api = editMessage(id.getParentKey().get());
+ } else {
+ api = editFile(id.getParentKey().get(), path);
+ }
+ api.get(cb);
+ }
+
+ /** Get meta info for change edit. */
+ public static void getMeta(PatchSet.Id id, String path,
+ AsyncCallback<EditFileInfo> cb) {
+ if (id.get() != 0) {
+ throw new IllegalStateException("only supported for edits");
+ }
+ editFile(id.getParentKey().get(), path).view("meta").get(cb);
+ }
+
+ /** Put message into a change edit. */
+ public static void putMessage(int id, String m, GerritCallback<VoidResult> cb) {
+ editMessage(id).put(m, cb);
+ }
+
+ /** Put contents into a file or commit message in a change edit. */
+ public static void put(int id, String path, String content,
+ GerritCallback<VoidResult> cb) {
+ if (Patch.COMMIT_MSG.equals(path)) {
+ putMessage(id, content, cb);
+ } else {
+ editFile(id, path).put(content, cb);
+ }
+ }
+
+ /** Delete a file in the pending edit. */
+ public static void delete(int id, String path, AsyncCallback<VoidResult> cb) {
+ editFile(id, path).delete(cb);
+ }
+
+ /** Rename a file in the pending edit. */
+ public static void rename(int id, String path, String newPath,
+ AsyncCallback<VoidResult> cb) {
+ Input in = Input.create();
+ in.old_path(path);
+ in.new_path(newPath);
+ ChangeApi.edit(id).post(in, cb);
+ }
+
+ /** Restore (undo delete/modify) a file in the pending edit. */
+ public static void restore(int id, String path, AsyncCallback<VoidResult> cb) {
+ Input in = Input.create();
+ in.restore_path(path);
+ ChangeApi.edit(id).post(in, cb);
+ }
+
+ private static RestApi editMessage(int id) {
+ return ChangeApi.change(id).view("edit:message");
+ }
+
+ private static RestApi editFile(int id, String path) {
+ return ChangeApi.edit(id).id(path);
+ }
+
+ private static class Input extends JavaScriptObject {
+ static Input create() {
+ return createObject().cast();
+ }
+
+ final native void restore_path(String p) /*-{ this.restore_path=p }-*/;
+ final native void old_path(String p) /*-{ this.old_path=p }-*/;
+ final native void new_path(String p) /*-{ this.new_path=p }-*/;
+
+ protected Input() {
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 0c1815f071..fff792ff84 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -24,6 +24,7 @@ import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
@@ -61,6 +62,12 @@ public class ChangeInfo extends JavaScriptObject {
return ts;
}
+ public final boolean hasEditBasedOnCurrentPatchSet() {
+ JsArray<RevisionInfo> revList = revisions().values();
+ RevisionInfo.sortRevisionInfoByNumber(revList);
+ return revList.get(revList.length() - 1).is_edit();
+ }
+
private final native Timestamp _get_cts() /*-{ return this._cts; }-*/;
private final native void _set_cts(Timestamp ts) /*-{ this._cts = ts; }-*/;
@@ -85,7 +92,7 @@ public class ChangeInfo extends JavaScriptObject {
public final native String branch() /*-{ return this.branch; }-*/;
public final native String topic() /*-{ return this.topic; }-*/;
public final native String change_id() /*-{ return this.change_id; }-*/;
- public final native boolean mergeable() /*-{ return this.mergeable; }-*/;
+ public final native boolean mergeable() /*-{ return this.mergeable || false; }-*/;
public final native int insertions() /*-{ return this.insertions; }-*/;
public final native int deletions() /*-{ return this.deletions; }-*/;
private final native String statusRaw() /*-{ return this.status; }-*/;
@@ -95,13 +102,17 @@ public class ChangeInfo extends JavaScriptObject {
private final native String updatedRaw() /*-{ return this.updated; }-*/;
public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
- public final native String _sortkey() /*-{ return this._sortkey; }-*/;
public final native NativeMap<LabelInfo> all_labels() /*-{ return this.labels; }-*/;
public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
public final native String current_revision() /*-{ return this.current_revision; }-*/;
+ public final native void set_current_revision(String r) /*-{ this.current_revision = r; }-*/;
public final native NativeMap<RevisionInfo> revisions() /*-{ return this.revisions; }-*/;
public final native RevisionInfo revision(String n) /*-{ return this.revisions[n]; }-*/;
public final native JsArray<MessageInfo> messages() /*-{ return this.messages; }-*/;
+ public final native void set_edit(EditInfo edit) /*-{ this.edit = edit; }-*/;
+ public final native EditInfo edit() /*-{ return this.edit; }-*/;
+ public final native boolean has_edit() /*-{ return this.hasOwnProperty('edit') }-*/;
+ public final native JsArrayString hashtags() /*-{ return this.hashtags; }-*/;
public final native boolean has_permitted_labels()
/*-{ return this.hasOwnProperty('permitted_labels') }-*/;
@@ -204,13 +215,45 @@ public class ChangeInfo extends JavaScriptObject {
}
}
+ public static class EditInfo extends JavaScriptObject {
+ public final native String name() /*-{ return this.name; }-*/;
+ public final native String set_name(String n) /*-{ this.name = n; }-*/;
+ public final native String base_revision() /*-{ return this.base_revision; }-*/;
+ public final native CommitInfo commit() /*-{ return this.commit; }-*/;
+
+ public final native boolean has_actions() /*-{ return this.hasOwnProperty('actions') }-*/;
+ public final native NativeMap<ActionInfo> actions() /*-{ return this.actions; }-*/;
+
+ public final native boolean has_fetch() /*-{ return this.hasOwnProperty('fetch') }-*/;
+ public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/;
+
+ public final native boolean has_files() /*-{ return this.hasOwnProperty('files') }-*/;
+ public final native NativeMap<FileInfo> files() /*-{ return this.files; }-*/;
+
+ protected EditInfo() {
+ }
+ }
+
public static class RevisionInfo extends JavaScriptObject {
+ public static RevisionInfo fromEdit(EditInfo edit) {
+ RevisionInfo revisionInfo = createObject().cast();
+ revisionInfo.takeFromEdit(edit);
+ return revisionInfo;
+ }
+ private final native void takeFromEdit(EditInfo edit) /*-{
+ this._number = 0;
+ this.name = edit.name;
+ this.commit = edit.commit;
+ this.edit_base = edit.base_revision;
+ }-*/;
public final native int _number() /*-{ return this._number; }-*/;
public final native String name() /*-{ return this.name; }-*/;
public final native boolean draft() /*-{ return this.draft || false; }-*/;
public final native boolean has_draft_comments() /*-{ return this.has_draft_comments || false; }-*/;
+ public final native boolean is_edit() /*-{ return this._number == 0; }-*/;
public final native CommitInfo commit() /*-{ return this.commit; }-*/;
public final native void set_commit(CommitInfo c) /*-{ this.commit = c; }-*/;
+ public final native String edit_base() /*-{ return this.edit_base; }-*/;
public final native boolean has_files() /*-{ return this.hasOwnProperty('files') }-*/;
public final native NativeMap<FileInfo> files() /*-{ return this.files; }-*/;
@@ -220,17 +263,45 @@ public class ChangeInfo extends JavaScriptObject {
public final native boolean has_fetch() /*-{ return this.hasOwnProperty('fetch') }-*/;
public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/;
- public final native JsArray<WebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
+ final int editParent = findEditParent(list);
Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {
@Override
public int compare(RevisionInfo a, RevisionInfo b) {
- return a._number() - b._number();
+ return num(a) - num(b);
+ }
+
+ private int num(RevisionInfo r) {
+ return !r.is_edit() ? 2 * (r._number() - 1) + 1 : 2 * editParent;
}
});
}
+ public static int findEditParent(JsArray<RevisionInfo> list) {
+ for (int i = 0; i < list.length(); i++) {
+ // edit under revisions?
+ RevisionInfo editInfo = list.get(i);
+ if (editInfo.is_edit()) {
+ String parentRevision = editInfo.edit_base();
+ // find parent
+ for (int j = 0; j < list.length(); j++) {
+ RevisionInfo parentInfo = list.get(j);
+ String name = parentInfo.name();
+ if (name.equals(parentRevision)) {
+ // found parent pacth set number
+ return parentInfo._number();
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ public final String id() {
+ return PatchSet.Id.toId(_number());
+ }
+
protected RevisionInfo () {
}
}
@@ -252,6 +323,7 @@ public class ChangeInfo extends JavaScriptObject {
public final native GitPerson committer() /*-{ return this.committer; }-*/;
public final native String subject() /*-{ return this.subject; }-*/;
public final native String message() /*-{ return this.message; }-*/;
+ public final native JsArray<WebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
protected CommitInfo() {
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
deleted file mode 100644
index 8fe7d56750..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
+++ /dev/null
@@ -1,223 +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.client.changes;
-
-import static com.google.gerrit.client.FormatUtil.mediumFormat;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.AccountLinkPanel;
-import com.google.gerrit.client.ui.BranchLink;
-import com.google.gerrit.client.ui.CommentedActionDialog;
-import com.google.gerrit.client.ui.InlineHyperlink;
-import com.google.gerrit.client.ui.ProjectSearchLink;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.AccountInfoCache;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.clippy.client.CopyableLabel;
-
-public class ChangeInfoBlock extends Composite {
- private static final int R_CHANGE_ID = 0;
- private static final int R_OWNER = 1;
- private static final int R_PROJECT = 2;
- private static final int R_BRANCH = 3;
- private static final int R_TOPIC = 4;
- private static final int R_UPLOADED = 5;
- private static final int R_UPDATED = 6;
- private static final int R_SUBMIT_TYPE = 7;
- private static final int R_STATUS = 8;
- private static final int R_MERGE_TEST = 9;
- private static final int R_CNT = 10;
-
- private final Grid table;
-
- public ChangeInfoBlock() {
- table = new Grid(R_CNT, 2);
- table.setStyleName(Gerrit.RESOURCES.css().infoBlock());
- table.addStyleName(Gerrit.RESOURCES.css().changeInfoBlock());
-
- initRow(R_CHANGE_ID, "Change-Id: ");
- initRow(R_OWNER, Util.C.changeInfoBlockOwner());
- initRow(R_PROJECT, Util.C.changeInfoBlockProject());
- initRow(R_BRANCH, Util.C.changeInfoBlockBranch());
- initRow(R_TOPIC, Util.C.changeInfoBlockTopic());
- initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
- initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
- initRow(R_STATUS, Util.C.changeInfoBlockStatus());
- initRow(R_SUBMIT_TYPE, Util.C.changeInfoBlockSubmitType());
- initRow(R_MERGE_TEST, Util.C.changeInfoBlockCanMerge());
-
- final CellFormatter fmt = table.getCellFormatter();
- fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
- fmt.addStyleName(R_CHANGE_ID, 1, Gerrit.RESOURCES.css().changeid());
- fmt.addStyleName(R_CNT - 2, 0, Gerrit.RESOURCES.css().bottomheader());
-
- initWidget(table);
- }
-
- private void initRow(final int row, final String name) {
- table.setText(row, 0, name);
- table.getCellFormatter().addStyleName(row, 0, Gerrit.RESOURCES.css().header());
- }
-
- public void display(final ChangeDetail changeDetail,
- final AccountInfoCache acc, SubmitTypeRecord submitTypeRecord) {
- final Change chg = changeDetail.getChange();
- final Branch.NameKey dst = chg.getDest();
-
- CopyableLabel changeIdLabel =
- new CopyableLabel("Change-Id: " + chg.getKey().get());
- changeIdLabel.setPreviewText(chg.getKey().get());
- table.setWidget(R_CHANGE_ID, 1, changeIdLabel);
-
- table.setWidget(R_OWNER, 1, AccountLinkPanel.link(acc, chg.getOwner()));
-
- final FlowPanel p = new FlowPanel();
- p.add(new ProjectSearchLink(chg.getProject()));
- p.add(new InlineHyperlink(chg.getProject().get(),
- PageLinks.toProject(chg.getProject())));
- table.setWidget(R_PROJECT, 1, p);
-
- table.setWidget(R_BRANCH, 1, new BranchLink(dst.getShortName(), chg
- .getProject(), chg.getStatus(), dst.get(), null));
- table.setWidget(R_TOPIC, 1, topic(changeDetail));
- table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
- table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
- table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
- String submitType;
- if (submitTypeRecord.status == SubmitTypeRecord.Status.OK) {
- submitType = com.google.gerrit.client.admin.Util
- .toLongString(submitTypeRecord.type);
- } else {
- submitType = submitTypeRecord.status.name();
- }
- table.setText(R_SUBMIT_TYPE, 1, submitType);
- final Change.Status status = chg.getStatus();
- if (Gerrit.getConfig().getNewFeatures()
- && (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT))) {
- table.getRowFormatter().setVisible(R_MERGE_TEST, true);
- table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
- .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
- } else {
- table.getRowFormatter().setVisible(R_MERGE_TEST, false);
- }
-
- if (status.isClosed()) {
- table.getCellFormatter().addStyleName(R_STATUS, 1, Gerrit.RESOURCES.css().closedstate());
- table.getRowFormatter().setVisible(R_SUBMIT_TYPE, false);
- } else {
- table.getCellFormatter().removeStyleName(R_STATUS, 1, Gerrit.RESOURCES.css().closedstate());
- table.getRowFormatter().setVisible(R_SUBMIT_TYPE, true);
- }
- }
-
- public Widget topic(final ChangeDetail changeDetail) {
- final Change chg = changeDetail.getChange();
- final Branch.NameKey dst = chg.getDest();
-
- FlowPanel fp = new FlowPanel();
- fp.addStyleName(Gerrit.RESOURCES.css().changeInfoTopicPanel());
- fp.add(new BranchLink(chg.getTopic(), chg.getProject(), chg.getStatus(),
- dst.get(), chg.getTopic()));
-
- if (changeDetail.canEditTopicName()) {
- final Image edit = new Image(Gerrit.RESOURCES.edit());
- edit.addStyleName(Gerrit.RESOURCES.css().link());
- edit.setTitle(Util.C.changeInfoBlockTopicAlterTopicToolTip());
- edit.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- new AlterTopicDialog(chg).center();
- }
- });
- fp.add(edit);
- }
-
- return fp;
- }
-
- private class AlterTopicDialog extends CommentedActionDialog<ChangeDetail>
- implements KeyPressHandler {
- TextBox newTopic;
- Change change;
-
- AlterTopicDialog(Change chg) {
- super(Util.C.alterTopicTitle(), Util.C.headingAlterTopicMessage(),
- new ChangeDetailCache.IgnoreErrorCallback());
- change = chg;
- message.setVisible(false);
-
- newTopic = new TextBox();
- newTopic.addKeyPressHandler(this);
- setFocusOn(newTopic);
- panel.insert(newTopic, 0);
- panel.insert(new InlineLabel(Util.C.alterTopicLabel()), 0);
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- newTopic.setText(change.getTopic());
- }
-
- private void doTopicEdit() {
- String topic = newTopic.getText();
- ChangeApi.topic(change.getId().get(), topic,
- new GerritCallback<String>() {
- @Override
- public void onSuccess(String result) {
- sent = true;
- Gerrit.display(PageLinks.toChange(change.getId()));
- hide();
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- enableButtons(true);
- super.onFailure(caught);
- }});
- }
-
- @Override
- public void onSend() {
- doTopicEdit();
- }
-
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (event.getSource() == newTopic
- && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- doTopicEdit();
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
index 63f5e291f7..b1866dd5e1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
@@ -15,7 +15,7 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index d435720fa7..76e321161f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -37,6 +37,7 @@ public interface ChangeMessages extends Messages {
String patchTableSize_LongModify(int insertions, int deletions);
String patchTableSize_Lines(@PluralCount int insertions);
+ String removeHashtag(String name);
String removeReviewer(String fullName);
String messageWrittenOn(String date);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index fa942fde3d..7069c4a1fc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -20,6 +20,7 @@ patchTableSize_Modify = +{0}, -{1}
patchTableSize_LongModify = {0} inserted, {1} deleted
patchTableSize_Lines = {0} lines
+removeHashtag = Remove hashtag {0}
removeReviewer = Remove reviewer {0}
messageWrittenOn = on {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeResources.java
deleted file mode 100644
index 892143c157..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeResources.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2010 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.client.changes;
-
-import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.ImageResource;
-
-public interface ChangeResources extends ClientBundle {
- @Source("removeReviewerNormal.png")
- public ImageResource removeReviewerNormal();
-
- @Source("removeReviewerPressed.png")
- public ImageResource removeReviewerPressed();
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
deleted file mode 100644
index 5a6e96c913..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ /dev/null
@@ -1,764 +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.client.changes;
-
-import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.FormatUtil;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
-import com.google.gerrit.client.change.RelatedChanges;
-import com.google.gerrit.client.change.RelatedChanges.ChangeAndCommit;
-import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
-import com.google.gerrit.client.diff.DiffApi;
-import com.google.gerrit.client.diff.FileInfo;
-import com.google.gerrit.client.projects.ConfigInfoCache;
-import com.google.gerrit.client.rpc.CallbackGroup;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.client.rpc.NativeString;
-import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.client.ui.CommentPanel;
-import com.google.gerrit.client.ui.ComplexDisclosurePanel;
-import com.google.gerrit.client.ui.ExpandAllCommand;
-import com.google.gerrit.client.ui.LinkMenuBar;
-import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
-import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.common.data.AccountInfoCache;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.extensions.common.ListChangesOption;
-import com.google.gerrit.extensions.common.SubmitType;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-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.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.gwt.core.client.JsArray;
-import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.DisclosurePanel;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.globalkey.client.KeyCommand;
-import com.google.gwtexpui.globalkey.client.KeyCommandSet;
-
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-
-public class ChangeScreen extends Screen
- implements ValueChangeHandler<ChangeDetail> {
- private final Change.Id changeId;
- private final PatchSet.Id openPatchSetId;
- private ChangeDetailCache detailCache;
- private com.google.gerrit.client.changes.ChangeInfo changeInfo;
-
- private ChangeDescriptionBlock descriptionBlock;
- private ApprovalTable approvals;
-
- private IncludedInTable includedInTable;
- private DisclosurePanel includedInPanel;
- private ComplexDisclosurePanel dependenciesPanel;
- private ChangeTable dependencies;
- private ChangeTable.Section dependsOn;
- private ChangeTable.Section neededBy;
-
- private PatchSetsBlock patchSetsBlock;
-
- private Panel comments;
- private CommentLinkProcessor commentLinkProcessor;
-
- private KeyCommandSet keysNavigation;
- private KeyCommandSet keysAction;
- private HandlerRegistration regNavigation;
- private HandlerRegistration regAction;
- private HandlerRegistration regDetailCache;
-
- private Grid patchesGrid;
- private ListBox patchesList;
-
- /**
- * The change id for which the old version history is valid.
- */
- private static Change.Id currentChangeId;
-
- /**
- * Which patch set id is the diff base.
- */
- private static PatchSet.Id diffBaseId;
-
- public ChangeScreen(final Change.Id toShow) {
- changeId = toShow;
- openPatchSetId = null;
- }
-
- public ChangeScreen(final PatchSet.Id toShow) {
- changeId = toShow.getParentKey();
- openPatchSetId = toShow;
- }
-
- public ChangeScreen(final ChangeInfo c) {
- this(c.getId());
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- detailCache.refresh();
- }
-
- @Override
- protected void onUnload() {
- if (regNavigation != null) {
- regNavigation.removeHandler();
- regNavigation = null;
- }
- if (regAction != null) {
- regAction.removeHandler();
- regAction = null;
- }
- if (regDetailCache != null) {
- regDetailCache.removeHandler();
- regDetailCache = null;
- }
- super.onUnload();
- }
-
- @Override
- public void registerKeys() {
- super.registerKeys();
- regNavigation = GlobalKey.add(this, keysNavigation);
- regAction = GlobalKey.add(this, keysAction);
- if (openPatchSetId != null) {
- patchSetsBlock.activate(openPatchSetId);
- }
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
-
- ChangeCache cache = ChangeCache.get(changeId);
-
- detailCache = cache.getChangeDetailCache();
- regDetailCache = detailCache.addValueChangeHandler(this);
-
- addStyleName(Gerrit.RESOURCES.css().changeScreen());
- addStyleName(Gerrit.RESOURCES.css().screenNoHeader());
-
- keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
- keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
- keysNavigation.add(new UpToListKeyCommand(0, 'u', Util.C.upToChangeList()));
- keysNavigation.add(new ExpandCollapseDependencySectionKeyCommand(0, 'd', Util.C.expandCollapseDependencies()));
-
- if (Gerrit.isSignedIn()) {
- keysAction.add(new PublishCommentsKeyCommand(0, 'r', Util.C
- .keyPublishComments()));
- }
-
- descriptionBlock = new ChangeDescriptionBlock(keysAction);
- add(descriptionBlock);
-
- approvals = new ApprovalTable();
- add(approvals);
-
- includedInPanel = new DisclosurePanel(Util.C.changeScreenIncludedIn());
- includedInTable = new IncludedInTable(changeId);
-
- includedInPanel.setContent(includedInTable);
- add(includedInPanel);
-
- dependencies = new ChangeTable() {
- {
- table.setWidth("auto");
- }
- };
- dependsOn = new ChangeTable.Section(Util.C.changeScreenDependsOn());
- dependsOn.setChangeRowFormatter(new ChangeTable.ChangeRowFormatter() {
- @Override
- public String getRowStyle(ChangeInfo c) {
- if (! c.isLatest() || Change.Status.ABANDONED.equals(c.getStatus())) {
- return Gerrit.RESOURCES.css().outdated();
- }
- return null;
- }
-
- @Override
- public String getDisplayText(final ChangeInfo c, final String displayText) {
- if (! c.isLatest()) {
- return displayText + " [OUTDATED]";
- }
- return displayText;
- }
- });
- neededBy = new ChangeTable.Section(Util.C.changeScreenNeededBy());
- dependencies.addSection(dependsOn);
- dependencies.addSection(neededBy);
-
- dependenciesPanel = new ComplexDisclosurePanel(
- Util.C.changeScreenDependencies(), false);
- dependenciesPanel.setContent(dependencies);
- add(dependenciesPanel);
-
- patchesList = new ListBox();
- patchesList.addChangeHandler(new ChangeHandler() {
- @Override
- public void onChange(ChangeEvent event) {
- final int index = patchesList.getSelectedIndex();
- final String selectedPatchSet = patchesList.getValue(index);
- if (index == 0) {
- diffBaseId = null;
- } else {
- diffBaseId = PatchSet.Id.parse(selectedPatchSet);
- }
- if (patchSetsBlock != null) {
- patchSetsBlock.refresh(diffBaseId);
- }
- }
- });
-
- patchesGrid = new Grid(1, 2);
- patchesGrid.setStyleName(Gerrit.RESOURCES.css().selectPatchSetOldVersion());
- patchesGrid.setText(0, 0, Util.C.referenceVersion());
- patchesGrid.setWidget(0, 1, patchesList);
- add(patchesGrid);
-
- patchSetsBlock = new PatchSetsBlock();
- add(patchSetsBlock);
-
- comments = new FlowPanel();
- comments.setStyleName(Gerrit.RESOURCES.css().changeComments());
- add(comments);
- }
-
- private void displayTitle(final Change.Key changeId, final String subject) {
- final StringBuilder titleBuf = new StringBuilder();
- if (LocaleInfo.getCurrentLocale().isRTL()) {
- if (subject != null) {
- titleBuf.append(subject);
- titleBuf.append(" :");
- }
- titleBuf.append(Util.M.changeScreenTitleId(changeId.abbreviate()));
- } else {
- titleBuf.append(Util.M.changeScreenTitleId(changeId.abbreviate()));
- if (subject != null) {
- titleBuf.append(": ");
- titleBuf.append(subject);
- }
- }
- setPageTitle(titleBuf.toString());
- setHeaderVisible(false);
- }
-
- @Override
- public void onValueChange(final ValueChangeEvent<ChangeDetail> event) {
- if (isAttached() && isLastValueChangeHandler()) {
- // Until this screen is fully migrated to the new API, these calls must
- // happen sequentially after the ChangeDetail lookup, because we can't
- // start an async get at the source of every call that might trigger a
- // value change.
- CallbackGroup cbs1 = new CallbackGroup();
- final CallbackGroup cbs2 = new CallbackGroup();
- final PatchSet.Id psId = event.getValue().getCurrentPatchSet().getId();
- final Map<String, Patch> patches = new HashMap<>();
- String revId =
- event.getValue().getCurrentPatchSetDetail().getInfo().getRevId();
-
- if (event.getValue().getChange().getStatus().isOpen()) {
- ChangeApi.revision(changeId.get(), "current")
- .view("submit_type")
- .get(cbs1.add(new GerritCallback<NativeString>() {
- @Override
- public void onSuccess(NativeString result) {
- event.getValue().setSubmitTypeRecord(SubmitTypeRecord.OK(
- SubmitType.valueOf(result.asString())));
- }
- public void onFailure(Throwable caught) {}
- }));
- }
- if (Gerrit.isSignedIn()) {
- ChangeApi.revision(changeId.get(), "" + psId.get())
- .view("related")
- .get(cbs1.add(new AsyncCallback<RelatedChanges.RelatedInfo>() {
- @Override
- public void onSuccess(RelatedChanges.RelatedInfo info) {
- if (info.changes() != null) {
- dependsOn(info);
- neededBy(info);
- }
- }
-
- private void dependsOn(RelatedChanges.RelatedInfo info) {
- ChangeAndCommit self = null;
- Map<String, ChangeAndCommit> m = new HashMap<>();
- for (int i = 0; i < info.changes().length(); i++) {
- ChangeAndCommit c = info.changes().get(i);
- if (changeId.equals(c.legacy_id())) {
- self = c;
- }
- if (c.commit() != null && c.commit().commit() != null) {
- m.put(c.commit().commit(), c);
- }
- }
- if (self != null && self.commit() != null
- && self.commit().parents() != null) {
- List<ChangeInfo> d = new ArrayList<>();
- for (CommitInfo p : Natives.asList(self.commit().parents())) {
- ChangeAndCommit pc = m.get(p.commit());
- if (pc != null && pc.has_change_number()) {
- ChangeInfo i = new ChangeInfo();
- load(pc, i);
- d.add(i);
- }
- }
- event.getValue().setDependsOn(d);
- }
- }
-
- private void neededBy(RelatedChanges.RelatedInfo info) {
- Set<String> mine = new HashSet<>();
- for (PatchSet ps : event.getValue().getPatchSets()) {
- mine.add(ps.getRevision().get());
- }
-
- List<ChangeInfo> n = new ArrayList<>();
- for (int i = 0; i < info.changes().length(); i++) {
- ChangeAndCommit c = info.changes().get(i);
- if (c.has_change_number()
- && c.commit() != null
- && c.commit().parents() != null) {
- for (int j = 0; j < c.commit().parents().length(); j++) {
- CommitInfo p = c.commit().parents().get(j);
- if (mine.contains(p.commit())) {
- ChangeInfo u = new ChangeInfo();
- load(c, u);
- n.add(u);
- break;
- }
- }
- }
- }
- event.getValue().setNeededBy(n);
- }
-
- private void load(final ChangeAndCommit pc, final ChangeInfo i) {
- RestApi call = ChangeApi.change(pc.legacy_id().get());
- ChangeList.addOptions(call, EnumSet.of(
- ListChangesOption.DETAILED_ACCOUNTS,
- ListChangesOption.CURRENT_REVISION));
- call.get(cbs2.add(new AsyncCallback<
- com.google.gerrit.client.changes.ChangeInfo>() {
- public void onFailure(Throwable caught) {}
- public void onSuccess(
- com.google.gerrit.client.changes.ChangeInfo result) {
- i.set(ChangeDetailCache.toChange(result),
- pc.patch_set_id());
- i.setStarred(result.starred());
- event.getValue().getAccounts()
- .merge(ChangeDetailCache.users(result));
- }}));
- }
- public void onFailure(Throwable caught) {}
- }));
- ChangeApi.revision(changeId.get(), revId)
- .view("files")
- .addParameterTrue("reviewed")
- .get(cbs1.add(new AsyncCallback<JsArrayString>() {
- @Override
- public void onSuccess(JsArrayString result) {
- for(int i = 0; i < result.length(); i++) {
- String path = result.get(i);
- Patch p = patches.get(path);
- if (p == null) {
- p = new Patch(new Patch.Key(psId, path));
- patches.put(path, p);
- }
- p.setReviewedByCurrentUser(true);
- }
- }
- public void onFailure(Throwable caught) {}
- }));
- final Set<PatchSet.Id> withDrafts = new HashSet<>();
- event.getValue().setPatchSetsWithDraftComments(withDrafts);
- for (PatchSet ps : event.getValue().getPatchSets()) {
- if (!ps.getId().equals(psId)) {
- final PatchSet.Id id = ps.getId();
- ChangeApi.revision(changeId.get(), "" + id.get())
- .view("drafts")
- .get(cbs1.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
- @Override
- public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
- if (!result.isEmpty()) {
- withDrafts.add(id);
- }
- }
- public void onFailure(Throwable caught) {}
- }));
- }
- }
- ChangeApi.revision(changeId.get(), "" + psId.get())
- .view("drafts")
- .get(cbs1.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
- @Override
- public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
- for (String path : result.keySet()) {
- Patch p = patches.get(path);
- if (p == null) {
- p = new Patch(new Patch.Key(psId, path));
- patches.put(path, p);
- }
- p.setDraftCount(result.get(path).length());
- }
- if (!result.isEmpty()) {
- withDrafts.add(psId);
- }
- }
- public void onFailure(Throwable caught) {}
- }));
- }
- ChangeApi.revision(changeId.get(), revId)
- .view("comments")
- .get(cbs1.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
- @Override
- public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
- for (String path : result.keySet()) {
- Patch p = patches.get(path);
- if (p == null) {
- p = new Patch(new Patch.Key(psId, path));
- patches.put(path, p);
- }
- p.setCommentCount(result.get(path).length());
- }
- }
- public void onFailure(Throwable caught) {}
- }));
- DiffApi.list(changeId.get(), null, revId,
- new AsyncCallback<NativeMap<FileInfo>>() {
- @Override
- public void onSuccess(NativeMap<FileInfo> result) {
- JsArray<FileInfo> fileInfos = result.values();
- FileInfo.sortFileInfoByPath(fileInfos);
- List<Patch> list = new ArrayList<>(fileInfos.length());
- for (FileInfo f : Natives.asList(fileInfos)) {
- Patch p = patches.get(f.path());
- if (p == null) {
- p = new Patch(new Patch.Key(psId, f.path()));
- patches.put(f.path(), p);
- }
- p.setInsertions(f.lines_inserted());
- p.setDeletions(f.lines_deleted());
- p.setPatchType(f.binary() ? PatchType.BINARY : PatchType.UNIFIED);
- if (f.status() == null) {
- p.setChangeType(ChangeType.MODIFIED);
- } else {
- p.setChangeType(ChangeType.forCode(f.status().charAt(0)));
- }
- list.add(p);
- }
- event.getValue().getCurrentPatchSetDetail().setPatches(list);
- }
- public void onFailure(Throwable caught) {}
- });
- ConfigInfoCache.get(
- event.getValue().getChange().getProject(),
- cbs1.add(new GerritCallback<ConfigInfoCache.Entry>() {
- @Override
- public void onSuccess(ConfigInfoCache.Entry result) {
- commentLinkProcessor = result.getCommentLinkProcessor();
- setTheme(result.getTheme());
- }
-
- @Override
- public void onFailure(Throwable caught) {
- // Handled by last callback's onFailure.
- }
- }));
- ChangeApi.detail(changeId.get(), cbs1.addFinal(
- new GerritCallback<com.google.gerrit.client.changes.ChangeInfo>() {
- @Override
- public void onSuccess(
- com.google.gerrit.client.changes.ChangeInfo result) {
- changeInfo = result;
- cbs2.addFinal(new AsyncCallback<Void>() {
- @Override
- public void onSuccess(Void result) {
- display(event.getValue());
- }
- public void onFailure(Throwable caught) {}
- }).onSuccess(null);
- }
- }));
- }
- }
-
- // Find the last attached screen.
- // When DialogBox is used (i. e. CommentedActionDialog) then the original
- // ChangeScreen is still in attached state.
- // Use here the fact, that the handlers (ChangeScreen) are sorted.
- private boolean isLastValueChangeHandler() {
- int count = detailCache.getHandlerCount();
- return count > 0 && detailCache.getHandler(count - 1) == this;
- }
-
- private void display(final ChangeDetail detail) {
- displayTitle(detail.getChange().getKey(), detail.getChange().getSubject());
- discardDiffBaseIfNotApplicable(detail.getChange().getId());
-
- if (Status.MERGED == detail.getChange().getStatus()) {
- includedInPanel.setVisible(true);
- includedInPanel.addOpenHandler(includedInTable);
- } else {
- includedInPanel.setVisible(false);
- }
-
- dependencies.setAccountInfoCache(detail.getAccounts());
-
- descriptionBlock.display(detail,
- detail.isStarred(),
- detail.canEditCommitMessage(),
- detail.getCurrentPatchSetDetail().getInfo(),
- detail.getAccounts(), detail.getSubmitTypeRecord(),
- commentLinkProcessor);
- dependsOn.display(detail.getDependsOn());
- neededBy.display(detail.getNeededBy());
- approvals.display(changeInfo);
-
- patchesList.clear();
- if (detail.getCurrentPatchSetDetail().getInfo().getParents().size() > 1) {
- patchesList.addItem(Util.C.autoMerge());
- } else {
- patchesList.addItem(Util.C.baseDiffItem());
- }
- for (PatchSet pId : detail.getPatchSets()) {
- patchesList.addItem(Util.M.patchSetHeader(pId.getPatchSetId()), pId
- .getId().toString());
- }
-
- if (diffBaseId != null) {
- patchesList.setSelectedIndex(diffBaseId.get());
- }
-
- patchSetsBlock.display(detail, diffBaseId);
- addComments(detail);
-
- // If any dependency change is still open, or is outdated,
- // or the change is needed by a change that is new or submitted,
- // show our dependency list.
- //
- boolean depsOpen = false;
- int outdated = 0;
- if (!detail.getChange().getStatus().isClosed()) {
- final List<ChangeInfo> dependsOn = detail.getDependsOn();
- if (dependsOn != null) {
- for (final ChangeInfo ci : dependsOn) {
- if (!ci.isLatest()) {
- depsOpen = true;
- outdated++;
- } else if (ci.getStatus() != Change.Status.MERGED) {
- depsOpen = true;
- }
- }
- }
- }
- final List<ChangeInfo> neededBy = detail.getNeededBy();
- if (neededBy != null) {
- for (final ChangeInfo ci : neededBy) {
- if ((ci.getStatus() == Change.Status.NEW) ||
- (ci.getStatus() == Change.Status.SUBMITTED) ||
- (ci.getStatus() == Change.Status.DRAFT)) {
- depsOpen = true;
- }
- }
- }
-
- dependenciesPanel.setOpen(depsOpen);
-
- dependenciesPanel.getHeader().clear();
- if (outdated > 0) {
- dependenciesPanel.getHeader().add(new InlineLabel(
- Util.M.outdatedHeader(outdated)));
- }
-
- if (!isCurrentView()) {
- display();
- }
- patchSetsBlock.setRegisterKeys(true);
- }
-
- private static void discardDiffBaseIfNotApplicable(final Change.Id toShow) {
- if (currentChangeId != null && !currentChangeId.equals(toShow)) {
- diffBaseId = null;
- }
- currentChangeId = toShow;
- }
-
- private void addComments(final ChangeDetail detail) {
- comments.clear();
-
- final AccountInfoCache accts = detail.getAccounts();
- final List<ChangeMessage> msgList = detail.getMessages();
-
- HorizontalPanel title = new HorizontalPanel();
- title.setWidth("100%");
- title.add(new Label(Util.C.changeScreenComments()));
- if (msgList.size() > 1) {
- title.add(messagesMenuBar());
- }
- title.setStyleName(Gerrit.RESOURCES.css().blockHeader());
- comments.add(title);
-
- final long AGE = 7 * 24 * 60 * 60 * 1000L;
- final Timestamp aged = new Timestamp(System.currentTimeMillis() - AGE);
-
- CommentVisibilityStrategy commentVisibilityStrategy =
- CommentVisibilityStrategy.EXPAND_RECENT;
- if (Gerrit.isSignedIn()) {
- commentVisibilityStrategy = Gerrit.getUserAccount()
- .getGeneralPreferences().getCommentVisibilityStrategy();
- }
-
- for (int i = 0; i < msgList.size(); i++) {
- final ChangeMessage msg = msgList.get(i);
-
- AccountInfo author;
- if (msg.getAuthor() != null) {
- author = FormatUtil.asInfo(accts.get(msg.getAuthor()));
- } else {
- author = AccountInfo.create(0, Util.C.messageNoAuthor(), null, null);
- }
-
- boolean isRecent;
- if (i == msgList.size() - 1) {
- isRecent = true;
- } else {
- // TODO Instead of opening messages by strict age, do it by "unread"?
- isRecent = msg.getWrittenOn().after(aged);
- }
-
- final CommentPanel cp = new CommentPanel(author, msg.getWrittenOn(),
- msg.getMessage(), commentLinkProcessor);
- cp.setRecent(isRecent);
- cp.addStyleName(Gerrit.RESOURCES.css().commentPanelBorder());
- if (i == msgList.size() - 1) {
- cp.addStyleName(Gerrit.RESOURCES.css().commentPanelLast());
- }
- boolean isOpen = false;
- switch (commentVisibilityStrategy) {
- case COLLAPSE_ALL:
- break;
- case EXPAND_ALL:
- isOpen = true;
- break;
- case EXPAND_MOST_RECENT:
- isOpen = i == msgList.size() - 1;
- break;
- case EXPAND_RECENT:
- default:
- isOpen = isRecent;
- break;
- }
- cp.setOpen(isOpen);
- comments.add(cp);
- }
-
- final Button b = new Button(Util.C.changeScreenAddComment());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- PatchSet.Id currentPatchSetId = patchSetsBlock.getCurrentPatchSet().getId();
- Gerrit.display(Dispatcher.toPublish(currentPatchSetId));
- }
- });
- comments.add(b);
- comments.setVisible(msgList.size() > 0);
- }
-
- private LinkMenuBar messagesMenuBar() {
- final Panel c = comments;
- final LinkMenuBar menuBar = new LinkMenuBar();
- menuBar.addItem(Util.C.messageExpandRecent(), new ExpandAllCommand(c, true) {
- @Override
- protected void expand(final CommentPanel w) {
- w.setOpen(w.isRecent());
- }
- });
- menuBar.addItem(Util.C.messageExpandAll(), new ExpandAllCommand(c, true));
- menuBar.addItem(Util.C.messageCollapseAll(), new ExpandAllCommand(c, false));
- menuBar.addStyleName(Gerrit.RESOURCES.css().commentPanelMenuBar());
- return menuBar;
- }
-
- public class UpToListKeyCommand extends KeyCommand {
- public UpToListKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- Gerrit.displayLastChangeList();
- }
- }
-
- public class ExpandCollapseDependencySectionKeyCommand extends KeyCommand {
- public ExpandCollapseDependencySectionKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(KeyPressEvent event) {
- dependenciesPanel.setOpen(!dependenciesPanel.isOpen());
- }
- }
-
- public class PublishCommentsKeyCommand extends NeedsSignInKeyCommand {
- public PublishCommentsKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- PatchSet.Id currentPatchSetId = patchSetsBlock.getCurrentPatchSet().getId();
- Gerrit.display(Dispatcher.toPublish(currentPatchSetId));
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 499979573c..8b714484bb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -14,9 +14,12 @@
package com.google.gerrit.client.changes;
+import static com.google.gerrit.client.FormatUtil.relativeFormat;
import static com.google.gerrit.client.FormatUtil.shortFormat;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.ui.AccountLinkPanel;
import com.google.gerrit.client.ui.BranchLink;
import com.google.gerrit.client.ui.ChangeLink;
@@ -24,54 +27,74 @@ import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
import com.google.gerrit.client.ui.ProjectLink;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.AccountInfoCache;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.List;
-import java.util.Set;
public class ChangeTable extends NavigationTable<ChangeInfo> {
private static final int C_STAR = 1;
- private static final int C_SUBJECT = 2;
- private static final int C_OWNER = 3;
- private static final int C_PROJECT = 4;
- private static final int C_BRANCH = 5;
- private static final int C_LAST_UPDATE = 6;
- private static final int COLUMNS = 7;
+ private static final int C_ID = 2;
+ private static final int C_SUBJECT = 3;
+ private static final int C_STATUS = 4;
+ private static final int C_OWNER = 5;
+ private static final int C_PROJECT = 6;
+ private static final int C_BRANCH = 7;
+ private static final int C_LAST_UPDATE = 8;
+ private static final int C_SIZE = 9;
+ private static final int BASE_COLUMNS = 10;
private final List<Section> sections;
- private AccountInfoCache accountCache = AccountInfoCache.empty();
+ private int columns;
+ private boolean showLegacyId;
+ private List<String> labelNames;
public ChangeTable() {
super(Util.C.changeItemHelp());
+ columns = BASE_COLUMNS;
+ labelNames = Collections.emptyList();
if (Gerrit.isSignedIn()) {
keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar()));
+ showLegacyId = Gerrit.getUserAccount()
+ .getGeneralPreferences()
+ .isLegacycidInChangeTable();
}
sections = new ArrayList<>();
table.setText(0, C_STAR, "");
+ table.setText(0, C_ID, Util.C.changeTableColumnID());
table.setText(0, C_SUBJECT, Util.C.changeTableColumnSubject());
+ table.setText(0, C_STATUS, Util.C.changeTableColumnStatus());
table.setText(0, C_OWNER, Util.C.changeTableColumnOwner());
table.setText(0, C_PROJECT, Util.C.changeTableColumnProject());
table.setText(0, C_BRANCH, Util.C.changeTableColumnBranch());
table.setText(0, C_LAST_UPDATE, Util.C.changeTableColumnLastUpdate());
+ table.setText(0, C_SIZE, Util.C.changeTableColumnSize());
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(0, C_STAR, Gerrit.RESOURCES.css().iconHeader());
- for (int i = C_SUBJECT; i < COLUMNS; i++) {
+ for (int i = C_ID; i < columns; i++) {
fmt.addStyleName(0, i, Gerrit.RESOURCES.css().dataHeader());
}
+ if (!showLegacyId) {
+ fmt.addStyleName(0, C_ID, Gerrit.RESOURCES.css().dataHeaderHidden());
+ }
table.addClickHandler(new ClickHandler() {
@Override
@@ -82,6 +105,8 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
}
if (cell.getCellIndex() == C_STAR) {
// Don't do anything (handled by star itself).
+ } else if (cell.getCellIndex() == C_STATUS) {
+ // Don't do anything.
} else if (cell.getCellIndex() == C_OWNER) {
// Don't do anything.
} else if (getRowItem(cell.getRowIndex()) != null) {
@@ -91,29 +116,23 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
});
}
- protected void onStarClick(final int row) {
- final ChangeInfo c = getRowItem(row);
- if (c != null && Gerrit.isSignedIn()) {
- ((StarredChanges.Icon) table.getWidget(row, C_STAR)).toggleStar();
- }
- }
-
@Override
protected Object getRowItemKey(final ChangeInfo item) {
- return item.getId();
+ return item.legacy_id();
}
@Override
protected void onOpenRow(final int row) {
final ChangeInfo c = getRowItem(row);
- Gerrit.display(PageLinks.toChange(c), new ChangeScreen(c));
+ final Change.Id id = c.legacy_id();
+ Gerrit.display(PageLinks.toChange(id));
}
private void insertNoneRow(final int row) {
insertRow(row);
table.setText(row, 0, Util.C.changeTableNone());
final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.setColSpan(row, 0, COLUMNS);
+ fmt.setColSpan(row, 0, columns);
fmt.setStyleName(row, 0, Gerrit.RESOURCES.css().emptySection());
}
@@ -127,88 +146,266 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
super.applyDataRowStyle(row);
final CellFormatter fmt = table.getCellFormatter();
fmt.addStyleName(row, C_STAR, Gerrit.RESOURCES.css().iconCell());
- for (int i = C_SUBJECT; i < COLUMNS; i++) {
+ for (int i = C_ID; i < columns; i++) {
fmt.addStyleName(row, i, Gerrit.RESOURCES.css().dataCell());
}
+ if (!showLegacyId) {
+ fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().dataCellHidden());
+ }
fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT());
+ fmt.addStyleName(row, C_STATUS, Gerrit.RESOURCES.css().cSTATUS());
fmt.addStyleName(row, C_OWNER, Gerrit.RESOURCES.css().cOWNER());
fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate());
+ fmt.addStyleName(row, C_SIZE, Gerrit.RESOURCES.css().cSIZE());
+
+ for (int i = C_SIZE + 1; i < columns; i++) {
+ fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
+ }
}
- private void populateChangeRow(final int row, final ChangeInfo c,
- final ChangeRowFormatter changeRowFormatter) {
- ChangeCache cache = ChangeCache.get(c.getId());
- cache.getChangeInfoCache().set(c);
+ public void updateColumnsForLabels(ChangeList... lists) {
+ labelNames = new ArrayList<>();
+ for (ChangeList list : lists) {
+ for (int i = 0; i < list.length(); i++) {
+ for (String name : list.get(i).labels()) {
+ if (!labelNames.contains(name)) {
+ labelNames.add(name);
+ }
+ }
+ }
+ }
+ Collections.sort(labelNames);
+
+ int baseColumns = BASE_COLUMNS;
+ if (baseColumns + labelNames.size() < columns) {
+ int n = columns - (baseColumns + labelNames.size());
+ for (int row = 0; row < table.getRowCount(); row++) {
+ table.removeCells(row, columns, n);
+ }
+ }
+ columns = baseColumns + labelNames.size();
+
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ for (int i = 0; i < labelNames.size(); i++) {
+ String name = labelNames.get(i);
+ int col = baseColumns + i;
+
+ String abbrev = getAbbreviation(name, "-");
+ table.setText(0, col, abbrev);
+ table.getCellFormatter().getElement(0, col).setTitle(name);
+ fmt.addStyleName(0, col, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ for (Section s : sections) {
+ if (s.titleRow >= 0) {
+ fmt.setColSpan(s.titleRow, 0, columns);
+ }
+ }
+ }
- table.setWidget(row, C_ARROW, null);
+ private void populateChangeRow(final int row, final ChangeInfo c,
+ boolean highlightUnreviewed) {
+ CellFormatter fmt = table.getCellFormatter();
if (Gerrit.isSignedIn()) {
- table.setWidget(row, C_STAR, StarredChanges.createIcon(c.getId(), c.isStarred()));
+ table.setWidget(row, C_STAR, StarredChanges.createIcon(
+ c.legacy_id(),
+ c.starred()));
}
+ table.setWidget(row, C_ID, new TableChangeLink(String.valueOf(c.legacy_id()), c));
- String s = Util.cropSubject(c.getSubject());
- if (c.getStatus() != null && c.getStatus() != Change.Status.NEW) {
- s += " (" + c.getStatus().name() + ")";
+ String subject = Util.cropSubject(c.subject());
+ table.setWidget(row, C_SUBJECT, new TableChangeLink(subject, c));
+
+ Change.Status status = c.status();
+ if (status != Change.Status.NEW) {
+ table.setText(row, C_STATUS, Util.toLongString(status));
+ } else if (!c.mergeable()) {
+ table.setText(row, C_STATUS, Util.C.changeTableNotMergeable());
}
- if (changeRowFormatter != null) {
- removeChangeStyle(row, changeRowFormatter);
- final String rowStyle = changeRowFormatter.getRowStyle(c);
- if (rowStyle != null) {
- table.getRowFormatter().addStyleName(row, rowStyle);
+
+ if (c.owner() != null) {
+ table.setWidget(row, C_OWNER, new AccountLinkPanel(c.owner(), status));
+ } else {
+ table.setText(row, C_OWNER, "");
+ }
+
+ table.setWidget(row, C_PROJECT, new ProjectLink(c.project_name_key()));
+ table.setWidget(row, C_BRANCH, new BranchLink(c.project_name_key(), c
+ .status(), c.branch(), c.topic()));
+ if (Gerrit.isSignedIn()
+ && Gerrit.getUserAccount().getGeneralPreferences()
+ .isRelativeDateInChangeTable()) {
+ table.setText(row, C_LAST_UPDATE, relativeFormat(c.updated()));
+ } else {
+ table.setText(row, C_LAST_UPDATE, shortFormat(c.updated()));
+ }
+
+ int col = C_SIZE;
+ if (Gerrit.isSignedIn()
+ && !Gerrit.getUserAccount().getGeneralPreferences()
+ .isSizeBarInChangeTable()) {
+ table.setText(row, col,
+ Util.M.insertionsAndDeletions(c.insertions(), c.deletions()));
+ } else {
+ table.setWidget(row, col, getSizeWidget(c));
+ fmt.getElement(row, col).setTitle(
+ Util.M.insertionsAndDeletions(c.insertions(), c.deletions()));
+ }
+ col++;
+
+ for (int idx = 0; idx < labelNames.size(); idx++, col++) {
+ String name = labelNames.get(idx);
+
+ LabelInfo label = c.label(name);
+ if (label == null) {
+ fmt.getElement(row, col).setTitle(Gerrit.C.labelNotApplicable());
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().labelNotApplicable());
+ continue;
+ }
+
+ String user;
+ String info;
+ ReviewCategoryStrategy reviewCategoryStrategy = Gerrit.isSignedIn()
+ ? Gerrit.getUserAccount().getGeneralPreferences()
+ .getReviewCategoryStrategy()
+ : ReviewCategoryStrategy.NONE;
+ if (label.rejected() != null) {
+ user = label.rejected().name();
+ info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
+ label.rejected());
+ if (info != null) {
+ FlowPanel panel = new FlowPanel();
+ panel.add(new Image(Gerrit.RESOURCES.redNot()));
+ panel.add(new InlineLabel(info));
+ table.setWidget(row, col, panel);
+ } else {
+ table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot()));
+ }
+ } else if (label.approved() != null) {
+ user = label.approved().name();
+ info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
+ label.approved());
+ if (info != null) {
+ FlowPanel panel = new FlowPanel();
+ panel.add(new Image(Gerrit.RESOURCES.greenCheck()));
+ panel.add(new InlineLabel(info));
+ table.setWidget(row, col, panel);
+ } else {
+ table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck()));
+ }
+ } else if (label.disliked() != null) {
+ user = label.disliked().name();
+ info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
+ label.disliked());
+ String vstr = String.valueOf(label._value());
+ if (info != null) {
+ vstr = vstr + " " + info;
+ }
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
+ table.setText(row, col, vstr);
+ } else if (label.recommended() != null) {
+ user = label.recommended().name();
+ info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
+ label.recommended());
+ String vstr = "+" + label._value();
+ if (info != null) {
+ vstr = vstr + " " + info;
+ }
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore());
+ table.setText(row, col, vstr);
+ } else {
+ table.clearCell(row, col);
+ continue;
+ }
+ fmt.addStyleName(row, col, Gerrit.RESOURCES.css().singleLine());
+
+ if (user != null) {
+ // Some web browsers ignore the embedded newline; some like it;
+ // so we include a space before the newline to accommodate both.
+ fmt.getElement(row, col).setTitle(name + " \nby " + user);
}
- s = changeRowFormatter.getDisplayText(c, s);
}
- table.setWidget(row, C_SUBJECT, new TableChangeLink(s, c));
- table.setWidget(row, C_OWNER, link(c.getOwner()));
- table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey()));
- table.setWidget(row, C_BRANCH, new BranchLink(c.getProject().getKey(), c
- .getStatus(), c.getBranch(), c.getTopic()));
- table.setText(row, C_LAST_UPDATE, shortFormat(c.getLastUpdatedOn()));
+ boolean needHighlight = false;
+ if (highlightUnreviewed && !c.reviewed()) {
+ needHighlight = true;
+ }
+ final Element tr = fmt.getElement(row, 0).getParentElement();
+ UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
+ needHighlight);
setRowItem(row, c);
}
- private void removeChangeStyle(int row,
- final ChangeRowFormatter changeRowFormatter) {
- final ChangeInfo oldChange = getRowItem(row);
- if (oldChange == null) {
- return;
+ private static String getReviewCategoryDisplayInfo(
+ ReviewCategoryStrategy reviewCategoryStrategy, AccountInfo accountInfo) {
+ switch (reviewCategoryStrategy) {
+ case NAME:
+ return accountInfo.name();
+ case EMAIL:
+ return accountInfo.email();
+ case USERNAME:
+ return accountInfo.username();
+ case ABBREV:
+ return getAbbreviation(accountInfo.name(), " ");
+ default:
+ return null;
}
+ }
- final String oldRowStyle = changeRowFormatter.getRowStyle(oldChange);
- if (oldRowStyle != null) {
- table.getRowFormatter().removeStyleName(row, oldRowStyle);
+ private static String getAbbreviation(String name, String token) {
+ StringBuilder abbrev = new StringBuilder();
+ if (name != null) {
+ for (String t : name.split(token)) {
+ abbrev.append(t.substring(0, 1).toUpperCase());
+ }
}
+ return abbrev.toString();
+ }
+
+ private static Widget getSizeWidget(ChangeInfo c) {
+ int largeChangeSize = Gerrit.getConfig().getLargeChangeSize();
+ int changedLines = c.insertions() + c.deletions();
+ int p = 100;
+ if (changedLines < largeChangeSize) {
+ p = changedLines * 100 / largeChangeSize;
+ }
+
+ int width = Math.max(2, 70 * p / 100);
+ int red = p >= 50 ? 255 : (int) Math.round((p) * 5.12);
+ int green = p <= 50 ? 255 : (int) Math.round(256 - (p - 50) * 5.12);
+ String bg = "#" + toHex(red) + toHex(green) + "00";
+
+ SimplePanel panel = new SimplePanel();
+ panel.setStyleName(Gerrit.RESOURCES.css().changeSize());
+ panel.setWidth(width + "px");
+ panel.getElement().getStyle().setBackgroundColor(bg);
+ return panel;
}
- private AccountLinkPanel link(final Account.Id id) {
- return AccountLinkPanel.link(accountCache, id);
+ private static String toHex(int i) {
+ String hex = Integer.toHexString(i);
+ return hex.length() == 1 ? "0" + hex : hex;
}
public void addSection(final Section s) {
assert s.parent == null;
- if (s.titleText != null) {
- s.titleRow = table.getRowCount();
- table.setText(s.titleRow, 0, s.titleText);
+ s.parent = this;
+ s.titleRow = table.getRowCount();
+ if (s.displayTitle()) {
final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.setColSpan(s.titleRow, 0, COLUMNS);
+ fmt.setColSpan(s.titleRow, 0, columns);
fmt.addStyleName(s.titleRow, 0, Gerrit.RESOURCES.css().sectionHeader());
} else {
s.titleRow = -1;
}
- s.parent = this;
s.dataBegin = table.getRowCount();
insertNoneRow(s.dataBegin);
sections.add(s);
}
- public void setAccountInfoCache(final AccountInfoCache aic) {
- assert aic != null;
- accountCache = aic;
- }
-
private int insertRow(final int beforeRow) {
for (final Section s : sections) {
if (beforeRow <= s.titleRow) {
@@ -240,13 +437,17 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
@Override
public void onKeyPress(final KeyPressEvent event) {
- onStarClick(getCurrentRow());
+ int row = getCurrentRow();
+ ChangeInfo c = getRowItem(row);
+ if (c != null && Gerrit.isSignedIn()) {
+ ((StarredChanges.Icon) table.getWidget(row, C_STAR)).toggleStar();
+ }
}
}
private final class TableChangeLink extends ChangeLink {
private TableChangeLink(final String text, final ChangeInfo c) {
- super(text, c);
+ super(text, c.legacy_id());
}
@Override
@@ -257,42 +458,47 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
}
public static class Section {
- String titleText;
-
ChangeTable parent;
- final Account.Id ownerId;
+ String titleText;
+ Widget titleWidget;
int titleRow = -1;
int dataBegin;
int rows;
+ private boolean highlightUnreviewed;
- private ChangeRowFormatter changeRowFormatter;
-
- public Section() {
- this(null, null);
- }
-
- public Section(final String titleText) {
- this(titleText, null);
- }
-
- public Section(final String titleText, final Account.Id owner) {
- setTitleText(titleText);
- ownerId = owner;
+ public void setHighlightUnreviewed(boolean value) {
+ this.highlightUnreviewed = value;
}
public void setTitleText(final String text) {
titleText = text;
+ titleWidget = null;
if (titleRow >= 0) {
parent.table.setText(titleRow, 0, titleText);
}
}
- public void setChangeRowFormatter(final ChangeRowFormatter changeRowFormatter) {
- this.changeRowFormatter = changeRowFormatter;
+ public void setTitleWidget(final Widget title) {
+ titleWidget = title;
+ titleText = null;
+ if (titleRow >= 0) {
+ parent.table.setWidget(titleRow, 0, title);
+ }
}
- public void display(final List<ChangeInfo> changeList) {
- final int sz = changeList != null ? changeList.size() : 0;
+ public boolean displayTitle() {
+ if (titleText != null) {
+ setTitleText(titleText);
+ return true;
+ } else if(titleWidget != null) {
+ setTitleWidget(titleWidget);
+ return true;
+ }
+ return false;
+ }
+
+ public void display(ChangeList changeList) {
+ final int sz = changeList != null ? changeList.length() : 0;
final boolean hadData = rows > 0;
if (hadData) {
@@ -300,51 +506,23 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
parent.removeRow(dataBegin);
rows--;
}
+ } else {
+ parent.removeRow(dataBegin);
}
if (sz == 0) {
- if (hadData) {
- parent.insertNoneRow(dataBegin);
- }
- } else {
- Set<Change.Id> cids = new HashSet<>();
-
- if (!hadData) {
- parent.removeRow(dataBegin);
- }
-
- while (rows < sz) {
- parent.insertChangeRow(dataBegin + rows);
- rows++;
- }
+ parent.insertNoneRow(dataBegin);
+ return;
+ }
- for (int i = 0; i < sz; i++) {
- ChangeInfo c = changeList.get(i);
- parent.populateChangeRow(dataBegin + i, c, changeRowFormatter);
- cids.add(c.getId());
- }
+ while (rows < sz) {
+ parent.insertChangeRow(dataBegin + rows);
+ rows++;
+ }
+ for (int i = 0; i < sz; i++) {
+ parent.populateChangeRow(dataBegin + i, changeList.get(i),
+ highlightUnreviewed);
}
}
}
-
- public static interface ChangeRowFormatter {
- /**
- * Returns the name of the CSS style that should be applied to the change
- * row.
- *
- * @param c the change for which the styling should be returned
- * @return the name of the CSS style that should be applied to the change
- * row
- */
- String getRowStyle(ChangeInfo c);
-
- /**
- * Returns the text that should be displayed for the change.
- *
- * @param c the change for which the display text should be returned
- * @param displayText the current display text
- * @return the new display text
- */
- String getDisplayText(ChangeInfo c, String displayText);
- }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
deleted file mode 100644
index 270b9f5153..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ /dev/null
@@ -1,542 +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.client.changes;
-
-import static com.google.gerrit.client.FormatUtil.relativeFormat;
-import static com.google.gerrit.client.FormatUtil.shortFormat;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
-import com.google.gerrit.client.account.AccountInfo;
-import com.google.gerrit.client.ui.AccountLinkPanel;
-import com.google.gerrit.client.ui.BranchLink;
-import com.google.gerrit.client.ui.ChangeLink;
-import com.google.gerrit.client.ui.NavigationTable;
-import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
-import com.google.gerrit.client.ui.ProjectLink;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTMLTable.Cell;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class ChangeTable2 extends NavigationTable<ChangeInfo> {
- private static final int C_STAR = 1;
- private static final int C_ID = 2;
- private static final int C_SUBJECT = 3;
- private static final int C_STATUS = 4;
- private static final int C_OWNER = 5;
- private static final int C_PROJECT = 6;
- private static final int C_BRANCH = 7;
- private static final int C_LAST_UPDATE = 8;
- private static final int C_SIZE = 9;
- private static final int BASE_COLUMNS = 10;
-
-
- private final boolean useNewFeatures = Gerrit.getConfig().getNewFeatures();
- private final List<Section> sections;
- private int columns;
- private List<String> labelNames;
-
- public ChangeTable2() {
- super(Util.C.changeItemHelp());
- columns = useNewFeatures ? BASE_COLUMNS : BASE_COLUMNS - 1;
- labelNames = Collections.emptyList();
-
- if (Gerrit.isSignedIn()) {
- keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar()));
- }
-
- sections = new ArrayList<>();
- table.setText(0, C_STAR, "");
- table.setText(0, C_ID, Util.C.changeTableColumnID());
- table.setText(0, C_SUBJECT, Util.C.changeTableColumnSubject());
- table.setText(0, C_STATUS, Util.C.changeTableColumnStatus());
- table.setText(0, C_OWNER, Util.C.changeTableColumnOwner());
- table.setText(0, C_PROJECT, Util.C.changeTableColumnProject());
- table.setText(0, C_BRANCH, Util.C.changeTableColumnBranch());
- table.setText(0, C_LAST_UPDATE, Util.C.changeTableColumnLastUpdate());
- if (useNewFeatures) {
- table.setText(0, C_SIZE, Util.C.changeTableColumnSize());
- }
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(0, C_STAR, Gerrit.RESOURCES.css().iconHeader());
- for (int i = C_ID; i < columns; i++) {
- fmt.addStyleName(0, i, Gerrit.RESOURCES.css().dataHeader());
- }
-
- if (!Gerrit.isSignedIn() ||
- (!Gerrit.getUserAccount().getGeneralPreferences()
- .isLegacycidInChangeTable())) {
- fmt.addStyleName(0, C_ID, Gerrit.RESOURCES.css().dataHeaderHidden());
- }
-
- table.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- final Cell cell = table.getCellForEvent(event);
- if (cell == null) {
- return;
- }
- if (cell.getCellIndex() == C_STAR) {
- // Don't do anything (handled by star itself).
- } else if (cell.getCellIndex() == C_STATUS) {
- // Don't do anything.
- } else if (cell.getCellIndex() == C_OWNER) {
- // Don't do anything.
- } else if (getRowItem(cell.getRowIndex()) != null) {
- movePointerTo(cell.getRowIndex());
- }
- }
- });
- }
-
- @Override
- protected Object getRowItemKey(final ChangeInfo item) {
- return item.legacy_id();
- }
-
- @Override
- protected void onOpenRow(final int row) {
- final ChangeInfo c = getRowItem(row);
- final Change.Id id = c.legacy_id();
- Gerrit.display(PageLinks.toChange(id));
- }
-
- private void insertNoneRow(final int row) {
- insertRow(row);
- table.setText(row, 0, Util.C.changeTableNone());
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.setColSpan(row, 0, columns);
- fmt.setStyleName(row, 0, Gerrit.RESOURCES.css().emptySection());
- }
-
- private void insertChangeRow(final int row) {
- insertRow(row);
- applyDataRowStyle(row);
- }
-
- @Override
- protected void applyDataRowStyle(final int row) {
- super.applyDataRowStyle(row);
- final CellFormatter fmt = table.getCellFormatter();
- fmt.addStyleName(row, C_STAR, Gerrit.RESOURCES.css().iconCell());
- for (int i = C_ID; i < columns; i++) {
- fmt.addStyleName(row, i, Gerrit.RESOURCES.css().dataCell());
- }
- fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT());
- fmt.addStyleName(row, C_STATUS, Gerrit.RESOURCES.css().cSTATUS());
- fmt.addStyleName(row, C_OWNER, Gerrit.RESOURCES.css().cOWNER());
- fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate());
-
- if (!Gerrit.isSignedIn() ||
- (!Gerrit.getUserAccount().getGeneralPreferences()
- .isLegacycidInChangeTable())) {
- fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().dataCellHidden());
- }
-
- int i = C_SIZE;
- if (useNewFeatures) {
- fmt.addStyleName(row, i++, Gerrit.RESOURCES.css().cSIZE());
- }
- for (; i < columns; i++) {
- fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
- }
- }
-
- public void updateColumnsForLabels(ChangeList... lists) {
- labelNames = new ArrayList<>();
- for (ChangeList list : lists) {
- for (int i = 0; i < list.length(); i++) {
- for (String name : list.get(i).labels()) {
- if (!labelNames.contains(name)) {
- labelNames.add(name);
- }
- }
- }
- }
- Collections.sort(labelNames);
-
- int baseColumns = useNewFeatures ? BASE_COLUMNS : BASE_COLUMNS - 1;
- if (baseColumns + labelNames.size() < columns) {
- int n = columns - (baseColumns + labelNames.size());
- for (int row = 0; row < table.getRowCount(); row++) {
- table.removeCells(row, columns, n);
- }
- }
- columns = baseColumns + labelNames.size();
-
- FlexCellFormatter fmt = table.getFlexCellFormatter();
- for (int i = 0; i < labelNames.size(); i++) {
- String name = labelNames.get(i);
- int col = baseColumns + i;
-
- String abbrev = getAbbreviation(name, "-");
- table.setText(0, col, abbrev);
- table.getCellFormatter().getElement(0, col).setTitle(name);
- fmt.addStyleName(0, col, Gerrit.RESOURCES.css().dataHeader());
- }
-
- for (Section s : sections) {
- if (s.titleRow >= 0) {
- fmt.setColSpan(s.titleRow, 0, columns);
- }
- }
- }
-
- private void populateChangeRow(final int row, final ChangeInfo c,
- boolean highlightUnreviewed) {
- CellFormatter fmt = table.getCellFormatter();
- if (Gerrit.isSignedIn()) {
- table.setWidget(row, C_STAR, StarredChanges.createIcon(
- c.legacy_id(),
- c.starred()));
- }
- table.setWidget(row, C_ID, new TableChangeLink(String.valueOf(c.legacy_id()), c));
-
- String subject = Util.cropSubject(c.subject());
- table.setWidget(row, C_SUBJECT, new TableChangeLink(subject, c));
-
- Change.Status status = c.status();
- if (status != Change.Status.NEW) {
- table.setText(row, C_STATUS, Util.toLongString(status));
- } else if (!c.mergeable() && useNewFeatures) {
- table.setText(row, C_STATUS, Util.C.changeTableNotMergeable());
- }
-
- if (c.owner() != null) {
- table.setWidget(row, C_OWNER, new AccountLinkPanel(c.owner(), status));
- } else {
- table.setText(row, C_OWNER, "");
- }
-
- table.setWidget(row, C_PROJECT, new ProjectLink(c.project_name_key()));
- table.setWidget(row, C_BRANCH, new BranchLink(c.project_name_key(), c
- .status(), c.branch(), c.topic()));
- if (Gerrit.isSignedIn()
- && Gerrit.getUserAccount().getGeneralPreferences()
- .isRelativeDateInChangeTable()) {
- table.setText(row, C_LAST_UPDATE, relativeFormat(c.updated()));
- } else {
- table.setText(row, C_LAST_UPDATE, shortFormat(c.updated()));
- }
- int col = C_SIZE;
- if (useNewFeatures) {
- if (Gerrit.isSignedIn()
- && !Gerrit.getUserAccount().getGeneralPreferences()
- .isSizeBarInChangeTable()) {
- table.setText(row, col,
- Util.M.insertionsAndDeletions(c.insertions(), c.deletions()));
- } else {
- table.setWidget(row, col, getSizeWidget(c));
- fmt.getElement(row, col).setTitle(
- Util.M.insertionsAndDeletions(c.insertions(), c.deletions()));
- }
- col++;
- }
-
- boolean displayInfo = Gerrit.isSignedIn() && Gerrit.getUserAccount()
- .getGeneralPreferences().isShowInfoInReviewCategory();
-
- for (int idx = 0; idx < labelNames.size(); idx++, col++) {
- String name = labelNames.get(idx);
-
- LabelInfo label = c.label(name);
- if (label == null) {
- fmt.getElement(row, col).setTitle(Gerrit.C.labelNotApplicable());
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().labelNotApplicable());
- continue;
- }
-
- String user;
- String info;
- ReviewCategoryStrategy reviewCategoryStrategy = Gerrit.isSignedIn()
- ? Gerrit.getUserAccount().getGeneralPreferences()
- .getReviewCategoryStrategy()
- : ReviewCategoryStrategy.NONE;
- if (label.rejected() != null) {
- user = label.rejected().name();
- info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
- label.rejected());
- if (displayInfo && info != null) {
- FlowPanel panel = new FlowPanel();
- panel.add(new Image(Gerrit.RESOURCES.redNot()));
- panel.add(new InlineLabel(info));
- table.setWidget(row, col, panel);
- } else {
- table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot()));
- }
- } else if (label.approved() != null) {
- user = label.approved().name();
- info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
- label.approved());
- if (displayInfo && info != null) {
- FlowPanel panel = new FlowPanel();
- panel.add(new Image(Gerrit.RESOURCES.greenCheck()));
- panel.add(new InlineLabel(info));
- table.setWidget(row, col, panel);
- } else {
- table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck()));
- }
- } else if (label.disliked() != null) {
- user = label.disliked().name();
- info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
- label.disliked());
- String vstr = String.valueOf(label._value());
- if (displayInfo && info != null) {
- vstr = vstr + " " + info;
- }
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
- table.setText(row, col, vstr);
- } else if (label.recommended() != null) {
- user = label.recommended().name();
- info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
- label.recommended());
- String vstr = "+" + label._value();
- if (displayInfo && info != null) {
- vstr = vstr + " " + info;
- }
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore());
- table.setText(row, col, vstr);
- } else {
- table.clearCell(row, col);
- continue;
- }
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().singleLine());
-
- if ((!displayInfo || reviewCategoryStrategy == ReviewCategoryStrategy.ABBREV)
- && user != null) {
- // Some web browsers ignore the embedded newline; some like it;
- // so we include a space before the newline to accommodate both.
- fmt.getElement(row, col).setTitle(name + " \nby " + user);
- }
- }
-
- boolean needHighlight = false;
- if (highlightUnreviewed && !c.reviewed()) {
- needHighlight = true;
- }
- final Element tr = fmt.getElement(row, 0).getParentElement();
- UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
- needHighlight);
-
- setRowItem(row, c);
- }
-
- private static String getReviewCategoryDisplayInfo(
- ReviewCategoryStrategy reviewCategoryStrategy, AccountInfo accountInfo) {
- switch (reviewCategoryStrategy) {
- case NAME:
- return accountInfo.name();
- case EMAIL:
- return accountInfo.email();
- case USERNAME:
- return accountInfo.username();
- case ABBREV:
- return getAbbreviation(accountInfo.name(), " ");
- default:
- return null;
- }
- }
-
- private static String getAbbreviation(String name, String token) {
- StringBuilder abbrev = new StringBuilder();
- if (name != null) {
- for (String t : name.split(token)) {
- abbrev.append(t.substring(0, 1).toUpperCase());
- }
- }
- return abbrev.toString();
- }
-
- private static Widget getSizeWidget(ChangeInfo c) {
- int largeChangeSize = Gerrit.getConfig().getLargeChangeSize();
- int changedLines = c.insertions() + c.deletions();
- int p = 100;
- if (changedLines < largeChangeSize) {
- p = changedLines * 100 / largeChangeSize;
- }
-
- int width = Math.max(2, 70 * p / 100);
- int red = p > 50 ? 255 : (int) Math.round((p) * 5.12);
- int green = p < 50 ? 255 : (int) Math.round(256 - (p - 50) * 5.12);
- String bg = "#" + toHex(red) + toHex(green) + "00";
-
- SimplePanel panel = new SimplePanel();
- panel.setStyleName(Gerrit.RESOURCES.css().changeSize());
- panel.setWidth(width + "px");
- panel.getElement().getStyle().setBackgroundColor(bg);
- return panel;
- }
-
- private static String toHex(int i) {
- String hex = Integer.toHexString(i);
- return hex.length() == 1 ? "0" + hex : hex;
- }
-
- public void addSection(final Section s) {
- assert s.parent == null;
-
- s.parent = this;
- s.titleRow = table.getRowCount();
- if (s.displayTitle()) {
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.setColSpan(s.titleRow, 0, columns);
- fmt.addStyleName(s.titleRow, 0, Gerrit.RESOURCES.css().sectionHeader());
- } else {
- s.titleRow = -1;
- }
-
- s.dataBegin = table.getRowCount();
- insertNoneRow(s.dataBegin);
- sections.add(s);
- }
-
- private int insertRow(final int beforeRow) {
- for (final Section s : sections) {
- if (beforeRow <= s.titleRow) {
- s.titleRow++;
- }
- if (beforeRow < s.dataBegin) {
- s.dataBegin++;
- }
- }
- return table.insertRow(beforeRow);
- }
-
- private void removeRow(final int row) {
- for (final Section s : sections) {
- if (row < s.titleRow) {
- s.titleRow--;
- }
- if (row < s.dataBegin) {
- s.dataBegin--;
- }
- }
- table.removeRow(row);
- }
-
- public class StarKeyCommand extends NeedsSignInKeyCommand {
- public StarKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- int row = getCurrentRow();
- ChangeInfo c = getRowItem(row);
- if (c != null && Gerrit.isSignedIn()) {
- ((StarredChanges.Icon) table.getWidget(row, C_STAR)).toggleStar();
- }
- }
- }
-
- private final class TableChangeLink extends ChangeLink {
- private TableChangeLink(final String text, final ChangeInfo c) {
- super(text, c.legacy_id());
- }
-
- @Override
- public void go() {
- movePointerTo(cid);
- super.go();
- }
- }
-
- public static class Section {
- ChangeTable2 parent;
- String titleText;
- Widget titleWidget;
- int titleRow = -1;
- int dataBegin;
- int rows;
- private boolean highlightUnreviewed;
-
- public void setHighlightUnreviewed(boolean value) {
- this.highlightUnreviewed = value;
- }
-
- public void setTitleText(final String text) {
- titleText = text;
- titleWidget = null;
- if (titleRow >= 0) {
- parent.table.setText(titleRow, 0, titleText);
- }
- }
-
- public void setTitleWidget(final Widget title) {
- titleWidget = title;
- titleText = null;
- if (titleRow >= 0) {
- parent.table.setWidget(titleRow, 0, title);
- }
- }
-
- public boolean displayTitle() {
- if (titleText != null) {
- setTitleText(titleText);
- return true;
- } else if(titleWidget != null) {
- setTitleWidget(titleWidget);
- return true;
- }
- return false;
- }
-
- public void display(ChangeList changeList) {
- final int sz = changeList != null ? changeList.length() : 0;
- final boolean hadData = rows > 0;
-
- if (hadData) {
- while (sz < rows) {
- parent.removeRow(dataBegin);
- rows--;
- }
- } else {
- parent.removeRow(dataBegin);
- }
-
- if (sz == 0) {
- parent.insertNoneRow(dataBegin);
- return;
- }
-
- while (rows < sz) {
- parent.insertChangeRow(dataBegin + rows);
- rows++;
- }
- for (int i = 0; i < sz; i++) {
- parent.populateChangeRow(dataBegin + i, changeList.get(i),
- highlightUnreviewed);
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
index e1ec47a5db..52e5d40837 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
@@ -16,7 +16,7 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.diff.CommentRange;
-import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.extensions.client.Side;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
index 53c5c6dee9..9e97c56359 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
@@ -20,7 +20,7 @@ import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.http.client.URL;
@@ -31,7 +31,7 @@ import java.util.EnumSet;
import java.util.List;
import java.util.ListIterator;
-public class DashboardTable extends ChangeTable2 {
+public class DashboardTable extends ChangeTable {
private List<Section> sections;
private String title;
private List<String> titles;
@@ -96,6 +96,7 @@ public class DashboardTable extends ChangeTable2 {
return unlimitedQuery.toString().trim();
}
+ @Override
public String getTitle() {
return title;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java
deleted file mode 100644
index 62fbfb4f2e..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (C) 2010 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.client.changes;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeInfo.IncludedInInfo;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.common.data.IncludedInDetail;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.event.logical.shared.OpenEvent;
-import com.google.gwt.event.logical.shared.OpenHandler;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.DisclosurePanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/** Displays a table of Branches and Tags containing the change record. */
-public class IncludedInTable extends Composite implements
- OpenHandler<DisclosurePanel> {
- private final Grid table;
- private final Change.Id changeId;
- private boolean loaded = false;
-
- public IncludedInTable(final Change.Id chId) {
- changeId = chId;
- table = new Grid(1, 1);
- initWidget(table);
- }
-
- public void loadTable(final IncludedInDetail detail) {
- int row = 0;
- table.resizeRows(detail.getBranches().size() + 1);
- table.addStyleName(Gerrit.RESOURCES.css().changeTable());
- final CellFormatter fmt = table.getCellFormatter();
- fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().dataHeader());
- table.setText(row, 0, Util.C.includedInTableBranch());
-
- for (final String branch : detail.getBranches()) {
- fmt.addStyleName(++row, 0, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().leftMostCell());
- table.setText(row, 0, branch);
- }
-
- if (!detail.getTags().isEmpty()) {
- table.resizeRows(table.getRowCount() + 2 + detail.getTags().size());
- row++;
- fmt.addStyleName(++row, 0, Gerrit.RESOURCES.css().dataHeader());
- table.setText(row, 0, Util.C.includedInTableTag());
-
- for (final String tag : detail.getTags()) {
- fmt.addStyleName(++row, 0, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().leftMostCell());
- table.setText(row, 0, tag);
- }
- }
-
- table.setVisible(true);
- loaded = true;
- }
-
- @Override
- public void onOpen(OpenEvent<DisclosurePanel> event) {
- if (!loaded) {
- ChangeApi.includedIn(changeId.get(),
- new GerritCallback<IncludedInInfo>() {
- @Override
- public void onSuccess(IncludedInInfo r) {
- IncludedInDetail result = new IncludedInDetail();
- result.setBranches(toList(r.branches()));
- result.setTags(toList(r.tags()));
- loadTable(result);
- }
-
- private List<String> toList(JsArrayString in) {
- List<String> r = new ArrayList<>();
- if (in != null) {
- for (int i = 0; i < in.length(); i++) {
- r.add(in.get(i));
- }
- }
- return r;
- }
- });
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
index f98ac78926..9527c36d97 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
@@ -31,8 +31,8 @@ public abstract class PagedSingleListScreen extends Screen {
private final String anchorPrefix;
protected ChangeList changes;
- private ChangeTable2 table;
- private ChangeTable2.Section section;
+ private ChangeTable table;
+ private ChangeTable.Section section;
private Hyperlink prev;
private Hyperlink next;
@@ -59,7 +59,7 @@ public abstract class PagedSingleListScreen extends Screen {
next = new Hyperlink(Util.C.pagedChangeListNext(), true, "");
next.setVisible(false);
- table = new ChangeTable2() {
+ table = new ChangeTable() {
{
keysNavigation.add(
new DoLinkCommand(0, 'p', Util.C.changeTablePagePrev(), prev),
@@ -77,7 +77,7 @@ public abstract class PagedSingleListScreen extends Screen {
});
}
};
- section = new ChangeTable2.Section();
+ section = new ChangeTable.Section();
table.addSection(section);
table.setSavePointerId(anchorPrefix);
add(table);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
deleted file mode 100644
index b7a0ec8a22..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ /dev/null
@@ -1,730 +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.client.changes;
-import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.ErrorDialog;
-import com.google.gerrit.client.FormatUtil;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.GitwebLink;
-import com.google.gerrit.client.change.DraftActions;
-import com.google.gerrit.client.download.DownloadPanel;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.NativeString;
-import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.client.ui.AccountLinkPanel;
-import com.google.gerrit.client.ui.ActionDialog;
-import com.google.gerrit.client.ui.CherryPickDialog;
-import com.google.gerrit.client.ui.ComplexDisclosurePanel;
-import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.common.data.UiCommandDetail;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
-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.UserIdentity;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.logical.shared.OpenEvent;
-import com.google.gwt.event.logical.shared.OpenHandler;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.DisclosurePanel;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.Panel;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel
- implements OpenHandler<DisclosurePanel> {
- private static final int R_AUTHOR = 0;
- private static final int R_COMMITTER = 1;
- private static final int R_PARENTS = 2;
- private static final int R_DOWNLOAD = 3;
- private static final int R_CNT = 4;
-
- private final ChangeDetailCache detailCache;
- private final ChangeDetail changeDetail;
- private final PatchSet patchSet;
- private final FlowPanel body;
-
- private Grid infoTable;
- private Panel actionsPanel;
- private PatchTable patchTable;
- private final Set<ClickHandler> registeredClickHandler = new HashSet<>();
-
- private PatchSet.Id diffBaseId;
-
- /**
- * Creates a closed complex disclosure panel for a patch set.
- * The patch set details are loaded when the complex disclosure panel is opened.
- */
- public PatchSetComplexDisclosurePanel(final PatchSet ps, boolean isOpen,
- boolean hasDraftComments) {
- super(Util.M.patchSetHeader(ps.getPatchSetId()), isOpen);
- detailCache = ChangeCache.get(ps.getId().getParentKey()).getChangeDetailCache();
- changeDetail = detailCache.get();
- patchSet = ps;
-
- body = new FlowPanel();
- setContent(body);
-
- if (hasDraftComments) {
- final Image draftComments = new Image(Gerrit.RESOURCES.draftComments());
- draftComments.setTitle(Util.C.patchSetWithDraftCommentsToolTip());
- getHeader().add(draftComments);
- }
-
- final GitwebLink gw = Gerrit.getGitwebLink();
- final InlineLabel revtxt = new InlineLabel(ps.getRevision().get() + " ");
- revtxt.addStyleName(Gerrit.RESOURCES.css().patchSetRevision());
- getHeader().add(revtxt);
- if (gw != null && gw.canLink(ps)) {
- final Anchor revlink =
- new Anchor(gw.getLinkName(), false, gw.toRevision(changeDetail.getChange()
- .getProject(), ps));
- revlink.addStyleName(Gerrit.RESOURCES.css().patchSetLink());
- getHeader().add(revlink);
- }
-
- if (ps.isDraft()) {
- final InlineLabel draftLabel = new InlineLabel(Util.C.draftPatchSetLabel());
- draftLabel.addStyleName(Gerrit.RESOURCES.css().patchSetRevision());
- getHeader().add(draftLabel);
- }
-
- if (isOpen) {
- ensureLoaded(changeDetail.getCurrentPatchSetDetail());
- } else {
- addOpenHandler(this);
- }
-
- }
-
- public void setDiffBaseId(PatchSet.Id diffBaseId) {
- this.diffBaseId = diffBaseId;
- }
-
- /**
- * Display the table showing the Author, Committer and Download links,
- * followed by the action buttons.
- */
- public void ensureLoaded(final PatchSetDetail detail) {
- loadInfoTable(detail);
- loadActionPanel(detail);
- loadPatchTable(detail);
- }
-
- public void loadInfoTable(final PatchSetDetail detail) {
- infoTable = new Grid(R_CNT, 2);
- infoTable.setStyleName(Gerrit.RESOURCES.css().infoBlock());
- infoTable.addStyleName(Gerrit.RESOURCES.css().patchSetInfoBlock());
-
- initRow(R_AUTHOR, Util.C.patchSetInfoAuthor());
- initRow(R_COMMITTER, Util.C.patchSetInfoCommitter());
- initRow(R_PARENTS, Util.C.patchSetInfoParents());
- initRow(R_DOWNLOAD, Util.C.patchSetInfoDownload());
-
- final CellFormatter itfmt = infoTable.getCellFormatter();
- itfmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
- itfmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
- itfmt.addStyleName(R_CNT - 1, 0, Gerrit.RESOURCES.css().bottomheader());
- itfmt.addStyleName(R_AUTHOR, 1, Gerrit.RESOURCES.css().useridentity());
- itfmt.addStyleName(R_COMMITTER, 1, Gerrit.RESOURCES.css().useridentity());
- itfmt.addStyleName(R_DOWNLOAD, 1, Gerrit.RESOURCES.css()
- .downloadLinkListCell());
-
- final PatchSetInfo info = detail.getInfo();
- displayUserIdentity(R_AUTHOR, info.getAuthor());
- displayUserIdentity(R_COMMITTER, info.getCommitter());
- displayParents(info.getParents());
- displayDownload();
-
- body.add(infoTable);
- }
-
- public void loadActionPanel(final PatchSetDetail detail) {
- if (!patchSet.getId().equals(diffBaseId)) {
- actionsPanel = new FlowPanel();
- actionsPanel.setStyleName(Gerrit.RESOURCES.css().patchSetActions());
- actionsPanel.setVisible(true);
- if (Gerrit.isSignedIn()) {
- if (changeDetail.canEdit()) {
- populateReviewAction();
- if (changeDetail.isCurrentPatchSet(detail)) {
- populateActions(detail);
- }
- populateCommands(detail);
- }
- if (detail.getPatchSet().isDraft()) {
- if (changeDetail.canPublish()) {
- populatePublishAction();
- }
- if (changeDetail.canDeleteDraft()
- && changeDetail.getPatchSets().size() > 1) {
- populateDeleteDraftPatchSetAction();
- }
- }
- }
- body.add(actionsPanel);
- }
- }
-
- public void loadPatchTable(final PatchSetDetail detail) {
- if (!patchSet.getId().equals(diffBaseId)) {
- patchTable = new PatchTable();
- patchTable.setSavePointerId("PatchTable " + patchSet.getId());
- patchTable.display(diffBaseId, detail);
- for (ClickHandler clickHandler : registeredClickHandler) {
- patchTable.addClickHandler(clickHandler);
- }
- patchTable.setRegisterKeys(true);
- setActive(true);
- body.add(patchTable);
- }
- }
-
- public class ChangeDownloadPanel extends DownloadPanel {
- public ChangeDownloadPanel(String project, String ref, boolean allowAnonymous) {
- super(project, ref, allowAnonymous);
- }
-
- @Override
- public void populateDownloadCommandLinks() {
- // This site prefers usage of the 'repo' tool, so suggest
- // that for easy fetch.
- //
- if (allowedSchemes.contains(DownloadScheme.REPO_DOWNLOAD)) {
- commands.add(cmdLinkfactory.new RepoCommandLink(projectName,
- changeDetail.getChange().getChangeId() + "/"
- + patchSet.getPatchSetId()));
- }
-
- if (!urls.isEmpty()) {
- if (allowedCommands.contains(DownloadCommand.CHECKOUT)
- || allowedCommands.contains(DownloadCommand.DEFAULT_DOWNLOADS)) {
- commands.add(cmdLinkfactory.new CheckoutCommandLink());
- }
- if (allowedCommands.contains(DownloadCommand.PULL)
- || allowedCommands.contains(DownloadCommand.DEFAULT_DOWNLOADS)) {
- commands.add(cmdLinkfactory.new PullCommandLink());
- }
- if (allowedCommands.contains(DownloadCommand.CHERRY_PICK)
- || allowedCommands.contains(DownloadCommand.DEFAULT_DOWNLOADS)) {
- commands.add(cmdLinkfactory.new CherryPickCommandLink());
- }
- if (allowedCommands.contains(DownloadCommand.FORMAT_PATCH)
- || allowedCommands.contains(DownloadCommand.DEFAULT_DOWNLOADS)) {
- commands.add(cmdLinkfactory.new FormatPatchCommandLink());
- }
- }
- }
- }
-
- private void displayDownload() {
- ChangeDownloadPanel dp = new ChangeDownloadPanel(
- changeDetail.getChange().getProject().get(),
- patchSet.getRefName(),
- changeDetail.isAllowsAnonymous());
-
- infoTable.setWidget(R_DOWNLOAD, 1, dp);
- }
-
- private void displayUserIdentity(final int row, final UserIdentity who) {
- if (who == null) {
- infoTable.clearCell(row, 1);
- return;
- }
-
- final FlowPanel fp = new FlowPanel();
- fp.setStyleName(Gerrit.RESOURCES.css().patchSetUserIdentity());
- if (who.getName() != null) {
- if (who.getAccount() != null) {
- fp.add(new AccountLinkPanel(who));
- } else {
- final InlineLabel lbl = new InlineLabel(who.getName());
- lbl.setStyleName(Gerrit.RESOURCES.css().accountName());
- fp.add(lbl);
- }
- }
- if (who.getEmail() != null) {
- fp.add(new InlineLabel("<" + who.getEmail() + ">"));
- }
- if (who.getDate() != null) {
- fp.add(new InlineLabel(FormatUtil.mediumFormat(who.getDate())));
- }
- infoTable.setWidget(row, 1, fp);
- }
-
- private void displayParents(final List<PatchSetInfo.ParentInfo> parents) {
- if (parents.size() == 0) {
- infoTable.setWidget(R_PARENTS, 1, new InlineLabel(Util.C.initialCommit()));
- return;
- }
- final Grid parentsTable = new Grid(parents.size(), 2);
-
- parentsTable.setStyleName(Gerrit.RESOURCES.css().parentsTable());
- parentsTable.addStyleName(Gerrit.RESOURCES.css().noborder());
- final CellFormatter ptfmt = parentsTable.getCellFormatter();
- int row = 0;
- for (PatchSetInfo.ParentInfo parent : parents) {
- parentsTable.setWidget(row, 0, new InlineLabel(parent.id.get()));
- ptfmt.addStyleName(row, 0, Gerrit.RESOURCES.css().noborder());
- ptfmt.addStyleName(row, 0, Gerrit.RESOURCES.css().monospace());
- parentsTable.setWidget(row, 1,
- new InlineLabel(Util.cropSubject(parent.shortMessage)));
- ptfmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
- row++;
- }
- infoTable.setWidget(R_PARENTS, 1, parentsTable);
- }
-
- private void populateActions(final PatchSetDetail detail) {
- final boolean isOpen = changeDetail.getChange().getStatus().isOpen();
-
- if (isOpen && changeDetail.canSubmit()) {
- final Button b =
- new Button(Util.M
- .submitPatchSet(detail.getPatchSet().getPatchSetId()));
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- ChangeApi.submit(
- patchSet.getId().getParentKey().get(),
- patchSet.getRevision().get(),
- new GerritCallback<SubmitInfo>() {
- public void onSuccess(SubmitInfo result) {
- redisplay();
- }
-
- public void onFailure(Throwable err) {
- if (SubmitFailureDialog.isConflict(err)) {
- new SubmitFailureDialog(err.getMessage()).center();
- redisplay();
- } else {
- b.setEnabled(true);
- super.onFailure(err);
- }
- }
-
- private void redisplay() {
- Gerrit.display(
- PageLinks.toChange(patchSet.getId().getParentKey()),
- new ChangeScreen(patchSet.getId().getParentKey()));
- }
- });
- }
- });
- actionsPanel.add(b);
- }
-
- if (changeDetail.canRevert()) {
- final Button b = new Button(Util.C.buttonRevertChangeBegin());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- new ActionDialog(b, true, Util.C.revertChangeTitle(),
- Util.C.headingRevertMessage()) {
- {
- sendButton.setText(Util.C.buttonRevertChangeSend());
- message.setText(Util.M.revertChangeDefaultMessage(
- detail.getInfo().getSubject(),
- detail.getPatchSet().getRevision().get())
- );
- }
-
- @Override
- public void onSend() {
- ChangeApi.revert(changeDetail.getChange().getChangeId(),
- getMessageText(), new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- sent = true;
- Gerrit.display(PageLinks.toChange(new Change.Id(result
- ._number())));
- hide();
- }
-
- @Override
- public void onFailure(Throwable caught) {
- enableButtons(true);
- super.onFailure(caught);
- }
- });
- }
- }.center();
- }
- });
- actionsPanel.add(b);
- }
-
- if (changeDetail.canCherryPick()) {
- final Button b = new Button(Util.C.buttonCherryPickChangeBegin());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- new CherryPickDialog(b, changeDetail.getChange().getProject()) {
- {
- sendButton.setText(Util.C.buttonCherryPickChangeSend());
- if (changeDetail.getChange().getStatus().isClosed()) {
- message.setText(Util.M.cherryPickedChangeDefaultMessage(
- detail.getInfo().getMessage().trim(),
- detail.getPatchSet().getRevision().get()));
- } else {
- message.setText(detail.getInfo().getMessage().trim());
- }
- }
-
- @Override
- public void onSend() {
- ChangeApi.cherrypick(changeDetail.getChange().getChangeId(),
- patchSet.getRevision().get(),
- getDestinationBranch(),
- getMessageText(),
- new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- sent = true;
- Gerrit.display(PageLinks.toChange(new Change.Id(result
- ._number())));
- hide();
- }
-
- @Override
- public void onFailure(Throwable caught) {
- enableButtons(true);
- super.onFailure(caught);
- }
- });
- }
- }.center();
- }
- });
- actionsPanel.add(b);
- }
-
- if (changeDetail.canAbandon()) {
- final Button b = new Button(Util.C.buttonAbandonChangeBegin());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- new ActionDialog(b, false, Util.C.abandonChangeTitle(),
- Util.C.headingAbandonMessage()) {
- {
- sendButton.setText(Util.C.buttonAbandonChangeSend());
- }
-
- @Override
- public void onSend() {
- // TODO: once the other users of ActionDialog have converted to
- // REST APIs, we can use createCallback() rather than providing
- // them directly.
- ChangeApi.abandon(changeDetail.getChange().getChangeId(),
- getMessageText(), new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- sent = true;
- Gerrit.display(PageLinks.toChange(new Change.Id(result
- ._number())));
- hide();
- }
-
- @Override
- public void onFailure(Throwable caught) {
- enableButtons(true);
- super.onFailure(caught);
- }
- });
- }
- }.center();
- }
- });
- actionsPanel.add(b);
- }
-
- if (changeDetail.getChange().getStatus() == Change.Status.DRAFT
- && changeDetail.canDeleteDraft()) {
- final Button b = new Button(Util.C.buttonDeleteDraftChange());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- ChangeApi.deleteChange(patchSet.getId().getParentKey().get(),
- new GerritCallback<JavaScriptObject>() {
- public void onSuccess(JavaScriptObject result) {
- Gerrit.display(PageLinks.MINE);
- }
-
- public void onFailure(Throwable err) {
- if (SubmitFailureDialog.isConflict(err)) {
- new SubmitFailureDialog(err.getMessage()).center();
- Gerrit.display(PageLinks.MINE);
- } else {
- b.setEnabled(true);
- super.onFailure(err);
- }
- }
- });
- }
- });
- actionsPanel.add(b);
- }
-
- if (changeDetail.canRestore()) {
- final Button b = new Button(Util.C.buttonRestoreChangeBegin());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- new ActionDialog(b, false, Util.C.restoreChangeTitle(),
- Util.C.headingRestoreMessage()) {
- {
- sendButton.setText(Util.C.buttonRestoreChangeSend());
- }
-
- @Override
- public void onSend() {
- ChangeApi.restore(changeDetail.getChange().getChangeId(),
- getMessageText(), new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- sent = true;
- Gerrit.display(PageLinks.toChange(new Change.Id(result
- ._number())));
- hide();
- }
-
- @Override
- public void onFailure(Throwable caught) {
- enableButtons(true);
- super.onFailure(caught);
- }
- });
- }
- }.center();
- }
- });
- actionsPanel.add(b);
- }
-
- if (changeDetail.canRebase()) {
- final Button b = new Button(Util.C.buttonRebaseChange());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- final Change.Id id = patchSet.getId().getParentKey();
- ChangeApi.rebase(id.get(), patchSet.getRevision().get(),
- new GerritCallback<ChangeInfo>() {
- public void onSuccess(ChangeInfo result) {
- Gerrit.display(PageLinks.toChange(id));
- }
- });
- }
- });
- actionsPanel.add(b);
- }
- }
-
- private void populateCommands(final PatchSetDetail detail) {
- for (final UiCommandDetail cmd : detail.getCommands()) {
- final Button b = new Button();
- b.setText(cmd.label);
- b.setEnabled(cmd.enabled);
- b.setTitle(cmd.title);
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- AsyncCallback<NativeString> cb =
- new AsyncCallback<NativeString>() {
- @Override
- public void onFailure(Throwable caught) {
- b.setEnabled(true);
- new ErrorDialog(caught).center();
- }
-
- @Override
- public void onSuccess(NativeString msg) {
- b.setEnabled(true);
- if (msg != null && !msg.asString().isEmpty()) {
- Window.alert(msg.asString());
- }
- Gerrit.display(PageLinks.toChange(patchSet.getId()));
- }
- };
- RestApi api = ChangeApi.revision(patchSet.getId()).view(cmd.id);
- if ("PUT".equalsIgnoreCase(cmd.method)) {
- api.put(JavaScriptObject.createObject(), cb);
- } else if ("DELETE".equalsIgnoreCase(cmd.method)) {
- api.delete(cb);
- } else {
- api.post(JavaScriptObject.createObject(), cb);
- }
- }
- });
- actionsPanel.add(b);
- }
- }
-
- private void populateReviewAction() {
- final Button b = new Button(Util.C.buttonReview());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- Gerrit.display(Dispatcher.toPublish(patchSet.getId()));
- }
- });
- actionsPanel.add(b);
- }
-
- private void populatePublishAction() {
- final Button b = new Button(Util.C.buttonPublishPatchSet());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- final Change.Id id = patchSet.getId().getParentKey();
- ChangeApi.publish(id.get(),
- patchSet.getRevision().get(),
- DraftActions.cs(id));
- }
- });
- actionsPanel.add(b);
- }
-
- private void populateDeleteDraftPatchSetAction() {
- final Button b = new Button(Util.C.buttonDeleteDraftPatchSet());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- b.setEnabled(false);
- final Change.Id id = patchSet.getId().getParentKey();
- ChangeApi.deleteRevision(id.get(),
- patchSet.getRevision().get(),
- DraftActions.cs(id));
- }
- });
- actionsPanel.add(b);
- }
-
- public void refresh() {
- if (patchSet.getId().equals(diffBaseId)) {
- if (patchTable != null) {
- patchTable.setVisible(false);
- }
- if (actionsPanel != null) {
- actionsPanel.setVisible(false);
- }
- } else {
- if (patchTable != null) {
- if (patchTable.getBase() == null && diffBaseId == null
- || patchTable.getBase() != null
- && patchTable.getBase().equals(diffBaseId)) {
- actionsPanel.setVisible(true);
- patchTable.setVisible(true);
- return;
- }
- }
-
- AccountDiffPreference diffPrefs;
- if (patchTable == null) {
- diffPrefs = new ListenableAccountDiffPreference().get();
- } else {
- diffPrefs = patchTable.getPreferences().get();
- patchTable.setVisible(false);
- }
-
- Util.DETAIL_SVC.patchSetDetail2(diffBaseId, patchSet.getId(), diffPrefs,
- new GerritCallback<PatchSetDetail>() {
- @Override
- public void onSuccess(PatchSetDetail result) {
- if (actionsPanel != null) {
- actionsPanel.setVisible(true);
- } else {
- loadActionPanel(result);
- }
- loadPatchTable(result);
- }
- });
- }
- }
-
- @Override
- public void onOpen(final OpenEvent<DisclosurePanel> event) {
- if (infoTable == null) {
- AccountDiffPreference diffPrefs;
- if (diffBaseId == null) {
- diffPrefs = null;
- } else {
- diffPrefs = new ListenableAccountDiffPreference().get();
- }
-
- Util.DETAIL_SVC.patchSetDetail2(diffBaseId, patchSet.getId(), diffPrefs,
- new GerritCallback<PatchSetDetail>() {
- public void onSuccess(final PatchSetDetail result) {
- loadInfoTable(result);
- loadActionPanel(result);
- }
- });
- }
- }
-
- private void initRow(final int row, final String name) {
- infoTable.setText(row, 0, name);
- infoTable.getCellFormatter().addStyleName(row, 0,
- Gerrit.RESOURCES.css().header());
- }
-
- public PatchSet getPatchSet() {
- return patchSet;
- }
-
- /**
- * Adds a click handler to the patch table.
- * If the patch table is not yet initialized it is guaranteed that the click handler
- * is added to the patch table after initialization.
- */
- public void addClickHandler(final ClickHandler clickHandler) {
- registeredClickHandler.add(clickHandler);
- if (patchTable != null) {
- patchTable.addClickHandler(clickHandler);
- }
- }
-
- /** Activates / Deactivates the key navigation and the highlighting of the current row for the patch table */
- public void setActive(boolean active) {
- if (patchTable != null) {
- patchTable.setActive(active);
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java
deleted file mode 100644
index 01c8fdb661..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright (C) 2010 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.client.changes;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.logical.shared.OpenEvent;
-import com.google.gwt.event.logical.shared.OpenHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.DisclosurePanel;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.globalkey.client.KeyCommand;
-import com.google.gwtexpui.globalkey.client.KeyCommandSet;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Composite that displays the patch sets of a change. This composite ensures
- * that keyboard navigation to each changed file in all patch sets is possible.
- */
-public class PatchSetsBlock extends Composite {
- private final Map<PatchSet.Id, PatchSetComplexDisclosurePanel> patchSetPanels =
- new HashMap<>();
-
- private final FlowPanel body;
- private HandlerRegistration regNavigation;
-
- private List<PatchSetComplexDisclosurePanel> patchSetPanelsList;
-
- /**
- * the patch set id of the patch set for which is the keyboard navigation is
- * currently enabled
- */
- private PatchSet.Id activePatchSetId;
-
- /** the patch set id of the current (latest) patch set */
- private PatchSet.Id currentPatchSetId;
-
- /** Patch sets on this change, in order. */
- private List<PatchSet> patchSets;
-
- PatchSetsBlock() {
- body = new FlowPanel();
- initWidget(body);
- }
-
- /** Adds UI elements for each patch set of the given change to this composite. */
- public void display(final ChangeDetail detail, final PatchSet.Id diffBaseId) {
- clear();
-
- final PatchSet currps = detail.getCurrentPatchSet();
- currentPatchSetId = currps.getId();
- patchSets = detail.getPatchSets();
-
- if (Gerrit.isSignedIn()) {
- final AccountGeneralPreferences p =
- Gerrit.getUserAccount().getGeneralPreferences();
- if (p.isReversePatchSetOrder()) {
- Collections.reverse(patchSets);
- }
- }
-
- patchSetPanelsList = new ArrayList<>();
-
- for (final PatchSet ps : patchSets) {
- final PatchSetComplexDisclosurePanel p =
- new PatchSetComplexDisclosurePanel(ps, ps == currps,
- detail.hasDraftComments(ps.getId()));
- if (diffBaseId != null) {
- p.setDiffBaseId(diffBaseId);
- if (ps == currps) {
- p.refresh();
- }
- }
- add(p);
- patchSetPanelsList.add(p);
- }
- }
-
- private void clear() {
- setRegisterKeys(false);
- body.clear();
- patchSetPanels.clear();
- }
-
- public void refresh(final PatchSet.Id diffBaseId) {
- if (patchSetPanelsList != null) {
- for (final PatchSetComplexDisclosurePanel p : patchSetPanelsList) {
- p.setDiffBaseId(diffBaseId);
- if (p.isOpen()) {
- p.refresh();
- }
- }
- }
- }
-
- /**
- * Adds the given patch set panel to this composite and ensures that handler
- * to activate / deactivate keyboard navigation for the patch set panel are
- * registered.
- */
- private void add(final PatchSetComplexDisclosurePanel patchSetPanel) {
- body.add(patchSetPanel);
-
- final PatchSet.Id id = patchSetPanel.getPatchSet().getId();
- ActivationHandler activationHandler = new ActivationHandler(id);
- patchSetPanel.addOpenHandler(activationHandler);
- patchSetPanel.addClickHandler(activationHandler);
- patchSetPanels.put(id, patchSetPanel);
- }
-
- public void setRegisterKeys(final boolean on) {
- if (on) {
- KeyCommandSet keysNavigation =
- new KeyCommandSet(Gerrit.C.sectionNavigation());
- keysNavigation.add(new PreviousPatchSetKeyCommand(0, 'p', Util.C
- .previousPatchSet()));
- keysNavigation.add(new NextPatchSetKeyCommand(0, 'n', Util.C
- .nextPatchSet()));
- regNavigation = GlobalKey.add(this, keysNavigation);
- if (activePatchSetId != null) {
- activate(activePatchSetId);
- } else {
- activate(currentPatchSetId);
- }
- } else {
- if (regNavigation != null) {
- regNavigation.removeHandler();
- regNavigation = null;
- }
- deactivate();
- }
- }
-
- @Override
- protected void onUnload() {
- setRegisterKeys(false);
- super.onUnload();
- }
-
- /**
- * Activates keyboard navigation for the patch set panel that displays the
- * patch set with the given patch set id.
- * The keyboard navigation for the previously active patch set panel is
- * automatically deactivated.
- * This method also ensures that the current row is only highlighted in the
- * table of the active patch set panel.
- */
- public void activate(final PatchSet.Id patchSetId) {
- if (indexOf(patchSetId) != -1) {
- if (!patchSetId.equals(activePatchSetId)) {
- deactivate();
- PatchSetComplexDisclosurePanel patchSetPanel =
- patchSetPanels.get(patchSetId);
- patchSetPanel.setActive(true);
- patchSetPanel.setOpen(true);
- activePatchSetId = patchSetId;
- }
- } else {
- Gerrit.display(PageLinks.toChange(patchSetId.getParentKey()));
- }
- }
-
- /** Deactivates the keyboard navigation for the currently active patch set panel. */
- private void deactivate() {
- if (activePatchSetId != null) {
- PatchSetComplexDisclosurePanel patchSetPanel =
- patchSetPanels.get(activePatchSetId);
- patchSetPanel.setActive(false);
- activePatchSetId = null;
- }
- }
-
- public PatchSet getCurrentPatchSet() {
- PatchSetComplexDisclosurePanel patchSetPanel =
- patchSetPanels.get(currentPatchSetId);
- if (patchSetPanel != null) {
- return patchSetPanel.getPatchSet();
- } else {
- return null;
- }
- }
-
- private int indexOf(PatchSet.Id id) {
- for (int i = 0; i < patchSets.size(); i++) {
- if (patchSets.get(i).getId().equals(id)) {
- return i;
- }
- }
- return -1;
- }
-
- private class ActivationHandler implements OpenHandler<DisclosurePanel>,
- ClickHandler {
-
- private final PatchSet.Id patchSetId;
-
- ActivationHandler(PatchSet.Id patchSetId) {
- this.patchSetId = patchSetId;
- }
-
- @Override
- public void onOpen(OpenEvent<DisclosurePanel> event) {
- // when a patch set panel is opened by the user
- // it should automatically become active
- PatchSetComplexDisclosurePanel patchSetPanel =
- patchSetPanels.get(patchSetId);
- patchSetPanel.refresh();
- activate(patchSetId);
- }
-
- @Override
- public void onClick(ClickEvent event) {
- // when a user clicks on a patch table the corresponding
- // patch set panel should automatically become active
- activate(patchSetId);
- }
-
- }
-
- public class PreviousPatchSetKeyCommand extends KeyCommand {
- public PreviousPatchSetKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- int index = indexOf(activePatchSetId) - 1;
- if (0 <= index) {
- activate(patchSets.get(index).getId());
- }
- }
- }
-
- public class NextPatchSetKeyCommand extends KeyCommand {
- public NextPatchSetKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- int index = indexOf(activePatchSetId) + 1;
- if (index < patchSets.size()) {
- activate(patchSets.get(index).getId());
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
deleted file mode 100644
index f718b5d4b7..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ /dev/null
@@ -1,523 +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.client.changes;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
-import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
-import com.google.gerrit.client.patches.AbstractPatchContentTable;
-import com.google.gerrit.client.patches.CommentEditorContainer;
-import com.google.gerrit.client.patches.CommentEditorPanel;
-import com.google.gerrit.client.projects.ConfigInfoCache;
-import com.google.gerrit.client.rpc.CallbackGroup;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.client.rpc.NativeString;
-import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.AccountScreen;
-import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.client.ui.PatchLink;
-import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.extensions.common.ListChangesOption;
-import com.google.gerrit.extensions.common.SubmitType;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.FormPanel;
-import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.RadioButton;
-import com.google.gwt.user.client.ui.VerticalPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
-import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtjsonrpc.common.VoidResult;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class PublishCommentScreen extends AccountScreen implements
- ClickHandler, CommentEditorContainer {
- private static SavedState lastState;
-
- private final PatchSet.Id patchSetId;
- private Collection<ValueRadioButton> approvalButtons;
- private ChangeDescriptionBlock descBlock;
- private ApprovalTable approvals;
- private Panel approvalPanel;
- private NpTextArea message;
- private FlowPanel draftsPanel;
- private Button send;
- private Button cancel;
- private boolean saveStateOnUnload = true;
- private List<CommentEditorPanel> commentEditors;
- private ChangeInfo change;
- private ChangeInfo detail;
- private NativeMap<JsArray<CommentInfo>> drafts;
- private SubmitTypeRecord submitTypeRecord;
- private CommentLinkProcessor commentLinkProcessor;
-
- public PublishCommentScreen(final PatchSet.Id psi) {
- patchSetId = psi;
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- addStyleName(Gerrit.RESOURCES.css().publishCommentsScreen());
-
- approvalButtons = new ArrayList<>();
- descBlock = new ChangeDescriptionBlock(null);
- add(descBlock);
-
- approvals = new ApprovalTable();
- add(approvals);
-
- final FormPanel form = new FormPanel();
- final FlowPanel body = new FlowPanel();
- form.setWidget(body);
- form.addSubmitHandler(new FormPanel.SubmitHandler() {
- @Override
- public void onSubmit(final SubmitEvent event) {
- event.cancel();
- }
- });
- add(form);
-
- approvalPanel = new FlowPanel();
- body.add(approvalPanel);
- initMessage(body);
-
- draftsPanel = new FlowPanel();
- body.add(draftsPanel);
-
- final FlowPanel buttonRow = new FlowPanel();
- buttonRow.setStyleName(Gerrit.RESOURCES.css().patchSetActions());
- body.add(buttonRow);
-
- send = new Button(Util.C.buttonPublishCommentsSend());
- send.addClickHandler(this);
- buttonRow.add(send);
-
- cancel = new Button(Util.C.buttonPublishCommentsCancel());
- cancel.addClickHandler(this);
- buttonRow.add(cancel);
- }
-
- private void enableForm(final boolean enabled) {
- for (final ValueRadioButton approvalButton : approvalButtons) {
- approvalButton.setEnabled(enabled);
- }
- message.setEnabled(enabled);
- for (final CommentEditorPanel commentEditor : commentEditors) {
- commentEditor.enableButtons(enabled);
- }
- send.setEnabled(enabled);
- cancel.setEnabled(enabled);
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
-
- CallbackGroup group = new CallbackGroup();
- RestApi call = ChangeApi.detail(patchSetId.getParentKey().get());
- ChangeList.addOptions(call, EnumSet.of(
- ListChangesOption.CURRENT_ACTIONS,
- ListChangesOption.ALL_REVISIONS,
- ListChangesOption.ALL_COMMITS));
- call.get(group.add(new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- detail = result;
- }
- }));
- ChangeApi.revision(patchSetId)
- .view("submit_type")
- .get(group.add(new GerritCallback<NativeString>() {
- @Override
- public void onSuccess(NativeString result) {
- submitTypeRecord = SubmitTypeRecord.OK(
- SubmitType.valueOf(result.asString()));
- }
- public void onFailure(Throwable caught) {}
- }));
- ChangeApi.revision(patchSetId.getParentKey().get(), "" + patchSetId.get())
- .view("drafts")
- .get(group.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
- @Override
- public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
- drafts = result;
- }
- public void onFailure(Throwable caught) {}
- }));
- ChangeApi.revision(patchSetId).view("review")
- .get(group.addFinal(new GerritCallback<ChangeInfo>() {
- @Override
- public void onSuccess(ChangeInfo result) {
- result.init();
- change = result;
- preDisplay(result);
- }
- }));
- }
-
- private void preDisplay(final ChangeInfo info) {
- ConfigInfoCache.get(info.project_name_key(),
- new ScreenLoadCallback<ConfigInfoCache.Entry>(this) {
- @Override
- protected void preDisplay(ConfigInfoCache.Entry result) {
- send.setEnabled(true);
- commentLinkProcessor = result.getCommentLinkProcessor();
- setTheme(result.getTheme());
- displayScreen();
- }
-
- @Override
- protected void postDisplay() {
- message.setFocus(true);
- }
- });
- }
-
- @Override
- protected void onUnload() {
- super.onUnload();
- lastState = saveStateOnUnload ? new SavedState(this) : null;
- }
-
- @Override
- public void onClick(final ClickEvent event) {
- final Widget sender = (Widget) event.getSource();
- if (send == sender) {
- onSend(false);
- } else if (cancel == sender) {
- saveStateOnUnload = false;
- goChange();
- }
- }
-
- @Override
- public void notifyDraftDelta(int delta) {
- }
-
- @Override
- public void remove(CommentEditorPanel editor) {
- commentEditors.remove(editor);
-
- // The editor should be embedded into a panel holding all
- // editors for the same file.
- //
- FlowPanel parent = (FlowPanel) editor.getParent();
- parent.remove(editor);
-
- // If the panel now holds no editors, remove it.
- //
- int editorCount = 0;
- for (Widget w : parent) {
- if (w instanceof CommentEditorPanel) {
- editorCount++;
- }
- }
- if (editorCount == 0) {
- parent.removeFromParent();
- }
-
- // If that was the last file with a draft, remove the heading.
- //
- if (draftsPanel.getWidgetCount() == 1) {
- draftsPanel.clear();
- }
- }
-
- private void initMessage(final Panel body) {
- body.add(new SmallHeading(Util.C.headingCoverMessage()));
-
- final VerticalPanel mwrap = new VerticalPanel();
- mwrap.setStyleName(Gerrit.RESOURCES.css().coverMessage());
- body.add(mwrap);
-
- message = new NpTextArea();
- message.setCharacterWidth(60);
- message.setVisibleLines(10);
- message.setSpellCheck(true);
- mwrap.add(message);
- }
-
- private void initApprovals(Panel body) {
- for (String labelName : change.labels()) {
- initLabel(labelName, body);
- }
- }
-
- private void initLabel(String labelName, Panel body) {
- if (!change.has_permitted_labels()) {
- return;
- }
- JsArrayString nativeValues = change.permitted_values(labelName);
- if (nativeValues == null || nativeValues.length() == 0) {
- return;
- }
- List<String> values = new ArrayList<>(nativeValues.length());
- for (int i = 0; i < nativeValues.length(); i++) {
- values.add(nativeValues.get(i));
- }
- Collections.reverse(values);
- LabelInfo label = change.label(labelName);
-
- body.add(new SmallHeading(label.name() + ":"));
-
- VerticalPanel vp = new VerticalPanel();
- vp.setStyleName(Gerrit.RESOURCES.css().labelList());
-
- Short prior = null;
- if (label.all() != null) {
- for (ApprovalInfo app : Natives.asList(label.all())) {
- if (app._account_id() == Gerrit.getUserAccount().getId().get()) {
- prior = app.value();
- break;
- }
- }
- }
-
- for (String value : values) {
- ValueRadioButton b = new ValueRadioButton(label, value);
- SafeHtml buf = new SafeHtmlBuilder().append(b.format());
- buf = commentLinkProcessor.apply(buf);
- SafeHtml.set(b, buf);
-
- if (lastState != null && patchSetId.equals(lastState.patchSetId)
- && lastState.approvals.containsKey(label.name())) {
- b.setValue(lastState.approvals.get(label.name()).equals(value));
- } else {
- b.setValue(b.parseValue() == (prior != null ? prior : 0));
- }
-
- approvalButtons.add(b);
- vp.add(b);
- }
- body.add(vp);
- }
-
- private void displayScreen() {
- ChangeDetail r = ChangeDetailCache.reverse(detail);
-
- setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
- patchSetId.get()));
- descBlock.display(r, null, false, r.getCurrentPatchSetDetail().getInfo(),
- r.getAccounts(), submitTypeRecord, commentLinkProcessor);
-
- if (r.getChange().getStatus().isOpen()) {
- initApprovals(approvalPanel);
- approvals.display(change);
- } else {
- approvals.setVisible(false);
- }
-
- if (lastState != null && patchSetId.equals(lastState.patchSetId)) {
- message.setText(lastState.message);
- }
-
- draftsPanel.clear();
- commentEditors = new ArrayList<>();
-
- if (!drafts.isEmpty()) {
- draftsPanel.add(new SmallHeading(Util.C.headingPatchComments()));
-
- Panel panel = null;
- String priorFile = "";
- for (final PatchLineComment c : draftList()) {
- final Patch.Key patchKey = c.getKey().getParentKey();
- final String fn = patchKey.get();
- if (!fn.equals(priorFile)) {
- panel = new FlowPanel();
- panel.addStyleName(Gerrit.RESOURCES.css().patchComments());
- draftsPanel.add(panel);
- // Parent table can be null here since we are not showing any
- // next/previous links
- panel.add(new PatchLink.SideBySide(
- PatchTable.getDisplayFileName(patchKey), null, patchKey, 0, null, null));
- priorFile = fn;
- }
-
- final CommentEditorPanel editor =
- new CommentEditorPanel(c, commentLinkProcessor);
- if (c.getLine() == AbstractPatchContentTable.R_HEAD) {
- editor.setAuthorNameText(Gerrit.getUserAccountInfo(),
- Util.C.fileCommentHeader());
- } else {
- editor.setAuthorNameText(Gerrit.getUserAccountInfo(),
- Util.M.lineHeader(c.getLine()));
- }
- editor.setOpen(true);
- commentEditors.add(editor);
- panel.add(editor);
- }
- }
- }
-
- private void onSend(final boolean submit) {
- if (commentEditors.isEmpty()) {
- onSend2(submit);
- } else {
- final GerritCallback<VoidResult> afterSaveDraft =
- new GerritCallback<VoidResult>() {
- private int done;
-
- @Override
- public void onSuccess(final VoidResult result) {
- if (++done == commentEditors.size()) {
- onSend2(submit);
- }
- }
- };
- for (final CommentEditorPanel p : commentEditors) {
- p.saveDraft(afterSaveDraft);
- }
- }
- }
-
- private void onSend2(final boolean submit) {
- ReviewInput data = ReviewInput.create();
- data.message(ChangeApi.emptyToNull(message.getText().trim()));
- for (final ValueRadioButton b : approvalButtons) {
- if (b.getValue()) {
- data.label(b.label.name(), b.parseValue());
- }
- }
-
- enableForm(false);
- new RestApi("/changes/")
- .id(String.valueOf(patchSetId.getParentKey().get()))
- .view("revisions").id(patchSetId.get()).view("review")
- .post(data, new GerritCallback<ReviewInput>() {
- @Override
- public void onSuccess(ReviewInput result) {
- if (submit) {
- submit();
- } else {
- saveStateOnUnload = false;
- goChange();
- }
- }
-
- @Override
- public void onFailure(Throwable caught) {
- super.onFailure(caught);
- enableForm(true);
- }
- });
- }
-
- private void submit() {
- ChangeApi.submit(
- patchSetId.getParentKey().get(),
- "" + patchSetId.get(),
- new GerritCallback<SubmitInfo>() {
- public void onSuccess(SubmitInfo result) {
- saveStateOnUnload = false;
- goChange();
- }
-
- @Override
- public void onFailure(Throwable err) {
- if (SubmitFailureDialog.isConflict(err)) {
- new SubmitFailureDialog(err.getMessage()).center();
- } else {
- super.onFailure(err);
- }
- goChange();
- }
- });
- }
-
- private void goChange() {
- final Change.Id ck = patchSetId.getParentKey();
- Gerrit.display(PageLinks.toChange(ck), new ChangeScreen(ck));
- }
-
- private List<PatchLineComment> draftList() {
- List<PatchLineComment> d = new ArrayList<>();
- List<String> paths = new ArrayList<>(drafts.keySet());
- Collections.sort(paths);
- for (String path : paths) {
- JsArray<CommentInfo> comments = drafts.get(path);
- for (int i = 0; i < comments.length(); i++) {
- d.add(CommentEditorPanel.toComment(patchSetId, path, comments.get(i)));
- }
- }
- return d;
- }
-
- private static class ValueRadioButton extends RadioButton {
- final LabelInfo label;
- final String value;
-
- ValueRadioButton(LabelInfo label, String value) {
- super(label.name());
- this.label = label;
- this.value = value;
- }
-
- String format() {
- return new StringBuilder().append(value).append(' ')
- .append(label.value_text(value)).toString();
- }
-
- short parseValue() {
- String value = this.value;
- if (value.startsWith(" ") || value.startsWith("+")) {
- value = value.substring(1);
- }
- return Short.parseShort(value);
- }
- }
-
- private static class SavedState {
- final PatchSet.Id patchSetId;
- final String message;
- final Map<String, String> approvals;
-
- SavedState(final PublishCommentScreen p) {
- patchSetId = p.patchSetId;
- message = p.message.getText();
- approvals = new HashMap<>();
- for (final ValueRadioButton b : p.approvalButtons) {
- if (b.getValue()) {
- approvals.put(b.label.name(), b.value);
- }
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index 9c16330c0e..d34492c77c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -14,27 +14,17 @@
package com.google.gerrit.client.changes;
-import com.google.gerrit.common.data.ChangeDetailService;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
-import com.google.gwtjsonrpc.client.JsonUtil;
public class Util {
public static final ChangeConstants C = GWT.create(ChangeConstants.class);
public static final ChangeMessages M = GWT.create(ChangeMessages.class);
- public static final ChangeResources R = GWT.create(ChangeResources.class);
-
- public static final ChangeDetailService DETAIL_SVC;
private static final int SUBJECT_MAX_LENGTH = 80;
private static final String SUBJECT_CROP_APPENDIX = "...";
private static final int SUBJECT_CROP_RANGE = 10;
- static {
- DETAIL_SVC = GWT.create(ChangeDetailService.class);
- JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
- }
-
public static String toLongString(final Change.Status status) {
if (status == null) {
return "";
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png
deleted file mode 100644
index e46f0aa63d..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
index bc13ac64ea..fed8f9186c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
@@ -16,9 +16,6 @@ package com.google.gerrit.client.diff;
import static com.google.gerrit.client.diff.DisplaySide.A;
import static com.google.gerrit.client.diff.DisplaySide.B;
-import static com.google.gerrit.client.diff.OverviewBar.MarkType.DELETE;
-import static com.google.gerrit.client.diff.OverviewBar.MarkType.EDIT;
-import static com.google.gerrit.client.diff.OverviewBar.MarkType.INSERT;
import com.google.gerrit.client.diff.DiffInfo.Region;
import com.google.gerrit.client.diff.DiffInfo.Span;
@@ -35,8 +32,8 @@ import com.google.gwt.user.client.EventListener;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.LineClassWhere;
import net.codemirror.lib.Configuration;
-import net.codemirror.lib.LineCharacter;
import net.codemirror.lib.LineWidget;
+import net.codemirror.lib.Pos;
import net.codemirror.lib.TextMarker;
import java.util.ArrayList;
@@ -44,7 +41,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-/** Colors modified regions for {@link SideBySide2}. */
+/** Colors modified regions for {@link SideBySide}. */
class ChunkManager {
private static final String DATA_LINES = "_cs2h";
private static double guessedLineHeightPx = 15;
@@ -62,12 +59,12 @@ class ChunkManager {
Element e = Element.as(event.getEventTarget());
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
EventListener l = DOM.getEventListener(e);
- if (l instanceof SideBySide2) {
- ((SideBySide2) l).getCmFromSide(side).focus();
+ if (l instanceof SideBySide) {
+ ((SideBySide) l).getCmFromSide(side).focus();
event.stopPropagation();
}
}
- };
+ }
static void focusOnClick(Element e, DisplaySide side) {
onClick(e, side == A ? focusA : focusB);
@@ -76,10 +73,10 @@ class ChunkManager {
private static final native void onClick(Element e, JavaScriptObject f)
/*-{ e.onclick = f }-*/;
- private final SideBySide2 host;
+ private final SideBySide host;
private final CodeMirror cmA;
private final CodeMirror cmB;
- private final OverviewBar sidePanel;
+ private final Scrollbar scrollbar;
private final LineMapper mapper;
private List<DiffChunkInfo> chunks;
@@ -88,14 +85,14 @@ class ChunkManager {
private List<LineWidget> padding;
private List<Element> paddingDivs;
- ChunkManager(SideBySide2 host,
+ ChunkManager(SideBySide host,
CodeMirror cmA,
CodeMirror cmB,
- OverviewBar sidePanel) {
+ Scrollbar scrollbar) {
this.host = host;
this.cmA = cmA;
this.cmB = cmB;
- this.sidePanel = sidePanel;
+ this.scrollbar = scrollbar;
this.mapper = new LineMapper();
}
@@ -150,7 +147,7 @@ class ChunkManager {
void adjustPadding() {
if (paddingDivs != null) {
- double h = host.getLineHeightPx();
+ double h = cmB.extras().lineHeightPx();
for (Element div : paddingDivs) {
int lines = div.getPropertyInt(DATA_LINES);
div.getStyle().setHeight(lines * h, Unit.PX);
@@ -188,20 +185,20 @@ class ChunkManager {
int endA = mapper.getLineA() - 1;
int endB = mapper.getLineB() - 1;
if (aLen > 0) {
- addDiffChunk(cmB, endB, endA, aLen, bLen > 0);
+ addDiffChunk(cmB, endA, aLen, bLen > 0);
}
if (bLen > 0) {
- addDiffChunk(cmA, endA, endB, bLen, aLen > 0);
+ addDiffChunk(cmA, endB, bLen, aLen > 0);
}
}
private void addGutterTag(Region region, int startA, int startB) {
if (region.a() == null) {
- sidePanel.add(cmB, startB, region.b().length(), INSERT);
+ scrollbar.insert(cmB, startB, region.b().length());
} else if (region.b() == null) {
- sidePanel.add(cmA, startA, region.a().length(), DELETE);
+ scrollbar.delete(cmA, cmB, startA, region.a().length());
} else {
- sidePanel.add(cmB, startB, region.b().length(), EDIT);
+ scrollbar.edit(cmB, startB, region.b().length());
}
}
@@ -220,20 +217,20 @@ class ChunkManager {
.set("className", DiffTable.style.diff())
.set("readOnly", true);
- LineCharacter last = CodeMirror.pos(0, 0);
+ Pos last = Pos.create(0, 0);
for (Span span : Natives.asList(edits)) {
- LineCharacter from = iter.advance(span.skip());
- LineCharacter to = iter.advance(span.mark());
- if (from.getLine() == last.getLine()) {
+ Pos from = iter.advance(span.skip());
+ Pos to = iter.advance(span.mark());
+ if (from.line() == last.line()) {
markers.add(cm.markText(last, from, bg));
} else {
- markers.add(cm.markText(CodeMirror.pos(from.getLine(), 0), from, bg));
+ markers.add(cm.markText(Pos.create(from.line(), 0), from, bg));
}
markers.add(cm.markText(from, to, diff));
last = to;
colorLines(cm, LineClassWhere.BACKGROUND,
DiffTable.style.diff(),
- from.getLine(), to.getLine());
+ from.line(), to.line());
}
}
@@ -284,8 +281,8 @@ class ChunkManager {
}
}
- private void addDiffChunk(CodeMirror cmToPad, int lineToPad,
- int lineOnOther, int chunkSize, boolean edit) {
+ private void addDiffChunk(CodeMirror cmToPad, int lineOnOther,
+ int chunkSize, boolean edit) {
chunks.add(new DiffChunkInfo(host.otherCm(cmToPad).side(),
lineOnOther - chunkSize + 1, lineOnOther, edit));
}
@@ -294,7 +291,9 @@ class ChunkManager {
return new Runnable() {
@Override
public void run() {
- int line = cm.hasActiveLine() ? cm.getLineNumber(cm.getActiveLine()) : 0;
+ int line = cm.extras().hasActiveLine()
+ ? cm.getLineNumber(cm.extras().activeLine())
+ : 0;
int res = Collections.binarySearch(
chunks,
new DiffChunkInfo(cm.side(), line, 0, false),
@@ -318,11 +317,11 @@ class ChunkManager {
DiffChunkInfo target = chunks.get(res);
CodeMirror targetCm = host.getCmFromSide(target.getSide());
- targetCm.setCursor(LineCharacter.create(target.getStart()));
+ targetCm.setCursor(Pos.create(target.getStart(), 0));
targetCm.focus();
targetCm.scrollToY(
targetCm.heightAtLine(target.getStart(), "local") -
- 0.5 * cmB.getScrollbarV().getClientHeight());
+ 0.5 * cmB.scrollbarV().getClientHeight());
}
};
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css
index da0754b528..441af22cc1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css
@@ -123,15 +123,15 @@
color: #fff;
}
-@sprite .go_prev {
- gwt-image: "go_prev";
+@sprite .goPrev {
+ gwt-image: "goPrev";
display: inline-block;
}
-@sprite .go_next {
- gwt-image: "go_next";
+@sprite .goNext {
+ gwt-image: "goNext";
display: inline-block;
}
-@sprite .go_up {
- gwt-image: "go_up";
+@sprite .goUp {
+ gwt-image: "goUp";
display: inline-block;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
index 1d7452085c..ca56bf4a9c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
@@ -19,6 +19,7 @@ import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.ui.Composite;
import net.codemirror.lib.CodeMirror;
@@ -32,8 +33,22 @@ abstract class CommentBox extends Composite {
Resources.I.style().ensureInjected();
}
+ interface Style extends CssResource {
+ String commentWidgets();
+ String commentBox();
+ String contents();
+ String message();
+ String header();
+ String summary();
+ String date();
+
+ String goPrev();
+ String goNext();
+ String goUp();
+ }
+
private final CommentGroup group;
- private OverviewBar.MarkHandle mark;
+ private ScrollbarAnnotation annotation;
private FromTo fromTo;
private TextMarker rangeMarker;
private TextMarker rangeHighlightMarker;
@@ -43,8 +58,8 @@ abstract class CommentBox extends Composite {
if (range != null) {
fromTo = FromTo.create(range);
rangeMarker = group.getCm().markText(
- fromTo.getFrom(),
- fromTo.getTo(),
+ fromTo.from(),
+ fromTo.to(),
Configuration.create()
.set("className", DiffTable.style.range()));
}
@@ -79,20 +94,20 @@ abstract class CommentBox extends Composite {
return group.getCommentManager();
}
- OverviewBar.MarkHandle getMark() {
- return mark;
+ ScrollbarAnnotation getAnnotation() {
+ return annotation;
}
- void setMark(OverviewBar.MarkHandle mh) {
- mark = mh;
+ void setAnnotation(ScrollbarAnnotation mh) {
+ annotation = mh;
}
void setRangeHighlight(boolean highlight) {
if (fromTo != null) {
if (highlight && rangeHighlightMarker == null) {
rangeHighlightMarker = group.getCm().markText(
- fromTo.getFrom(),
- fromTo.getTo(),
+ fromTo.from(),
+ fromTo.to(),
Configuration.create()
.set("className", DiffTable.style.rangeHighlight()));
} else if (!highlight && rangeHighlightMarker != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
index 7bcf4da471..a8a56fcc24 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
@@ -218,8 +218,8 @@ class CommentGroup extends Composite {
private void updateSelection() {
if (cm.somethingSelected()) {
FromTo r = cm.getSelectedRange();
- if (r.getTo().getLine() >= line) {
- cm.setSelection(r.getFrom(), r.getTo());
+ if (r.to().line() >= line) {
+ cm.setSelection(r.from(), r.to());
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
index 5d5662bb34..514a3bef18 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
@@ -20,13 +20,13 @@ import com.google.gerrit.client.patches.SkippedLine;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArray;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.LineHandle;
-import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.Pos;
import net.codemirror.lib.TextMarker.FromTo;
import java.util.ArrayList;
@@ -38,9 +38,9 @@ import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
-/** Tracks comment widgets for {@link SideBySide2}. */
+/** Tracks comment widgets for {@link SideBySide}. */
class CommentManager {
- private final SideBySide2 host;
+ private final SideBySide host;
private final PatchSet.Id base;
private final PatchSet.Id revision;
private final String path;
@@ -52,16 +52,19 @@ class CommentManager {
private final Set<DraftBox> unsavedDrafts;
private boolean attached;
private boolean expandAll;
+ private boolean open;
- CommentManager(SideBySide2 host,
+ CommentManager(SideBySide host,
PatchSet.Id base, PatchSet.Id revision,
String path,
- CommentLinkProcessor clp) {
+ CommentLinkProcessor clp,
+ boolean open) {
this.host = host;
this.base = base;
this.revision = revision;
this.path = path;
this.commentLinkProcessor = clp;
+ this.open = open;
published = new HashMap<>();
sideA = new TreeMap<>();
@@ -69,7 +72,7 @@ class CommentManager {
unsavedDrafts = new HashSet<>();
}
- SideBySide2 getSideBySide2() {
+ SideBySide getSideBySide() {
return host;
}
@@ -91,8 +94,8 @@ class CommentManager {
// It is only necessary to search one side to find a comment
// on either side of the editor pair.
SortedMap<Integer, CommentGroup> map = map(src.side());
- int line = src.hasActiveLine()
- ? src.getLineNumber(src.getActiveLine()) + 1
+ int line = src.extras().hasActiveLine()
+ ? src.getLineNumber(src.extras().activeLine()) + 1
: 0;
if (dir == Direction.NEXT) {
map = map.tailMap(line + 1);
@@ -115,8 +118,8 @@ class CommentManager {
CodeMirror cm = g.getCm();
double y = cm.heightAtLine(g.getLine() - 1, "local");
- cm.setCursor(LineCharacter.create(g.getLine() - 1));
- cm.scrollToY(y - 0.5 * cm.getScrollbarV().getClientHeight());
+ cm.setCursor(Pos.create(g.getLine() - 1));
+ cm.scrollToY(y - 0.5 * cm.scrollbarV().getClientHeight());
cm.focus();
}
};
@@ -157,12 +160,12 @@ class CommentManager {
group,
commentLinkProcessor,
getPatchSetIdFromSide(side),
- info);
+ info,
+ open);
group.add(box);
- box.setMark(host.diffTable.overview.add(
+ box.setAnnotation(host.diffTable.scrollbar.comment(
host.getCmFromSide(side),
- Math.max(0, info.line() - 1), 1,
- OverviewBar.MarkType.COMMENT));
+ Math.max(0, info.line() - 1)));
published.put(info.id(), box);
}
}
@@ -223,10 +226,9 @@ class CommentManager {
}
group.add(box);
- box.setMark(host.diffTable.overview.add(
+ box.setAnnotation(host.diffTable.scrollbar.draft(
host.getCmFromSide(side),
- Math.max(0, info.line() - 1), 1,
- OverviewBar.MarkType.DRAFT));
+ Math.max(0, info.line() - 1)));
return box;
}
@@ -295,10 +297,11 @@ class CommentManager {
Runnable toggleOpenBox(final CodeMirror cm) {
return new Runnable() {
+ @Override
public void run() {
- if (cm.hasActiveLine()) {
+ if (cm.extras().hasActiveLine()) {
CommentGroup w = map(cm.side()).get(
- cm.getLineNumber(cm.getActiveLine()) + 1);
+ cm.getLineNumber(cm.extras().activeLine()) + 1);
if (w != null) {
w.openCloseLast();
}
@@ -311,9 +314,9 @@ class CommentManager {
return new Runnable() {
@Override
public void run() {
- if (cm.hasActiveLine()) {
+ if (cm.extras().hasActiveLine()) {
CommentGroup w = map(cm.side()).get(
- cm.getLineNumber(cm.getActiveLine()) + 1);
+ cm.getLineNumber(cm.extras().activeLine()) + 1);
if (w != null) {
w.openCloseAll();
}
@@ -328,8 +331,8 @@ class CommentManager {
@Override
public void run() {
String token = host.getToken();
- if (cm.hasActiveLine()) {
- LineHandle handle = cm.getActiveLine();
+ if (cm.extras().hasActiveLine()) {
+ LineHandle handle = cm.extras().activeLine();
int line = cm.getLineNumber(handle) + 1;
token += "@" + (cm.side() == DisplaySide.A ? "a" : "") + line;
}
@@ -339,22 +342,22 @@ class CommentManager {
}
return new Runnable() {
+ @Override
public void run() {
- if (cm.hasActiveLine()) {
- newDraft(cm);
+ if (cm.extras().hasActiveLine()) {
+ newDraft(cm, cm.getLineNumber(cm.extras().activeLine()) + 1);
}
}
};
}
- private void newDraft(CodeMirror cm) {
- int line = cm.getLineNumber(cm.getActiveLine()) + 1;
+ void newDraft(CodeMirror cm, int line) {
if (cm.somethingSelected()) {
FromTo fromTo = cm.getSelectedRange();
- LineCharacter end = fromTo.getTo();
- if (end.getCh() == 0) {
- end.setLine(end.getLine() - 1);
- end.setCh(cm.getLine(end.getLine()).length());
+ Pos end = fromTo.to();
+ if (end.ch() == 0) {
+ end.line(end.line() - 1);
+ end.ch(cm.getLine(end.line()).length());
}
addDraftBox(cm.side(), CommentInfo.create(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
index 9887dbfdb5..a38a6cab93 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
@@ -16,7 +16,7 @@ package com.google.gerrit.client.diff;
import com.google.gwt.core.client.JavaScriptObject;
-import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.Pos;
import net.codemirror.lib.TextMarker.FromTo;
public class CommentRange extends JavaScriptObject {
@@ -31,11 +31,11 @@ public class CommentRange extends JavaScriptObject {
return null;
}
- LineCharacter from = fromTo.getFrom();
- LineCharacter to = fromTo.getTo();
+ Pos from = fromTo.from();
+ Pos to = fromTo.to();
return create(
- from.getLine() + 1, from.getCh(),
- to.getLine() + 1, to.getCh());
+ from.line() + 1, from.ch(),
+ to.line() + 1, to.ch());
}
public final native int start_line() /*-{ return this.start_line; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
index 1fe85b372b..b23a8cf8d2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
@@ -18,11 +18,11 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentApi;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.rpc.CallbackGroup;
-import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import java.util.Collections;
import java.util.Comparator;
@@ -53,39 +53,55 @@ class CommentsCollections {
}
}
- private GerritCallback<NativeMap<JsArray<CommentInfo>>> publishedBase() {
- return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+ private AsyncCallback<NativeMap<JsArray<CommentInfo>>> publishedBase() {
+ return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
publishedBase = sort(result.get(path));
}
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
};
}
- private GerritCallback<NativeMap<JsArray<CommentInfo>>> publishedRevision() {
- return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+ private AsyncCallback<NativeMap<JsArray<CommentInfo>>> publishedRevision() {
+ return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
publishedRevision = sort(result.get(path));
}
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
};
}
- private GerritCallback<NativeMap<JsArray<CommentInfo>>> draftsBase() {
- return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+ private AsyncCallback<NativeMap<JsArray<CommentInfo>>> draftsBase() {
+ return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
draftsBase = sort(result.get(path));
}
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
};
}
- private GerritCallback<NativeMap<JsArray<CommentInfo>>> draftsRevision() {
- return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+ private AsyncCallback<NativeMap<JsArray<CommentInfo>>> draftsRevision() {
+ return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
draftsRevision = sort(result.get(path));
}
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
};
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
index 5e88ac9676..18d673ab39 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
@@ -63,6 +63,11 @@ public class DiffApi {
return this;
}
+ public DiffApi webLinksOnly() {
+ call.addParameterTrue("weblinks-only");
+ return this;
+ }
+
public DiffApi ignoreWhitespace(AccountDiffPreference.Whitespace w) {
switch (w) {
default:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
index c0d0532ef3..7140e07d14 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
@@ -14,11 +14,18 @@
package com.google.gerrit.client.diff;
+import com.google.gerrit.client.DiffWebLinkInfo;
+import com.google.gerrit.client.WebLinkInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
+import java.util.LinkedList;
+import java.util.List;
+
public class DiffInfo extends JavaScriptObject {
public static final String GITLINK = "x-git/gitlink";
public static final String SYMLINK = "x-git/symlink";
@@ -27,6 +34,34 @@ public class DiffInfo extends JavaScriptObject {
public final native FileMeta meta_b() /*-{ return this.meta_b; }-*/;
public final native JsArrayString diff_header() /*-{ return this.diff_header; }-*/;
public final native JsArray<Region> content() /*-{ return this.content; }-*/;
+ public final native JsArray<DiffWebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
+ public final native boolean binary() /*-{ return this.binary || false; }-*/;
+
+ public final List<WebLinkInfo> side_by_side_web_links() {
+ return filterWebLinks(DiffView.SIDE_BY_SIDE);
+ }
+
+ public final List<WebLinkInfo> unified_web_links() {
+ return filterWebLinks(DiffView.UNIFIED_DIFF);
+ }
+
+ private final List<WebLinkInfo> filterWebLinks(DiffView diffView) {
+ List<WebLinkInfo> filteredDiffWebLinks = new LinkedList<>();
+ List<DiffWebLinkInfo> allDiffWebLinks = Natives.asList(web_links());
+ if (allDiffWebLinks != null) {
+ for (DiffWebLinkInfo webLink : allDiffWebLinks) {
+ if (diffView == DiffView.SIDE_BY_SIDE
+ && webLink.showOnSideBySideDiffView()) {
+ filteredDiffWebLinks.add(webLink);
+ }
+ if (diffView == DiffView.UNIFIED_DIFF
+ && webLink.showOnUnifiedDiffView()) {
+ filteredDiffWebLinks.add(webLink);
+ }
+ }
+ }
+ return filteredDiffWebLinks;
+ }
public final ChangeType change_type() {
return ChangeType.valueOf(change_typeRaw());
@@ -100,6 +135,7 @@ public class DiffInfo extends JavaScriptObject {
public final native String name() /*-{ return this.name; }-*/;
public final native String content_type() /*-{ return this.content_type; }-*/;
public final native int lines() /*-{ return this.lines || 0 }-*/;
+ public final native JsArray<WebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
protected FileMeta() {
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index d56654fef4..1105476a59 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -48,20 +48,17 @@ class DiffTable extends Composite {
String dark();
String diff();
String noIntraline();
- String activeLine();
String range();
String rangeHighlight();
- String showTabs();
String showLineNumbers();
String hideA();
String hideB();
- String columnMargin();
String padding();
}
@UiField Element cmA;
@UiField Element cmB;
- @UiField OverviewBar overview;
+ Scrollbar scrollbar;
@UiField Element patchSetNavRow;
@UiField Element patchSetNavCellA;
@UiField Element patchSetNavCellB;
@@ -71,28 +68,27 @@ class DiffTable extends Composite {
@UiField static DiffTableStyle style;
@UiField(provided = true)
- PatchSetSelectBox2 patchSetSelectBoxA;
+ PatchSetSelectBox patchSetSelectBoxA;
@UiField(provided = true)
- PatchSetSelectBox2 patchSetSelectBoxB;
+ PatchSetSelectBox patchSetSelectBoxB;
- private SideBySide2 parent;
+ private SideBySide parent;
private boolean header;
- private boolean headerVisible;
private boolean visibleA;
private ChangeType changeType;
- DiffTable(SideBySide2 parent, PatchSet.Id base, PatchSet.Id revision,
+ DiffTable(SideBySide parent, PatchSet.Id base, PatchSet.Id revision,
String path) {
- patchSetSelectBoxA = new PatchSetSelectBox2(
+ patchSetSelectBoxA = new PatchSetSelectBox(
parent, DisplaySide.A, revision.getParentKey(), base, path);
- patchSetSelectBoxB = new PatchSetSelectBox2(
+ patchSetSelectBoxB = new PatchSetSelectBox(
parent, DisplaySide.B, revision.getParentKey(), revision, path);
- PatchSetSelectBox2.link(patchSetSelectBoxA, patchSetSelectBoxB);
+ PatchSetSelectBox.link(patchSetSelectBoxA, patchSetSelectBoxB);
initWidget(uiBinder.createAndBindUi(this));
+ this.scrollbar = new Scrollbar(this);
this.parent = parent;
- this.headerVisible = true;
this.visibleA = true;
}
@@ -128,20 +124,17 @@ class DiffTable extends Composite {
}
}
- boolean isHeaderVisible() {
- return headerVisible;
- }
-
void setHeaderVisible(boolean show) {
- headerVisible = show;
- UIObject.setVisible(patchSetNavRow, show);
- UIObject.setVisible(diffHeaderRow, show && header);
- if (show) {
- parent.header.removeStyleName(style.fullscreen());
- } else {
- parent.header.addStyleName(style.fullscreen());
+ if (show != UIObject.isVisible(patchSetNavRow)) {
+ UIObject.setVisible(patchSetNavRow, show);
+ UIObject.setVisible(diffHeaderRow, show && header);
+ if (show) {
+ parent.header.removeStyleName(style.fullscreen());
+ } else {
+ parent.header.addStyleName(style.fullscreen());
+ }
+ parent.resizeCodeMirror();
}
- parent.resizeCodeMirror();
}
int getHeaderHeight() {
@@ -156,10 +149,13 @@ class DiffTable extends Composite {
return changeType;
}
- void set(DiffPreferences prefs, JsArray<RevisionInfo> list, DiffInfo info) {
+ void set(DiffPreferences prefs, JsArray<RevisionInfo> list, DiffInfo info,
+ boolean editExists, int currentPatchSet, boolean open, boolean binary) {
this.changeType = info.change_type();
- patchSetSelectBoxA.setUpPatchSetNav(list, info.meta_a());
- patchSetSelectBoxB.setUpPatchSetNav(list, info.meta_b());
+ patchSetSelectBoxA.setUpPatchSetNav(list, info.meta_a(), editExists,
+ currentPatchSet, open, binary);
+ patchSetSelectBoxB.setUpPatchSetNav(list, info.meta_b(), editExists,
+ currentPatchSet, open, binary);
JsArrayString hdr = info.diff_header();
if (hdr != null) {
@@ -195,7 +191,6 @@ class DiffTable extends Composite {
}
void refresh() {
- overview.refresh();
if (header) {
CodeMirror cm = parent.getCmFromSide(DisplaySide.A);
diffHeaderText.getStyle().setMarginLeft(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index e08a25d4e9..504d9c077f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -18,11 +18,11 @@ limitations under the License.
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:d='urn:import:com.google.gerrit.client.diff'>
<ui:style type='com.google.gerrit.client.diff.DiffTable.DiffTableStyle'>
- @external .CodeMirror, .CodeMirror-lines, .CodeMirror-selectedtext;
- @external .CodeMirror-linenumber, .CodeMirror-vscrollbar .CodeMirror-scroll;
+ @external .CodeMirror, .CodeMirror-selectedtext;
+ @external .CodeMirror-linenumber;
+ @external .CodeMirror-overlayscroll-vertical, .CodeMirror-scroll;
@external .CodeMirror-dialog-bottom;
- @external .cm-keymap-fat-cursor, CodeMirror-cursor;
- @external .cm-searching, .cm-trailingspace, .cm-tab;
+ @external .CodeMirror-cursor;
.fullscreen {
background-color: #f7f7f7;
@@ -38,13 +38,10 @@ limitations under the License.
-ms-user-select: none;
}
- .difftable .CodeMirror-lines { padding: 0; }
.difftable .CodeMirror pre {
- padding: 0;
overflow: hidden;
border-right: 0;
width: auto;
- line-height: normal;
}
/* Preserve space for underscores. If this changes
@@ -75,14 +72,9 @@ limitations under the License.
.hideA .psNavB, .hideA .b { width: 100% }
.hideB .psNavA, .hideB .a { width: 100% }
- .overview {
- width: 10px;
- vertical-align: top;
- }
-
- /* Hide scrollbars, OverviewBar controls both views. */
- .difftable .CodeMirror-scroll { padding-right: 0; }
- .difftable .CodeMirror-vscrollbar { display: none !important; }
+ /* Hide scrollbars on A, B controls both views. */
+ .a .CodeMirror-scroll { margin-right: -36px; }
+ .a .CodeMirror-overlayscroll-vertical { display: none !important; }
.showLineNumbers .b { border-left: none; }
.b { border-left: 1px solid #ddd; }
@@ -110,23 +102,12 @@ limitations under the License.
overflow-x: auto;
}
- .activeLine .CodeMirror-linenumber {
- background-color: #bcf !important;
- color: #000;
- }
-
.range {
background-color: #ffd500 !important;
}
.rangeHighlight {
background-color: #ffff00 !important;
}
- .cm-searching {
- background-color: #ffa !important;
- }
- .cm-trailingspace {
- background-color: red !important;
- }
.difftable .CodeMirror-selectedtext {
background-color: inherit !important;
}
@@ -134,10 +115,8 @@ limitations under the License.
height: 1.11em;
cursor: pointer;
}
- .difftable .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
- background: transparent;
- text-decoration: underline;
- z-index: 2;
+ .difftable .CodeMirror div.CodeMirror-cursor {
+ border-left: 2px solid black;
}
.difftable .CodeMirror-dialog-bottom {
border-top: 0;
@@ -149,24 +128,10 @@ limitations under the License.
bottom: auto;
left: auto;
}
- .showTabs .cm-tab:before {
- position: absolute;
- content: "\00bb";
- color: #f00;
- }
.showLineNumbers .padding {
margin-left: 21px;
border-left: 2px solid #d64040;
}
- .columnMargin {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 0;
- border-right: 1px dashed #ffa500;
- z-index: 2;
- cursor: text;
- }
.diff_header {
font-size: 12px;
@@ -181,21 +146,18 @@ limitations under the License.
<table class='{style.table}'>
<tr ui:field='patchSetNavRow' class='{style.patchSetNav}'>
<td ui:field='patchSetNavCellA' class='{style.psNavA}'>
- <d:PatchSetSelectBox2 ui:field='patchSetSelectBoxA' />
+ <d:PatchSetSelectBox ui:field='patchSetSelectBoxA' />
</td>
<td ui:field='patchSetNavCellB' class='{style.psNavB}'>
- <d:PatchSetSelectBox2 ui:field='patchSetSelectBoxB' />
+ <d:PatchSetSelectBox ui:field='patchSetSelectBoxB' />
</td>
- <td class='{style.overview}' />
</tr>
<tr ui:field='diffHeaderRow' class='{style.diff_header}'>
<td colspan='2'><pre ui:field='diffHeaderText' /></td>
- <td class='{style.overview}' />
</tr>
<tr>
<td ui:field='cmA' class='{style.a}' />
<td ui:field='cmB' class='{style.b}' />
- <td class='{style.overview}'><d:OverviewBar ui:field='overview'/></td>
</tr>
</table>
<g:FlowPanel ui:field='widgets' visible='false'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
index 6b0d69a7b1..5bf56c2a8f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
@@ -49,6 +49,8 @@ import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+import net.codemirror.lib.CodeMirror;
+
/** An HtmlPanel for displaying and editing a draft */
class DraftBox extends CommentBox {
interface Binder extends UiBinder<HTMLPanel, DraftBox> {}
@@ -236,14 +238,14 @@ class DraftBox extends CommentBox {
getCommentManager().setUnsaved(this, false);
setRangeHighlight(false);
clearRange();
- getMark().remove();
+ getAnnotation().remove();
getCommentGroup().remove(this);
getCm().focus();
}
private void restoreSelection() {
if (getFromTo() != null && comment.in_reply_to() == null) {
- getCm().setSelection(getFromTo().getFrom(), getFromTo().getTo());
+ getCm().setSelection(getFromTo().from(), getFromTo().to());
}
}
@@ -253,7 +255,7 @@ class DraftBox extends CommentBox {
}
@UiHandler("message")
- void onMessageDoubleClick(DoubleClickEvent e) {
+ void onMessageDoubleClick(@SuppressWarnings("unused") DoubleClickEvent e) {
setEdit(true);
}
@@ -312,7 +314,9 @@ class DraftBox extends CommentBox {
} else {
CommentApi.updateDraft(psId, input.id(), input, group.add(cb));
}
- getCm().focus();
+ CodeMirror cm = getCm();
+ cm.vim().handleKey("<Esc>");
+ cm.focus();
}
private void enableEdit(boolean on) {
@@ -389,7 +393,7 @@ class DraftBox extends CommentBox {
}
@UiHandler("editArea")
- void onBlur(BlurEvent e) {
+ void onBlur(@SuppressWarnings("unused") BlurEvent e) {
resizeTimer.cancel();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/EditIterator.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/EditIterator.java
index 4c8d80e00c..ce3a5625cb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/EditIterator.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/EditIterator.java
@@ -16,7 +16,7 @@ package com.google.gerrit.client.diff;
import com.google.gwt.core.client.JsArrayString;
-import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.Pos;
/** An iterator for intraline edits */
class EditIterator {
@@ -30,13 +30,13 @@ class EditIterator {
startLine = start;
}
- LineCharacter advance(int numOfChar) {
+ Pos advance(int numOfChar) {
numOfChar = adjustForNegativeDelta(numOfChar);
while (line < lines.length()) {
int len = lines.get(line).length() - pos + 1; // + 1 for LF
if (numOfChar < len) {
- LineCharacter at = LineCharacter.create(
+ Pos at = Pos.create(
startLine + line,
numOfChar + pos);
pos += numOfChar;
@@ -48,7 +48,7 @@ class EditIterator {
pos = 0;
if (numOfChar == 0) {
- return LineCharacter.create(startLine + line, 0);
+ return Pos.create(startLine + line, 0);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
index dc52aa3fbb..c4459b67f7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
@@ -43,6 +43,20 @@ public class FileInfo extends JavaScriptObject {
} else if (Patch.COMMIT_MSG.equals(b.path())) {
return 1;
}
+ // Look at file suffixes to check if it makes sense to use a different order
+ int s1 = a.path().lastIndexOf('.');
+ int s2 = b.path().lastIndexOf('.');
+ if (s1 > 0 && s2 > 0 &&
+ a.path().substring(0, s1).equals(b.path().substring(0, s2))) {
+ String suffixA = a.path().substring(s1);
+ String suffixB = b.path().substring(s2);
+ // C++ and C: give priority to header files (.h/.hpp/...)
+ if (suffixA.indexOf(".h") == 0) {
+ return -1;
+ } else if (suffixB.indexOf(".h") == 0) {
+ return 1;
+ }
+ }
return a.path().compareTo(b.path());
}
});
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
index 2b3e2be34c..2c551c0dc2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
@@ -17,6 +17,7 @@ package com.google.gerrit.client.diff;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
@@ -47,6 +48,7 @@ import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.UIObject;
@@ -55,7 +57,9 @@ import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-class Header extends Composite {
+import java.util.List;
+
+public class Header extends Composite {
interface Binder extends UiBinder<HTMLPanel, Header> {}
private static final Binder uiBinder = GWT.create(Binder.class);
static {
@@ -71,6 +75,7 @@ class Header extends Composite {
@UiField Element filePath;
@UiField Element noDiff;
+ @UiField FlowPanel linkPanel;
@UiField InlineHyperlink prev;
@UiField InlineHyperlink up;
@@ -101,11 +106,10 @@ class Header extends Composite {
SafeHtml.setInnerHTML(filePath, formatPath(path, null, null));
up.setTargetHistoryToken(PageLinks.toChange(
patchSetId.getParentKey(),
- base != null ? String.valueOf(base.get()) : null,
- String.valueOf(patchSetId.get())));
+ base != null ? base.getId() : null, patchSetId.getId()));
}
- private static SafeHtml formatPath(String path, String project, String commit) {
+ public static SafeHtml formatPath(String path, String project, String commit) {
SafeHtmlBuilder b = new SafeHtmlBuilder();
if (Patch.COMMIT_MSG.equals(path)) {
return b.append(Util.C.commitMessage());
@@ -191,7 +195,7 @@ class Header extends Composite {
GitwebLink gw = Gerrit.getGitwebLink();
if (gw != null) {
for (RevisionInfo rev : Natives.asList(info.revisions().values())) {
- if (rev._number() == patchSetId.get()) {
+ if (patchSetId.getId().equals(rev.id())) {
String c = rev.name();
SafeHtml.setInnerHTML(filePath, formatPath(path, info.project(), c));
SafeHtml.setInnerHTML(project, new SafeHtmlBuilder()
@@ -208,9 +212,17 @@ class Header extends Composite {
project.setInnerText(info.project());
}
- void init(PreferencesAction pa) {
+ void init(PreferencesAction pa, List<InlineHyperlink> links,
+ List<WebLinkInfo> webLinks) {
prefsAction = pa;
prefsAction.setPartner(preferences);
+
+ for (InlineHyperlink link : links) {
+ linkPanel.add(link);
+ }
+ for (WebLinkInfo webLink : webLinks) {
+ linkPanel.add(webLink.toAnchor());
+ }
}
@UiHandler("reviewed")
@@ -243,7 +255,7 @@ class Header extends Composite {
}
@UiHandler("preferences")
- void onPreferences(ClickEvent e) {
+ void onPreferences(@SuppressWarnings("unused") ClickEvent e) {
prefsAction.show();
}
@@ -275,13 +287,14 @@ class Header extends Composite {
return k;
} else {
link.getElement().getStyle().setVisibility(Visibility.HIDDEN);
- keys.add(new UpToChangeCommand2(patchSetId, 0, key));
+ keys.add(new UpToChangeCommand(patchSetId, 0, key));
return null;
}
}
Runnable toggleReviewed() {
return new Runnable() {
+ @Override
public void run() {
reviewed.setValue(!reviewed.getValue(), true);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
index 7d1f1e5eb5..c58eef7e6c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
@@ -18,6 +18,7 @@ limitations under the License.
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:x='urn:import:com.google.gerrit.client.ui'>
+ <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
<ui:with field='res' type='com.google.gerrit.client.diff.Resources'/>
<ui:style>
.header {
@@ -46,6 +47,13 @@ limitations under the License.
vertical-align: top;
font-weight: bold;
margin-right: 1em;
+ float: left;
+ }
+ .linkPanel {
+ float: left;
+ }
+ .linkPanel img {
+ padding-right: 3px;
}
.preferences {
cursor: pointer;
@@ -60,16 +68,17 @@ limitations under the License.
<span class='{style.path}'><span ui:field='project'/> / <span ui:field='filePath'/></span>
<div class='{style.navigation}'>
<span ui:field='noDiff' class='{style.nodiff}'><ui:msg>No Differences</ui:msg></span>
- <x:InlineHyperlink ui:field='prev' styleName='{res.style.go_prev}'/>
+ <g:FlowPanel ui:field='linkPanel' styleName='{style.linkPanel}'/>
+ <x:InlineHyperlink ui:field='prev' styleName='{res.style.goPrev}'/>
<x:InlineHyperlink ui:field='up'
- styleName='{res.style.go_up}'
+ styleName='{res.style.goUp}'
title='Up to change (Shortcut: u)'>
<ui:attribute name='title'/>
</x:InlineHyperlink>
- <x:InlineHyperlink ui:field='next' styleName='{res.style.go_next}'/>
+ <x:InlineHyperlink ui:field='next' styleName='{res.style.goNext}'/>
<g:Image ui:field='preferences'
styleName='{style.preferences}'
- resource='{res.gear}'
+ resource='{ico.gear}'
title='Diff preferences (Shortcut: ,)'>
<ui:attribute name='title'/>
</g:Image>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
index 6c7423cc4e..9ec760c028 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
@@ -17,6 +17,7 @@ package com.google.gerrit.client.diff;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/** Helper class to handle calculations involving line gaps. */
class LineMapper {
@@ -193,6 +194,11 @@ class LineMapper {
}
@Override
+ public int hashCode() {
+ return Objects.hash(line, aligned);
+ }
+
+ @Override
public String toString() {
return line + " " + aligned;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java
deleted file mode 100644
index b9e6375080..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java
+++ /dev/null
@@ -1,266 +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.client.diff;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseMoveEvent;
-import com.google.gwt.event.dom.client.MouseUpEvent;
-import com.google.gwt.resources.client.CssResource;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.Widget;
-
-import net.codemirror.lib.CodeMirror;
-import net.codemirror.lib.LineCharacter;
-import net.codemirror.lib.ScrollInfo;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/** Displays overview of all edits and comments in this file. */
-class OverviewBar extends Composite implements ClickHandler {
- interface Binder extends UiBinder<HTMLPanel, OverviewBar> {}
- private static final Binder uiBinder = GWT.create(Binder.class);
-
- interface Style extends CssResource {
- String gutter();
- String halfGutter();
- String comment();
- String draft();
- String insert();
- String delete();
- String viewportDrag();
- }
-
- enum MarkType {
- COMMENT, DRAFT, INSERT, DELETE, EDIT
- }
-
- @UiField Style style;
- @UiField Label viewport;
-
- private final List<MarkHandle> diff;
- private final Set<MarkHandle> comments;
- private CodeMirror cmB;
-
- private boolean dragging;
- private int startY;
- private double ratio;
-
- OverviewBar() {
- initWidget(uiBinder.createAndBindUi(this));
- diff = new ArrayList<>();
- comments = new HashSet<>();
- addDomHandler(this, ClickEvent.getType());
- }
-
- void init(CodeMirror cmB) {
- this.cmB = cmB;
- }
-
- void refresh() {
- update(cmB.getScrollInfo());
- }
-
- void update(ScrollInfo si) {
- double viewHeight = si.getClientHeight();
- double r = ratio(si);
-
- com.google.gwt.dom.client.Style style = viewport.getElement().getStyle();
- style.setTop(si.getTop() * r, Unit.PX);
- style.setHeight(Math.max(10, viewHeight * r), Unit.PX);
- getElement().getStyle().setHeight(viewHeight, Unit.PX);
- for (MarkHandle info : diff) {
- info.position(r);
- }
- for (MarkHandle info : comments) {
- info.position(r);
- }
- }
-
- @Override
- protected void onUnload() {
- super.onUnload();
- if (dragging) {
- DOM.releaseCapture(viewport.getElement());
- }
- }
-
- @Override
- public void onClick(ClickEvent e) {
- if (e.getY() < viewport.getElement().getOffsetTop()) {
- CodeMirror.handleVimKey(cmB, "<PageUp>");
- } else {
- CodeMirror.handleVimKey(cmB, "<PageDown>");
- }
- cmB.focus();
- }
-
- @UiHandler("viewport")
- void onMouseDown(MouseDownEvent e) {
- if (cmB != null) {
- dragging = true;
- ratio = ratio(cmB.getScrollInfo());
- startY = e.getY();
- viewport.addStyleName(style.viewportDrag());
- DOM.setCapture(viewport.getElement());
- e.preventDefault();
- e.stopPropagation();
- }
- }
-
- @UiHandler("viewport")
- void onMouseMove(MouseMoveEvent e) {
- if (dragging) {
- int y = e.getRelativeY(getElement()) - startY;
- cmB.scrollToY(Math.max(0, y / ratio));
- e.preventDefault();
- e.stopPropagation();
- }
- }
-
- @UiHandler("viewport")
- void onMouseUp(MouseUpEvent e) {
- if (dragging) {
- dragging = false;
- DOM.releaseCapture(viewport.getElement());
- viewport.removeStyleName(style.viewportDrag());
- e.preventDefault();
- e.stopPropagation();
- }
- }
-
- private double ratio(ScrollInfo si) {
- double barHeight = si.getClientHeight();
- double contentHeight = si.getHeight();
- return barHeight / contentHeight;
- }
-
- MarkHandle add(CodeMirror cm, int line, int height, MarkType type) {
- MarkHandle mark = new MarkHandle(cm, line, height);
- switch (type) {
- case COMMENT:
- mark.addStyleName(style.comment());
- comments.add(mark);
- break;
- case DRAFT:
- mark.addStyleName(style.draft());
- mark.getElement().setInnerText("*");
- comments.add(mark);
- break;
- case INSERT:
- mark.addStyleName(style.insert());
- diff.add(mark);
- break;
- case DELETE:
- mark.addStyleName(style.delete());
- diff.add(mark);
- break;
- case EDIT:
- mark.edit = DOM.createDiv();
- mark.edit.setClassName(style.halfGutter());
- mark.getElement().appendChild(mark.edit);
- mark.addStyleName(style.insert());
- diff.add(mark);
- break;
- }
- if (cmB != null) {
- mark.position(ratio(cmB.getScrollInfo()));
- }
- ((HTMLPanel) getWidget()).add(mark);
- return mark;
- }
-
- void clearDiffMarkers() {
- for (MarkHandle mark : diff) {
- mark.removeFromParent();
- }
- diff.clear();
- }
-
- class MarkHandle extends Widget implements ClickHandler {
- private static final int MIN_HEIGHT = 3;
-
- private final CodeMirror cm;
- private final int line;
- private final int height;
- private Element edit;
-
- MarkHandle(CodeMirror cm, int line, int height) {
- this.cm = cm;
- this.line = line;
- this.height = height;
-
- setElement((Element)(DOM.createDiv()));
- setStyleName(style.gutter());
- addDomHandler(this, ClickEvent.getType());
- }
-
- void position(double ratio) {
- double y = cm.heightAtLine(line, "local");
- getElement().getStyle().setTop(y * ratio, Unit.PX);
- if (height > 1) {
- double e = cm.heightAtLine(line + height, "local");
- double h = Math.max(MIN_HEIGHT, (e - y) * ratio);
- getElement().getStyle().setHeight(h, Unit.PX);
- if (edit != null) {
- edit.getStyle().setHeight(h, Unit.PX);
- }
- }
- }
-
- @Override
- public void onClick(ClickEvent e) {
- if (height == 1 || !visible()) {
- e.stopPropagation();
-
- double y = cm.heightAtLine(line, "local");
- double viewport = cm.getScrollInfo().getClientHeight();
- cm.setCursor(LineCharacter.create(line));
- cm.scrollToY(y - 0.5 * viewport);
- cm.focus();
- }
- }
-
- private boolean visible() {
- int markT = getElement().getOffsetTop();
- int markE = markT + getElement().getOffsetHeight();
-
- int viewT = viewport.getElement().getOffsetTop();
- int viewE = viewT + viewport.getElement().getOffsetHeight();
-
- return (viewT <= markT && markT < viewE) // mark top within viewport
- || (viewT <= markE && markE < viewE) // mark end within viewport
- || (markT <= viewT && viewE <= markE); // mark contains viewport
- }
-
- void remove() {
- removeFromParent();
- comments.remove(this);
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
deleted file mode 100644
index a56c3da859..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
- xmlns:g='urn:import:com.google.gwt.user.client.ui'>
- <ui:style type='com.google.gerrit.client.diff.OverviewBar.Style'>
- .overview {
- position: relative;
- }
- .gutter {
- cursor: pointer;
- position: absolute;
- height: 3px;
- width: 6px;
- border: 1px solid grey;
- margin-left: 1px;
- }
- .halfGutter {
- cursor: pointer;
- position: absolute;
- height: 3px;
- width: 3px;
- background-color: #faa;
- }
- .comment, .draft {
- background-color: #fcfa96;
- z-index: 2;
- }
- .draft {
- text-align: center;
- font-size: small;
- line-height: 0.5;
- color: inherit !important;
- text-decoration: none !important;
- }
- .delete {
- background-color: #faa;
- }
- .insert {
- background-color: #9f9;
- }
- .viewport {
- position: absolute;
- background-color: rgba(128, 128, 128, 0.50);
- border: 1px solid #444;
- width: 8px;
- cursor: default;
- z-index: 3;
- border-radius: 4px;
- }
- .viewportDrag {
- cursor: move;
- }
- </ui:style>
- <g:HTMLPanel styleName='{style.overview}'>
- <g:Label ui:field='viewport' styleName='{style.viewport}'/>
- </g:HTMLPanel>
-</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
index 7154c9fcb8..aed2218d59 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
@@ -16,8 +16,10 @@ package com.google.gerrit.client.diff;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
@@ -34,11 +36,14 @@ import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ImageResourceRenderer;
+import com.google.gwt.user.client.ui.Widget;
import com.google.gwtorm.client.KeyUtil;
+import java.util.List;
+
/** HTMLPanel to select among patch sets */
-class PatchSetSelectBox2 extends Composite {
- interface Binder extends UiBinder<HTMLPanel, PatchSetSelectBox2> {}
+class PatchSetSelectBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, PatchSetSelectBox> {}
private static final Binder uiBinder = GWT.create(Binder.class);
interface BoxStyle extends CssResource {
@@ -49,16 +54,16 @@ class PatchSetSelectBox2 extends Composite {
@UiField HTMLPanel linkPanel;
@UiField BoxStyle style;
- private SideBySide2 parent;
+ private SideBySide parent;
private DisplaySide side;
private boolean sideA;
private String path;
private Change.Id changeId;
private PatchSet.Id revision;
private PatchSet.Id idActive;
- private PatchSetSelectBox2 other;
+ private PatchSetSelectBox other;
- PatchSetSelectBox2(SideBySide2 parent,
+ PatchSetSelectBox(SideBySide parent,
DisplaySide side,
Change.Id changeId,
PatchSet.Id revision,
@@ -76,7 +81,8 @@ class PatchSetSelectBox2 extends Composite {
this.path = path;
}
- void setUpPatchSetNav(JsArray<RevisionInfo> list, DiffInfo.FileMeta meta) {
+ void setUpPatchSetNav(JsArray<RevisionInfo> list, DiffInfo.FileMeta meta,
+ boolean editExists, int currentPatchSet, boolean open, boolean binary) {
InlineHyperlink baseLink = null;
InlineHyperlink selectedLink = null;
if (sideA) {
@@ -85,10 +91,10 @@ class PatchSetSelectBox2 extends Composite {
}
for (int i = 0; i < list.length(); i++) {
RevisionInfo r = list.get(i);
- InlineHyperlink link = createLink(
- String.valueOf(r._number()), new PatchSet.Id(changeId, r._number()));
+ InlineHyperlink link = createLink(r.id(),
+ new PatchSet.Id(changeId, r._number()));
linkPanel.add(link);
- if (revision != null && r._number() == revision.get()) {
+ if (revision != null && r.id().equals(revision.getId())) {
selectedLink = link;
}
}
@@ -97,12 +103,37 @@ class PatchSetSelectBox2 extends Composite {
} else if (sideA) {
baseLink.setStyleName(style.selected());
}
- if (meta != null && !Patch.COMMIT_MSG.equals(path)) {
+
+ if (meta == null) {
+ return;
+ }
+ if (!Patch.COMMIT_MSG.equals(path)) {
linkPanel.add(createDownloadLink());
}
+ if (!binary && open && idActive != null && Gerrit.isSignedIn()) {
+ if ((editExists && idActive.get() == 0)
+ || (!editExists && idActive.get() == currentPatchSet)) {
+ linkPanel.add(createEditIcon());
+ }
+ }
+ List<WebLinkInfo> webLinks = Natives.asList(meta.web_links());
+ if (webLinks != null) {
+ for (WebLinkInfo webLink : webLinks) {
+ linkPanel.add(webLink.toAnchor());
+ }
+ }
+ }
+
+ private Widget createEditIcon() {
+ PatchSet.Id id = (idActive == null) ? other.idActive : idActive;
+ Anchor anchor = new Anchor(
+ new ImageResourceRenderer().render(Gerrit.RESOURCES.edit()),
+ "#" + Dispatcher.toEditScreen(id, path));
+ anchor.setTitle(PatchUtil.C.edit());
+ return anchor;
}
- static void link(PatchSetSelectBox2 a, PatchSetSelectBox2 b) {
+ static void link(PatchSetSelectBox a, PatchSetSelectBox b) {
a.other = b;
b.other = a;
}
@@ -130,7 +161,7 @@ class PatchSetSelectBox2 extends Composite {
}
@UiHandler("icon")
- void onIconClick(ClickEvent e) {
+ void onIconClick(@SuppressWarnings("unused") ClickEvent e) {
parent.getCmFromSide(side).scrollToY(0);
parent.getCommentManager().insertNewDraft(side, 0);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.ui.xml
index dca0cd58c7..cc8dd74a11 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.ui.xml
@@ -20,8 +20,9 @@ limitations under the License.
<ui:with field='res' type='com.google.gerrit.client.GerritResources'/>
<ui:with field='patchConstants'
type='com.google.gerrit.client.patches.PatchConstants'/>
- <ui:style type='com.google.gerrit.client.diff.PatchSetSelectBox2.BoxStyle'>
+ <ui:style type='com.google.gerrit.client.diff.PatchSetSelectBox.BoxStyle'>
@eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+ @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
.table {
width: 100%;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
index 4cbe1be2ab..869b4a300c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
@@ -22,13 +22,13 @@ import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.gwt.user.client.ui.Widget;
class PreferencesAction {
- private final SideBySide2 view;
+ private final SideBySide view;
private final DiffPreferences prefs;
private PopupPanel popup;
private PreferencesBox current;
private Widget partner;
- PreferencesAction(SideBySide2 view, DiffPreferences prefs) {
+ PreferencesAction(SideBySide view, DiffPreferences prefs) {
this.view = view;
this.prefs = prefs;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
index ac11cd5c50..498799cf32 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -27,14 +27,14 @@ import com.google.gerrit.client.account.AccountApi;
import com.google.gerrit.client.account.DiffPreferences;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.NpIntTextBox;
+import com.google.gerrit.extensions.client.Theme;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyDownEvent;
@@ -55,11 +55,11 @@ import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ToggleButton;
-import net.codemirror.lib.ModeInjector;
+import net.codemirror.mode.ModeInfo;
+import net.codemirror.mode.ModeInjector;
+import net.codemirror.theme.ThemeLoader;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.Objects;
/** Displays current diff preferences. */
class PreferencesBox extends Composite {
@@ -70,7 +70,7 @@ class PreferencesBox extends Composite {
String dialog();
}
- private final SideBySide2 view;
+ private final SideBySide view;
private DiffPreferences prefs;
private int contextLastValue;
private Timer updateContextTimer;
@@ -90,6 +90,7 @@ class PreferencesBox extends Composite {
@UiField ToggleButton leftSide;
@UiField ToggleButton emptyPane;
@UiField ToggleButton topMenu;
+ @UiField ToggleButton autoHideDiffTableHeader;
@UiField ToggleButton manualReview;
@UiField ToggleButton expandAllComments;
@UiField ToggleButton renderEntireFile;
@@ -98,7 +99,7 @@ class PreferencesBox extends Composite {
@UiField Button apply;
@UiField Button save;
- PreferencesBox(SideBySide2 view) {
+ PreferencesBox(SideBySide view) {
this.view = view;
initWidget(uiBinder.createAndBindUi(this));
@@ -147,7 +148,13 @@ class PreferencesBox extends Composite {
setIgnoreWhitespace(prefs.ignoreWhitespace());
tabWidth.setIntValue(prefs.tabSize());
- lineLength.setIntValue(prefs.lineLength());
+ if (Patch.COMMIT_MSG.equals(view.getPath())) {
+ lineLength.setEnabled(false);
+ lineLength.setIntValue(72);
+ } else {
+ lineLength.setEnabled(true);
+ lineLength.setIntValue(prefs.lineLength());
+ }
syntaxHighlighting.setValue(prefs.syntaxHighlighting());
whitespaceErrors.setValue(prefs.showWhitespaceErrors());
showTabs.setValue(prefs.showTabs());
@@ -157,6 +164,7 @@ class PreferencesBox extends Composite {
leftSide.setEnabled(!(prefs.hideEmptyPane()
&& view.diffTable.getChangeType() == ChangeType.ADDED));
topMenu.setValue(!prefs.hideTopMenu());
+ autoHideDiffTableHeader.setValue(!prefs.autoHideDiffTableHeader());
manualReview.setValue(prefs.manualReview());
expandAllComments.setValue(prefs.expandAllComments());
renderEntireFile.setValue(prefs.renderEntireFile());
@@ -192,7 +200,7 @@ class PreferencesBox extends Composite {
}
@UiHandler("ignoreWhitespace")
- void onIgnoreWhitespace(ChangeEvent e) {
+ void onIgnoreWhitespace(@SuppressWarnings("unused") ChangeEvent e) {
prefs.ignoreWhitespace(Whitespace.valueOf(
ignoreWhitespace.getValue(ignoreWhitespace.getSelectedIndex())));
view.reloadDiffInfo();
@@ -322,6 +330,12 @@ class PreferencesBox extends Composite {
view.resizeCodeMirror();
}
+ @UiHandler("autoHideDiffTableHeader")
+ void onAutoHideDiffTableHeader(ValueChangeEvent<Boolean> e) {
+ prefs.autoHideDiffTableHeader(!e.getValue());
+ view.setAutoHideDiffHeader(!e.getValue());
+ }
+
@UiHandler("manualReview")
void onManualReview(ValueChangeEvent<Boolean> e) {
prefs.manualReview(e.getValue());
@@ -338,26 +352,31 @@ class PreferencesBox extends Composite {
}
@UiHandler("mode")
- void onMode(ChangeEvent e) {
- final String m = mode.getValue(mode.getSelectedIndex());
+ void onMode(@SuppressWarnings("unused") ChangeEvent e) {
+ final String mode = getSelectedMode();
prefs.syntaxHighlighting(true);
syntaxHighlighting.setValue(true, false);
- Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+ new ModeInjector().add(mode).inject(new GerritCallback<Void>() {
@Override
- public boolean execute() {
- if (prefs.syntaxHighlighting() && view.isAttached()) {
+ public void onSuccess(Void result) {
+ if (prefs.syntaxHighlighting()
+ && Objects.equals(mode, getSelectedMode())
+ && view.isAttached()) {
view.operation(new Runnable() {
@Override
public void run() {
- String mode = m != null && !m.isEmpty() ? m : null;
view.getCmFromSide(DisplaySide.A).setOption("mode", mode);
view.getCmFromSide(DisplaySide.B).setOption("mode", mode);
}
});
}
- return false;
}
- }, 50);
+ });
+ }
+
+ private String getSelectedMode() {
+ String m = mode.getValue(mode.getSelectedIndex());
+ return m != null && !m.isEmpty() ? m : null;
}
@UiHandler("whitespaceErrors")
@@ -380,26 +399,38 @@ class PreferencesBox extends Composite {
}
@UiHandler("theme")
- void onTheme(ChangeEvent e) {
- prefs.theme(Theme.valueOf(theme.getValue(theme.getSelectedIndex())));
- view.setThemeStyles(prefs.theme().isDark());
- view.operation(new Runnable() {
+ void onTheme(@SuppressWarnings("unused") ChangeEvent e) {
+ final Theme newTheme = getSelectedTheme();
+ prefs.theme(newTheme);
+ ThemeLoader.loadTheme(newTheme, new GerritCallback<Void>() {
@Override
- public void run() {
- String t = prefs.theme().name().toLowerCase();
- view.getCmFromSide(DisplaySide.A).setOption("theme", t);
- view.getCmFromSide(DisplaySide.B).setOption("theme", t);
+ public void onSuccess(Void result) {
+ view.operation(new Runnable() {
+ @Override
+ public void run() {
+ if (getSelectedTheme() == newTheme && isAttached()) {
+ String t = newTheme.name().toLowerCase();
+ view.getCmFromSide(DisplaySide.A).setOption("theme", t);
+ view.getCmFromSide(DisplaySide.B).setOption("theme", t);
+ view.setThemeStyles(newTheme.isDark());
+ }
+ }
+ });
}
});
}
+ private Theme getSelectedTheme() {
+ return Theme.valueOf(theme.getValue(theme.getSelectedIndex()));
+ }
+
@UiHandler("apply")
- void onApply(ClickEvent e) {
+ void onApply(@SuppressWarnings("unused") ClickEvent e) {
close();
}
@UiHandler("save")
- void onSave(ClickEvent e) {
+ void onSave(@SuppressWarnings("unused") ClickEvent e) {
AccountApi.putDiffPreferences(prefs, new GerritCallback<DiffPreferences>() {
@Override
public void onSuccess(DiffPreferences result) {
@@ -454,46 +485,22 @@ class PreferencesBox extends Composite {
IGNORE_ALL_SPACE.name());
}
- private static final Map<String, String> NAME_TO_MODE;
- private static final Map<String, String> NORMALIZED_MODES;
- static {
- NAME_TO_MODE = new TreeMap<>();
- NORMALIZED_MODES = new HashMap<>();
- for (String type : ModeInjector.getKnownMimeTypes()) {
- String name = type;
- if (name.startsWith("text/x-")) {
- name = name.substring("text/x-".length());
- } else if (name.startsWith("text/")) {
- name = name.substring("text/".length());
- } else if (name.startsWith("application/")) {
- name = name.substring("application/".length());
- }
-
- String normalized = NAME_TO_MODE.get(name);
- if (normalized == null) {
- normalized = type;
- NAME_TO_MODE.put(name, normalized);
- }
- NORMALIZED_MODES.put(type, normalized);
- }
- }
-
private void initMode() {
mode.addItem("", "");
- for (Map.Entry<String, String> e : NAME_TO_MODE.entrySet()) {
- mode.addItem(e.getKey(), e.getValue());
+ for (ModeInfo m : Natives.asList(ModeInfo.all())) {
+ mode.addItem(m.name(), m.mime());
}
}
private void setMode(String modeType) {
if (modeType != null && !modeType.isEmpty()) {
- if (NORMALIZED_MODES.containsKey(modeType)) {
- modeType = NORMALIZED_MODES.get(modeType);
- }
- for (int i = 0; i < mode.getItemCount(); i++) {
- if (mode.getValue(i).equals(modeType)) {
- mode.setSelectedIndex(i);
- return;
+ ModeInfo m = ModeInfo.findModeByMIME(modeType);
+ if (m != null) {
+ for (int i = 0; i < mode.getItemCount(); i++) {
+ if (mode.getValue(i).equals(m.mime())) {
+ mode.setSelectedIndex(i);
+ return;
+ }
}
}
}
@@ -512,26 +519,8 @@ class PreferencesBox extends Composite {
}
private void initTheme() {
- theme.addItem(
- Theme.DEFAULT.name().toLowerCase(),
- Theme.DEFAULT.name());
- theme.addItem(
- Theme.ECLIPSE.name().toLowerCase(),
- Theme.ECLIPSE.name());
- theme.addItem(
- Theme.ELEGANT.name().toLowerCase(),
- Theme.ELEGANT.name());
- theme.addItem(
- Theme.NEAT.name().toLowerCase(),
- Theme.NEAT.name());
- theme.addItem(
- Theme.MIDNIGHT.name().toLowerCase(),
- Theme.MIDNIGHT.name());
- theme.addItem(
- Theme.NIGHT.name().toLowerCase(),
- Theme.NIGHT.name());
- theme.addItem(
- Theme.TWILIGHT.name().toLowerCase(),
- Theme.TWILIGHT.name());
+ for (Theme t : Theme.values()) {
+ theme.addItem(t.name().toLowerCase(), t.name());
+ }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
index af539165e3..62d2eacff3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
@@ -73,6 +73,10 @@ limitations under the License.
color: #dddd00;
}
+ .box input.gwt-TextBox:disabled {
+ background-color: #cacaca;
+ }
+
.box .gwt-ToggleButton {
position: relative;
height: 19px;
@@ -250,6 +254,13 @@ limitations under the License.
</g:ToggleButton></td>
</tr>
<tr>
+ <th><ui:msg>Auto Hide Diff Table Header</ui:msg></th>
+ <td><g:ToggleButton ui:field='autoHideDiffTableHeader'>
+ <g:upFace><ui:msg>Yes</ui:msg></g:upFace>
+ <g:downFace><ui:msg>No</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
<th><ui:msg>Mark Reviewed</ui:msg></th>
<td><g:ToggleButton ui:field='manualReview'>
<g:upFace><ui:msg>Automatic</ui:msg></g:upFace>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
index f5f2891345..7d74c2b0cf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
@@ -15,6 +15,7 @@
package com.google.gerrit.client.diff;
import com.google.gerrit.client.AvatarImage;
+import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.CommentApi;
@@ -60,6 +61,7 @@ class PublishedBox extends CommentBox {
@UiField Element buttons;
@UiField Button reply;
@UiField Button done;
+ @UiField Button fix;
@UiField(provided = true)
AvatarImage avatar;
@@ -68,7 +70,8 @@ class PublishedBox extends CommentBox {
CommentGroup group,
CommentLinkProcessor clp,
PatchSet.Id psId,
- CommentInfo info) {
+ CommentInfo info,
+ boolean open) {
super(group, info.range());
this.psId = psId;
@@ -97,6 +100,8 @@ class PublishedBox extends CommentBox {
message.setInnerSafeHtml(clp.apply(
new SafeHtmlBuilder().append(msg).wikify()));
}
+
+ fix.setVisible(open);
}
@Override
@@ -109,6 +114,7 @@ class PublishedBox extends CommentBox {
return UIObject.isVisible(message);
}
+ @Override
void setOpen(boolean open) {
UIObject.setVisible(summary, !open);
UIObject.setVisible(message, open);
@@ -145,7 +151,7 @@ class PublishedBox extends CommentBox {
void doReply() {
if (!Gerrit.isSignedIn()) {
- Gerrit.doSignIn(getCommentManager().getSideBySide2().getToken());
+ Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
} else if (replyBox == null) {
addReplyBox();
} else {
@@ -163,7 +169,7 @@ class PublishedBox extends CommentBox {
void onReplyDone(ClickEvent e) {
e.stopPropagation();
if (!Gerrit.isSignedIn()) {
- Gerrit.doSignIn(getCommentManager().getSideBySide2().getToken());
+ Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
} else if (replyBox == null) {
done.setEnabled(false);
CommentInfo input = CommentInfo.createReply(comment);
@@ -183,6 +189,17 @@ class PublishedBox extends CommentBox {
}
}
+ @UiHandler("fix")
+ void onFix(ClickEvent e) {
+ e.stopPropagation();
+ String t = Dispatcher.toEditScreen(psId, comment.path(), comment.line());
+ if (!Gerrit.isSignedIn()) {
+ Gerrit.doSignIn(t);
+ } else {
+ Gerrit.display(t);
+ }
+ }
+
private static String authorName(CommentInfo info) {
if (info.author() != null) {
if (info.author().name() != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
index 6e88c5b3fc..bcc34e272f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
@@ -66,6 +66,11 @@ limitations under the License.
<ui:attribute name='title'/>
<div><ui:msg>Done</ui:msg></div>
</g:Button>
+ <g:Button ui:field='fix' styleName='' visible='false'
+ title='Fix this comment in the inline editor'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Fix</ui:msg></div>
+ </g:Button>
</div>
</div>
</g:HTMLPanel>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
index 8c4fc516b6..e379d60d65 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
@@ -16,30 +16,16 @@ package com.google.gerrit.client.diff;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
/** Resources used by diff. */
interface Resources extends ClientBundle {
static final Resources I = GWT.create(Resources.class);
- @Source("CommentBoxUi.css") Style style();
- @Source("go-prev.png") ImageResource go_prev();
- @Source("go-next.png") ImageResource go_next();
- @Source("go-up.png") ImageResource go_up();
- @Source("gear.png") ImageResource gear();
+ @Source("CommentBox.css") CommentBox.Style style();
+ @Source("Scrollbar.css") Scrollbar.Style scrollbarStyle();
- interface Style extends CssResource {
- String commentWidgets();
- String commentBox();
- String contents();
- String message();
- String header();
- String summary();
- String date();
-
- String go_prev();
- String go_next();
- String go_up();
- }
+ @Source("goPrev.png") ImageResource goPrev();
+ @Source("goNext.png") ImageResource goNext();
+ @Source("goUp.png") ImageResource goUp();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
index abff8ef95a..4ee09d25d8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
@@ -22,17 +22,18 @@ import net.codemirror.lib.ScrollInfo;
class ScrollSynchronizer {
private DiffTable diffTable;
private LineMapper mapper;
- private OverviewBar overview;
private ScrollCallback active;
private ScrollCallback callbackA;
private ScrollCallback callbackB;
+ private CodeMirror cmB;
+ private boolean autoHideDiffTableHeader;
ScrollSynchronizer(DiffTable diffTable,
CodeMirror cmA, CodeMirror cmB,
LineMapper mapper) {
this.diffTable = diffTable;
this.mapper = mapper;
- this.overview = diffTable.overview;
+ this.cmB = cmB;
callbackA = new ScrollCallback(cmA, cmB, DisplaySide.A);
callbackB = new ScrollCallback(cmB, cmA, DisplaySide.B);
@@ -40,15 +41,23 @@ class ScrollSynchronizer {
cmB.on("scroll", callbackB);
}
+ void setAutoHideDiffTableHeader(boolean autoHide) {
+ if (autoHide) {
+ updateDiffTableHeader(cmB.getScrollInfo());
+ } else {
+ diffTable.setHeaderVisible(true);
+ }
+ autoHideDiffTableHeader = autoHide;
+ }
+
void syncScroll(DisplaySide masterSide) {
(masterSide == DisplaySide.A ? callbackA : callbackB).sync();
}
- private void updateScreenHeader(ScrollInfo si) {
- if (si.getTop() == 0 && !diffTable.isHeaderVisible()) {
+ private void updateDiffTableHeader(ScrollInfo si) {
+ if (si.top() == 0) {
diffTable.setHeaderVisible(true);
- } else if (si.getTop() > 0.5 * si.getClientHeight()
- && diffTable.isHeaderVisible()) {
+ } else if (si.top() > 0.5 * si.clientHeight()) {
diffTable.setHeaderVisible(false);
}
}
@@ -75,7 +84,7 @@ class ScrollSynchronizer {
}
void sync() {
- dst.scrollToY(align(src.getScrollInfo().getTop()));
+ dst.scrollToY(align(src.getScrollInfo().top()));
}
@Override
@@ -86,9 +95,10 @@ class ScrollSynchronizer {
}
if (active == this) {
ScrollInfo si = src.getScrollInfo();
- updateScreenHeader(si);
- overview.update(si);
- dst.scrollTo(si.getLeft(), align(si.getTop()));
+ if (autoHideDiffTableHeader) {
+ updateDiffTableHeader(si);
+ }
+ dst.scrollTo(si.left(), align(si.top()));
state = 0;
}
}
@@ -97,7 +107,7 @@ class ScrollSynchronizer {
switch (state) {
case 0:
state = 1;
- dst.scrollToY(align(src.getScrollInfo().getTop()));
+ dst.scrollToY(align(src.getScrollInfo().top()));
break;
case 1:
state = 2;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css
new file mode 100644
index 0000000000..383f2782cf
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css
@@ -0,0 +1,38 @@
+/* Copyright (C) 2014 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.
+ */
+
+.comment, .draft, .insert, .delete, .edit {
+ min-height: 5px;
+ position: absolute;
+ right: 0;
+ z-index: 7;
+}
+
+.comment, .draft {
+ color: #0d0d0d;
+ font-size: 9px;
+}
+
+.delete {
+ background-color: #faa;
+}
+.insert {
+ background-color: #9f9;
+}
+.edit {
+ border-left: 3px solid #faa;
+ width: 2px !important;
+ background-color: #9f9;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java
new file mode 100644
index 0000000000..b72ab43711
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java
@@ -0,0 +1,99 @@
+// Copyright (C) 2014 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.client.diff;
+
+import com.google.gwt.resources.client.CssResource;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.Pos;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Displays overview of all edits and comments in this file. */
+class Scrollbar {
+ static {
+ Resources.I.scrollbarStyle().ensureInjected();
+ }
+
+ interface Style extends CssResource {
+ String comment();
+ String draft();
+ String insert();
+ String delete();
+ String edit();
+ }
+
+ private final List<ScrollbarAnnotation> diff = new ArrayList<>();
+ private final DiffTable parent;
+
+ Scrollbar(DiffTable d) {
+ parent = d;
+ }
+
+ ScrollbarAnnotation comment(CodeMirror cm, int line) {
+ ScrollbarAnnotation a = new ScrollbarAnnotation(cm);
+ a.setStyleName(Resources.I.scrollbarStyle().comment());
+ a.at(line);
+ a.getElement().setInnerText("\u2736"); // Six pointed black star
+ parent.add(a);
+ return a;
+ }
+
+ ScrollbarAnnotation draft(CodeMirror cm, int line) {
+ ScrollbarAnnotation a = new ScrollbarAnnotation(cm);
+ a.setStyleName(Resources.I.scrollbarStyle().draft());
+ a.at(line);
+ a.getElement().setInnerText("\u270D"); // Writing hand
+ parent.add(a);
+ return a;
+ }
+
+ ScrollbarAnnotation insert(CodeMirror cm, int line, int len) {
+ ScrollbarAnnotation a = diff(cm, line, len);
+ a.setStyleName(Resources.I.scrollbarStyle().insert());
+ parent.add(a);
+ return a;
+ }
+
+ ScrollbarAnnotation delete(CodeMirror cmA, CodeMirror cmB, int line, int len) {
+ ScrollbarAnnotation a = diff(cmA, line, len);
+ a.setStyleName(Resources.I.scrollbarStyle().delete());
+ a.renderOn(cmB);
+ parent.add(a);
+ return a;
+ }
+
+ ScrollbarAnnotation edit(CodeMirror cm, int line, int len) {
+ ScrollbarAnnotation a = diff(cm, line, len);
+ a.setStyleName(Resources.I.scrollbarStyle().edit());
+ parent.add(a);
+ return a;
+ }
+
+ private ScrollbarAnnotation diff(CodeMirror cm, int s, int n) {
+ ScrollbarAnnotation a = new ScrollbarAnnotation(cm);
+ a.at(Pos.create(s), Pos.create(s + n));
+ diff.add(a);
+ return a;
+ }
+
+ void removeDiffAnnotations() {
+ for (ScrollbarAnnotation a : diff) {
+ a.remove();
+ }
+ diff.clear();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java
new file mode 100644
index 0000000000..c8f991166d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2014 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.client.diff;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Widget;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.RegisteredHandler;
+import net.codemirror.lib.Pos;
+
+/** Displayed on the vertical scrollbar to place a chunk or comment. */
+class ScrollbarAnnotation extends Widget implements ClickHandler {
+ private final CodeMirror cm;
+ private CodeMirror cmB;
+ private RegisteredHandler refresh;
+ private Pos from;
+ private Pos to;
+ private double scale;
+
+ ScrollbarAnnotation(CodeMirror cm) {
+ setElement((Element) DOM.createDiv());
+ getElement().setAttribute("not-content", "true");
+ addDomHandler(this, ClickEvent.getType());
+ this.cm = cm;
+ this.cmB = cm;
+ }
+
+ void remove() {
+ removeFromParent();
+ }
+
+ void at(int line) {
+ at(Pos.create(line), Pos.create(line + 1));
+ }
+
+ void at(Pos from, Pos to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ void renderOn(CodeMirror cm) {
+ this.cmB = cm;
+ }
+
+ @Override
+ protected void onLoad() {
+ cmB.getWrapperElement().appendChild(getElement());
+ refresh = cmB.on("refresh", new Runnable() {
+ @Override
+ public void run() {
+ if (updateScale()) {
+ updatePosition();
+ }
+ }
+ });
+ updateScale();
+ updatePosition();
+ }
+
+ @Override
+ protected void onUnload() {
+ cmB.off("refresh", refresh);
+ }
+
+ private boolean updateScale() {
+ double old = scale;
+ double docHeight = cmB.getWrapperElement().getClientHeight();
+ double lineHeight = cmB.heightAtLine(cmB.lastLine() + 1, "local");
+ scale = (docHeight - cmB.barHeight()) / lineHeight;
+ return old != scale;
+ }
+
+ private void updatePosition() {
+ double top = cm.charCoords(from, "local").top() * scale;
+ double bottom = cm.charCoords(to, "local").bottom() * scale;
+
+ Element e = getElement();
+ e.getStyle().setTop(top, Unit.PX);
+ e.getStyle().setWidth(Math.max(2, cm.barWidth() - 1), Unit.PX);
+ e.getStyle().setHeight(Math.max(3, bottom - top), Unit.PX);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ event.stopPropagation();
+
+ int line = from.line();
+ int h = to.line() - line;
+ if (h > 5) {
+ // Map click inside of the annotation to the relative position
+ // within the region covered by the annotation.
+ double s = ((double) event.getY()) / getElement().getOffsetHeight();
+ line += (int) (s * h);
+ }
+
+ double y = cm.heightAtLine(line, "local");
+ double viewport = cm.getScrollInfo().clientHeight();
+ cm.setCursor(from);
+ cm.scrollTo(0, y - 0.5 * viewport);
+ cm.focus();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
index 30306f236a..d06b59e05d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
@@ -17,12 +17,15 @@ package com.google.gerrit.client.diff;
import static com.google.gerrit.reviewdb.client.AccountDiffPreference.WHOLE_FILE_CONTEXT;
import static java.lang.Double.POSITIVE_INFINITY;
+import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.JumpKeys;
import com.google.gerrit.client.account.DiffPreferences;
-import com.google.gerrit.client.change.ChangeScreen2;
+import com.google.gerrit.client.change.ChangeScreen;
+import com.google.gerrit.client.change.FileTable;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.diff.DiffInfo.FileMeta;
@@ -33,10 +36,12 @@ import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
@@ -45,7 +50,6 @@ import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -55,10 +59,10 @@ import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
@@ -67,22 +71,24 @@ import com.google.gwtexpui.globalkey.client.ShowHelpCommand;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler;
import net.codemirror.lib.CodeMirror.GutterClickHandler;
-import net.codemirror.lib.CodeMirror.LineClassWhere;
import net.codemirror.lib.CodeMirror.LineHandle;
import net.codemirror.lib.Configuration;
import net.codemirror.lib.KeyMap;
-import net.codemirror.lib.LineCharacter;
-import net.codemirror.lib.ModeInjector;
+import net.codemirror.lib.Pos;
+import net.codemirror.mode.ModeInfo;
+import net.codemirror.mode.ModeInjector;
+import net.codemirror.theme.ThemeLoader;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
-public class SideBySide2 extends Screen {
+public class SideBySide extends Screen {
private static final KeyMap RENDER_ENTIRE_FILE_KEYMAP = KeyMap.create()
- .on("Ctrl-F", false);
+ .propagate("Ctrl-F");
- interface Binder extends UiBinder<FlowPanel, SideBySide2> {}
+ interface Binder extends UiBinder<FlowPanel, SideBySide> {}
private static final Binder uiBinder = GWT.create(Binder.class);
enum FileSize {
@@ -110,18 +116,16 @@ public class SideBySide2 extends Screen {
private DisplaySide startSide;
private int startLine;
private DiffPreferences prefs;
+ private Change.Status changeStatus;
private CodeMirror cmA;
private CodeMirror cmB;
- private Element columnMarginA;
- private Element columnMarginB;
- private double charWidthPx;
- private double lineHeightPx;
private HandlerRegistration resizeHandler;
private ScrollSynchronizer scrollSynchronizer;
private DiffInfo diff;
private FileSize fileSize;
+ private EditInfo edit;
private ChunkManager chunkManager;
private CommentManager commentManager;
private SkipManager skipManager;
@@ -133,7 +137,7 @@ public class SideBySide2 extends Screen {
private PreferencesAction prefsAction;
private int reloadVersionId;
- public SideBySide2(
+ public SideBySide(
PatchSet.Id base,
PatchSet.Id revision,
String path,
@@ -166,22 +170,36 @@ public class SideBySide2 extends Screen {
protected void onLoad() {
super.onLoad();
- CallbackGroup cmGroup = new CallbackGroup();
- CodeMirror.initLibrary(cmGroup.add(CallbackGroup.<Void> emptyCallback()));
- final CallbackGroup group = new CallbackGroup();
- final AsyncCallback<Void> modeInjectorCb =
- group.add(CallbackGroup.<Void> emptyCallback());
+ CallbackGroup group1 = new CallbackGroup();
+ final CallbackGroup group2 = new CallbackGroup();
+
+ CodeMirror.initLibrary(group1.add(new AsyncCallback<Void>() {
+ final AsyncCallback<Void> themeCallback = group2.addEmpty();
+
+ @Override
+ public void onSuccess(Void result) {
+ // Load theme after CM library to ensure theme can override CSS.
+ ThemeLoader.loadTheme(prefs.theme(), themeCallback);
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
DiffApi.diff(revision, path)
.base(base)
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
- .get(cmGroup.addFinal(new GerritCallback<DiffInfo>() {
+ .get(group1.addFinal(new GerritCallback<DiffInfo>() {
+ final AsyncCallback<Void> modeInjectorCb = group2.addEmpty();
+
@Override
public void onSuccess(DiffInfo diffInfo) {
diff = diffInfo;
fileSize = bucketFileSize(diffInfo);
+
if (prefs.syntaxHighlighting()) {
if (fileSize.compareTo(FileSize.SMALL) > 0) {
modeInjectorCb.onSuccess(null);
@@ -194,30 +212,58 @@ public class SideBySide2 extends Screen {
}
}));
+ if (Gerrit.isSignedIn()) {
+ ChangeApi.edit(changeId.get(), group2.add(
+ new AsyncCallback<EditInfo>() {
+ @Override
+ public void onSuccess(EditInfo result) {
+ edit = result;
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+ }
+
final CommentsCollections comments = new CommentsCollections();
- comments.load(base, revision, path, group);
+ comments.load(base, revision, path, group2);
RestApi call = ChangeApi.detail(changeId.get());
ChangeList.addOptions(call, EnumSet.of(
ListChangesOption.ALL_REVISIONS));
- call.get(group.add(new GerritCallback<ChangeInfo>() {
+ call.get(group2.add(new AsyncCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo info) {
+ changeStatus = info.status();
info.revisions().copyKeysIntoChildren("name");
+ if (edit != null) {
+ edit.set_name(edit.commit().commit());
+ info.set_edit(edit);
+ info.revisions().put(edit.name(), RevisionInfo.fromEdit(edit));
+ }
+ int currentPatchSet = info.revision(info.current_revision())._number();
JsArray<RevisionInfo> list = info.revisions().values();
RevisionInfo.sortRevisionInfoByNumber(list);
- diffTable.set(prefs, list, diff);
+ diffTable.set(prefs, list, diff, edit != null, currentPatchSet,
+ changeStatus.isOpen(), diff.binary());
header.setChangeInfo(info);
- }}));
+ }
- ConfigInfoCache.get(changeId, group.addFinal(
- new ScreenLoadCallback<ConfigInfoCache.Entry>(SideBySide2.this) {
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+
+ ConfigInfoCache.get(changeId, group2.addFinal(
+ new ScreenLoadCallback<ConfigInfoCache.Entry>(SideBySide.this) {
@Override
protected void preDisplay(ConfigInfoCache.Entry result) {
commentManager = new CommentManager(
- SideBySide2.this,
+ SideBySide.this,
base, revision, path,
- result.getCommentLinkProcessor());
+ result.getCommentLinkProcessor(),
+ changeStatus.isOpen());
setTheme(result.getTheme());
display(comments);
}
@@ -239,18 +285,16 @@ public class SideBySide2 extends Screen {
}
});
- final int height = getCodeMirrorHeight();
operation(new Runnable() {
@Override
public void run() {
- cmA.setHeight(height);
- cmB.setHeight(height);
+ resizeCodeMirror();
chunkManager.adjustPadding();
cmA.refresh();
cmB.refresh();
}
});
- setLineLength(prefs.lineLength());
+ setLineLength(Patch.COMMIT_MSG.equals(path) ? 72 : prefs.lineLength());
diffTable.refresh();
if (startLine == 0) {
@@ -266,15 +310,11 @@ public class SideBySide2 extends Screen {
}
}
if (startSide != null && startLine > 0) {
- int line = startLine - 1;
CodeMirror cm = getCmFromSide(startSide);
- if (cm.lineAtHeight(height - 20) < line) {
- cm.scrollToY(cm.heightAtLine(line, "local") - 0.5 * height);
- }
- cm.setCursor(LineCharacter.create(line));
+ cm.scrollToLine(startLine - 1);
cm.focus();
} else {
- cmA.setCursor(LineCharacter.create(0));
+ cmA.setCursor(Pos.create(0));
cmA.focus();
}
if (Gerrit.isSignedIn() && prefs.autoReview()) {
@@ -320,11 +360,9 @@ public class SideBySide2 extends Screen {
}
private void registerCmEvents(final CodeMirror cm) {
- cm.on("beforeSelectionChange", onSelectionChange(cm));
cm.on("cursorActivity", updateActiveLine(cm));
- cm.on("gutterClick", onGutterClick(cm));
cm.on("focus", updateActiveLine(cm));
- cm.addKeyMap(KeyMap.create()
+ KeyMap keyMap = KeyMap.create()
.on("A", upToChange(true))
.on("U", upToChange(false))
.on("[", header.navigate(Direction.PREV))
@@ -332,8 +370,8 @@ public class SideBySide2 extends Screen {
.on("R", header.toggleReviewed())
.on("O", commentManager.toggleOpenBox(cm))
.on("Enter", commentManager.toggleOpenBox(cm))
- .on("C", commentManager.insertNewDraft(cm))
.on("N", maybeNextVimSearch(cm))
+ .on("E", openEditScreen(cm))
.on("P", chunkManager.diffChunkNav(cm, Direction.PREV))
.on("Shift-A", diffTable.toggleA())
.on("Shift-M", header.reviewedAndNext())
@@ -343,6 +381,7 @@ public class SideBySide2 extends Screen {
.on("Shift-Left", moveCursorToSide(cm, DisplaySide.A))
.on("Shift-Right", moveCursorToSide(cm, DisplaySide.B))
.on("I", new Runnable() {
+ @Override
public void run() {
switch (getIntraLineStatus()) {
case OFF:
@@ -369,19 +408,19 @@ public class SideBySide2 extends Screen {
.on("Space", new Runnable() {
@Override
public void run() {
- CodeMirror.handleVimKey(cm, "<C-d>");
+ cm.vim().handleKey("<C-d>");
}
})
.on("Shift-Space", new Runnable() {
@Override
public void run() {
- CodeMirror.handleVimKey(cm, "<C-u>");
+ cm.vim().handleKey("<C-u>");
}
})
.on("Ctrl-F", new Runnable() {
@Override
public void run() {
- CodeMirror.handleVimKey(cm, "/");
+ cm.vim().handleKey("/");
}
})
.on("Ctrl-A", new Runnable() {
@@ -389,7 +428,13 @@ public class SideBySide2 extends Screen {
public void run() {
cm.execCommand("selectAll");
}
- }));
+ });
+ if (revision.get() != 0) {
+ cm.on("beforeSelectionChange", onSelectionChange(cm));
+ cm.on("gutterClick", onGutterClick(cm));
+ keyMap.on("C", commentManager.insertNewDraft(cm));
+ }
+ cm.addKeyMap(keyMap);
if (prefs.renderEntireFile()) {
cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
}
@@ -400,10 +445,8 @@ public class SideBySide2 extends Screen {
private InsertCommentBubble bubble;
@Override
- public void handle(CodeMirror cm, LineCharacter anchor, LineCharacter head) {
- if (anchor == head
- || (anchor.getLine() == head.getLine()
- && anchor.getCh() == head.getCh())) {
+ public void handle(CodeMirror cm, Pos anchor, Pos head) {
+ if (anchor.equals(head)) {
if (bubble != null) {
bubble.setVisible(false);
}
@@ -416,10 +459,10 @@ public class SideBySide2 extends Screen {
bubble.position(cm.charCoords(head, "local"));
}
- private void init(LineCharacter anchor) {
+ private void init(Pos anchor) {
bubble = new InsertCommentBubble(commentManager, cm);
add(bubble);
- cm.addWidget(anchor, bubble.getElement(), false);
+ cm.addWidget(anchor, bubble.getElement());
}
};
}
@@ -428,7 +471,7 @@ public class SideBySide2 extends Screen {
public void registerKeys() {
super.registerKeys();
- keysNavigation.add(new UpToChangeCommand2(revision, 0, 'u'));
+ keysNavigation.add(new UpToChangeCommand(revision, 0, 'u'));
keysNavigation.add(
new NoOpKeyCommand(KeyCommand.M_SHIFT, KeyCodes.KEY_LEFT, PatchUtil.C.focusSideA()),
new NoOpKeyCommand(KeyCommand.M_SHIFT, KeyCodes.KEY_RIGHT, PatchUtil.C.focusSideB()));
@@ -445,6 +488,7 @@ public class SideBySide2 extends Screen {
new NoOpKeyCommand(KeyCommand.M_CTRL, 'f', Gerrit.C.keySearch()));
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
+ keysAction.add(new NoOpKeyCommand(0, 'e', PatchUtil.C.openEditScreen()));
keysAction.add(new NoOpKeyCommand(0, KeyCodes.KEY_ENTER,
PatchUtil.C.expandComment()));
keysAction.add(new NoOpKeyCommand(0, 'o', PatchUtil.C.expandComment()));
@@ -521,24 +565,20 @@ public class SideBySide2 extends Screen {
private void display(final CommentsCollections comments) {
setThemeStyles(prefs.theme().isDark());
- setShowTabs(prefs.showTabs());
setShowIntraline(prefs.intralineDifference());
if (prefs.showLineNumbers()) {
diffTable.addStyleName(DiffTable.style.showLineNumbers());
}
- cmA = newCM(diff.meta_a(), diff.text_a(), DisplaySide.A, diffTable.cmA);
- cmB = newCM(diff.meta_b(), diff.text_b(), DisplaySide.B, diffTable.cmB);
- diffTable.overview.init(cmB);
- chunkManager = new ChunkManager(this, cmA, cmB, diffTable.overview);
- skipManager = new SkipManager(this, commentManager);
+ cmA = newCM(diff.meta_a(), diff.text_a(), diffTable.cmA);
+ cmB = newCM(diff.meta_b(), diff.text_b(), diffTable.cmB);
+
+ cmA.extras().side(DisplaySide.A);
+ cmB.extras().side(DisplaySide.B);
+ setShowTabs(prefs.showTabs());
- columnMarginA = DOM.createDiv();
- columnMarginB = DOM.createDiv();
- columnMarginA.setClassName(DiffTable.style.columnMargin());
- columnMarginB.setClassName(DiffTable.style.columnMargin());
- cmA.getMoverElement().appendChild(columnMarginA);
- cmB.getMoverElement().appendChild(columnMarginB);
+ chunkManager = new ChunkManager(this, cmA, cmB, diffTable.scrollbar);
+ skipManager = new SkipManager(this, commentManager);
if (prefs.renderEntireFile() && !canEnableRenderEntireFile(prefs)) {
// CodeMirror is too slow to layout an entire huge file.
@@ -546,6 +586,7 @@ public class SideBySide2 extends Screen {
}
operation(new Runnable() {
+ @Override
public void run() {
// Estimate initial CM3 height, fixed up in onShowView.
int height = Window.getClientHeight()
@@ -565,7 +606,8 @@ public class SideBySide2 extends Screen {
chunkManager.getLineMapper());
prefsAction = new PreferencesAction(this, prefs);
- header.init(prefsAction);
+ header.init(prefsAction, getLinks(), diff.side_by_side_web_links());
+ scrollSynchronizer.setAutoHideDiffTableHeader(prefs.autoHideDiffTableHeader());
if (prefs.syntaxHighlighting() && fileSize.compareTo(FileSize.SMALL) > 0) {
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@@ -580,22 +622,29 @@ public class SideBySide2 extends Screen {
}
}
+ private List<InlineHyperlink> getLinks() {
+ InlineHyperlink toUnifiedDiffLink = new InlineHyperlink();
+ toUnifiedDiffLink.setHTML(
+ new ImageResourceRenderer().render(Gerrit.RESOURCES.unifiedDiff()));
+ toUnifiedDiffLink.setTargetHistoryToken(
+ Dispatcher.toUnified(base, revision, path));
+ toUnifiedDiffLink.setTitle(PatchUtil.C.unifiedDiff());
+ return Collections.singletonList(toUnifiedDiffLink);
+ }
+
private CodeMirror newCM(
DiffInfo.FileMeta meta,
String contents,
- DisplaySide side,
Element parent) {
- String mode = fileSize == FileSize.SMALL
- ? getContentType(meta)
- : null;
- return CodeMirror.create(side, parent, Configuration.create()
+ return CodeMirror.create(parent, Configuration.create()
.set("readOnly", true)
.set("cursorBlinkRate", 0)
.set("cursorHeight", 0.85)
.set("lineNumbers", prefs.showLineNumbers())
.set("tabSize", prefs.tabSize())
- .set("mode", mode)
+ .set("mode", fileSize == FileSize.SMALL ? getContentType(meta) : null)
.set("lineWrapping", false)
+ .set("scrollbarStyle", "overlay")
.set("styleSelectedText", true)
.set("showTrailingSpace", prefs.showWhitespaceErrors())
.set("keyMap", "vim_ro")
@@ -625,61 +674,14 @@ public class SideBySide2 extends Screen {
}
}
- void setShowTabs(boolean b) {
- if (b) {
- diffTable.addStyleName(DiffTable.style.showTabs());
- } else {
- diffTable.removeStyleName(DiffTable.style.showTabs());
- }
+ void setShowTabs(boolean show) {
+ cmA.extras().showTabs(show);
+ cmB.extras().showTabs(show);
}
void setLineLength(int columns) {
- double w = columns * getCharWidthPx();
- columnMarginA.getStyle().setMarginLeft(w, Style.Unit.PX);
- columnMarginB.getStyle().setMarginLeft(w, Style.Unit.PX);
- }
-
- double getLineHeightPx() {
- if (lineHeightPx <= 1) {
- Element p = DOM.createDiv();
- int lines = 1;
- for (int i = 0; i < lines; i++) {
- Element e = DOM.createDiv();
- p.appendChild(e);
-
- Element pre = DOM.createElement("pre");
- pre.setInnerText("gqyŚŻŹŃ");
- e.appendChild(pre);
- }
-
- cmB.getMeasureElement().appendChild(p);
- lineHeightPx = ((double) p.getOffsetHeight()) / lines;
- p.removeFromParent();
- }
- return lineHeightPx;
- }
-
- private double getCharWidthPx() {
- if (charWidthPx <= 1) {
- int len = 100;
- StringBuilder s = new StringBuilder();
- for (int i = 0; i < len; i++) {
- s.append('m');
- }
- Element e = DOM.createSpan();
- e.getStyle().setDisplay(Style.Display.INLINE_BLOCK);
- e.setInnerText(s.toString());
-
- cmA.getMeasureElement().appendChild(e);
- double a = ((double) e.getOffsetWidth()) / len;
- e.removeFromParent();
-
- cmB.getMeasureElement().appendChild(e);
- double b = ((double) e.getOffsetWidth()) / len;
- e.removeFromParent();
- charWidthPx = Math.max(a, b);
- }
- return charWidthPx;
+ cmA.extras().lineLength(columns);
+ cmB.extras().lineLength(columns);
}
void setShowLineNumbers(boolean b) {
@@ -736,11 +738,14 @@ public class SideBySide2 extends Screen {
public void run() {
skipManager.removeAll();
skipManager.render(context, diff);
- diffTable.overview.refresh();
}
});
}
+ void setAutoHideDiffHeader(boolean hide) {
+ scrollSynchronizer.setAutoHideDiffTableHeader(hide);
+ }
+
private void render(DiffInfo diff) {
header.setNoDiff(diff);
chunkManager.render(diff);
@@ -758,18 +763,10 @@ public class SideBySide2 extends Screen {
return chunkManager.getLineMapper().lineOnOther(side, line);
}
- private void clearActiveLine(CodeMirror cm) {
- if (cm.hasActiveLine()) {
- LineHandle activeLine = cm.getActiveLine();
- cm.removeLineClass(activeLine,
- LineClassWhere.WRAP, DiffTable.style.activeLine());
- cm.setActiveLine(null);
- }
- }
-
private Runnable updateActiveLine(final CodeMirror cm) {
final CodeMirror other = otherCm(cm);
return new Runnable() {
+ @Override
public void run() {
// The rendering of active lines has to be deferred. Reflow
// caused by adding and removing styles chokes Firefox when arrow
@@ -780,25 +777,20 @@ public class SideBySide2 extends Screen {
@Override
public void execute() {
operation(new Runnable() {
+ @Override
public void run() {
- LineHandle handle = cm.getLineHandleVisualStart(
- cm.getCursor("end").getLine());
- if (cm.hasActiveLine() && cm.getActiveLine().equals(handle)) {
+ LineHandle handle =
+ cm.getLineHandleVisualStart(cm.getCursor("end").line());
+ if (!cm.extras().activeLine(handle)) {
return;
}
- clearActiveLine(cm);
- clearActiveLine(other);
- cm.setActiveLine(handle);
- cm.addLineClass(
- handle, LineClassWhere.WRAP, DiffTable.style.activeLine());
LineOnOtherInfo info =
lineOnOther(cm.side(), cm.getLineNumber(handle));
if (info.isAligned()) {
- LineHandle oLineHandle = other.getLineHandle(info.getLine());
- other.setActiveLine(oLineHandle);
- other.addLineClass(oLineHandle, LineClassWhere.WRAP,
- DiffTable.style.activeLine());
+ other.extras().activeLine(other.getLineHandle(info.getLine()));
+ } else {
+ other.extras().clearActiveLine();
}
}
});
@@ -811,21 +803,18 @@ public class SideBySide2 extends Screen {
private GutterClickHandler onGutterClick(final CodeMirror cm) {
return new GutterClickHandler() {
@Override
- public void handle(CodeMirror instance, int line, String gutter,
+ public void handle(CodeMirror instance, final int line, String gutter,
NativeEvent clickEvent) {
if (clickEvent.getButton() == NativeEvent.BUTTON_LEFT
&& !clickEvent.getMetaKey()
&& !clickEvent.getAltKey()
&& !clickEvent.getCtrlKey()
&& !clickEvent.getShiftKey()) {
- if (!(cm.hasActiveLine() &&
- cm.getLineNumber(cm.getActiveLine()) == line)) {
- cm.setCursor(LineCharacter.create(line));
- }
+ cm.setCursor(Pos.create(line));
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
- commentManager.insertNewDraft(cm).run();
+ commentManager.newDraft(cm, line + 1);
}
});
}
@@ -835,6 +824,7 @@ public class SideBySide2 extends Screen {
private Runnable upToChange(final boolean openReplyBox) {
return new Runnable() {
+ @Override
public void run() {
CallbackGroup group = new CallbackGroup();
commentManager.saveAllDrafts(group);
@@ -842,11 +832,12 @@ public class SideBySide2 extends Screen {
group.addListener(new GerritCallback<Void>() {
@Override
public void onSuccess(Void result) {
- String b = base != null ? String.valueOf(base.get()) : null;
- String rev = String.valueOf(revision.get());
+ String b = base != null ? base.getId() : null;
+ String rev = revision.getId();
Gerrit.display(
PageLinks.toChange(changeId, b, rev),
- new ChangeScreen2(changeId, b, rev, openReplyBox));
+ new ChangeScreen(changeId, b, rev, openReplyBox,
+ FileTable.Mode.REVIEW));
}
});
}
@@ -865,11 +856,12 @@ public class SideBySide2 extends Screen {
final DisplaySide sideSrc = cmSrc.side();
return new Runnable() {
+ @Override
public void run() {
- if (cmSrc.hasActiveLine()) {
- cmDst.setCursor(LineCharacter.create(lineOnOther(
+ if (cmSrc.extras().hasActiveLine()) {
+ cmDst.setCursor(Pos.create(lineOnOther(
sideSrc,
- cmSrc.getLineNumber(cmSrc.getActiveLine())).getLine()));
+ cmSrc.getLineNumber(cmSrc.extras().activeLine())).getLine()));
}
cmDst.focus();
}
@@ -880,8 +872,8 @@ public class SideBySide2 extends Screen {
return new Runnable() {
@Override
public void run() {
- if (cm.hasVimSearchHighlight()) {
- CodeMirror.handleVimKey(cm, "N");
+ if (cm.vim().hasSearchHighlight()) {
+ cm.vim().handleKey("N");
} else {
commentManager.commentNav(cm, Direction.NEXT).run();
}
@@ -893,8 +885,8 @@ public class SideBySide2 extends Screen {
return new Runnable() {
@Override
public void run() {
- if (cm.hasVimSearchHighlight()) {
- CodeMirror.handleVimKey(cm, "n");
+ if (cm.vim().hasSearchHighlight()) {
+ cm.vim().handleKey("n");
} else {
chunkManager.diffChunkNav(cm, Direction.NEXT).run();
}
@@ -902,6 +894,69 @@ public class SideBySide2 extends Screen {
};
}
+ private int adjustCommitMessageLine(int line) {
+ /* When commit messages are shown in the side-by-side screen they include
+ a header block that looks like this:
+
+ 1 Parent: deadbeef (Parent commit title)
+ 2 Author: A. U. Thor <author@example.com>
+ 3 AuthorDate: 2015-02-27 19:20:52 +0900
+ 4 Commit: A. U. Thor <author@example.com>
+ 5 CommitDate: 2015-02-27 19:20:52 +0900
+ 6 [blank line]
+ 7 Commit message title
+ 8
+ 9 Commit message body
+ 10 ...
+ 11 ...
+
+ If the commit is a merge commit, both parent commits are listed in the
+ first two lines instead of a 'Parent' line:
+
+ 1 Merge Of: deadbeef (Parent 1 commit title)
+ 2 beefdead (Parent 2 commit title)
+
+ */
+
+ // Offset to compensate for header lines until the blank line
+ // after 'CommitDate'
+ int offset = 6;
+
+ // Adjust for merge commits, which have two parent lines
+ if (diff.text_b().startsWith("Merge")) {
+ offset += 1;
+ }
+
+ // If the cursor is inside the header line, reset to the first line of the
+ // commit message. Otherwise if the cursor is on an actual line of the commit
+ // message, adjust the line number to compensate for the header lines, so the
+ // focus is on the correct line.
+ if (line <= offset) {
+ return 1;
+ } else {
+ return line - offset;
+ }
+ }
+
+ private Runnable openEditScreen(final CodeMirror cm) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ LineHandle handle = cm.extras().activeLine();
+ int line = cm.getLineNumber(handle) + 1;
+ if (Patch.COMMIT_MSG.equals(path)) {
+ line = adjustCommitMessageLine(line);
+ }
+ String token = Dispatcher.toEditScreen(revision, path, line);
+ if (!Gerrit.isSignedIn()) {
+ Gerrit.doSignIn(token);
+ } else {
+ Gerrit.display(token);
+ }
+ }
+ };
+ }
+
void updateRenderEntireFile() {
cmA.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
cmB.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
@@ -915,18 +970,9 @@ public class SideBySide2 extends Screen {
}
void resizeCodeMirror() {
- int height = getCodeMirrorHeight();
- cmA.setHeight(height);
- cmB.setHeight(height);
- diffTable.overview.refresh();
- }
-
- private int getCodeMirrorHeight() {
- int rest = Gerrit.getHeaderFooterHeight()
- + header.getOffsetHeight()
- + diffTable.getHeaderHeight()
- + 5; // Estimate
- return Window.getClientHeight() - rest;
+ int hdr = header.getOffsetHeight() + diffTable.getHeaderHeight();
+ cmA.adjustHeight(hdr);
+ cmB.adjustHeight(hdr);
}
void syncScroll(DisplaySide masterSide) {
@@ -936,11 +982,12 @@ public class SideBySide2 extends Screen {
}
private String getContentType(DiffInfo.FileMeta meta) {
- return prefs.syntaxHighlighting()
- && meta != null
- && meta.content_type() != null
- ? ModeInjector.getContentType(meta.content_type())
- : null;
+ if (prefs.syntaxHighlighting() && meta != null
+ && meta.content_type() != null) {
+ ModeInfo m = ModeInfo.findMode(meta.content_type(), path);
+ return m != null ? m.mime() : null;
+ }
+ return null;
}
private void injectMode(DiffInfo diffInfo, AsyncCallback<Void> cb) {
@@ -950,6 +997,10 @@ public class SideBySide2 extends Screen {
.inject(cb);
}
+ String getPath() {
+ return path;
+ }
+
DiffPreferences getPrefs() {
return prefs;
}
@@ -1021,7 +1072,7 @@ public class SideBySide2 extends Screen {
public void run() {
skipManager.removeAll();
chunkManager.reset();
- diffTable.overview.clearDiffMarkers();
+ diffTable.scrollbar.removeDiffAnnotations();
setShowIntraline(prefs.intralineDifference());
render(diff);
chunkManager.adjustPadding();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.ui.xml
index 8c26873403..da5b35158c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.ui.xml
@@ -18,12 +18,12 @@ limitations under the License.
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:d='urn:import:com.google.gerrit.client.diff'>
<ui:style>
- .sbs2 {
+ .sbs {
margin-left: -5px;
margin-right: -5px;
}
</ui:style>
- <g:FlowPanel styleName='{style.sbs2}'>
+ <g:FlowPanel styleName='{style.sbs}'>
<d:Header ui:field='header'/>
<d:DiffTable ui:field='diffTable'/>
</g:FlowPanel>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
index 4efaf54df5..4c12cc05ff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
@@ -30,6 +30,7 @@ import com.google.gwt.user.client.ui.HTMLPanel;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.Configuration;
import net.codemirror.lib.LineWidget;
+import net.codemirror.lib.Pos;
import net.codemirror.lib.TextMarker;
import net.codemirror.lib.TextMarker.FromTo;
@@ -95,8 +96,8 @@ class SkipBar extends Composite {
}
textMarker = cm.markText(
- CodeMirror.pos(start, 0),
- CodeMirror.pos(end),
+ Pos.create(start, 0),
+ Pos.create(end),
Configuration.create()
.set("collapsed", true)
.set("inclusiveLeft", true)
@@ -133,14 +134,13 @@ class SkipBar extends Composite {
void expandBefore(int cnt) {
expandSideBefore(cnt);
otherBar.expandSideBefore(cnt);
- manager.getOverviewBar().refresh();
}
private void expandSideBefore(int cnt) {
FromTo range = textMarker.find();
- int oldStart = range.getFrom().getLine();
+ int oldStart = range.from().line();
int newStart = oldStart + cnt;
- int end = range.getTo().getLine();
+ int end = range.to().line();
clearMarkerAndWidget();
collapse(newStart, end, true);
updateSelection();
@@ -153,8 +153,8 @@ class SkipBar extends Composite {
private void expandAfter() {
FromTo range = textMarker.find();
- int start = range.getFrom().getLine();
- int oldEnd = range.getTo().getLine();
+ int start = range.from().line();
+ int oldEnd = range.to().line();
int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
boolean attach = start == 0;
if (attach) {
@@ -169,12 +169,12 @@ class SkipBar extends Composite {
private void updateSelection() {
if (cm.somethingSelected()) {
FromTo sel = cm.getSelectedRange();
- cm.setSelection(sel.getFrom(), sel.getTo());
+ cm.setSelection(sel.from(), sel.to());
}
}
@UiHandler("skipNum")
- void onExpandAll(ClickEvent e) {
+ void onExpandAll(@SuppressWarnings("unused") ClickEvent e) {
expandAll();
updateSelection();
otherBar.updateSelection();
@@ -185,20 +185,18 @@ class SkipBar extends Composite {
expandSideAll();
otherBar.expandSideAll();
manager.remove(this, otherBar);
- manager.getOverviewBar().refresh();
}
@UiHandler("upArrow")
- void onExpandBefore(ClickEvent e) {
+ void onExpandBefore(@SuppressWarnings("unused") ClickEvent e) {
expandBefore(NUM_ROWS_TO_EXPAND);
cm.focus();
}
@UiHandler("downArrow")
- void onExpandAfter(ClickEvent e) {
+ void onExpandAfter(@SuppressWarnings("unused") ClickEvent e) {
expandAfter();
otherBar.expandAfter();
- manager.getOverviewBar().refresh();
cm.focus();
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
index 5ba275fd9e..4972f5ddb7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
@@ -26,22 +26,18 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-/** Collapses common regions with {@link SkipBar} for {@link SideBySide2}. */
+/** Collapses common regions with {@link SkipBar} for {@link SideBySide}. */
class SkipManager {
- private final SideBySide2 host;
+ private final SideBySide host;
private final CommentManager commentManager;
private Set<SkipBar> skipBars;
private SkipBar line0;
- SkipManager(SideBySide2 host, CommentManager commentManager) {
+ SkipManager(SideBySide host, CommentManager commentManager) {
this.host = host;
this.commentManager = commentManager;
}
- OverviewBar getOverviewBar() {
- return host.diffTable.overview;
- }
-
void render(int context, DiffInfo diff) {
if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
return;
@@ -111,7 +107,6 @@ class SkipManager {
for (SkipBar bar : skipBars) {
bar.expandSideAll();
}
- getOverviewBar().refresh();
skipBars = null;
line0 = null;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UpToChangeCommand2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UpToChangeCommand.java
index 7071e7fa94..5d554940c2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UpToChangeCommand2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/UpToChangeCommand.java
@@ -21,10 +21,10 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwtexpui.globalkey.client.KeyCommand;
-class UpToChangeCommand2 extends KeyCommand {
+class UpToChangeCommand extends KeyCommand {
private final PatchSet.Id revision;
- UpToChangeCommand2(PatchSet.Id revision, int mask, int key) {
+ UpToChangeCommand(PatchSet.Id revision, int mask, int key) {
super(mask, key, PatchUtil.C.upToChange());
this.revision = revision;
}
@@ -33,6 +33,6 @@ class UpToChangeCommand2 extends KeyCommand {
public void onKeyPress(final KeyPressEvent event) {
Gerrit.display(PageLinks.toChange(
revision.getParentKey(),
- String.valueOf(revision.get())));
+ revision.getId()));
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goNext.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goNext.png
new file mode 100644
index 0000000000..872c1979e0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goNext.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goPrev.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goPrev.png
new file mode 100644
index 0000000000..d68f29b34c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goPrev.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goUp.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goUp.png
new file mode 100644
index 0000000000..f75bed4478
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/goUp.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
index f176372798..b57cdacda5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
@@ -16,9 +16,9 @@ package com.google.gerrit.client.documentation;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
index a323a767df..cea9106213 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
@@ -129,7 +129,7 @@ public abstract class DownloadCommandLink extends Anchor implements ClickHandler
protected void setCurrentUrl(DownloadUrlLink link) {
widget.setVisible(true);
- String sshPort = "29418";
+ String sshPort = null;
String sshAddr = Gerrit.getConfig().getSshdAddress();
int p = sshAddr.lastIndexOf(':');
if (p != -1 && !sshAddr.endsWith(":")) {
@@ -139,9 +139,12 @@ public abstract class DownloadCommandLink extends Anchor implements ClickHandler
StringBuilder cmd = new StringBuilder();
cmd.append("git clone ");
cmd.append(link.getUrlData());
- cmd.append(" && scp -p -P ");
- cmd.append(sshPort);
- cmd.append(" ");
+ cmd.append(" && scp -p ");
+ if (sshPort != null) {
+ cmd.append("-P ");
+ cmd.append(sshPort);
+ cmd.append(" ");
+ }
cmd.append(Gerrit.getUserAccount().getUserName());
cmd.append("@");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
index 845537e8f5..ce5c0606ba 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
@@ -19,9 +19,9 @@ import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gwt.aria.client.Roles;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Widget;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png
deleted file mode 100644
index 2927275f56..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.java
new file mode 100644
index 0000000000..e95fdfcdf6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2015 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.client.editor;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.Constants;
+
+interface EditConstants extends Constants {
+ static final EditConstants I = GWT.create(EditConstants.class);
+
+ String closeUnsavedChanges();
+ String cancelUnsavedChanges();
+
+ String gotoLineNumber();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.properties
new file mode 100644
index 0000000000..2e8a08708f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditConstants.properties
@@ -0,0 +1,7 @@
+closeUnsavedChanges = Unsaved changes were made to this file.
+
+cancelUnsavedChanges = Unsaved changes were made to this file.\n\
+ \n\
+ Discard unsaved changes?
+
+gotoLineNumber = Go to Line: \ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditFileInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditFileInfo.java
new file mode 100644
index 0000000000..c833c5d999
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditFileInfo.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2015 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.client.editor;
+
+import com.google.gerrit.client.DiffWebLinkInfo;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class EditFileInfo extends JavaScriptObject {
+ public final native JsArray<DiffWebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
+
+ protected EditFileInfo() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
new file mode 100644
index 0000000000..dd36657954
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -0,0 +1,477 @@
+// Copyright (C) 2014 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.client.editor;
+
+import static com.google.gwt.dom.client.Style.Visibility.HIDDEN;
+import static com.google.gwt.dom.client.Style.Visibility.VISIBLE;
+
+import com.google.gerrit.client.DiffWebLinkInfo;
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.JumpKeys;
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.account.DiffPreferences;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeEditApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.diff.DiffApi;
+import com.google.gerrit.client.diff.DiffInfo;
+import com.google.gerrit.client.diff.FileInfo;
+import com.google.gerrit.client.diff.Header;
+import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.HttpCallback;
+import com.google.gerrit.client.rpc.HttpResponse;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.InlineHyperlink;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.ChangesHandler;
+import net.codemirror.lib.Configuration;
+import net.codemirror.lib.KeyMap;
+import net.codemirror.lib.Pos;
+import net.codemirror.mode.ModeInfo;
+import net.codemirror.mode.ModeInjector;
+import net.codemirror.theme.ThemeLoader;
+
+import java.util.List;
+
+public class EditScreen extends Screen {
+ interface Binder extends UiBinder<HTMLPanel, EditScreen> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ private final PatchSet.Id base;
+ private final PatchSet.Id revision;
+ private final String path;
+ private final int startLine;
+ private DiffPreferences prefs;
+ private CodeMirror cm;
+ private HttpResponse<NativeString> content;
+ private EditFileInfo editFileInfo;
+ private JsArray<DiffWebLinkInfo> diffLinks;
+
+ @UiField Element header;
+ @UiField Element project;
+ @UiField Element filePath;
+ @UiField FlowPanel linkPanel;
+ @UiField Element cursLine;
+ @UiField Element cursCol;
+ @UiField Element dirty;
+ @UiField Button close;
+ @UiField Button save;
+ @UiField Element editor;
+
+ private HandlerRegistration resizeHandler;
+ private HandlerRegistration closeHandler;
+ private int generation;
+
+ public EditScreen(PatchSet.Id base, Patch.Key patch, int startLine) {
+ this.base = base;
+ this.revision = patch.getParentKey();
+ this.path = patch.get();
+ this.startLine = startLine - 1;
+ prefs = DiffPreferences.create(Gerrit.getAccountDiffPreference());
+ add(uiBinder.createAndBindUi(this));
+ addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ setHeaderVisible(false);
+ setWindowTitle(FileInfo.getFileName(path));
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+
+ CallbackGroup group1 = new CallbackGroup();
+ final CallbackGroup group2 = new CallbackGroup();
+ final CallbackGroup group3 = new CallbackGroup();
+
+ CodeMirror.initLibrary(group1.add(new AsyncCallback<Void>() {
+ final AsyncCallback<Void> themeCallback = group3.addEmpty();
+
+ @Override
+ public void onSuccess(Void result) {
+ // Load theme after CM library to ensure theme can override CSS.
+ ThemeLoader.loadTheme(prefs.theme(), themeCallback);
+
+ group2.done();
+ group3.done();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+
+ ChangeApi.detail(revision.getParentKey().get(),
+ group1.add(new AsyncCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo c) {
+ project.setInnerText(c.project());
+ SafeHtml.setInnerHTML(filePath, Header.formatPath(path, null, null));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+
+
+ if (revision.get() == 0) {
+ ChangeEditApi.getMeta(revision, path,
+ group1.add(new AsyncCallback<EditFileInfo>() {
+ @Override
+ public void onSuccess(EditFileInfo editInfo) {
+ editFileInfo = editInfo;
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ }
+ }));
+ } else {
+ // TODO(davido): We probably want to create dedicated GET EditScreenMeta
+ // REST endpoint. Abuse GET diff for now, as it retrieves links we need.
+ DiffApi.diff(revision, path)
+ .base(base)
+ .webLinksOnly()
+ .get(group1.add(new AsyncCallback<DiffInfo>() {
+ @Override
+ public void onSuccess(DiffInfo diffInfo) {
+ diffLinks = diffInfo.web_links();
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ }
+ }));
+ }
+
+ ChangeEditApi.get(revision, path,
+ group2.add(new HttpCallback<NativeString>() {
+ final AsyncCallback<Void> modeCallback = group3.addEmpty();
+
+ @Override
+ public void onSuccess(HttpResponse<NativeString> fc) {
+ content = fc;
+ if (prefs.syntaxHighlighting()) {
+ injectMode(fc.getContentType(), modeCallback);
+ } else {
+ modeCallback.onSuccess(null);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ // "Not Found" means it's a new file.
+ if (RestApi.isNotFound(e)) {
+ content = null;
+ modeCallback.onSuccess(null);
+ } else {
+ GerritCallback.showFailure(e);
+ }
+ }
+ }));
+
+ group3.addListener(new ScreenLoadCallback<Void>(this) {
+ @Override
+ protected void preDisplay(Void result) {
+ initEditor(content);
+ content = null;
+
+ renderLinks(editFileInfo, diffLinks);
+ editFileInfo = null;
+ diffLinks = null;
+ }
+ });
+ group1.done();
+ }
+
+ @Override
+ public void registerKeys() {
+ super.registerKeys();
+ cm.addKeyMap(KeyMap.create()
+ .on("Ctrl-L", gotoLine())
+ .on("Cmd-L", gotoLine()));
+ }
+
+ private Runnable gotoLine() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ String n = Window.prompt(EditConstants.I.gotoLineNumber(), "");
+ if (n != null) {
+ try {
+ int line = Integer.parseInt(n);
+ line--;
+ if (line >= 0) {
+ cm.scrollToLine(line);
+ }
+ } catch (NumberFormatException e) {
+ // ignore non valid numbers
+ // We don't want to popup another ugly dialog just to say
+ // "The number you've provided is invalid, try again"
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onShowView() {
+ super.onShowView();
+ Window.enableScrolling(false);
+ JumpKeys.enable(false);
+ if (prefs.hideTopMenu()) {
+ Gerrit.setHeaderVisible(false);
+ }
+ resizeHandler = Window.addResizeHandler(new ResizeHandler() {
+ @Override
+ public void onResize(ResizeEvent event) {
+ cm.adjustHeight(header.getOffsetHeight());
+ }
+ });
+ closeHandler = Window.addWindowClosingHandler(new ClosingHandler() {
+ @Override
+ public void onWindowClosing(ClosingEvent event) {
+ if (!cm.isClean(generation)) {
+ event.setMessage(EditConstants.I.closeUnsavedChanges());
+ }
+ }
+ });
+
+ generation = cm.changeGeneration(true);
+ setClean(true);
+ cm.on(new ChangesHandler() {
+ @Override
+ public void handle(CodeMirror cm) {
+ setClean(cm.isClean(generation));
+ }
+ });
+
+ cm.adjustHeight(header.getOffsetHeight());
+ cm.on("cursorActivity", updateCursorPosition());
+ cm.extras().showTabs(prefs.showTabs());
+ cm.extras().lineLength(
+ Patch.COMMIT_MSG.equals(path) ? 72 : prefs.lineLength());
+ cm.refresh();
+ cm.focus();
+
+ if (startLine > 0) {
+ cm.scrollToLine(startLine);
+ }
+ updateActiveLine();
+ }
+
+ @Override
+ protected void onUnload() {
+ super.onUnload();
+ if (cm != null) {
+ cm.getWrapperElement().removeFromParent();
+ }
+ if (resizeHandler != null) {
+ resizeHandler.removeHandler();
+ }
+ if (closeHandler != null) {
+ closeHandler.removeHandler();
+ }
+ Window.enableScrolling(true);
+ Gerrit.setHeaderVisible(true);
+ JumpKeys.enable(true);
+ }
+
+ @UiHandler("save")
+ void onSave(@SuppressWarnings("unused") ClickEvent e) {
+ save().run();
+ }
+
+ @UiHandler("close")
+ void onClose(@SuppressWarnings("unused") ClickEvent e) {
+ if (cm.isClean(generation)
+ || Window.confirm(EditConstants.I.cancelUnsavedChanges())) {
+ upToChange();
+ }
+ }
+
+ private void upToChange() {
+ Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey()));
+ }
+
+ private void initEditor(HttpResponse<NativeString> file) {
+ ModeInfo mode = null;
+ String content = "";
+ if (file != null && file.getResult() != null) {
+ content = file.getResult().asString();
+ if (prefs.syntaxHighlighting()) {
+ mode = ModeInfo.findMode(file.getContentType(), path);
+ }
+ }
+ cm = CodeMirror.create(editor, Configuration.create()
+ .set("value", content)
+ .set("readOnly", false)
+ .set("cursorBlinkRate", 0)
+ .set("cursorHeight", 0.85)
+ .set("lineNumbers", true)
+ .set("tabSize", prefs.tabSize())
+ .set("lineWrapping", false)
+ .set("scrollbarStyle", "overlay")
+ .set("styleSelectedText", true)
+ .set("showTrailingSpace", true)
+ .set("keyMap", "default")
+ .set("theme", prefs.theme().name().toLowerCase())
+ .set("mode", mode != null ? mode.mode() : null));
+ cm.addKeyMap(KeyMap.create()
+ .on("Cmd-S", save())
+ .on("Ctrl-S", save()));
+ }
+
+ private void renderLinks(EditFileInfo editInfo,
+ JsArray<DiffWebLinkInfo> diffLinks) {
+ renderLinksToDiff();
+
+ if (editInfo != null) {
+ renderLinks(Natives.asList(editInfo.web_links()));
+ } else if (diffLinks != null) {
+ renderLinks(Natives.asList(diffLinks));
+ }
+ }
+
+ private void renderLinks(List<DiffWebLinkInfo> links) {
+ if (links != null) {
+ for (DiffWebLinkInfo webLink : links) {
+ linkPanel.add(webLink.toAnchor());
+ }
+ }
+ }
+
+ private void renderLinksToDiff() {
+ InlineHyperlink sbs = new InlineHyperlink();
+ sbs.setHTML(new ImageResourceRenderer()
+ .render(Gerrit.RESOURCES.sideBySideDiff()));
+ sbs.setTargetHistoryToken(
+ Dispatcher.toPatch("sidebyside", base, new Patch.Key(revision, path)));
+ sbs.setTitle(PatchUtil.C.sideBySideDiff());
+ linkPanel.add(sbs);
+
+ InlineHyperlink unified = new InlineHyperlink();
+ unified.setHTML(new ImageResourceRenderer()
+ .render(Gerrit.RESOURCES.unifiedDiff()));
+ unified.setTargetHistoryToken(
+ Dispatcher.toPatch("unified", base, new Patch.Key(revision, path)));
+ unified.setTitle(PatchUtil.C.unifiedDiff());
+ linkPanel.add(unified);
+ }
+
+ private Runnable updateCursorPosition() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ // The rendering of active lines has to be deferred. Reflow
+ // caused by adding and removing styles chokes Firefox when arrow
+ // key (or j/k) is held down. Performance on Chrome is fine
+ // without the deferral.
+ //
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ cm.operation(new Runnable() {
+ @Override
+ public void run() {
+ updateActiveLine();
+ }
+ });
+ }
+ });
+ }
+ };
+ }
+
+ private void updateActiveLine() {
+ Pos p = cm.getCursor("end");
+ cursLine.setInnerText(Integer.toString(p.line() + 1));
+ cursCol.setInnerText(Integer.toString(p.ch() + 1));
+ cm.extras().activeLine(cm.getLineHandleVisualStart(p.line()));
+ }
+
+ private void setClean(boolean clean) {
+ save.setEnabled(!clean);
+ close.setEnabled(true);
+ dirty.getStyle().setVisibility(!clean ? VISIBLE : HIDDEN);
+ }
+
+ private Runnable save() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ if (!cm.isClean(generation)) {
+ close.setEnabled(false);
+ String text = cm.getValue();
+ final int g = cm.changeGeneration(false);
+ ChangeEditApi.put(revision.getParentKey().get(), path, text,
+ new GerritCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ generation = g;
+ setClean(cm.isClean(g));
+ }
+ @Override
+ public void onFailure(final Throwable caught) {
+ close.setEnabled(true);
+ }
+ });
+ }
+ }
+ };
+ }
+
+ private void injectMode(String type, AsyncCallback<Void> cb) {
+ new ModeInjector().add(type).inject(cb);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
new file mode 100644
index 0000000000..93c3bb9db6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ <ui:style>
+ @external .CodeMirror, .CodeMirror-cursor;
+
+ .header {
+ position: relative;
+ height: 16px;
+ line-height: 16px;
+ }
+
+ .header .CodeMirror div.CodeMirror-cursor {
+ border-left: 2px solid black;
+ }
+
+ .headerLine {
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ddd;
+ padding-left: 30px;
+ }
+
+ .headerButtons {
+ display: inline-block;
+ padding-right: 5px;
+ border-right: 1px inset #ddd;
+ margin-right: 5px;
+ }
+
+ .headerButtons button:disabled {
+ background-color: #ddd;
+ font-weight: normal;
+ cursor: default;
+ }
+
+ .headerButtons button {
+ margin: 2px 0 2px 0;
+ text-align: center;
+ font-size: 8pt;
+ cursor: pointer;
+ border: 1px solid;
+ color: rgba(0, 0, 0, 0.15);
+ background-color: #f5f5f5;
+ -webkit-border-radius: 1px;
+ -webkit-box-sizing: content-box;
+ }
+
+ .headerButtons button div {
+ color: #444;
+ min-width: 54px;
+ white-space: nowrap;
+ line-height: 8pt;
+ }
+
+ .save {
+ font-weight: bold;
+ }
+
+ .path {
+ white-space: nowrap;
+ }
+
+ .statusLine {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 175px;
+ height: 19px;
+ background-color: #f7f7f7;
+ border-top: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+ }
+ .statusLine div {
+ height: inherit;
+ }
+
+ .cursorPosition {
+ display: inline-block;
+ margin: 0 5px 0 35px;
+ white-space: nowrap;
+ }
+
+ .dirty {
+ display: inline-block;
+ margin: 0 5px 0 5px;
+ padding: 0 0 0 5px;
+ border-left: 1px solid #ddd;
+ font-weight: bold;
+ }
+
+ .navigation {
+ position: absolute;
+ top: 0;
+ right: 10px;
+ }
+ .linkPanel {
+ float: left;
+ }
+ .linkPanel img {
+ padding-top: 2px;
+ padding-right: 3px;
+ }
+ </ui:style>
+ <g:HTMLPanel styleName='{style.header}'>
+ <div class='{style.headerLine}' ui:field='header'>
+ <div class='{style.headerButtons}'>
+ <g:Button ui:field='close'
+ styleName=''
+ title='Close file and return to change'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Close</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='save'
+ styleName='{style.save}'
+ title='Save'>
+ <ui:attribute name='title'/>
+ <div><ui:msg>Save</ui:msg></div>
+ </g:Button>
+ </div>
+ <span class='{style.path}'><span ui:field='project'/> / <span ui:field='filePath'/></span>
+ <div class='{style.navigation}'>
+ <g:FlowPanel ui:field='linkPanel' styleName='{style.linkPanel}'/>
+ </div>
+ </div>
+ <div ui:field='editor' />
+ <div class='{style.statusLine}'>
+ <div class='{style.cursorPosition}'><span ui:field='cursLine'/> : <span ui:field='cursCol'/></div>
+ <div class='{style.dirty}' ui:field='dirty'>Unsaved</div>
+ </div>
+ </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.png
deleted file mode 100644
index 2f84e4734f..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index b3602a2360..8d961ca20d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -84,10 +84,6 @@ a:hover {
color: black;
}
-.hyperlink {
- text-decoration: underline;
-}
-
.accountLinkPanel {
display: inline;
}
@@ -105,10 +101,6 @@ a:hover {
top: -1px;
}
-.accountName {
- white-space: nowrap;
-}
-
.inputFieldTypeHint {
color: grey;
}
@@ -118,13 +110,6 @@ a:hover {
font-weight: bold;
}
-.blockHeader {
- font-size: small;
- font-weight: bold;
- background: trimColor;
- padding: 0.2em 0.2em 0.2em 0.5em;
-}
-
.link {
cursor: pointer;
}
@@ -156,47 +141,6 @@ a:hover {
}
/** CommentPanel **/
-.commentPanelBorder {
- border-top: 1px solid lightgray;
- border-left: 1px solid lightgray;
- border-right: 1px solid lightgray;
- border-top-left-radius: 8px;
- border-top-right-radius: 8px;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
-}
-.commentPanelBorder.commentPanelLast {
- border-bottom: 1px solid lightgray;
- border-bottom-left-radius: 8px;
- border-bottom-right-radius: 8px;
-}
-
-@if user.agent safari {
- .commentPanelBorder {
- \-webkit-border-top-left-radius: 8px;
- \-webkit-border-top-right-radius: 8px;
- \-webkit-border-bottom-left-radius: 0px;
- \-webkit-border-bottom-right-radius: 0px;
- }
- .commentPanelBorder.commentPanelLast {
- \-webkit-border-bottom-left-radius: 8px;
- \-webkit-border-bottom-right-radius: 8px;
- }
-}
-
-@if user.agent gecko1_8 {
- .commentPanelBorder {
- \-moz-border-radius-topleft: 8px;
- \-moz-border-radius-topright: 8px;
- \-moz-border-radius-bottomleft: 0;
- \-moz-border-radius-bottomright: 0;
- }
- .commentPanelBorder.commentPanelLast {
- \-moz-border-radius-bottomleft: 8px;
- \-moz-border-radius-bottomright: 8px;
- }
-}
-
.commentPanelHeader {
cursor: pointer;
width: 100%;
@@ -224,14 +168,12 @@ a:hover {
padding-left: 0.5em;
padding-right: 0.5em;
}
-.commentPanelMenuBar {
- float: right;
-}
.commentPanelMessage p {
margin-top: 0px;
margin-bottom: 0px;
padding-top: 0.5em;
padding-bottom: 0.5em;
+ max-height: 100000px;
}
.commentPanelButtons {
margin-left: 0.5em;
@@ -427,10 +369,6 @@ a:hover {
width: 100%;
margin-top: 15px;
}
-.errorDialogText {
- font-size: 15px;
- font-family: verdana;
-}
.errorDialog a,
.errorDialog a:visited,
.errorDialog a:hover {
@@ -458,10 +396,6 @@ a:hover {
overflow: hidden;
}
-.screenNoHeader {
- margin-top: 5px;
-}
-
/** ChangeTable **/
.changeTable {
border-collapse: separate;
@@ -476,10 +410,6 @@ a:hover {
background: tableOddRowColor;
}
-.changeTable .outdated {
- background: changeTableOutdatedColor !important;
-}
-
.changeTable .iconCell {
width: 1px;
padding: 0px;
@@ -674,15 +604,15 @@ a:hover {
/** PatchScreen **/
-.patchScreenDisplayControls .gwt-CheckBox {
- margin-right: 1em;
-}
-
.reviewedPanelBottom {
float: right;
font-size: small;
}
+.linkPanel img {
+ padding-right: 3px;
+}
+
/** PatchContentTable **/
.patchContentTable {
@@ -711,10 +641,6 @@ a:hover {
border-left: thin solid #b0bdcc;
}
-.patchContentTable .diffTextForBinaryInSideBySide {
- width: 50%;
-}
-
.patchContentTable .diffTextFileHeader {
color: grey;
font-weight: bold;
@@ -782,9 +708,6 @@ a:hover {
background: white;
border-bottom: 1px solid white;
}
-.lineNumber.rightmost {
- border-left: thin solid #b0bdcc;
-}
.lineNumber.rightBorder {
border-right: thin solid #b0bdcc;
}
@@ -804,14 +727,6 @@ a:hover {
.lineNumber.fileColumnHeader {
border-bottom: 1px solid trimColor;
}
-.noLineLineNumber {
- font-family: mono-font;
- width: 3.5em;
- padding-left: 0.2em;
- padding-right: 0.2em;
- background: white;
- border-bottom: 1px solid white;
-}
.fileLine {
padding-left: 0;
@@ -819,22 +734,11 @@ a:hover {
white-space: pre;
border-left: thin solid #b0bdcc;
}
-.fileLineNone {
- background: #eeeeee;
- border-bottom: 1px solid #eeeeee;
-}
-.fileLineMode {
- font-weight: bold;
-}
.fileLineDELETE,
.fileLineDELETE .wdc {
background: #ffeeee;
border-bottom: 1px solid #ffeeee;
}
-.fileLineCONTEXT {
- background: white;
- border-bottom: 1px solid white;
-}
.fileLineINSERT,
.fileLineINSERT .wdc {
background: #ddffdd;
@@ -847,27 +751,6 @@ a:hover {
border-bottom: 1px solid #9F9;
}
-.patchContentTable .skipLine .iconCell,
-.patchContentTable .skipLine {
- font-family: norm-font;
- text-align: center;
- font-style: italic;
- background: #def;
- color: grey;
-}
-.patchContentTable .skipLine div {
- display: inline;
-}
-.patchContentTable a.skipLine {
- color: grey;
- text-decoration: none;
-}
-.patchContentTable a:hover.skipLine {
- background: white;
- color: #00A;
- text-decoration: underline;
-}
-
.patchContentTable td.cellsNextToFileComment {
background: trimColor;
border-top: trimColor;
@@ -903,36 +786,6 @@ a:hover {
margin-right: 5px;
}
-.changeScreen .gwt-DisclosurePanel .header td {
- font-weight: bold;
- white-space: nowrap;
-}
-
-.changeScreen .gwt-DisclosurePanel .complexHeader {
- white-space: nowrap;
-}
-.changeScreen .gwt-DisclosurePanel .complexHeader span {
- white-space: nowrap;
-}
-
-.patchSetRevision {
- padding-left: 20px;
- font-size: 8pt;
-}
-
-.patchSetLink {
- padding-left: 0.5em;
- font-size: 8pt;
-}
-
-.changeScreen .gwt-DisclosurePanel .content {
- margin-bottom: 10px;
-}
-
-.gwt-DisclosurePanel .content {
- margin-left: 10px;
-}
-
.changeScreenDescription,
.changeScreenDescription textarea {
white-space: pre;
@@ -944,81 +797,6 @@ a:hover {
padding-top: 0.5em;
}
-.changeComments {
- padding-top: 1em;
- width: 60em;
-}
-
-.infoTable {
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-.infoTable td {
- border-left: 1px solid trimColor;
- border-bottom: 1px solid trimColor;
- padding: 2px 6px 1px;
-}
-
-.infoTable td.header {
- background-color: trimColor;
- font-weight: normal;
- padding: 2px 4px 0 6px;
- font-style: italic;
- text-align: left;
- vertical-align: top;
- white-space: nowrap;
-}
-
-.rightmost {
- border-right: 1px solid trimColor;
-}
-
-.sideBySideTableBinaryHeader {
- border-left: thin solid #b0bdcc;
- width: 100%;
- color: grey;
- font-weight: bold;
-}
-
-.infoTable td.approvalrole {
- width: 5em;
- border-left: none;
- font-style: italic;
- white-space: nowrap;
-}
-
-.infoTable td.approvalscore {
- text-align: center;
-}
-.infoTable td.notVotable {
- background: #F5F5F5;
-}
-.infoTable td.negscore {
- color: red;
-}
-.infoTable td.posscore {
- color: #08a400;
-}
-
-.infoTable td.approvalhint {
- white-space: nowrap;
- text-align: left;
- color: #444444;
-}
-
-.changeInfoBlock {
- margin-right: 15px;
-}
-
-.changeInfoTopicPanel img {
- float: right;
-}
-
-.changeInfoTopicPanel a {
- float: left;
-}
-
.avatarInfoPanel {
margin-right: 10px;
}
@@ -1062,27 +840,6 @@ a:hover {
border-bottom: 1px solid trimColor;
}
-.infoBlock td.closedstate {
- font-weight: bold;
-}
-
-.infoBlock td.useridentity {
- white-space: nowrap;
-}
-.changeInfoBlock td.changeid {
- font-size: x-small;
-}
-
-
-.patchSetInfoBlock {
- margin-bottom: 10px;
-}
-.patchSetUserIdentity {
- white-space: nowrap;
-}
-.patchSetUserIdentity .gwt-InlineLabel {
- margin-left: 0.2em;
-}
.patchSetActions {
margin-bottom: 10px;
@@ -1092,42 +849,10 @@ a:hover {
font-size: 8pt;
}
-.selectPatchSetOldVersion {
- font-weight: bold;
- margin-right: 30px;
- margin-top: 5px;
-}
-
-.approvalTable {
- margin-top: 1em;
- margin-bottom: 1em;
-}
-.missingApprovalList {
- margin-top: 5px;
- margin-left: 1em;
- padding-left: 1em;
- margin-bottom: 0px;
-}
-.missingApproval {
- font-size: small;
- white-space: nowrap;
-}
-.addReviewer {
- margin-left: 1em;
- margin-top: 5px;
- white-space: nowrap;
-}
-.removeReviewer {
- padding: 0px;
-}
-td.removeReviewerCell {
- padding-left: 4em;
- border-left: none;
-}
-
.downloadBox {
min-width: 580px;
margin: 5px;
+ margin-right: 15px;
}
.downloadBoxTable {
border-spacing: 0;
@@ -1169,9 +894,6 @@ td.removeReviewerCell {
.downloadBoxCopyLabel div {
float: right;
}
-td.downloadLinkListCell {
- padding: 0px;
-}
.downloadLinkHeader {
background: trimColor;
white-space: nowrap;
@@ -1210,28 +932,6 @@ a:hover.downloadLink {
width: 30em;
}
-.parentsTable {
- border-style: none;
- border: 1px 1px 1px 0px;
- outline: 0px;
- padding: 0px;
- border-spacing: 0px;
- text-align: left;
- font-family: mono-font;
- font-size: 10px;
-}
-
-.parentsTable td.noborder {
- border: none;
-}
-
-.parentsTable td.monospace {
- font-family: mono-font;
- font-size: 10px;
- margin: 0px;
- padding-left: 0px;
-}
-
/** UnifiedScreen **/
.unifiedTable {
width: 100%;
@@ -1239,17 +939,6 @@ a:hover.downloadLink {
display: table;
}
-/** SideBySideScreen **/
-.sideBySideScreenSideBySideTable {
- width: 100%;
- border: 1px solid #B0BDCC;
- display: table;
-}
-
-.sideBySideScreenSideBySideTable .fileLine {
- width: 50%;
-}
-
.sideBySideScreenLinkTable {
width: 100%;
}
@@ -1361,10 +1050,6 @@ a:hover.downloadLink {
width: 100%;
}
-.createGroupLink {
- margin-bottom: 10px;
-}
-
.createProjectPanel {
margin-bottom: 10px;
background-color: trimColor;
@@ -1450,66 +1135,10 @@ a:hover.downloadLink {
width: 45em;
}
-.projectAdminLabelRangeLine {
- white-space: nowrap;
-}
-.projectAdminLabelValue {
- font-family: mono-font;
- font-size: small;
-}
.projectActions {
margin-bottom: 10px;
}
-/** PublishCommentsScreen **/
-.publishCommentsScreen .smallHeading {
- font-size: small;
- font-weight: bold;
- white-space: nowrap;
-}
-.publishCommentsScreen .labelList {
- margin-bottom: 10px;
- margin-left: 10px;
- background: trimColor;
- width: 25em;
- white-space: nowrap;
- padding-top: 2px;
- padding-bottom: 2px;
-}
-.publishCommentsScreen .coverMessage {
- margin-left: 10px;
- padding: 5px 5px 5px 5px;
-}
-.publishCommentsScreen .coverMessage textarea {
- font-size: small;
-}
-.publishCommentsScreen .labelList .gwt-RadioButton {
- font-size: smaller;
-}
-.publishCommentsScreen .patchComments {
- margin-left: 1em;
- margin-right: 0.5em;
- margin-bottom: 0.5em;
-}
-.publishCommentsScreen .gwt-Hyperlink {
- white-space: nowrap;
- font-size: small;
-}
-.publishCommentsScreen .lineHeader {
- white-space: nowrap;
- font-family: mono-font;
- font-size: small;
- font-style: italic;
- padding-left: 3px;
-}
-.publishCommentsScreen .commentPanel {
- border: none;
- width: 35em;
-}
-.publishCommentsScreen .commentPanelDateCell {
- display: none;
-}
-
/** CommentedActionDialog **/
.commentedActionDialog .gwt-DisclosurePanel .header td {
@@ -1533,6 +1162,16 @@ a:hover.downloadLink {
white-space: nowrap;
font-size: small;
}
+.commentedActionDialog .rebaseContentPanel {
+ margin-left: 10px;
+ background: trimColor;
+ padding: 5px 5px 5px 5px;
+ width: 300px;
+}
+.commentedActionDialog .rebaseContentPanel .rebaseSuggestBox {
+ font-size: small;
+ width: 100%;
+}
/** PatchBrowserPopup **/
.patchBrowserPopup {
@@ -1552,10 +1191,6 @@ a:hover.downloadLink {
.groupDescriptionPanel {
margin-bottom: 3px;
}
-.groupExternalNameFilterTextBox {
- margin-right: 2px;
- margin-bottom: 2px;
-}
.groupNamePanel {
margin-bottom: 3px;
}
@@ -1571,9 +1206,6 @@ a:hover.downloadLink {
.groupOwnerTextBox {
margin-bottom: 2px;
}
-.groupTypeSelectListBox {
- margin-bottom: 2px;
-}
/** AccountGroupMembersScreen **/
@@ -1610,6 +1242,20 @@ a:hover.downloadLink {
cursor: pointer;
}
+.branchTablePrevNextLinks {
+ position: relative;
+}
+.branchTablePrevNextLinks td {
+ float: left;
+ width: 5em;
+ text-align: left;
+ padding-right: 10px;
+}
+.branchTablePrevNextLinks .gwt-Hyperlink {
+ font-size: 9pt;
+ color: #2a5db0;
+}
+
/** PluginListScreen **/
.pluginsTable {
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gwt_override.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gwt_override.css
index f9a8cc04ef..c92117c337 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gwt_override.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gwt_override.css
@@ -36,7 +36,7 @@ body {
.gwt-DialogBox .dialogMiddleCenter {
background: backgroundColor;
color: textColor;
-}
+}
.gwt-Button {
white-space: nowrap;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index 26dd173a67..efeb2ecea9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -14,14 +14,11 @@
package com.google.gerrit.client.patches;
-import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.changes.CommentApi;
import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gerrit.client.changes.PatchTable;
-import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.CommentPanel;
@@ -72,7 +69,7 @@ import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
-public abstract class AbstractPatchContentTable extends NavigationTable<Object>
+abstract class AbstractPatchContentTable extends NavigationTable<Object>
implements CommentEditorContainer, FocusHandler, BlurHandler {
public static final int R_HEAD = 0;
static final short FILE_SIDE_A = (short) 0;
@@ -110,8 +107,6 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
if (Gerrit.isSignedIn()) {
keysAction.add(new InsertCommentCommand(0, 'c', PatchUtil.C
.commentInsert()));
- keysAction.add(new PublishCommentsKeyCommand(0, 'r', Util.C
- .keyPublishComments()));
// See CommentEditorPanel
//
@@ -131,11 +126,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
abstract void createFileCommentEditorOnSideB();
- abstract PatchScreen.Type getPatchScreenType();
-
protected void initHeaders(PatchScript script, PatchSetDetail detail) {
- PatchScreen.Type type = getPatchScreenType();
- headerSideA = new PatchSetSelectBox(PatchSetSelectBox.Side.A, type);
+ headerSideA = new PatchSetSelectBox(PatchSetSelectBox.Side.A);
headerSideA.display(detail, script, patchKey, idSideA, idSideB);
headerSideA.addDoubleClickHandler(new DoubleClickHandler() {
@Override
@@ -145,7 +137,7 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
}
}
});
- headerSideB = new PatchSetSelectBox(PatchSetSelectBox.Side.B, type);
+ headerSideB = new PatchSetSelectBox(PatchSetSelectBox.Side.B);
headerSideB.display(detail, script, patchKey, idSideA, idSideB);
headerSideB.addDoubleClickHandler(new DoubleClickHandler() {
@Override
@@ -541,6 +533,11 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
}
}
+ /**
+ * Update cursor after selecting a comment.
+ *
+ * @param newComment comment that was selected.
+ */
protected void updateCursor(final PatchLineComment newComment) {
}
@@ -724,7 +721,7 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
}
protected void bindComment(final int row, final int col,
- final PatchLineComment line, final boolean isLast, boolean expandComment) {
+ final PatchLineComment line, boolean expandComment) {
if (line.getStatus() == PatchLineComment.Status.DRAFT) {
final CommentEditorPanel plc =
new CommentEditorPanel(line, commentLinkProcessor);
@@ -833,18 +830,6 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
}
}
- public class PublishCommentsKeyCommand extends NeedsSignInKeyCommand {
- public PublishCommentsKeyCommand(int mask, char key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- final PatchSet.Id id = patchKey.getParentKey();
- Gerrit.display(Dispatcher.toPublish(id));
- }
- }
-
public class PrevChunkKeyCmd extends KeyCommand {
public PrevChunkKeyCmd(int mask, int key, String help) {
super(mask, key, help);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
index 32302f4f93..2538102d3d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
@@ -20,7 +20,7 @@ import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.CommentPanel;
-import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -245,6 +245,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
final PatchSet.Id psId = comment.getKey().getParentKey().getParentKey();
final boolean wasNew = isNew();
GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
+ @Override
public void onSuccess(CommentInfo result) {
notifyDraftDelta(wasNew ? 1 : 0);
comment = toComment(psId, comment.getKey().getParentKey().get(), result);
@@ -300,6 +301,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
comment.getKey().getParentKey().getParentKey(),
comment.getKey().get(),
new GerritCallback<JavaScriptObject>() {
+ @Override
public void onSuccess(JavaScriptObject result) {
notifyDraftDelta(-1);
removeUI();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommitMessageBlock.java
index 1f66b7274b..f336382af6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommitMessageBlock.java
@@ -12,40 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.client.changes;
+package com.google.gerrit.client.patches;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.changes.StarredChanges;
+import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.client.ui.CommentedActionDialog;
-import com.google.gerrit.client.ui.TextBoxChangeListener;
-import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.PreElement;
import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-
-public class CommitMessageBlock extends Composite {
- interface Binder extends UiBinder<HTMLPanel, CommitMessageBlock> {
- }
+class CommitMessageBlock extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, CommitMessageBlock> {}
private static final Binder uiBinder = GWT.create(Binder.class);
private KeyCommandSet keysAction;
@@ -59,53 +50,22 @@ public class CommitMessageBlock extends Composite {
@UiField
PreElement commitBodyPre;
- public CommitMessageBlock() {
+ CommitMessageBlock() {
initWidget(uiBinder.createAndBindUi(this));
}
- public CommitMessageBlock(KeyCommandSet keysAction) {
+ CommitMessageBlock(KeyCommandSet keysAction) {
this.keysAction = keysAction;
initWidget(uiBinder.createAndBindUi(this));
}
- public void display(String commitMessage,
+ void display(String commitMessage,
CommentLinkProcessor commentLinkProcessor) {
- display(null, null, null, false, commitMessage, commentLinkProcessor);
- }
-
- private abstract class CommitMessageEditDialog
- extends CommentedActionDialog<JavaScriptObject> {
- private final String originalMessage;
- public CommitMessageEditDialog(final String title, final String heading,
- final String commitMessage, AsyncCallback<JavaScriptObject> callback) {
- super(title, heading, callback);
- originalMessage = commitMessage.trim();
- message.setCharacterWidth(72);
- message.setVisibleLines(20);
- message.setText(originalMessage);
- message.addStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
- sendButton.setEnabled(false);
-
- new TextBoxChangeListener(message) {
- public void onTextChanged(String newText) {
- // Trim the new text so we don't consider trailing
- // newlines as changes
- sendButton.setEnabled(!newText.trim().equals(originalMessage));
- }
- };
- }
-
- public String getMessageText() {
- // As we rely on commit message lines ending in LF, we convert CRLF to
- // LF. Additionally, the commit message should be trimmed to remove any
- // excess newlines at the end, but we need to make sure it still has at
- // least one trailing newline.
- return message.getText().replaceAll("\r\n", "\n").trim() + '\n';
- }
+ display(null, null, null, commitMessage, commentLinkProcessor);
}
- public void display(final PatchSet.Id patchSetId, final String revision,
- Boolean starred, Boolean canEditCommitMessage, final String commitMessage,
+ void display(final PatchSet.Id patchSetId, final String revision,
+ Boolean starred, final String commitMessage,
CommentLinkProcessor commentLinkProcessor) {
starPanel.clear();
if (patchSetId != null && starred != null && Gerrit.isSignedIn()) {
@@ -125,37 +85,6 @@ public class CommitMessageBlock extends Composite {
permalinkPanel.add(new ChangeLink(Util.C.changePermalink(), changeId));
permalinkPanel.add(new CopyableLabel(ChangeLink.permalink(changeId),
false));
- if (canEditCommitMessage) {
- final Image edit = new Image(Gerrit.RESOURCES.edit());
- edit.setTitle(Util.C.editCommitMessageToolTip());
- edit.addStyleName(Gerrit.RESOURCES.css().link());
- edit.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- new CommitMessageEditDialog(Util.C.titleEditCommitMessage(),
- Util.C.headingEditCommitMessage(),
- commitMessage,
- new GerritCallback<JavaScriptObject>() {
- @Override
- public void onSuccess(JavaScriptObject result) {}
- }) {
- @Override
- public void onSend() {
- ChangeApi.message(changeId.get(), revision, getMessageText(),
- new GerritCallback<JavaScriptObject>() {
- @Override
- public void onSuccess(JavaScriptObject msg) {
- Gerrit.display(PageLinks.toChange(changeId));
- hide();
- }
- });
- }
- }.center();
- }
- });
-
- permalinkPanel.add(edit);
- }
}
String[] splitCommitMessage = commitMessage.split("\n", 2);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommitMessageBlock.ui.xml
index ca81537927..ca81537927 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommitMessageBlock.ui.xml
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
index 6875a96a0b..c4fc1b056b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
@@ -33,10 +33,10 @@ import java.util.List;
* A table used to specify which two patch sets should be diff'ed.
*/
class HistoryTable extends FancyFlexTable<Patch> {
- private final PatchScreen screen;
+ private final UnifiedPatchScreen screen;
final List<HistoryRadio> all = new ArrayList<>();
- HistoryTable(final PatchScreen parent) {
+ HistoryTable(final UnifiedPatchScreen parent) {
setStyleName(Gerrit.RESOURCES.css().patchHistoryTable());
screen = parent;
table.setWidth("auto");
@@ -58,14 +58,7 @@ class HistoryTable extends FancyFlexTable<Patch> {
}
enableAll(false);
Patch.Key k = new Patch.Key(sideB, screen.getPatchKey().get());
- switch (screen.getPatchScreenType()) {
- case SIDE_BY_SIDE:
- Gerrit.display(Dispatcher.toPatchSideBySide(sideA, k));
- break;
- case UNIFIED:
- Gerrit.display(Dispatcher.toPatchUnified(sideA, k));
- break;
- }
+ Gerrit.display(Dispatcher.toUnified(sideA, k));
}
void enableAll(final boolean on) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
index 9f36342b75..2a04c38fc4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
@@ -15,13 +15,14 @@
package com.google.gerrit.client.patches;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.PatchTable;
+import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
@@ -29,10 +30,12 @@ import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import com.google.gwtexpui.safehtml.client.SafeHtml;
+import java.util.List;
+
class NavLinks extends Composite {
public enum Nav {
PREV (0, '[', PatchUtil.C.previousFileHelp(), 0),
- NEXT (2, ']', PatchUtil.C.nextFileHelp(), 1);
+ NEXT (3, ']', PatchUtil.C.nextFileHelp(), 1);
public int col; // Table Cell column to display link in
public int key; // key code shortcut to activate link
@@ -56,7 +59,7 @@ class NavLinks extends Composite {
NavLinks(KeyCommandSet kcs, PatchSet.Id forPatch) {
patchSetId = forPatch;
keys = kcs;
- table = new Grid(1, 3);
+ table = new Grid(1, 4);
initWidget(table);
final CellFormatter fmt = table.getCellFormatter();
@@ -64,20 +67,32 @@ class NavLinks extends Composite {
fmt.setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_LEFT);
fmt.setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_CENTER);
fmt.setHorizontalAlignment(0, 2, HasHorizontalAlignment.ALIGN_RIGHT);
+ fmt.setHorizontalAlignment(0, 3, HasHorizontalAlignment.ALIGN_RIGHT);
final ChangeLink up = new ChangeLink("", patchSetId);
SafeHtml.set(up, SafeHtml.asis(Util.C.upToChangeIconLink()));
table.setWidget(0, 1, up);
}
- void display(int patchIndex, PatchScreen.Type type, PatchTable fileList) {
+ void display(int patchIndex, PatchTable fileList,
+ List<InlineHyperlink> links, List<WebLinkInfo> webLinks) {
if (fileList != null) {
- setupNav(Nav.PREV, fileList.getPreviousPatchLink(patchIndex, type));
- setupNav(Nav.NEXT, fileList.getNextPatchLink(patchIndex, type));
+ setupNav(Nav.PREV, fileList.getPreviousPatchLink(patchIndex));
+ setupNav(Nav.NEXT, fileList.getNextPatchLink(patchIndex));
} else {
setupNav(Nav.PREV, null);
setupNav(Nav.NEXT, null);
}
+
+ FlowPanel linkPanel = new FlowPanel();
+ linkPanel.setStyleName(Gerrit.RESOURCES.css().linkPanel());
+ for (InlineHyperlink link : links) {
+ linkPanel.add(link);
+ }
+ for (WebLinkInfo webLink : webLinks) {
+ linkPanel.add(webLink.toAnchor());
+ }
+ table.setWidget(0, 2, linkPanel);
}
protected void setupNav(final Nav nav, final InlineHyperlink link) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java
index 9af1aa3f35..e60ce76909 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java
@@ -15,7 +15,6 @@
package com.google.gerrit.client.patches;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gwt.event.logical.shared.ResizeEvent;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index 9ff98933f0..39aadc3393 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -58,6 +58,8 @@ public interface PatchConstants extends Constants {
String toggleIntraline();
String showPreferences();
+ String openEditScreen();
+
String toggleReviewed();
String markAsReviewedAndGoToNext();
@@ -77,6 +79,7 @@ public interface PatchConstants extends Constants {
String reviewedAnd();
String next();
String download();
+ String edit();
String addFileCommentToolTip();
String addFileCommentByDoubleClick();
@@ -88,4 +91,7 @@ public interface PatchConstants extends Constants {
String patchSkipRegionStart();
String patchSkipRegionEnd();
+
+ String sideBySideDiff();
+ String unifiedDiff();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 0a4761329d..2f68822825 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -40,6 +40,7 @@ toggleSideA = Toggle left side
toggleIntraline = Toggle intraline difference
showPreferences = Show diff preferences
+openEditScreen = Edit file in browser
toggleReviewed = Toggle the reviewed flag
markAsReviewedAndGoToNext = Mark patch as reviewed and go to next unreviewed patch
@@ -59,6 +60,7 @@ nextFileHelp = Next file
reviewedAnd = Reviewed &
next = next
download = Download
+edit = Edit
addFileCommentToolTip = Click to add file comment
addFileCommentByDoubleClick = Double click to add file comment
@@ -67,3 +69,6 @@ fileTypeGitlink = Type: Git Commit in Subproject
patchSkipRegionStart = ... skipped
patchSkipRegionEnd = common lines ...
+
+sideBySideDiff = Side-by-side diff
+unifiedDiff = Unified diff
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
index 2fe2d45880..264e043afc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
@@ -230,12 +230,12 @@ public class PatchScriptSettingsPanel extends Composite {
}
@UiHandler("update")
- void onUpdate(ClickEvent event) {
+ void onUpdate(@SuppressWarnings("unused") ClickEvent event) {
update();
}
@UiHandler("save")
- void onSave(ClickEvent event) {
+ void onSave(@SuppressWarnings("unused") ClickEvent event) {
save();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
index 083820b330..6762383ab4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
@@ -41,18 +41,13 @@ import java.util.HashMap;
import java.util.Map;
public class PatchSetSelectBox extends Composite {
- interface Binder extends UiBinder<HTMLPanel, PatchSetSelectBox> {
- }
-
+ interface Binder extends UiBinder<HTMLPanel, PatchSetSelectBox> {}
private static final Binder uiBinder = GWT.create(Binder.class);
interface BoxStyle extends CssResource {
String selected();
-
String hidden();
-
String sideMarker();
-
String patchSetLabel();
}
@@ -66,7 +61,6 @@ public class PatchSetSelectBox extends Composite {
PatchSet.Id idSideB;
PatchSet.Id idActive;
Side side;
- PatchScreen.Type screenType;
Map<Integer, Anchor> links;
private Label patchSet;
@@ -76,9 +70,8 @@ public class PatchSetSelectBox extends Composite {
@UiField
BoxStyle style;
- public PatchSetSelectBox(Side side, final PatchScreen.Type type) {
+ public PatchSetSelectBox(Side side) {
this.side = side;
- this.screenType = type;
initWidget(uiBinder.createAndBindUi(this));
}
@@ -102,11 +95,9 @@ public class PatchSetSelectBox extends Composite {
patchSet.addStyleName(style.patchSetLabel());
linkPanel.add(patchSet);
- if (screenType == PatchScreen.Type.UNIFIED) {
- Label sideMarker = new Label((side == Side.A) ? "(-)" : "(+)");
- sideMarker.addStyleName(style.sideMarker());
- linkPanel.add(sideMarker);
- }
+ Label sideMarker = new Label((side == Side.A) ? "(-)" : "(+)");
+ sideMarker.addStyleName(style.sideMarker());
+ linkPanel.add(sideMarker);
Anchor baseLink;
if (detail.getInfo().getParents().size() > 1) {
@@ -116,9 +107,7 @@ public class PatchSetSelectBox extends Composite {
}
links.put(0, baseLink);
- if (screenType == PatchScreen.Type.UNIFIED || side == Side.A) {
- linkPanel.add(baseLink);
- }
+ linkPanel.add(baseLink);
if (side == Side.B) {
links.get(0).setStyleName(style.hidden());
@@ -126,7 +115,7 @@ public class PatchSetSelectBox extends Composite {
for (Patch patch : script.getHistory()) {
PatchSet.Id psId = patch.getKey().getParentKey();
- Anchor anchor = createLink(Integer.toString(psId.get()), psId);
+ Anchor anchor = createLink(psId.getId(), psId);
links.put(psId.get(), anchor);
linkPanel.add(anchor);
}
@@ -161,19 +150,9 @@ public class PatchSetSelectBox extends Composite {
}
Patch.Key keySideB = new Patch.Key(idSideB, patchKey.get());
-
- switch (screenType) {
- case SIDE_BY_SIDE:
- Gerrit.display(Dispatcher.toPatchSideBySide(idSideA, keySideB));
- break;
- case UNIFIED:
- Gerrit.display(Dispatcher.toPatchUnified(idSideA, keySideB));
- break;
- }
+ Gerrit.display(Dispatcher.toUnified(idSideA, keySideB));
}
-
});
-
return anchor;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
index 638ec13cad..178a58362f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.client.changes;
+package com.google.gerrit.client.patches;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.patches.PatchScreen;
+import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
import com.google.gerrit.client.ui.NavigationTable;
@@ -25,7 +25,6 @@ import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
-import com.google.gerrit.reviewdb.client.Patch.Key;
import com.google.gerrit.reviewdb.client.Patch.PatchType;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.Scheduler;
@@ -53,8 +52,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class PatchTable extends Composite {
- public interface PatchValidator {
+class PatchTable extends Composite {
+ interface PatchValidator {
/**
* @param patch
* @return true if patch is valid.
@@ -62,7 +61,7 @@ public class PatchTable extends Composite {
boolean isValid(Patch patch);
}
- public final PatchValidator PREFERENCE_VALIDATOR =
+ final PatchValidator PREFERENCE_VALIDATOR =
new PatchValidator() {
@Override
public boolean isValid(Patch patch) {
@@ -88,22 +87,22 @@ public class PatchTable extends Composite {
private boolean active;
private boolean registerKeys;
- public PatchTable(ListenableAccountDiffPreference prefs) {
+ PatchTable(ListenableAccountDiffPreference prefs) {
listenablePrefs = prefs;
myBody = new FlowPanel();
initWidget(myBody);
}
- public PatchTable() {
+ PatchTable() {
this(new ListenableAccountDiffPreference());
}
- public int indexOf(Patch.Key patch) {
+ int indexOf(Patch.Key patch) {
Integer i = patchMap().get(patch);
return i != null ? i : -1;
}
- private Map<Key, Integer> patchMap() {
+ private Map<Patch.Key, Integer> patchMap() {
if (patchMap == null) {
patchMap = new HashMap<>();
for (int i = 0; i < patchList.size(); i++) {
@@ -113,7 +112,7 @@ public class PatchTable extends Composite {
return patchMap;
}
- public void display(PatchSet.Id base, PatchSetDetail detail) {
+ void display(PatchSet.Id base, PatchSetDetail detail) {
this.base = base;
this.detail = detail;
this.patchList = detail.getPatches();
@@ -129,19 +128,19 @@ public class PatchTable extends Composite {
}
}
- public PatchSet.Id getBase() {
+ PatchSet.Id getBase() {
return base;
}
- public void setSavePointerId(final String id) {
+ void setSavePointerId(final String id) {
savePointerId = id;
}
- public boolean isLoaded() {
+ boolean isLoaded() {
return myTable != null;
}
- public void onTableLoaded(final Command cmd) {
+ void onTableLoaded(final Command cmd) {
if (myTable != null) {
cmd.execute();
} else {
@@ -149,7 +148,7 @@ public class PatchTable extends Composite {
}
}
- public void addClickHandler(final ClickHandler clickHandler) {
+ void addClickHandler(final ClickHandler clickHandler) {
if (myTable != null) {
myTable.addClickHandler(clickHandler);
} else {
@@ -160,27 +159,27 @@ public class PatchTable extends Composite {
}
}
- public void setRegisterKeys(final boolean on) {
+ void setRegisterKeys(final boolean on) {
registerKeys = on;
if (myTable != null) {
myTable.setRegisterKeys(on);
}
}
- public void movePointerTo(final Patch.Key k) {
+ void movePointerTo(final Patch.Key k) {
if (myTable != null) {
myTable.movePointerTo(k);
}
}
- public void setActive(boolean active) {
+ void setActive(boolean active) {
this.active = active;
if (myTable != null) {
myTable.setActive(active);
}
}
- public void notifyDraftDelta(final Patch.Key k, final int delta) {
+ void notifyDraftDelta(final Patch.Key k, final int delta) {
if (myTable != null) {
myTable.notifyDraftDelta(k, delta);
}
@@ -214,46 +213,42 @@ public class PatchTable extends Composite {
/**
* @return a link to the previous file in this patch set, or null.
*/
- public InlineHyperlink getPreviousPatchLink(int index,
- PatchScreen.Type patchType) {
+ InlineHyperlink getPreviousPatchLink(int index) {
int previousPatchIndex = getPreviousPatch(index, PREFERENCE_VALIDATOR);
if (previousPatchIndex < 0) {
return null;
}
- return createLink(previousPatchIndex, patchType,
+ return createLink(previousPatchIndex,
SafeHtml.asis(Util.C.prevPatchLinkIcon()), null);
}
/**
* @return a link to the next file in this patch set, or null.
*/
- public InlineHyperlink getNextPatchLink(int index, PatchScreen.Type patchType) {
+ InlineHyperlink getNextPatchLink(int index) {
int nextPatchIndex = getNextPatch(index, false, PREFERENCE_VALIDATOR);
if (nextPatchIndex < 0) {
return null;
}
- return createLink(nextPatchIndex, patchType, null,
+ return createLink(nextPatchIndex, null,
SafeHtml.asis(Util.C.nextPatchLinkIcon()));
}
/**
* @return a link to the the given patch.
* @param index The patch to link to
- * @param screenType The screen type of patch display
* @param before A string to display at the beginning of the href text
* @param after A string to display at the end of the href text
*/
- public PatchLink createLink(int index, PatchScreen.Type screenType,
- SafeHtml before, SafeHtml after) {
+ PatchLink createLink(int index, SafeHtml before, SafeHtml after) {
Patch patch = patchList.get(index);
-
- Key thisKey = patch.getKey();
+ Patch.Key thisKey = patch.getKey();
PatchLink link;
- if (isUnifiedPatchLink(patch, screenType)) {
- link = new PatchLink.Unified("", base, thisKey, index, detail, this);
+ if (isUnifiedPatchLink(patch)) {
+ link = new PatchLink.Unified("", base, thisKey);
} else {
- link = new PatchLink.SideBySide("", base, thisKey, index, detail, this);
+ link = new PatchLink.SideBySide("", base, thisKey);
}
SafeHtmlBuilder text = new SafeHtmlBuilder();
@@ -264,15 +259,11 @@ public class PatchTable extends Composite {
return link;
}
- private static boolean isUnifiedPatchLink(final Patch patch,
- final PatchScreen.Type screenType) {
- if (Dispatcher.isChangeScreen2()) {
- return (patch.getPatchType().equals(PatchType.BINARY)
- || (Gerrit.isSignedIn()
- && Gerrit.getUserAccount().getGeneralPreferences().getDiffView()
- .equals(DiffView.UNIFIED_DIFF)));
- }
- return screenType == PatchScreen.Type.UNIFIED;
+ private static boolean isUnifiedPatchLink(final Patch patch) {
+ return (patch.getPatchType().equals(PatchType.BINARY)
+ || (Gerrit.isSignedIn()
+ && Gerrit.getUserAccount().getGeneralPreferences().getDiffView()
+ .equals(DiffView.UNIFIED_DIFF)));
}
private static String getFileNameOnly(Patch patch) {
@@ -287,11 +278,11 @@ public class PatchTable extends Composite {
return fileName;
}
- public static String getDisplayFileName(Patch patch) {
+ static String getDisplayFileName(Patch patch) {
return getDisplayFileName(patch.getKey());
}
- public static String getDisplayFileName(Patch.Key patchKey) {
+ static String getDisplayFileName(Patch.Key patchKey) {
if (Patch.COMMIT_MSG.equals(patchKey.get())) {
return Util.C.commitMessage();
}
@@ -301,13 +292,13 @@ public class PatchTable extends Composite {
/**
* Update the reviewed status for the given patch.
*/
- public void updateReviewedStatus(Patch.Key patchKey, boolean reviewed) {
+ void updateReviewedStatus(Patch.Key patchKey, boolean reviewed) {
if (myTable != null) {
myTable.updateReviewedStatus(patchKey, reviewed);
}
}
- public ListenableAccountDiffPreference getPreferences() {
+ ListenableAccountDiffPreference getPreferences() {
return listenablePrefs;
}
@@ -385,7 +376,7 @@ public class PatchTable extends Composite {
}
/** Activates / Deactivates the key navigation and the highlighting of the current row for this table */
- public void setActive(boolean active) {
+ void setActive(boolean active) {
if (active) {
if(activeRow > 0 && getCurrentRow() != activeRow) {
super.movePointerTo(activeRow);
@@ -404,9 +395,8 @@ public class PatchTable extends Composite {
Patch patch = PatchTable.this.patchList.get(row - 1);
setRowItem(row, patch);
- Widget nameCol;
- nameCol = new PatchLink.SideBySide(getDisplayFileName(patch), base,
- patch.getKey(), row - 1, detail, PatchTable.this);
+ Widget nameCol = new PatchLink.SideBySide(getDisplayFileName(patch), base,
+ patch.getKey());
if (patch.getSourceFileName() != null) {
final String text;
@@ -428,14 +418,12 @@ public class PatchTable extends Composite {
int C_UNIFIED = C_SIDEBYSIDE + 1;
- PatchLink sideBySide =
- new PatchLink.SideBySide(Util.C.patchTableDiffSideBySide(), base,
- patch.getKey(), row - 1, detail, PatchTable.this);
+ PatchLink sideBySide = new PatchLink.SideBySide(
+ Util.C.patchTableDiffSideBySide(), base, patch.getKey());
sideBySide.setStyleName("gwt-Anchor");
- PatchLink unified =
- new PatchLink.Unified(Util.C.patchTableDiffUnified(), base,
- patch.getKey(), row - 1, detail, PatchTable.this);
+ PatchLink unified = new PatchLink.Unified(Util.C.patchTableDiffUnified(),
+ base, patch.getKey());
unified.setStyleName("gwt-Anchor");
table.setWidget(row, C_SIDEBYSIDE, sideBySide);
@@ -448,7 +436,7 @@ public class PatchTable extends Composite {
@Override
public void onClick(ClickEvent event) {
for (Patch p : detail.getPatches()) {
- openWindow(Dispatcher.toPatchSideBySide(base, p.getKey()));
+ openWindow(Dispatcher.toSideBySide(base, p.getKey()));
}
}
});
@@ -457,9 +445,10 @@ public class PatchTable extends Composite {
int C_UNIFIED = C_SIDEBYSIDE - 2 + 1;
Anchor unified = new Anchor(Util.C.diffAllUnified());
unified.addClickHandler(new ClickHandler() {
+ @Override
public void onClick(ClickEvent event) {
for (Patch p : detail.getPatches()) {
- openWindow(Dispatcher.toPatchUnified(base, p.getKey()));
+ openWindow(Dispatcher.toUnified(base, p.getKey()));
}
}
});
@@ -744,6 +733,7 @@ public class PatchTable extends Composite {
/**
* Add the files contained in the list of patches to the table, one per row.
*/
+ @Override
@SuppressWarnings("fallthrough")
public boolean execute() {
final boolean attachedNow = isAttached();
@@ -844,7 +834,7 @@ public class PatchTable extends Composite {
* true
* @return index of next valid patch, or -1 if no valid patches
*/
- public int getNextPatch(int currentIndex, boolean loopAround,
+ int getNextPatch(int currentIndex, boolean loopAround,
PatchValidator... validators) {
return getNextPatchHelper(currentIndex, loopAround, detail.getPatches()
.size(), validators);
@@ -878,7 +868,7 @@ public class PatchTable extends Composite {
/**
* @return the index to the previous patch
*/
- public int getPreviousPatch(int currentIndex, PatchValidator... validators) {
+ int getPreviousPatch(int currentIndex, PatchValidator... validators) {
for (int i = currentIndex - 1; i >= 0; i--) {
Patch patch = detail.getPatches().get(i);
if (patch != null && patchIsValid(patch, validators)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchUtil.java
index 709685f795..e9491948e8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchUtil.java
@@ -14,6 +14,7 @@
package com.google.gerrit.client.patches;
+import com.google.gerrit.common.data.ChangeDetailService;
import com.google.gerrit.common.data.PatchDetailService;
import com.google.gwt.core.client.GWT;
import com.google.gwtjsonrpc.client.JsonUtil;
@@ -21,10 +22,14 @@ import com.google.gwtjsonrpc.client.JsonUtil;
public class PatchUtil {
public static final PatchConstants C = GWT.create(PatchConstants.class);
public static final PatchMessages M = GWT.create(PatchMessages.class);
- public static final PatchDetailService DETAIL_SVC;
+ public static final ChangeDetailService CHANGE_SVC;
+ public static final PatchDetailService PATCH_SVC;
static {
- DETAIL_SVC = GWT.create(PatchDetailService.class);
- JsonUtil.bind(DETAIL_SVC, "rpc/PatchDetailService");
+ CHANGE_SVC = GWT.create(ChangeDetailService.class);
+ JsonUtil.bind(CHANGE_SVC, "rpc/ChangeDetailService");
+
+ PATCH_SVC = GWT.create(PatchDetailService.class);
+ JsonUtil.bind(PATCH_SVC, "rpc/PatchDetailService");
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/ReviewedPanels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/ReviewedPanels.java
index 68df45d359..d889c7976a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/ReviewedPanels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/ReviewedPanels.java
@@ -16,9 +16,8 @@ package com.google.gerrit.client.patches;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
-import com.google.gerrit.client.changes.PatchTable;
-import com.google.gerrit.client.changes.PatchTable.PatchValidator;
import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.patches.PatchTable.PatchValidator;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.InlineHyperlink;
@@ -35,10 +34,9 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-public class ReviewedPanels {
-
- public final FlowPanel top;
- public final FlowPanel bottom;
+class ReviewedPanels {
+ final FlowPanel top;
+ final FlowPanel bottom;
private Patch.Key patchKey;
private PatchTable fileList;
@@ -46,17 +44,16 @@ public class ReviewedPanels {
private CheckBox checkBoxTop;
private CheckBox checkBoxBottom;
- public ReviewedPanels() {
+ ReviewedPanels() {
this.top = new FlowPanel();
this.bottom = new FlowPanel();
this.bottom.setStyleName(Gerrit.RESOURCES.css().reviewedPanelBottom());
}
- public void populate(Patch.Key pk, PatchTable pt, int patchIndex,
- PatchScreen.Type patchScreenType) {
+ void populate(Patch.Key pk, PatchTable pt, int patchIndex) {
patchKey = pk;
fileList = pt;
- reviewedLink = createReviewedLink(patchIndex, patchScreenType);
+ reviewedLink = createReviewedLink(patchIndex);
top.clear();
checkBoxTop = createReviewedCheckbox();
@@ -87,54 +84,56 @@ public class ReviewedPanels {
return checkBox;
}
- public boolean getValue() {
+ boolean getValue() {
return checkBoxTop.getValue();
}
- public void setValue(final boolean value) {
+ void setValue(final boolean value) {
checkBoxTop.setValue(value);
checkBoxBottom.setValue(value);
}
- public void setReviewedByCurrentUser(boolean reviewed) {
- if (fileList != null) {
- fileList.updateReviewedStatus(patchKey, reviewed);
- }
-
+ void setReviewedByCurrentUser(boolean reviewed) {
PatchSet.Id ps = patchKey.getParentKey();
- RestApi api = new RestApi("/changes/").id(ps.getParentKey().get())
- .view("revisions").id(ps.get())
- .view("files").id(patchKey.getFileName())
- .view("reviewed");
-
- AsyncCallback<VoidResult> cb = new AsyncCallback<VoidResult>() {
- @Override
- public void onFailure(Throwable arg0) {
- // nop
+ if (ps.get() != 0) {
+ if (fileList != null) {
+ fileList.updateReviewedStatus(patchKey, reviewed);
}
- @Override
- public void onSuccess(VoidResult result) {
- // nop
+ RestApi api = new RestApi("/changes/").id(ps.getParentKey().get())
+ .view("revisions").id(ps.get())
+ .view("files").id(patchKey.getFileName())
+ .view("reviewed");
+
+ AsyncCallback<VoidResult> cb = new AsyncCallback<VoidResult>() {
+ @Override
+ public void onFailure(Throwable arg0) {
+ // nop
+ }
+
+ @Override
+ public void onSuccess(VoidResult result) {
+ // nop
+ }
+ };
+ if (reviewed) {
+ api.put(cb);
+ } else {
+ api.delete(cb);
}
- };
- if (reviewed) {
- api.put(cb);
- } else {
- api.delete(cb);
}
}
- public void go() {
+ void go() {
if (reviewedLink != null) {
setReviewedByCurrentUser(true);
reviewedLink.go();
}
}
- private InlineHyperlink createReviewedLink(final int patchIndex,
- final PatchScreen.Type patchScreenType) {
+ private InlineHyperlink createReviewedLink(final int patchIndex) {
final PatchValidator unreviewedValidator = new PatchValidator() {
+ @Override
public boolean isValid(Patch patch) {
return !patch.isReviewedByCurrentUser();
}
@@ -149,8 +148,7 @@ public class ReviewedPanels {
if (nextUnreviewedPatchIndex > -1) {
// Create invisible patch link to change page
reviewedLink =
- fileList.createLink(nextUnreviewedPatchIndex, patchScreenType,
- null, null);
+ fileList.createLink(nextUnreviewedPatchIndex, null, null);
reviewedLink.setText("");
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
deleted file mode 100644
index 2d58816952..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ /dev/null
@@ -1,682 +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.client.patches;
-
-import static com.google.gerrit.client.patches.PatchLine.Type.CONTEXT;
-import static com.google.gerrit.client.patches.PatchLine.Type.DELETE;
-import static com.google.gerrit.client.patches.PatchLine.Type.INSERT;
-import static com.google.gerrit.client.patches.PatchLine.Type.REPLACE;
-
-import com.google.gerrit.client.Gerrit;
-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.common.data.PatchScript.FileMode;
-import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.prettify.client.SparseHtmlFile;
-import com.google.gerrit.prettify.common.EditList;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.HasVerticalAlignment;
-import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-
-import org.eclipse.jgit.diff.Edit;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-
-public class SideBySideTable extends AbstractPatchContentTable {
- private static final int A = 2;
- private static final int B = 3;
- private static final int NUM_ROWS_TO_EXPAND = 10;
-
- private SparseHtmlFile a;
- private SparseHtmlFile b;
- private boolean isHugeFile;
- protected boolean isFileCommentBorderRowExist;
-
- protected void createFileCommentEditorOnSideA() {
- createCommentEditor(R_HEAD + 1, A, R_HEAD, FILE_SIDE_A);
- }
-
- protected void createFileCommentEditorOnSideB() {
- createCommentEditor(R_HEAD + 1, B, R_HEAD, FILE_SIDE_B);
- }
-
- @Override
- protected void onCellDoubleClick(final int row, int column) {
- if (column > C_ARROW && getRowItem(row) instanceof PatchLine) {
- final PatchLine line = (PatchLine) getRowItem(row);
- if (column == 1 || column == A) {
- createCommentEditor(row + 1, A, line.getLineA(), (short) 0);
- } else if (column == B || column == 4) {
- createCommentEditor(row + 1, B, line.getLineB(), (short) 1);
- }
- }
- }
-
- @Override
- protected void onCellSingleClick(Event event, int row, int column) {
- super.onCellSingleClick(event, row, column);
- if (column == 1 || column == 4) {
- onCellDoubleClick(row, column);
- }
- }
-
- @Override
- protected void onInsertComment(final PatchLine line) {
- final int row = getCurrentRow();
- createCommentEditor(row + 1, B, line.getLineB(), (short) 1);
- }
-
- @Override
- protected void render(final PatchScript script, final PatchSetDetail detail) {
- final ArrayList<Object> lines = new ArrayList<>();
- final SafeHtmlBuilder nc = new SafeHtmlBuilder();
- isHugeFile = script.isHugeFile();
- allocateTableHeader(script, nc);
- lines.add(null);
- if (!isDisplayBinary) {
- if (script.getFileModeA() != FileMode.FILE
- || script.getFileModeB() != FileMode.FILE) {
- openLine(nc);
- appendModeLine(nc, script.getFileModeA());
- appendModeLine(nc, script.getFileModeB());
- closeLine(nc);
- lines.add(null);
- }
-
- if (hasDifferences(script)) {
- int lastA = 0;
- int lastB = 0;
- final boolean ignoreWS = script.isIgnoreWhitespace();
- a = getSparseHtmlFileA(script);
- b = getSparseHtmlFileB(script);
- final boolean intraline =
- script.getDiffPrefs().isIntralineDifference()
- && script.hasIntralineDifference();
- for (final EditList.Hunk hunk : script.getHunks()) {
- if (!hunk.isStartOfFile()) {
- appendSkipLine(nc, hunk.getCurB() - lastB);
- lines.add(new SkippedLine(lastA, lastB, hunk.getCurB() - lastB));
- }
-
- while (hunk.next()) {
- if (hunk.isContextLine()) {
- openLine(nc);
- final SafeHtml ctx = a.getSafeHtmlLine(hunk.getCurA());
- appendLineNumber(nc, hunk.getCurA(), false);
- appendLineText(nc, CONTEXT, ctx, false, false);
- if (ignoreWS && b.contains(hunk.getCurB())) {
- appendLineText(nc, CONTEXT, b, hunk.getCurB(), false);
- } else {
- appendLineText(nc, CONTEXT, ctx, false, false);
- }
- appendLineNumber(nc, hunk.getCurB(), true);
- closeLine(nc);
- hunk.incBoth();
- lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
-
- } else if (hunk.isModifiedLine()) {
- final boolean del = hunk.isDeletedA();
- final boolean ins = hunk.isInsertedB();
- final boolean full =
- intraline && hunk.getCurEdit().getType() != Edit.Type.REPLACE;
- openLine(nc);
-
- if (del) {
- appendLineNumber(nc, hunk.getCurA(), false);
- appendLineText(nc, DELETE, a, hunk.getCurA(), full);
- hunk.incA();
- } else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
- appendLineNumber(nc, false);
- appendLineNone(nc, DELETE);
- } else {
- appendLineNumber(nc, false);
- appendLineNone(nc, CONTEXT);
- }
-
- if (ins) {
- appendLineText(nc, INSERT, b, hunk.getCurB(), full);
- appendLineNumber(nc, hunk.getCurB(), true);
- hunk.incB();
- } else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
- appendLineNone(nc, INSERT);
- appendLineNumber(nc, true);
- } else {
- appendLineNone(nc, CONTEXT);
- appendLineNumber(nc, true);
- }
-
- closeLine(nc);
-
- if (del && ins) {
- lines.add(new PatchLine(REPLACE, hunk.getCurA(), hunk.getCurB()));
- } else if (del) {
- lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
- } else if (ins) {
- lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
- }
- }
- }
- lastA = hunk.getCurA();
- lastB = hunk.getCurB();
- }
- if (lastB != b.size()) {
- appendSkipLine(nc, b.size() - lastB);
- lines.add(new SkippedLine(lastA, lastB, b.size() - lastB));
- }
- }
- } else {
- // Display the patch header for binary
- for (final String line : script.getPatchHeader()) {
- appendFileHeader(nc, line);
- }
- // If there is a safe picture involved, we show it
- if (script.getDisplayMethodA() == DisplayMethod.IMG
- || script.getDisplayMethodB() == DisplayMethod.IMG) {
- appendImageLine(script, nc);
- }
- }
- if (!hasDifferences(script)) {
- appendNoDifferences(nc);
- }
- resetHtml(nc);
- populateTableHeader(script, detail);
- if (hasDifferences(script)) {
- initScript(script);
- if (!isDisplayBinary) {
- for (int row = 0; row < lines.size(); row++) {
- setRowItem(row, lines.get(row));
- if (lines.get(row) instanceof SkippedLine) {
- createSkipLine(row, (SkippedLine) lines.get(row), isHugeFile);
- }
- }
- }
- }
- }
-
- private SafeHtml createImage(String url) {
- SafeHtmlBuilder m = new SafeHtmlBuilder();
- m.openElement("img");
- m.setAttribute("src", url);
- m.closeElement("img");
- return m.toSafeHtml();
- }
-
- private void appendImageLine(final PatchScript script,
- final SafeHtmlBuilder m) {
- m.openTr();
- m.setAttribute("valign", "center");
- m.setAttribute("align", "center");
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().iconCell());
- m.closeTd();
-
- appendLineNumber(m, false);
- if (script.getDisplayMethodA() == DisplayMethod.IMG) {
- final String url = getUrlA();
- appendLineText(m, DELETE, createImage(url), false, true);
- } else {
- appendLineNone(m, DELETE);
- }
- if (script.getDisplayMethodB() == DisplayMethod.IMG) {
- final String url = getUrlB();
- appendLineText(m, INSERT, createImage(url), false, true);
- } else {
- appendLineNone(m, INSERT);
- }
-
- appendLineNumber(m, true);
- m.closeTr();
- }
-
- private void populateTableHeader(final PatchScript script,
- final PatchSetDetail detail) {
- initHeaders(script, detail);
- table.setWidget(R_HEAD, A, headerSideA);
- table.setWidget(R_HEAD, B, headerSideB);
-
- // Populate icons to lineNumber column header.
- if (headerSideA.isFileOrCommitMessage()) {
- table.setWidget(R_HEAD, A - 1, iconA);
- }
- if (headerSideB.isFileOrCommitMessage()) {
- table.setWidget(R_HEAD, B + 1, iconB);
- }
- }
-
- private void appendModeLine(final SafeHtmlBuilder nc, final FileMode mode) {
- nc.openTd();
- nc.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- nc.nbsp();
- nc.closeTd();
-
- nc.openTd();
- nc.addStyleName(Gerrit.RESOURCES.css().fileLine());
- nc.addStyleName(Gerrit.RESOURCES.css().fileLineMode());
- switch(mode){
- case FILE:
- nc.nbsp();
- break;
- case SYMLINK:
- nc.append(PatchUtil.C.fileTypeSymlink());
- break;
- case GITLINK:
- nc.append(PatchUtil.C.fileTypeGitlink());
- break;
- }
- nc.closeTd();
- }
-
- @Override
- protected PatchScreen.Type getPatchScreenType() {
- return PatchScreen.Type.SIDE_BY_SIDE;
- }
-
- @Override
- public void display(final CommentDetail cd, boolean expandComments) {
- if (cd.isEmpty()) {
- return;
- }
- setAccountInfoCache(cd.getAccounts());
-
- for (int row = 0; row < table.getRowCount();) {
- final Iterator<PatchLineComment> ai;
- final Iterator<PatchLineComment> bi;
-
- if (row == R_HEAD) {
- ai = cd.getForA(R_HEAD).iterator();
- bi = cd.getForB(R_HEAD).iterator();
- } else if (getRowItem(row) instanceof PatchLine) {
- final PatchLine pLine = (PatchLine) getRowItem(row);
- ai = cd.getForA(pLine.getLineA()).iterator();
- bi = cd.getForB(pLine.getLineB()).iterator();
- } else {
- row++;
- continue;
- }
-
- row++;
- while (ai.hasNext() && bi.hasNext()) {
- final PatchLineComment ac = ai.next();
- final PatchLineComment bc = bi.next();
- if (ac.getLine() == R_HEAD) {
- insertFileCommentRow(row);
- } else {
- insertRow(row);
- }
- bindComment(row, A, ac, !ai.hasNext(), expandComments);
- bindComment(row, B, bc, !bi.hasNext(), expandComments);
- row++;
- }
-
- row = finish(ai, row, A, expandComments);
- row = finish(bi, row, B, expandComments);
- }
- }
-
- private void defaultStyle(final int row, final CellFormatter fmt) {
- fmt.addStyleName(row, A - 1, Gerrit.RESOURCES.css().lineNumber());
- fmt.addStyleName(row, A, Gerrit.RESOURCES.css().diffText());
- if (isDisplayBinary) {
- fmt.addStyleName(row, A, Gerrit.RESOURCES.css().diffTextForBinaryInSideBySide());
- }
- fmt.addStyleName(row, B, Gerrit.RESOURCES.css().diffText());
- fmt.addStyleName(row, B + 1, Gerrit.RESOURCES.css().lineNumber());
- fmt.addStyleName(row, B + 1, Gerrit.RESOURCES.css().rightmost());
- }
-
- @Override
- protected void insertRow(final int row) {
- super.insertRow(row);
- final CellFormatter fmt = table.getCellFormatter();
- defaultStyle(row, fmt);
- }
-
- @Override
- protected void insertFileCommentRow(final int row) {
- table.insertRow(row);
- final CellFormatter fmt = table.getCellFormatter();
- fmt.addStyleName(row, C_ARROW, Gerrit.RESOURCES.css().iconCellOfFileCommentRow());
- defaultStyle(row, fmt);
-
- fmt.addStyleName(row, C_ARROW, //
- Gerrit.RESOURCES.css().cellsNextToFileComment());
- fmt.addStyleName(row, A - 1, //
- Gerrit.RESOURCES.css().cellsNextToFileComment());
- fmt.addStyleName(row, B + 1, //
- Gerrit.RESOURCES.css().cellsNextToFileComment());
- createFileCommentBorderRow(row);
- }
-
- private void createFileCommentBorderRow(final int row) {
- if (row == 1 && !isFileCommentBorderRowExist) {
- isFileCommentBorderRowExist = true;
- table.insertRow(R_HEAD + 2);
-
- final CellFormatter fmt = table.getCellFormatter();
-
- fmt.addStyleName(R_HEAD + 2, C_ARROW, //
- Gerrit.RESOURCES.css().iconCellOfFileCommentRow());
- defaultStyle(R_HEAD + 2, fmt);
-
- final Element iconCell = fmt.getElement(R_HEAD + 2, C_ARROW);
- UIObject.setStyleName(DOM.getParent(iconCell), Gerrit.RESOURCES.css()
- .fileCommentBorder(), true);
- }
- }
-
- private int finish(final Iterator<PatchLineComment> i, int row, final int col, boolean expandComment) {
- while (i.hasNext()) {
- final PatchLineComment c = i.next();
- if (c.getLine() == R_HEAD) {
- insertFileCommentRow(row);
- } else {
- insertRow(row);
- }
- bindComment(row, col, c, !i.hasNext(), expandComment);
- row++;
- }
- return row;
- }
-
- private void allocateTableHeader(PatchScript script, final SafeHtmlBuilder m) {
- m.openTr();
-
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().iconCell());
- m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.nbsp();
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.addStyleName(Gerrit.RESOURCES.css().fileLine());
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.addStyleName(Gerrit.RESOURCES.css().fileLine());
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.addStyleName(Gerrit.RESOURCES.css().rightmost());
- m.closeTd();
-
- m.closeTr();
- }
-
- private void appendFileHeader(final SafeHtmlBuilder m, final String line) {
- m.openTr();
-
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().iconCell());
- m.closeTd();
-
- appendLineNumber(m, false);
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().sideBySideTableBinaryHeader());
- m.setAttribute("colspan", 2);
- m.append(line);
- m.closeTd();
-
- appendLineNumber(m, true);
-
- m.closeTr();
- }
-
- private void appendSkipLine(final SafeHtmlBuilder m, final int skipCnt) {
- m.openTr();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().iconCell());
- m.addStyleName(Gerrit.RESOURCES.css().skipLine());
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().skipLine());
- m.setAttribute("colspan", 4);
- m.closeTd();
- m.closeTr();
- }
-
- private ClickHandler expandAllListener = new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- expand(event, 0);
- }
- };
-
- private ClickHandler expandBeforeListener = new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- expand(event, NUM_ROWS_TO_EXPAND);
- }
- };
-
- private ClickHandler expandAfterListener = new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- expand(event, -NUM_ROWS_TO_EXPAND);
- }
- };
-
- private void expand(ClickEvent event, final int numRows) {
- int row = table.getCellForEvent(event).getRowIndex();
- if (!(getRowItem(row) instanceof SkippedLine)) {
- return;
- }
-
- SkippedLine line = (SkippedLine) getRowItem(row);
- int loopTo = numRows;
- if (numRows == 0) {
- loopTo = line.getSize();
- } else if (numRows < 0) {
- loopTo = -numRows;
- }
- int offset = 0;
- if (numRows < 0) {
- offset = 1;
- }
-
- CellFormatter fmt = table.getCellFormatter();
- for (int i = 0 + offset; i < loopTo + offset; i++) {
- insertRow(row + i);
- table.getRowFormatter().setVerticalAlign(row + i,
- HasVerticalAlignment.ALIGN_TOP);
- int lineA = line.getStartA() + i;
- int lineB = line.getStartB() + i;
- if (numRows < 0) {
- lineA = line.getStartA() + line.getSize() + numRows + i - offset;
- lineB = line.getStartB() + line.getSize() + numRows + i - offset;
- }
-
- table.setHTML(row + i, A - 1, "<a href=\"javascript:;\">" + (lineA + 1) + "</a>");
- fmt.addStyleName(row + i, A - 1, Gerrit.RESOURCES.css().lineNumber());
-
- table.setHTML(row + i, A, a.getSafeHtmlLine(lineA).asString());
- fmt.addStyleName(row + i, A, Gerrit.RESOURCES.css().fileLine());
- fmt.addStyleName(row + i, A, Gerrit.RESOURCES.css().fileLineCONTEXT());
-
- table.setHTML(row + i, B, b.getSafeHtmlLine(lineB).asString());
- fmt.addStyleName(row + i, B, Gerrit.RESOURCES.css().fileLine());
- fmt.addStyleName(row + i, B, Gerrit.RESOURCES.css().fileLineCONTEXT());
-
- table.setHTML(row + i, B + 1, "<a href=\"javascript:;\">" + (lineB + 1) + "</a>");
- fmt.addStyleName(row + i, B + 1, Gerrit.RESOURCES.css().lineNumber());
-
- setRowItem(row + i, new PatchLine(CONTEXT, lineA, lineB));
- }
-
- if (numRows > 0) {
- line.incrementStart(numRows);
- createSkipLine(row + loopTo, line, isHugeFile);
- } else if (numRows < 0) {
- line.reduceSize(-numRows);
- createSkipLine(row, line, isHugeFile);
- } else {
- table.removeRow(row + loopTo);
- }
- }
-
- private void createSkipLine(int row, SkippedLine line, boolean isHugeFile) {
- FlowPanel p = new FlowPanel();
- InlineLabel l1 = new InlineLabel(" " + PatchUtil.C.patchSkipRegionStart() + " ");
- InlineLabel l2 = new InlineLabel(" " + PatchUtil.C.patchSkipRegionEnd() + " ");
-
- Anchor all = new Anchor(String.valueOf(line.getSize()));
- all.addClickHandler(expandAllListener);
- all.setStyleName(Gerrit.RESOURCES.css().skipLine());
-
- if (line.getSize() > 30) {
- // Only show the expand before/after if skipped more than 30 lines.
- Anchor b = new Anchor(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND), true);
- Anchor a = new Anchor(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND), true);
-
- b.addClickHandler(expandBeforeListener);
- a.addClickHandler(expandAfterListener);
-
- b.setStyleName(Gerrit.RESOURCES.css().skipLine());
- a.setStyleName(Gerrit.RESOURCES.css().skipLine());
-
- p.add(b);
- p.add(l1);
- if (isHugeFile) {
- p.add(new InlineLabel(" " + line.getSize() + " "));
- } else {
- p.add(all);
- }
- p.add(l2);
- p.add(a);
- } else {
- p.add(l1);
- p.add(all);
- p.add(l2);
- }
- table.setWidget(row, 1, p);
- }
-
- private void openLine(final SafeHtmlBuilder m) {
- m.openTr();
- m.setAttribute("valign", "top");
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().iconCell());
- m.closeTd();
- }
-
- private void appendLineNumber(SafeHtmlBuilder m, boolean right) {
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- if (right) {
- m.addStyleName(Gerrit.RESOURCES.css().rightmost());
- }
- m.closeTd();
- }
-
- private void appendLineNumber(SafeHtmlBuilder m, int lineNumberMinusOne, boolean right) {
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- if (right) {
- m.addStyleName(Gerrit.RESOURCES.css().rightmost());
- }
- m.append(SafeHtml.asis("<a href=\"javascript:;\">"+ (lineNumberMinusOne + 1) + "</a>"));
- m.closeTd();
- }
-
- private void appendLineText(final SafeHtmlBuilder m,
- final PatchLine.Type type, final SparseHtmlFile src, final int i,
- final boolean fullBlock) {
- appendLineText(m, type, src.getSafeHtmlLine(i), src.hasTrailingEdit(i), fullBlock);
- }
-
- private void appendLineText(final SafeHtmlBuilder m,
- final PatchLine.Type type, final SafeHtml lineHtml,
- final boolean trailingEdit, final boolean fullBlock) {
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().fileLine());
- switch (type) {
- case CONTEXT:
- m.addStyleName(Gerrit.RESOURCES.css().fileLineCONTEXT());
- break;
- case DELETE:
- m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
- if (trailingEdit || fullBlock) {
- m.addStyleName("wdd");
- }
- break;
- case INSERT:
- m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
- if (trailingEdit || fullBlock) {
- m.addStyleName("wdi");
- }
- break;
- case REPLACE:
- break;
- }
- m.append(lineHtml);
- m.closeTd();
- }
-
- private void appendLineNone(final SafeHtmlBuilder m, final PatchLine.Type type) {
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().fileLine());
- switch (type != null ? type : PatchLine.Type.CONTEXT) {
- case DELETE:
- m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
- break;
- case INSERT:
- m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
- break;
- default:
- m.addStyleName(Gerrit.RESOURCES.css().fileLineNone());
- break;
- }
- m.closeTd();
- }
-
- private void closeLine(final SafeHtmlBuilder m) {
- m.closeTr();
- }
-
- @Override
- protected void destroyCommentRow(final int row) {
- super.destroyCommentRow(row);
- if (row == R_HEAD + 1) {
- table.removeRow(row);
- isFileCommentBorderRowExist = false;
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
index 9ec4a8b7a1..10c029f45f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
@@ -46,6 +46,7 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
private static final int PC = 3;
private static final Comparator<PatchLineComment> BY_DATE =
new Comparator<PatchLineComment>() {
+ @Override
public int compare(final PatchLineComment o1, final PatchLineComment o2) {
return o1.getWrittenOn().compareTo(o2.getWrittenOn());
}
@@ -164,10 +165,12 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
nc.closeElement("img");
}
+ @Override
protected void createFileCommentEditorOnSideA() {
createCommentEditor(R_HEAD + 1, PC, R_HEAD, FILE_SIDE_A);
}
+ @Override
protected void createFileCommentEditorOnSideB() {
createCommentEditor(rowOfTableHeaderB + 1, PC, R_HEAD, FILE_SIDE_B);
createFileCommentBorderRow();
@@ -352,7 +355,7 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
@Override
public void display(final CommentDetail cd, boolean expandComments) {
- if (cd.isEmpty()) {
+ if (cd == null || cd.isEmpty()) {
return;
}
setAccountInfoCache(cd.getAccounts());
@@ -367,13 +370,13 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
row++;
if (!fora.isEmpty()) {
- row = insert(fora, row, expandComments);
+ row = insert(fora, row);
}
rowOfTableHeaderB = row;
borderRowOfFileComment = row + 1;
if (!forb.isEmpty()) {
row++;// Skip the Header of sideB.
- row = insert(forb, row, expandComments);
+ row = insert(forb, row);
borderRowOfFileComment = row;
createFileCommentBorderRow();
}
@@ -388,13 +391,13 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
all.addAll(fora);
all.addAll(forb);
Collections.sort(all, BY_DATE);
- row = insert(all, row, expandComments);
+ row = insert(all, row);
} else if (!fora.isEmpty()) {
- row = insert(fora, row, expandComments);
+ row = insert(fora, row);
} else if (!forb.isEmpty()) {
- row = insert(forb, row, expandComments);
+ row = insert(forb, row);
}
} else {
row++;
@@ -417,12 +420,7 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
defaultStyle(row, fmt);
}
- @Override
- protected PatchScreen.Type getPatchScreenType() {
- return PatchScreen.Type.UNIFIED;
- }
-
- private int insert(final List<PatchLineComment> in, int row, boolean expandComment) {
+ private int insert(final List<PatchLineComment> in, int row) {
for (Iterator<PatchLineComment> ci = in.iterator(); ci.hasNext();) {
final PatchLineComment c = ci.next();
if (c.getLine() == R_HEAD) {
@@ -430,7 +428,7 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
} else {
insertRow(row);
}
- bindComment(row, PC, c, !ci.hasNext(), expandComment);
+ bindComment(row, PC, c, !ci.hasNext());
row++;
}
return row;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedPatchScreen.java
index ad96f243c1..a5c14841c8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedPatchScreen.java
@@ -18,14 +18,15 @@ import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.RpcStatus;
-import com.google.gerrit.client.changes.CommitMessageBlock;
-import com.google.gerrit.client.changes.PatchTable;
-import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.WebLinkInfo;
+import com.google.gerrit.client.diff.DiffApi;
+import com.google.gerrit.client.diff.DiffInfo;
import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.data.PatchScript;
@@ -43,51 +44,19 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
-public abstract class PatchScreen extends Screen implements
+import java.util.Collections;
+import java.util.List;
+
+public class UnifiedPatchScreen extends Screen implements
CommentEditorContainer {
static final PrettyFactory PRETTY = ClientSideFormatter.FACTORY;
static final short LARGE_FILE_CONTEXT = 100;
- public static class SideBySide extends PatchScreen {
- public SideBySide(final Patch.Key id, final int patchIndex,
- final PatchSetDetail patchSetDetail, final PatchTable patchTable,
- final TopView topView, final PatchSet.Id baseId) {
- super(id, patchIndex, patchSetDetail, patchTable, topView, baseId);
- }
-
- @Override
- protected SideBySideTable createContentTable() {
- return new SideBySideTable();
- }
-
- @Override
- public PatchScreen.Type getPatchScreenType() {
- return PatchScreen.Type.SIDE_BY_SIDE;
- }
- }
-
- public static class Unified extends PatchScreen {
- public Unified(final Patch.Key id, final int patchIndex,
- final PatchSetDetail patchSetDetail, final PatchTable patchTable,
- final TopView topView, final PatchSet.Id baseId) {
- super(id, patchIndex, patchSetDetail, patchTable, topView, baseId);
- }
-
- @Override
- protected UnifiedDiffTable createContentTable() {
- return new UnifiedDiffTable();
- }
-
- @Override
- public PatchScreen.Type getPatchScreenType() {
- return PatchScreen.Type.UNIFIED;
- }
- }
-
/**
* What should be displayed in the top of the screen
*/
@@ -108,7 +77,7 @@ public abstract class PatchScreen extends Screen implements
private HistoryTable historyTable;
private FlowPanel topPanel;
private FlowPanel contentPanel;
- private AbstractPatchContentTable contentTable;
+ private UnifiedDiffTable contentTable;
private CommitMessageBlock commitMessageBlock;
private NavLinks topNav;
private NavLinks bottomNav;
@@ -129,24 +98,12 @@ public abstract class PatchScreen extends Screen implements
private boolean intralineFailure;
private boolean intralineTimeout;
- /**
- * How this patch should be displayed in the patch screen.
- */
- public static enum Type {
- UNIFIED, SIDE_BY_SIDE
- }
-
- protected PatchScreen(final Patch.Key id, final int patchIndex,
- final PatchSetDetail detail, final PatchTable patchTable,
- final TopView top, final PatchSet.Id baseId) {
+ public UnifiedPatchScreen(Patch.Key id, TopView top, PatchSet.Id baseId) {
patchKey = id;
- patchSetDetail = detail;
- fileList = patchTable;
topView = top;
idSideA = baseId; // null here means we're diff'ing from the Base
idSideB = id.getParentKey();
- this.patchIndex = patchIndex;
prefs = fileList != null
? fileList.getPreferences()
@@ -248,7 +205,7 @@ public abstract class PatchScreen extends Screen implements
topPanel = new FlowPanel();
add(topPanel);
- contentTable = createContentTable();
+ contentTable = new UnifiedDiffTable();
contentTable.fileList = fileList;
topNav = new NavLinks(keysNavigation, patchKey.getParentKey());
@@ -256,12 +213,7 @@ public abstract class PatchScreen extends Screen implements
add(topNav);
contentPanel = new FlowPanel();
- if (getPatchScreenType() == PatchScreen.Type.SIDE_BY_SIDE) {
- contentPanel.setStyleName(//
- Gerrit.RESOURCES.css().sideBySideScreenSideBySideTable());
- } else {
- contentPanel.setStyleName(Gerrit.RESOURCES.css().unifiedTable());
- }
+ contentPanel.setStyleName(Gerrit.RESOURCES.css().unifiedTable());
contentPanel.add(contentTable);
add(contentPanel);
@@ -271,17 +223,48 @@ public abstract class PatchScreen extends Screen implements
}
if (fileList != null) {
- topNav.display(patchIndex, getPatchScreenType(), fileList);
- bottomNav.display(patchIndex, getPatchScreenType(), fileList);
+ displayNav();
}
}
+ private void displayNav() {
+ DiffApi.diff(idSideB, patchKey.getFileName())
+ .base(idSideA)
+ .webLinksOnly()
+ .get(new GerritCallback<DiffInfo>() {
+ @Override
+ public void onSuccess(DiffInfo diffInfo) {
+ topNav.display(patchIndex, fileList,
+ getLinks(), getWebLinks(diffInfo));
+ bottomNav.display(patchIndex, fileList,
+ getLinks(), getWebLinks(diffInfo));
+ }
+ });
+ }
+
+ private List<InlineHyperlink> getLinks() {
+ InlineHyperlink toSideBySideDiffLink = new InlineHyperlink();
+ toSideBySideDiffLink.setHTML(new ImageResourceRenderer().render(Gerrit.RESOURCES.sideBySideDiff()));
+ toSideBySideDiffLink.setTargetHistoryToken(getSideBySideDiffUrl());
+ toSideBySideDiffLink.setTitle(PatchUtil.C.sideBySideDiff());
+ return Collections.singletonList(toSideBySideDiffLink);
+ }
+
+ private List<WebLinkInfo> getWebLinks(DiffInfo diffInfo) {
+ return diffInfo.unified_web_links();
+ }
+
+ private String getSideBySideDiffUrl() {
+ return Dispatcher.toPatch("sidebyside", idSideA,
+ new Patch.Key(idSideB, patchKey.getFileName()));
+ }
+
@Override
protected void onLoad() {
super.onLoad();
if (patchSetDetail == null) {
- Util.DETAIL_SVC.patchSetDetail(idSideB,
+ PatchUtil.CHANGE_SVC.patchSetDetail(idSideB,
new GerritCallback<PatchSetDetail>() {
@Override
public void onSuccess(PatchSetDetail result) {
@@ -334,10 +317,6 @@ public abstract class PatchScreen extends Screen implements
}
}
- protected abstract AbstractPatchContentTable createContentTable();
-
- public abstract PatchScreen.Type getPatchScreenType();
-
public PatchSet.Id getSideA() {
return idSideA;
}
@@ -366,7 +345,7 @@ public abstract class PatchScreen extends Screen implements
final int rpcseq = ++rpcSequence;
lastScript = null;
settingsPanel.setEnabled(false);
- reviewedPanels.populate(patchKey, fileList, patchIndex, getPatchScreenType());
+ reviewedPanels.populate(patchKey, fileList, patchIndex);
if (isFirst && fileList != null && fileList.isLoaded()) {
fileList.movePointerTo(patchKey);
}
@@ -386,7 +365,7 @@ public abstract class PatchScreen extends Screen implements
// Handled by ScreenLoadCallback.onFailure.
}
}));
- PatchUtil.DETAIL_SVC.patchScript(patchKey, idSideA, idSideB,
+ PatchUtil.PATCH_SVC.patchScript(patchKey, idSideA, idSideB,
settingsPanel.getValue(), cb.addFinal(
new ScreenLoadCallback<PatchScript>(this) {
@Override
@@ -423,7 +402,7 @@ public abstract class PatchScreen extends Screen implements
commentLinkProcessor);
} else {
commitMessageBlock.setVisible(false);
- Util.DETAIL_SVC.patchSetDetail(idSideB,
+ PatchUtil.CHANGE_SVC.patchSetDetail(idSideB,
new GerritCallback<PatchSetDetail>() {
@Override
public void onSuccess(PatchSetDetail result) {
@@ -445,22 +424,6 @@ public abstract class PatchScreen extends Screen implements
}
}
- if (contentTable instanceof SideBySideTable
- && contentTable.isPureMetaChange(script)
- && !contentTable.isDisplayBinary) {
- // User asked for SideBySide (or a link guessed, wrong) and we can't
- // show a pure-rename change there accurately. Switch to
- // the unified view instead. User can set file comments on binary file
- // in SideBySide view.
- //
- contentTable.removeFromParent();
- contentTable = new UnifiedDiffTable();
- contentTable.fileList = fileList;
- contentTable.setCommentLinkProcessor(commentLinkProcessor);
- contentPanel.add(contentTable);
- setToken(Dispatcher.toPatchUnified(idSideA, patchKey));
- }
-
if (script.isHugeFile()) {
AccountDiffPreference dp = script.getDiffPrefs();
int context = dp.getContext();
@@ -485,8 +448,7 @@ public abstract class PatchScreen extends Screen implements
lastScript = script;
if (fileList != null) {
- topNav.display(patchIndex, getPatchScreenType(), fileList);
- bottomNav.display(patchIndex, getPatchScreenType(), fileList);
+ displayNav();
}
if (Gerrit.isSignedIn()) {
@@ -562,8 +524,9 @@ public abstract class PatchScreen extends Screen implements
final PatchSet.Id psid = patchKey.getParentKey();
fileList = new PatchTable(prefs);
fileList.setSavePointerId("PatchTable " + psid);
- Util.DETAIL_SVC.patchSetDetail(psid,
+ PatchUtil.CHANGE_SVC.patchSetDetail(psid,
new GerritCallback<PatchSetDetail>() {
+ @Override
public void onSuccess(final PatchSetDetail result) {
fileList.display(idSideA, result);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
index 849863ee7b..6c1a841680 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
@@ -14,10 +14,12 @@
package com.google.gerrit.client.projects;
+import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.actions.ActionInfo;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
public class BranchInfo extends JavaScriptObject {
public final String getShortName() {
@@ -30,6 +32,7 @@ public class BranchInfo extends JavaScriptObject {
public final native String revision() /*-{ return this.revision; }-*/;
public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
public final native NativeMap<ActionInfo> actions() /*-{ return this.actions }-*/;
+ public final native JsArray<WebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
protected BranchInfo() {
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index e3c77b82fb..7aa5be5762 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -17,9 +17,9 @@ package com.google.gerrit.client.projects;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.actions.ActionInfo;
import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
@@ -44,6 +44,9 @@ public class ConfigInfo extends JavaScriptObject {
public final native InheritedBooleanInfo use_contributor_agreements()
/*-{ return this.use_contributor_agreements; }-*/;
+ public final native InheritedBooleanInfo create_new_change_for_all_not_in_target()
+ /*-{ return this.create_new_change_for_all_not_in_target; }-*/;
+
public final native InheritedBooleanInfo use_signed_off_by()
/*-{ return this.use_signed_off_by; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 4762027b7f..b121d07432 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -15,13 +15,12 @@ package com.google.gerrit.client.projects;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
-import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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.reviewdb.client.Project;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
@@ -59,22 +58,36 @@ public class ProjectApi {
project(name).view("branches").get(cb);
}
+ public static void getBranches(Project.NameKey name, int limit, int start,
+ String match, AsyncCallback<JsArray<BranchInfo>> cb) {
+ RestApi call = project(name).view("branches");
+ call.addParameter("n", limit);
+ call.addParameter("s", start);
+ if (match != null) {
+ if (match.startsWith("^")) {
+ call.addParameter("r", match);
+ } else {
+ call.addParameter("m", match);
+ }
+ }
+ call.get(cb);
+ }
+
/**
- * Delete branches. For each branch to be deleted a separate DELETE request is
- * fired to the server. The {@code onSuccess} method of the provided callback
- * is invoked once after all requests succeeded. If any request fails the
- * callbacks' {@code onFailure} method is invoked. In a failure case it can be
- * that still some of the branches were successfully deleted.
+ * Delete branches. One call is fired to the server to delete all the
+ * branches.
*/
public static void deleteBranches(Project.NameKey name,
Set<String> refs, AsyncCallback<VoidResult> cb) {
- CallbackGroup group = new CallbackGroup();
- for (String ref : refs) {
- project(name).view("branches").id(ref)
- .delete(group.add(cb));
- cb = CallbackGroup.emptyCallback();
+ if (refs.size() == 1) {
+ project(name).view("branches").id(refs.iterator().next()).delete(cb);
+ } else {
+ DeleteBranchesInput d = DeleteBranchesInput.create();
+ for (String ref : refs) {
+ d.add_branch(ref);
+ }
+ project(name).view("branches:delete").post(d, cb);
}
- group.done();
}
public static void getConfig(Project.NameKey name,
@@ -85,6 +98,7 @@ public class ProjectApi {
public static void setConfig(Project.NameKey name, String description,
InheritableBoolean useContributorAgreements,
InheritableBoolean useContentMerge, InheritableBoolean useSignedOffBy,
+ InheritableBoolean createNewChangeForAllNotInTarget,
InheritableBoolean requireChangeId, String maxObjectSizeLimit,
SubmitType submitType, ProjectState state,
Map<String, Map<String, ConfigParameterValue>> pluginConfigValues,
@@ -95,6 +109,7 @@ public class ProjectApi {
in.setUseContentMerge(useContentMerge);
in.setUseSignedOffBy(useSignedOffBy);
in.setRequireChangeId(requireChangeId);
+ in.setCreateNewChangeForAllNotInTarget(createNewChangeForAllNotInTarget);
in.setMaxObjectSizeLimit(maxObjectSizeLimit);
in.setSubmitType(submitType);
in.setState(state);
@@ -209,6 +224,12 @@ public class ProjectApi {
private final native void setRequireChangeIdRaw(String v)
/*-{ if(v)this.require_change_id=v; }-*/;
+ final void setCreateNewChangeForAllNotInTarget(InheritableBoolean v) {
+ setCreateNewChangeForAllNotInTargetRaw(v.name());
+ }
+ private final native void setCreateNewChangeForAllNotInTargetRaw(String v)
+ /*-{ if(v)this.create_new_change_for_all_not_in_target=v; }-*/;
+
final native void setMaxObjectSizeLimit(String l)
/*-{ if(l)this.max_object_size_limit=l; }-*/;
@@ -284,4 +305,18 @@ public class ProjectApi {
final native void setRef(String r) /*-{ if(r)this.ref=r; }-*/;
}
+
+ private static class DeleteBranchesInput extends JavaScriptObject {
+ static DeleteBranchesInput create() {
+ DeleteBranchesInput d = createObject().cast();
+ d.init();
+ return d;
+ }
+
+ protected DeleteBranchesInput() {
+ }
+
+ final native void init() /*-{ this.branches = []; }-*/;
+ final native void add_branch(String b) /*-{ this.branches.push(b); }-*/;
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
index 048ebbdb1b..fe9872ca58 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
@@ -15,7 +15,7 @@
package com.google.gerrit.client.projects;
import com.google.gerrit.client.WebLinkInfo;
-import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
index 29a8a019b2..0f121c816c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
@@ -50,6 +50,7 @@ public class ProjectMap extends NativeMap<ProjectInfo> {
.addParameter("n", limit)
.addParameterRaw("type", "ALL")
.addParameterTrue("d") // description
+ .background()
.get(NativeMap.copyKeysIntoChildren(cb));
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
index 7eaada02d3..071ca72376 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
@@ -42,8 +42,8 @@ import java.util.Set;
* processing it.
*/
public class CallbackGroup {
- private final List<CallbackImpl<?>> callbacks;
- private final Set<CallbackImpl<?>> remaining;
+ private final List<CallbackGlue> callbacks;
+ private final Set<CallbackGlue> remaining;
private boolean finalAdded;
private boolean failed;
@@ -66,11 +66,37 @@ public class CallbackGroup {
remaining = new HashSet<>();
}
+ public <T> Callback<T> addEmpty() {
+ Callback<T> cb = emptyCallback();
+ return add(cb);
+ }
+
public <T> Callback<T> add(final AsyncCallback<T> cb) {
checkFinalAdded();
return handleAdd(cb);
}
+ public <T> HttpCallback<T> add(HttpCallback<T> cb) {
+ checkFinalAdded();
+ if (failed) {
+ cb.onFailure(failedThrowable);
+ return new HttpCallback<T>() {
+ @Override
+ public void onSuccess(HttpResponse<T> result) {
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ };
+ }
+
+ HttpCallbackImpl<T> w = new HttpCallbackImpl<>(cb);
+ callbacks.add(w);
+ remaining.add(w);
+ return w;
+ }
+
public <T> Callback<T> addFinal(final AsyncCallback<T> cb) {
checkFinalAdded();
finalAdded = true;
@@ -79,7 +105,7 @@ public class CallbackGroup {
public void done() {
finalAdded = true;
- applyAllSuccess();
+ apply();
}
public void addListener(AsyncCallback<Void> cb) {
@@ -91,13 +117,33 @@ public class CallbackGroup {
}
public void addListener(CallbackGroup group) {
- addListener(group.add(CallbackGroup.<Void> emptyCallback()));
+ addListener(group.<Void> addEmpty());
}
- private void applyAllSuccess() {
- if (!failed && finalAdded && remaining.isEmpty()) {
- for (CallbackImpl<?> cb : callbacks) {
- cb.applySuccess();
+ private void success(CallbackGlue cb) {
+ remaining.remove(cb);
+ apply();
+ }
+
+ private <T> void failure(CallbackGlue w, Throwable caught) {
+ if (!failed) {
+ failed = true;
+ failedThrowable = caught;
+ }
+ remaining.remove(w);
+ apply();
+ }
+
+ private void apply() {
+ if (finalAdded && remaining.isEmpty()) {
+ if (failed) {
+ for (CallbackGlue cb : callbacks) {
+ cb.applyFailed();
+ }
+ } else {
+ for (CallbackGlue cb : callbacks) {
+ cb.applySuccess();
+ }
}
callbacks.clear();
}
@@ -125,7 +171,12 @@ public class CallbackGroup {
extends AsyncCallback<T>, com.google.gwtjsonrpc.common.AsyncCallback<T> {
}
- private class CallbackImpl<T> implements Callback<T> {
+ private interface CallbackGlue {
+ void applySuccess();
+ void applyFailed();
+ }
+
+ private class CallbackImpl<T> implements Callback<T>, CallbackGlue {
AsyncCallback<T> delegate;
T result;
@@ -135,39 +186,73 @@ public class CallbackGroup {
@Override
public void onSuccess(T value) {
- if (failed) {
- return;
- }
-
this.result = value;
- remaining.remove(this);
- CallbackGroup.this.applyAllSuccess();
+ success(this);
}
@Override
public void onFailure(Throwable caught) {
- if (failed) {
- return;
- }
+ failure(this, caught);
+ }
- failed = true;
- failedThrowable = caught;
- for (CallbackImpl<?> cb : callbacks) {
- cb.delegate.onFailure(failedThrowable);
- cb.delegate = null;
- cb.result = null;
+ @Override
+ public void applySuccess() {
+ AsyncCallback<T> cb = delegate;
+ if (cb != null) {
+ delegate = null;
+ cb.onSuccess(result);
+ result = null;
}
- callbacks.clear();
- remaining.clear();
}
- void applySuccess() {
+ @Override
+ public void applyFailed() {
AsyncCallback<T> cb = delegate;
if (cb != null) {
delegate = null;
+ result = null;
+ cb.onFailure(failedThrowable);
+ }
+ }
+ }
+
+ private class HttpCallbackImpl<T> implements HttpCallback<T>, CallbackGlue {
+ private HttpCallback<T> delegate;
+ private HttpResponse<T> result;
+
+ HttpCallbackImpl(HttpCallback<T> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void onSuccess(HttpResponse<T> result) {
+ this.result = result;
+ success(this);
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ failure(this, caught);
+ }
+
+ @Override
+ public void applySuccess() {
+ HttpCallback<T> cb = delegate;
+ if (cb != null) {
+ delegate = null;
cb.onSuccess(result);
result = null;
}
}
+
+ @Override
+ public void applyFailed() {
+ HttpCallback<T> cb = delegate;
+ if (cb != null) {
+ delegate = null;
+ result = null;
+ cb.onFailure(failedThrowable);
+ }
+ }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index 06d1f0bcad..bccd237bff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -23,7 +23,6 @@ import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.common.errors.NotSignedInException;
-import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwtjsonrpc.client.RemoteJsonException;
import com.google.gwtjsonrpc.client.ServerUnavailableException;
@@ -33,7 +32,12 @@ import com.google.gwtjsonrpc.common.JsonConstants;
public abstract class GerritCallback<T> implements
com.google.gwtjsonrpc.common.AsyncCallback<T>,
com.google.gwt.user.client.rpc.AsyncCallback<T> {
+ @Override
public void onFailure(final Throwable caught) {
+ showFailure(caught);
+ }
+
+ public static void showFailure(Throwable caught) {
if (isNotSignedIn(caught) || isInvalidXSRF(caught)) {
new NotSignedInDialog().center();
@@ -69,7 +73,6 @@ public abstract class GerritCallback<T> implements
new ErrorDialog(RpcConstants.C.errorServerUnavailable()).center();
} else {
- GWT.log(getClass().getName() + " caught " + caught, caught);
new ErrorDialog(caught).center();
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java
new file mode 100644
index 0000000000..a97642edd1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2015 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.client.rpc;
+
+/** AsyncCallback supplied with HTTP response headers. */
+public interface HttpCallback<T> {
+ void onSuccess(HttpResponse<T> result);
+ void onFailure(Throwable caught);
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java
new file mode 100644
index 0000000000..969dd30931
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2015 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.client.rpc;
+
+import com.google.gwt.http.client.Response;
+
+/** Wraps decoded server reply with HTTP headers. */
+public class HttpResponse<T> {
+ private final Response httpResponse;
+ private final String contentType;
+ private final T result;
+
+ HttpResponse(Response httpResponse, String contentType, T result) {
+ this.httpResponse = httpResponse;
+ this.contentType = contentType;
+ this.result = result;
+ }
+
+ /** HTTP status code, always in the 2xx family. */
+ public int getStatusCode() {
+ return httpResponse.getStatusCode();
+ }
+
+ /**
+ * Content type supplied by the server.
+ *
+ * This helper simplifies the common {@code getHeader("Content-Type")} case.
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /** Lookup an arbitrary reply header. */
+ public String getHeader(String header) {
+ if ("Content-Type".equals(header)) {
+ return contentType;
+ }
+ return httpResponse.getHeader(header);
+ }
+
+ public T getResult() {
+ return result;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
index 2bc4ac1e9b..e48477fe77 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
@@ -45,6 +45,7 @@ public class RestApi {
private static final String JSON_TYPE = "application/json";
private static final String JSON_UTF8 = JSON_TYPE + "; charset=utf-8";
private static final String TEXT_TYPE = "text/plain";
+ private static final String TEXT_UTF8 = TEXT_TYPE + "; charset=utf-8";
/**
* Expected JSON content body prefix that prevents XSSI.
@@ -104,21 +105,21 @@ public class RestApi {
}
}
- private static class HttpCallback<T extends JavaScriptObject>
+ private static class HttpImpl<T extends JavaScriptObject>
implements RequestCallback {
private final boolean background;
- private final AsyncCallback<T> cb;
+ private final HttpCallback<T> cb;
- HttpCallback(boolean bg, AsyncCallback<T> cb) {
+ HttpImpl(boolean bg, HttpCallback<T> cb) {
this.background = bg;
this.cb = cb;
}
@Override
- public void onResponseReceived(Request req, Response res) {
+ public void onResponseReceived(Request req, final Response res) {
int status = res.getStatusCode();
if (status == Response.SC_NO_CONTENT) {
- cb.onSuccess(null);
+ cb.onSuccess(new HttpResponse<T>(res, null, null));
if (!background) {
RpcStatus.INSTANCE.onRpcComplete();
}
@@ -126,12 +127,17 @@ public class RestApi {
} else if (200 <= status && status < 300) {
long start = System.currentTimeMillis();
final T data;
- if (isTextBody(res)) {
- data = NativeString.wrap(res.getText()).cast();
- } else if (isJsonBody(res)) {
+ final String type;
+ if (isJsonBody(res)) {
try {
- // javac generics bug
- data = RestApi.<T>cast(parseJson(res));
+ JSONValue val = parseJson(res);
+ if (isJsonEncoded(res) && val.isString() != null) {
+ data = NativeString.wrap(val.isString().stringValue()).cast();
+ type = simpleType(res.getHeader("X-FYI-Content-Type"));
+ } else {
+ data = RestApi.<T> cast(val);
+ type = JSON_TYPE;
+ }
} catch (JSONException e) {
if (!background) {
RpcStatus.INSTANCE.onRpcComplete();
@@ -140,6 +146,9 @@ public class RestApi {
"Invalid JSON: " + e.getMessage()));
return;
}
+ } else if (isTextBody(res)) {
+ data = NativeString.wrap(res.getText()).cast();
+ type = TEXT_TYPE;
} else {
if (!background) {
RpcStatus.INSTANCE.onRpcComplete();
@@ -154,7 +163,7 @@ public class RestApi {
@Override
public void execute() {
try {
- cb.onSuccess(data);
+ cb.onSuccess(new HttpResponse<>(res, type, data));
} finally {
if (!background) {
RpcStatus.INSTANCE.onRpcComplete();
@@ -318,16 +327,24 @@ public class RestApi {
}
public <T extends JavaScriptObject> void get(AsyncCallback<T> cb) {
+ get(wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void get(HttpCallback<T> cb) {
send(GET, cb);
}
public <T extends JavaScriptObject> void delete(AsyncCallback<T> cb) {
+ delete(wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void delete(HttpCallback<T> cb) {
send(DELETE, cb);
}
- private <T extends JavaScriptObject> void send(
- Method method, AsyncCallback<T> cb) {
- HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
+ private <T extends JavaScriptObject> void send(Method method,
+ HttpCallback<T> cb) {
+ HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
try {
if (!background) {
RpcStatus.INSTANCE.onRpcStart();
@@ -341,33 +358,59 @@ public class RestApi {
public <T extends JavaScriptObject> void post(
JavaScriptObject content,
AsyncCallback<T> cb) {
+ post(content, wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void post(
+ JavaScriptObject content,
+ HttpCallback<T> cb) {
sendJSON(POST, content, cb);
}
public <T extends JavaScriptObject> void post(String content,
AsyncCallback<T> cb) {
- sendRaw(POST, content, cb);
+ post(content, wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void post(String content,
+ HttpCallback<T> cb) {
+ sendText(POST, content, cb);
}
public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) {
+ put(wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void put(HttpCallback<T> cb) {
send(PUT, cb);
}
public <T extends JavaScriptObject> void put(String content,
AsyncCallback<T> cb) {
- sendRaw(PUT, content, cb);
+ put(content, wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void put(String content,
+ HttpCallback<T> cb) {
+ sendText(PUT, content, cb);
}
public <T extends JavaScriptObject> void put(
JavaScriptObject content,
AsyncCallback<T> cb) {
+ put(content, wrap(cb));
+ }
+
+ public <T extends JavaScriptObject> void put(
+ JavaScriptObject content,
+ HttpCallback<T> cb) {
sendJSON(PUT, content, cb);
}
private <T extends JavaScriptObject> void sendJSON(
Method method, JavaScriptObject content,
- AsyncCallback<T> cb) {
- HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
+ HttpCallback<T> cb) {
+ HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
try {
if (!background) {
RpcStatus.INSTANCE.onRpcStart();
@@ -380,17 +423,18 @@ public class RestApi {
}
}
- private static native String str(JavaScriptObject jso) /*-{ return JSON.stringify(jso); }-*/;
+ private static native String str(JavaScriptObject jso)
+ /*-{ return JSON.stringify(jso) }-*/;
- private <T extends JavaScriptObject> void sendRaw(Method method, String body,
- AsyncCallback<T> cb) {
- HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
+ private <T extends JavaScriptObject> void sendText(Method method, String body,
+ HttpCallback<T> cb) {
+ HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
try {
if (!background) {
RpcStatus.INSTANCE.onRpcStart();
}
RequestBuilder req = request(method);
- req.setHeader("Content-Type", TEXT_TYPE);
+ req.setHeader("Content-Type", TEXT_UTF8);
req.sendRequest(body, httpCallback);
} catch (RequestException e) {
httpCallback.onError(null, e);
@@ -417,16 +461,21 @@ public class RestApi {
return isContentType(res, TEXT_TYPE);
}
+ private static boolean isJsonEncoded(Response res) {
+ return "json".equals(res.getHeader("X-FYI-Content-Encoding"));
+ }
+
private static boolean isContentType(Response res, String want) {
String type = res.getHeader("Content-Type");
- if (type == null) {
- return false;
- }
+ return type != null && want.equals(simpleType(type));
+ }
+
+ private static String simpleType(String type) {
int semi = type.indexOf(';');
if (semi >= 0) {
- type = type.substring(0, semi).trim();
+ return type.substring(0, semi).trim();
}
- return want.equals(type);
+ return type;
}
private static JSONValue parseJson(Response res)
@@ -459,4 +508,19 @@ public class RestApi {
throw new JSONException("unsupported JSON type");
}
}
+
+ private static <T extends JavaScriptObject> HttpCallback<T> wrap(
+ final AsyncCallback<T> cb) {
+ return new HttpCallback<T>() {
+ @Override
+ public void onSuccess(HttpResponse<T> r) {
+ cb.onSuccess(r.getResult());
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ cb.onFailure(e);
+ }
+ };
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java
index b8b9209eae..8128afec10 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java
@@ -28,6 +28,7 @@ public abstract class ScreenLoadCallback<T> extends GerritCallback<T> {
screen = s;
}
+ @Override
public final void onSuccess(final T result) {
if (screen.isAttached()) {
preDisplay(result);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/starFilled.gif b/gerrit-gwtui/src/main/java/com/google/gerrit/client/starFilled.gif
deleted file mode 100644
index 77619f0dc3..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/starFilled.gif
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/starOpen.gif b/gerrit-gwtui/src/main/java/com/google/gerrit/client/starOpen.gif
deleted file mode 100644
index e8dc0a3aa2..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/starOpen.gif
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
index 604699512a..5f4081ba1f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
@@ -35,10 +35,12 @@ public class AccountGroupSuggestOracle extends SuggestAfterTypingNCharsOracle {
@Override
public void _onRequestSuggestions(final Request req, final Callback callback) {
RpcStatus.hide(new Runnable() {
+ @Override
public void run() {
SuggestUtil.SVC.suggestAccountGroupForProject(
projectName, req.getQuery(), req.getLimit(),
new GerritCallback<List<GroupReference>>() {
+ @Override
public void onSuccess(final List<GroupReference> result) {
priorResults.clear();
final ArrayList<AccountGroupSuggestion> r =
@@ -66,10 +68,12 @@ public class AccountGroupSuggestOracle extends SuggestAfterTypingNCharsOracle {
info = k;
}
+ @Override
public String getDisplayString() {
return info.getName();
}
+ @Override
public String getReplacementString() {
return info.getName();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
index 4ffcd1878d..78350db9d1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
@@ -15,9 +15,11 @@
package com.google.gerrit.client.ui;
import com.google.gerrit.client.FormatUtil;
-import com.google.gerrit.client.RpcStatus;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.common.data.AccountInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.ui.SuggestOracle;
import java.util.ArrayList;
@@ -26,23 +28,18 @@ import java.util.List;
/** Suggestion Oracle for Account entities. */
public class AccountSuggestOracle extends SuggestAfterTypingNCharsOracle {
@Override
- public void _onRequestSuggestions(final Request req, final Callback callback) {
- RpcStatus.hide(new Runnable() {
- public void run() {
- SuggestUtil.SVC.suggestAccount(req.getQuery(), Boolean.TRUE,
- req.getLimit(),
- new GerritCallback<List<AccountInfo>>() {
- public void onSuccess(final List<AccountInfo> result) {
- final ArrayList<AccountSuggestion> r =
- new ArrayList<>(result.size());
- for (final AccountInfo p : result) {
- r.add(new AccountSuggestion(p));
- }
- callback.onSuggestionsReady(req, new Response(r));
- }
- });
- }
- });
+ public void _onRequestSuggestions(final Request req, final Callback cb) {
+ AccountApi.suggest(req.getQuery(), req.getLimit(),
+ new GerritCallback<JsArray<AccountInfo>>() {
+ @Override
+ public void onSuccess(JsArray<AccountInfo> in) {
+ List<AccountSuggestion> r = new ArrayList<>(in.length());
+ for (AccountInfo p : Natives.asList(in)) {
+ r.add(new AccountSuggestion(p));
+ }
+ cb.onSuggestionsReady(req, new Response(r));
+ }
+ });
}
private static class AccountSuggestion implements SuggestOracle.Suggestion {
@@ -52,12 +49,14 @@ public class AccountSuggestOracle extends SuggestAfterTypingNCharsOracle {
info = k;
}
+ @Override
public String getDisplayString() {
- return FormatUtil.nameEmail(FormatUtil.asInfo(info));
+ return FormatUtil.nameEmail(info);
}
+ @Override
public String getReplacementString() {
- return FormatUtil.nameEmail(FormatUtil.asInfo(info));
+ return FormatUtil.nameEmail(info);
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java
deleted file mode 100644
index 295842cc0d..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java
+++ /dev/null
@@ -1,42 +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.client.ui;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeDetailCache;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gwt.user.client.ui.FocusWidget;
-
-public abstract class ActionDialog extends CommentedActionDialog<ChangeDetail> {
- public ActionDialog(final FocusWidget enableOnFailure, final boolean redirect,
- String dialogTitle, String dialogHeading) {
- super(dialogTitle, dialogHeading, new ChangeDetailCache.IgnoreErrorCallback() {
- @Override
- public void onSuccess(ChangeDetail result) {
- if (redirect) {
- Gerrit.display(PageLinks.toChange(result.getChange().getId()));
- } else {
- super.onSuccess(result);
- }
- }
-
- @Override
- public void onFailure(Throwable caught) {
- enableOnFailure.setEnabled(true);
- }
- });
- }
-} \ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java
index d4aaa4c78a..72233f5a47 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java
@@ -15,96 +15,56 @@
package com.google.gerrit.client.ui;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.admin.Util;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.SuggestBox;
-import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
import com.google.gwt.user.client.ui.SuggestOracle;
-import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
public class AddMemberBox extends Composite {
private final FlowPanel addPanel;
private final Button addMember;
- private final HintTextBox nameTxtBox;
- private final SuggestBox nameTxt;
- private boolean submitOnSelection;
-
- public AddMemberBox() {
- this(Util.C.buttonAddGroupMember(), Util.C.defaultAccountName(),
- new AccountSuggestOracle());
- }
+ private final RemoteSuggestBox suggestBox;
public AddMemberBox(final String buttonLabel, final String hint,
final SuggestOracle suggestOracle) {
addPanel = new FlowPanel();
addMember = new Button(buttonLabel);
- nameTxtBox = new HintTextBox();
- nameTxt = new SuggestBox(new RPCSuggestOracle(
- suggestOracle), nameTxtBox);
- nameTxt.setStyleName(Gerrit.RESOURCES.css().addMemberTextBox());
-
- nameTxtBox.setVisibleLength(50);
- nameTxtBox.setHintText(hint);
- nameTxtBox.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- submitOnSelection = false;
- if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- if (((DefaultSuggestionDisplay) nameTxt.getSuggestionDisplay())
- .isSuggestionListShowing()) {
- submitOnSelection = true;
- } else {
- doAdd();
- }
- }
- }
- });
- nameTxt.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ suggestBox = new RemoteSuggestBox(suggestOracle);
+ suggestBox.setStyleName(Gerrit.RESOURCES.css().addMemberTextBox());
+ suggestBox.setVisibleLength(50);
+ suggestBox.setHintText(hint);
+ suggestBox.addSelectionHandler(new SelectionHandler<String>() {
@Override
- public void onSelection(SelectionEvent<Suggestion> event) {
- nameTxtBox.setFocus(true);
- if (submitOnSelection) {
- submitOnSelection = false;
- doAdd();
- }
+ public void onSelection(SelectionEvent<String> event) {
+ addMember.fireEvent(new ClickEvent() {});
}
});
- addPanel.add(nameTxt);
+ addPanel.add(suggestBox);
addPanel.add(addMember);
initWidget(addPanel);
}
- public void addClickHandler(final ClickHandler handler) {
+ public void addClickHandler(ClickHandler handler) {
addMember.addClickHandler(handler);
}
public String getText() {
- String s = nameTxtBox.getText();
- return s == null ? "" : s;
+ return suggestBox.getText();
}
public void setEnabled(boolean enabled) {
addMember.setEnabled(enabled);
- nameTxtBox.setEnabled(enabled);
+ suggestBox.setEnabled(enabled);
}
public void setText(String text) {
- nameTxtBox.setText(text);
- }
-
- private void doAdd() {
- addMember.fireEvent(new ClickEvent() {});
+ suggestBox.setText(text);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
index 54c4c53466..00269f946d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
@@ -23,7 +23,6 @@ import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwtexpui.globalkey.client.GlobalKey;
@@ -32,12 +31,12 @@ import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
import java.util.LinkedList;
import java.util.List;
-public abstract class CherryPickDialog extends ActionDialog {
+public abstract class CherryPickDialog extends TextAreaActionDialog {
private SuggestBox newBranch;
private List<BranchInfo> branches;
- public CherryPickDialog(final FocusWidget enableOnFailure, Project.NameKey project) {
- super(enableOnFailure, true, Util.C.cherryPickTitle(), Util.C
+ public CherryPickDialog(Project.NameKey project) {
+ super(Util.C.cherryPickTitle(), Util.C
.cherryPickCommitMessage());
ProjectApi.getBranches(project,
new GerritCallback<JsArray<BranchInfo>>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
index bb50b19c7c..748cd3cd5f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
@@ -257,6 +257,7 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
private static class DoubleClickHTML extends HTML implements
HasDoubleClickHandlers {
+ @Override
public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
return addDomHandler(handler, DoubleClickEvent.getType());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
index b8fa373c57..60b5f930d2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
@@ -15,47 +15,35 @@
package com.google.gerrit.client.ui;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.SmallHeading;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtexpui.user.client.AutoCenterDialogBox;
-public abstract class CommentedActionDialog<T> extends AutoCenterDialogBox
+public abstract class CommentedActionDialog extends AutoCenterDialogBox
implements CloseHandler<PopupPanel> {
protected final FlowPanel panel;
- protected final NpTextArea message;
protected final Button sendButton;
protected final Button cancelButton;
protected final FlowPanel buttonPanel;
- protected AsyncCallback<T> callback;
+ protected final FlowPanel contentPanel;
protected FocusWidget focusOn;
protected boolean sent = false;
- public CommentedActionDialog(final String title, final String heading,
- AsyncCallback<T> callback) {
+ public CommentedActionDialog(final String title, final String heading) {
super(/* auto hide */false, /* modal */true);
- this.callback = callback;
setGlassEnabled(true);
setText(title);
addStyleName(Gerrit.RESOURCES.css().commentedActionDialog());
- message = new NpTextArea();
- message.setCharacterWidth(60);
- message.setVisibleLines(10);
- message.getElement().setPropertyBoolean("spellcheck", true);
- setFocusOn(message);
sendButton = new Button(Util.C.commentedActionButtonSend());
sendButton.addClickHandler(new ClickHandler() {
@Override
@@ -74,9 +62,8 @@ public abstract class CommentedActionDialog<T> extends AutoCenterDialogBox
}
});
- final FlowPanel mwrap = new FlowPanel();
- mwrap.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
- mwrap.add(message);
+ contentPanel = new FlowPanel();
+ contentPanel.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
buttonPanel = new FlowPanel();
buttonPanel.add(sendButton);
@@ -84,8 +71,10 @@ public abstract class CommentedActionDialog<T> extends AutoCenterDialogBox
buttonPanel.getElement().getStyle().setProperty("marginTop", "4px");
panel = new FlowPanel();
- panel.add(new SmallHeading(heading));
- panel.add(mwrap);
+ if (heading != null) {
+ panel.add(new SmallHeading(heading));
+ }
+ panel.add(contentPanel);
panel.add(buttonPanel);
add(panel);
@@ -112,38 +101,8 @@ public abstract class CommentedActionDialog<T> extends AutoCenterDialogBox
@Override
public void onClose(CloseEvent<PopupPanel> event) {
- if (!sent) {
- // the dialog was closed without the send button being pressed
- // e.g. the user pressed Cancel or ESC to close the dialog
- if (callback != null) {
- callback.onFailure(null);
- }
- }
sent = false;
}
public abstract void onSend();
-
- public String getMessageText() {
- return message.getText().trim();
- }
-
- public AsyncCallback<T> createCallback() {
- return new GerritCallback<T>(){
- @Override
- public void onSuccess(T result) {
- sent = true;
- if (callback != null) {
- callback.onSuccess(result);
- }
- hide();
- }
-
- @Override
- public void onFailure(Throwable caught) {
- enableButtons(true);
- super.onFailure(caught);
- }
- };
- }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
index eb62809891..4cc6a16d0a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
@@ -14,7 +14,6 @@
package com.google.gerrit.client.ui;
-import com.google.gerrit.client.Gerrit;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
@@ -56,7 +55,6 @@ public class ComplexDisclosurePanel extends Composite implements
{
setElement((Element)(DOM.createTD()));
getElement().setInnerHTML("&nbsp;");
- addStyleName(Gerrit.RESOURCES.css().complexHeader());
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java
new file mode 100644
index 0000000000..021f39c4a1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2014 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.client.ui;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.projects.BranchInfo;
+import com.google.gerrit.client.projects.ProjectApi;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class CreateChangeDialog extends TextAreaActionDialog {
+ private SuggestBox newChange;
+ private List<BranchInfo> branches;
+
+ public CreateChangeDialog(Project.NameKey project) {
+ super(Util.C.dialogCreateChangeTitle(),
+ Util.C.dialogCreateChangeHeading());
+ ProjectApi.getBranches(project,
+ new GerritCallback<JsArray<BranchInfo>>() {
+ @Override
+ public void onSuccess(JsArray<BranchInfo> result) {
+ branches = Natives.asList(result);
+ }
+ });
+
+ newChange = new SuggestBox(new HighlightSuggestOracle() {
+ @Override
+ protected void onRequestSuggestions(Request request, Callback done) {
+ List<BranchSuggestion> suggestions = new ArrayList<>();
+ for (BranchInfo b : branches) {
+ if (b.ref().contains(request.getQuery())) {
+ suggestions.add(new BranchSuggestion(b));
+ }
+ }
+ done.onSuggestionsReady(request, new Response(suggestions));
+ }
+ });
+
+ newChange.setWidth("100%");
+ newChange.getElement().getStyle().setProperty("boxSizing", "border-box");
+ message.setCharacterWidth(70);
+
+ FlowPanel mwrap = new FlowPanel();
+ mwrap.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
+ mwrap.add(newChange);
+
+ panel.insert(mwrap, 0);
+ panel.insert(new SmallHeading(Util.C.newChangeBranchSuggestion()), 0);
+ }
+
+ @Override
+ public void center() {
+ super.center();
+ GlobalKey.dialog(this);
+ newChange.setFocus(true);
+ }
+
+ public String getDestinationBranch() {
+ return newChange.getText();
+ }
+
+ class BranchSuggestion implements Suggestion {
+ private BranchInfo branch;
+
+ public BranchSuggestion(BranchInfo branch) {
+ this.branch = branch;
+ }
+
+ @Override
+ public String getDisplayString() {
+ if (branch.ref().startsWith(Branch.R_HEADS)) {
+ return branch.ref().substring(Branch.R_HEADS.length());
+ }
+ return branch.ref();
+ }
+
+ @Override
+ public String getReplacementString() {
+ return branch.getShortName();
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ExpandAllCommand.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ExpandAllCommand.java
index 43721d168d..9abf135e08 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ExpandAllCommand.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ExpandAllCommand.java
@@ -28,6 +28,7 @@ public class ExpandAllCommand implements Command {
open = isOpen;
}
+ @Override
public void execute() {
for (final Widget w : panel) {
if (w instanceof CommentPanel) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
deleted file mode 100644
index 3ea7acc9e7..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
+++ /dev/null
@@ -1,25 +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.client.ui;
-
-public interface FilteredUserInterface {
- /**
- * Return the value by which the user interface is currently filtered.
- *
- * @return value by which the user interface is currently filtered,
- * {@code null} or empty String if currently no filter is applied
- */
- public String getCurrentFilter();
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java
index 09550a6819..2ec6cd933b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java
@@ -43,6 +43,7 @@ public class HintTextBox extends NpTextBox {
private boolean isFocused;
+ @Override
public String getText() {
if (hintOn) {
return "";
@@ -50,6 +51,7 @@ public class HintTextBox extends NpTextBox {
return super.getText();
}
+ @Override
public void setText(String text) {
focusHint();
@@ -196,6 +198,7 @@ public class HintTextBox extends NpTextBox {
}
}
+ @Override
public void setFocus(boolean focus) {
super.setFocus(focus);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java
deleted file mode 100644
index c9cadccc75..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/IgnoreOutdatedFilterResultsCallbackWrapper.java
+++ /dev/null
@@ -1,50 +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.client.ui;
-
-import com.google.gerrit.client.rpc.GerritCallback;
-
-/**
- * GerritCallback to be used on user interfaces that allow filtering to handle
- * RPC's that request filtering. The user may change the filter quickly so that
- * a response may be outdated when the client receives it. In this case the
- * response must be ignored because the responses to RCP's may come out-of-order
- * and an outdated response would overwrite the correct result which was
- * received before.
- */
-public class IgnoreOutdatedFilterResultsCallbackWrapper<T> extends GerritCallback<T> {
- private final FilteredUserInterface filteredUI;
- private final String myFilter;
- private final GerritCallback<T> cb;
-
- public IgnoreOutdatedFilterResultsCallbackWrapper(
- final FilteredUserInterface filteredUI, final GerritCallback<T> cb) {
- this.filteredUI = filteredUI;
- this.myFilter = filteredUI.getCurrentFilter();
- this.cb = cb;
- }
-
- @Override
- public void onSuccess(final T result) {
- if ((myFilter == null && filteredUI.getCurrentFilter() == null)
- || (myFilter != null && myFilter.equals(filteredUI.getCurrentFilter()))) {
- cb.onSuccess(result);
- }
- // Else ignore the result, the user has already changed the filter
- // and the result is not relevant anymore. If multiple RPC's are
- // fired the results may come back out-of-order and a non-relevant
- // result could overwrite the correct result if not ignored.
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java
index deca808e3f..4f7d419623 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java
@@ -86,6 +86,7 @@ public class LinkMenuBar extends Composite implements ScreenLoadHandler {
return body.getWidgetIndex(i);
}
+ @Override
public void onScreenLoad(ScreenLoadEvent event) {
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java
index 29c053bc6e..9cc91a046f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java
@@ -38,6 +38,7 @@ public class LinkMenuItem extends InlineHyperlink implements ScreenLoadHandler {
this.bar = bar;
}
+ @Override
public void onScreenLoad(ScreenLoadEvent event) {
if (match(event.getScreen().getToken())) {
Gerrit.selectMenu(bar);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableOldValue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableOldValue.java
index 47bca2bc09..758dff4168 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableOldValue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableOldValue.java
@@ -22,6 +22,7 @@ public class ListenableOldValue<T> extends ListenableValue<T> {
return oldValue;
}
+ @Override
public void set(final T value) {
try {
oldValue = get();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
index d834eb21fb..c8bb12e88c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
@@ -37,10 +37,12 @@ public class ListenableValue<T> implements HasValueChangeHandlers<T> {
fireEvent(new ValueChangeEvent<T>(value) {});
}
+ @Override
public void fireEvent(GwtEvent<?> event) {
manager.fireEvent(event);
}
+ @Override
public HandlerRegistration addValueChangeHandler(
ValueChangeHandler<T> handler) {
return manager.addHandler(ValueChangeEvent.getType(), handler);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 4a56558b24..91fedef36e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -139,12 +139,23 @@ public abstract class NavigationTable<RowItem> extends FancyFlexTable<RowItem> {
}
}
- /** Invoked when the user double clicks on a table cell. */
+ /**
+ * Invoked when the user double clicks on a table cell.
+ *
+ * @param row row number.
+ * @param column column number.
+ */
protected void onCellDoubleClick(int row, int column) {
onOpenRow(row);
}
- /** Invoked when the user clicks on a table cell. */
+ /**
+ * Invoked when the user clicks on a table cell.
+ *
+ * @param event click event.
+ * @param row row number.
+ * @param column column number.
+ */
protected void onCellSingleClick(Event event, int row, int column) {
movePointerTo(row);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
index 4bf8fefab2..e254f6e073 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
@@ -21,26 +21,22 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.SuggestBox;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
import java.util.HashSet;
import java.util.Set;
public class ParentProjectBox extends Composite {
- private final NpTextBox textBox;
- private final SuggestBox suggestBox;
+ private final RemoteSuggestBox suggestBox;
private final ParentProjectNameSuggestOracle suggestOracle;
public ParentProjectBox() {
- textBox = new NpTextBox();
suggestOracle = new ParentProjectNameSuggestOracle();
- suggestBox = new SuggestBox(suggestOracle, textBox);
+ suggestBox = new RemoteSuggestBox(suggestOracle);
initWidget(suggestBox);
}
public void setVisibleLength(int len) {
- textBox.setVisibleLength(len);
+ suggestBox.setVisibleLength(len);
}
public void setProject(final Project.NameKey project) {
@@ -82,6 +78,7 @@ public class ParentProjectBox extends Composite {
@Override
public void _onRequestSuggestions(Request req, final Callback callback) {
super._onRequestSuggestions(req, new Callback() {
+ @Override
public void onSuggestionsReady(Request request, Response response) {
if (exclude.size() > 0) {
Set<Suggestion> filteredSuggestions =
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
index f79dc510db..09244c9158 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
@@ -15,90 +15,23 @@
package com.google.gerrit.client.ui;
import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.changes.PatchTable;
-import com.google.gerrit.client.patches.PatchScreen;
-import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
public class PatchLink extends InlineHyperlink {
- protected PatchSet.Id base;
- protected Patch.Key patchKey;
- protected int patchIndex;
- protected PatchSetDetail patchSetDetail;
- protected PatchTable parentPatchTable;
- protected PatchScreen.TopView topView;
-
- /**
- * @param text The text of this link
- * @param base optional base to compare against.
- * @param patchKey The key for this patch
- * @param patchIndex The index of the current patch in the patch set
- * @param historyToken The history token
- * @param patchSetDetail Detailed information about the patch set.
- * @param parentPatchTable The table used to display this link
- */
- protected PatchLink(String text, PatchSet.Id base, Patch.Key patchKey,
- int patchIndex, String historyToken,
- PatchSetDetail patchSetDetail, PatchTable parentPatchTable,
- PatchScreen.TopView topView) {
+ private PatchLink(String text, String historyToken) {
super(text, historyToken);
- this.base = base;
- this.patchKey = patchKey;
- this.patchIndex = patchIndex;
- this.patchSetDetail = patchSetDetail;
- this.parentPatchTable = parentPatchTable;
- this.parentPatchTable = parentPatchTable;
- this.topView = topView;
- }
-
- /**
- * @param text The text of this link
- * @param type The type of the link to create (unified/side-by-side)
- * @param patchScreen The patchScreen to grab contents to link to from
- */
- public PatchLink(String text, PatchScreen.Type type, PatchScreen patchScreen) {
- this(text, //
- patchScreen.getSideA(), //
- patchScreen.getPatchKey(), //
- patchScreen.getPatchIndex(), //
- Dispatcher.toPatch(type, patchScreen.getPatchKey()), //
- patchScreen.getPatchSetDetail(), //
- patchScreen.getFileList(), //
- patchScreen.getTopView() //
- );
- }
-
- @Override
- public void go() {
- Dispatcher.patch( //
- getTargetHistoryToken(), //
- base, //
- patchKey, //
- patchIndex, //
- patchSetDetail, //
- parentPatchTable,
- topView //
- );
}
public static class SideBySide extends PatchLink {
- public SideBySide(String text, PatchSet.Id base, Patch.Key patchKey,
- int patchIndex, PatchSetDetail patchSetDetail,
- PatchTable parentPatchTable) {
- super(text, base, patchKey, patchIndex,
- Dispatcher.toPatchSideBySide(base, patchKey),
- patchSetDetail, parentPatchTable, null);
+ public SideBySide(String text, PatchSet.Id base, Patch.Key id) {
+ super(text, Dispatcher.toSideBySide(base, id));
}
}
public static class Unified extends PatchLink {
- public Unified(String text, PatchSet.Id base, final Patch.Key patchKey,
- int patchIndex, PatchSetDetail patchSetDetail,
- PatchTable parentPatchTable) {
- super(text, base, patchKey, patchIndex,
- Dispatcher.toPatchUnified(base, patchKey),
- patchSetDetail, parentPatchTable, null);
+ public Unified(String text, PatchSet.Id base, Patch.Key id) {
+ super(text, Dispatcher.toUnified(base, id));
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
index 2917505115..119f5ef034 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
@@ -19,7 +19,7 @@ import com.google.gerrit.client.admin.ProjectScreen;
import com.google.gerrit.reviewdb.client.Project;
public class ProjectLinkMenuItem extends LinkMenuItem {
- private final String panel;
+ protected final String panel;
public ProjectLinkMenuItem(String text, String panel) {
super(text, "");
@@ -38,10 +38,14 @@ public class ProjectLinkMenuItem extends LinkMenuItem {
if (projectKey != null) {
setVisible(true);
- setTargetHistoryToken(Dispatcher.toProjectAdmin(projectKey, panel));
+ onScreenLoad(projectKey);
} else {
setVisible(false);
}
super.onScreenLoad(event);
}
+
+ protected void onScreenLoad(Project.NameKey project) {
+ setTargetHistoryToken(Dispatcher.toProjectAdmin(project, panel));
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
index cac766794e..dc27790c5b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
@@ -35,13 +35,14 @@ import com.google.gwtexpui.globalkey.client.HidePopupPanelCommand;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtexpui.user.client.PluginSafeDialogBox;
-/** It creates a popup containing all the projects. */
-public class ProjectListPopup implements FilteredUserInterface {
+/** A popup containing all projects. */
+public class ProjectListPopup {
private HighlightingProjectsTable projectsTab;
private PluginSafeDialogBox popup;
private NpTextBox filterTxt;
private HorizontalPanel filterPanel;
- private String subname;
+ private String match;
+ private Query query;
private Button close;
private ScrollPanel sp;
private PopupPanel.PositionCallback popupPosition;
@@ -88,9 +89,19 @@ public class ProjectListPopup implements FilteredUserInterface {
};
}
+ /**
+ * Invoked after moving pointer to a project.
+ *
+ * @param projectName project name.
+ */
protected void onMovePointerTo(String projectName) {
}
+ /**
+ * Invoked after opening a project row.
+ *
+ * @param projectName project name.
+ */
protected void openRow(String projectName) {
}
@@ -107,12 +118,16 @@ public class ProjectListPopup implements FilteredUserInterface {
filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
filterPanel.add(filterLabel);
filterTxt = new NpTextBox();
- filterTxt.setValue(subname);
filterTxt.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
- subname = filterTxt.getValue();
- populateProjects();
+ Query q = new Query(filterTxt.getValue());
+ if (!match.equals(q.qMatch)) {
+ if (query == null) {
+ q.run();
+ }
+ query = q;
+ }
}
});
filterPanel.add(filterTxt);
@@ -148,7 +163,8 @@ public class ProjectListPopup implements FilteredUserInterface {
public void displayPopup() {
poppingUp = true;
if (firstPopupLoad) { // For sizing/positioning, delay display until loaded
- populateProjects();
+ match = "";
+ query = new Query(match).run();
} else {
popup.setPopupPositionAndShow(popupPosition);
GlobalKey.dialog(popup);
@@ -173,23 +189,39 @@ public class ProjectListPopup implements FilteredUserInterface {
this.preferredLeft = left;
}
- protected void populateProjects() {
- ProjectMap.match(subname,
- new IgnoreOutdatedFilterResultsCallbackWrapper<ProjectMap>(this,
- new GerritCallback<ProjectMap>() {
- @Override
- public void onSuccess(final ProjectMap result) {
- projectsTab.display(result, subname);
- if (firstPopupLoad) { // Display was delayed until table was loaded
- firstPopupLoad = false;
- displayPopup();
- }
- }
- }));
- }
+ private class Query {
+ private final String qMatch;
- @Override
- public String getCurrentFilter() {
- return subname;
+ Query(String match) {
+ this.qMatch = match;
+ }
+
+ Query run() {
+ ProjectMap.match(qMatch, new GerritCallback<ProjectMap>() {
+ @Override
+ public void onSuccess(ProjectMap result) {
+ if (!firstPopupLoad && !popup.isShowing()) {
+ query = null;
+ } else if (query == Query.this) {
+ query = null;
+ showMap(result);
+ } else {
+ query.run();
+ }
+ }
+ });
+ return this;
+ }
+
+ private void showMap(ProjectMap result) {
+ ProjectListPopup.this.match = qMatch;
+ projectsTab.display(result, qMatch);
+
+ if (firstPopupLoad) {
+ // Display was delayed until table was loaded
+ firstPopupLoad = false;
+ displayPopup();
+ }
+ }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java
index 80364cfb8d..49120f6ea5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java
@@ -14,7 +14,6 @@
package com.google.gerrit.client.ui;
-import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
@@ -23,17 +22,12 @@ import com.google.gerrit.client.rpc.Natives;
public class ProjectNameSuggestOracle extends SuggestAfterTypingNCharsOracle {
@Override
public void _onRequestSuggestions(final Request req, final Callback callback) {
- RpcStatus.hide(new Runnable() {
- @Override
- public void run() {
- ProjectMap.suggest(req.getQuery(), req.getLimit(),
- new GerritCallback<ProjectMap>() {
- @Override
- public void onSuccess(ProjectMap map) {
- callback.onSuggestionsReady(req, new Response(Natives.asList(map.values())));
- }
- });
- }
- });
+ ProjectMap.suggest(req.getQuery(), req.getLimit(),
+ new GerritCallback<ProjectMap>() {
+ @Override
+ public void onSuccess(ProjectMap map) {
+ callback.onSuggestionsReady(req, new Response(Natives.asList(map.values())));
+ }
+ });
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java
deleted file mode 100644
index 1068c3dadc..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2010 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.client.ui;
-
-import com.google.gwt.user.client.ui.SuggestOracle;
-
-/** This class will proxy SuggestOracle requests to another SuggestOracle
- * while keeping track of the latest request. Any repsonse that belongs
- * to a request which is not the latest request will be dropped to prevent
- * invalid deliveries.
- */
-
-public class RPCSuggestOracle extends SuggestOracle {
-
- private SuggestOracle oracle;
- private SuggestOracle.Request request;
- private SuggestOracle.Callback callback;
- private SuggestOracle.Callback myCallback = new SuggestOracle.Callback() {
- public void onSuggestionsReady(SuggestOracle.Request req,
- SuggestOracle.Response response) {
- if (request == req) {
- callback.onSuggestionsReady(req, response);
- request = null;
- callback = null;
- }
- }
- };
-
-
- public RPCSuggestOracle(SuggestOracle ora) {
- oracle = ora;
- }
-
- public void requestSuggestions(SuggestOracle.Request req,
- SuggestOracle.Callback cb) {
- request = req;
- callback = cb;
- oracle.requestSuggestions(req, myCallback);
- }
-
- public boolean isDisplayStringHTML() {
- return oracle.isDisplayStringHTML();
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
new file mode 100644
index 0000000000..5f47d981e5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
@@ -0,0 +1,147 @@
+// Copyright (C) 2015 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.client.ui;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeList;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class RebaseDialog extends CommentedActionDialog {
+ private final SuggestBox base;
+ private final CheckBox cb;
+ private List<ChangeInfo> changes;
+ private final boolean sendEnabled;
+
+ public RebaseDialog(final String project, final String branch,
+ final Change.Id changeId, final boolean sendEnabled) {
+ super(Util.C.rebaseTitle(), null);
+ this.sendEnabled = sendEnabled;
+ sendButton.setText(Util.C.buttonRebaseChangeSend());
+
+ // create the suggestion box
+ base = new SuggestBox(new HighlightSuggestOracle() {
+ @Override
+ protected void onRequestSuggestions(Request request, Callback done) {
+ String query = request.getQuery().toLowerCase();
+ LinkedList<ChangeSuggestion> suggestions = new LinkedList<>();
+ for (final ChangeInfo ci : changes) {
+ if (changeId.equals(ci.legacy_id())) {
+ continue; // do not suggest current change
+ }
+ String id = String.valueOf(ci.legacy_id().get());
+ if (id.contains(query) || ci.subject().toLowerCase().contains(query)) {
+ suggestions.add(new ChangeSuggestion(ci));
+ if (suggestions.size() >= 50) { // limit to 50 suggestions
+ break;
+ }
+ }
+ }
+ done.onSuggestionsReady(request, new Response(suggestions));
+ }
+ });
+ base.getElement().setAttribute("placeholder",
+ Util.C.rebasePlaceholderMessage());
+ base.setStyleName(Gerrit.RESOURCES.css().rebaseSuggestBox());
+
+ // the checkbox which must be clicked before the change list is populated
+ cb = new CheckBox(Util.C.rebaseConfirmMessage());
+ cb.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ boolean checked = ((CheckBox) event.getSource()).getValue();
+ if (checked) {
+ ChangeList.next("project:" + project + " AND branch:" + branch
+ + " AND is:open NOT age:90d", 0, 1000,
+ new GerritCallback<ChangeList>() {
+ @Override
+ public void onSuccess(ChangeList result) {
+ changes = Natives.asList(result);
+ updateControls(true);
+ }
+ });
+ } else {
+ updateControls(false);
+ }
+ }
+ });
+
+ // add the checkbox and suggestbox widgets to the content panel
+ contentPanel.add(cb);
+ contentPanel.add(base);
+ contentPanel.setStyleName(Gerrit.RESOURCES.css().rebaseContentPanel());
+ }
+
+ @Override
+ public void center() {
+ super.center();
+ GlobalKey.dialog(this);
+ updateControls(false);
+ }
+
+ private void updateControls(boolean changeParentEnabled) {
+ if (changeParentEnabled) {
+ sendButton.setTitle(null);
+ sendButton.setEnabled(true);
+ base.setEnabled(true);
+ base.setFocus(true);
+ } else {
+ base.setEnabled(false);
+ sendButton.setEnabled(sendEnabled);
+ if (sendEnabled) {
+ sendButton.setTitle(null);
+ sendButton.setFocus(true);
+ } else {
+ sendButton.setTitle(Util.C.rebaseNotPossibleMessage());
+ cancelButton.setFocus(true);
+ }
+ }
+ }
+
+ public String getBase() {
+ return cb.getValue() ? base.getText() : null;
+ }
+
+ private static class ChangeSuggestion implements Suggestion {
+ private ChangeInfo change;
+
+ public ChangeSuggestion(ChangeInfo change) {
+ this.change = change;
+ }
+
+ @Override
+ public String getDisplayString() {
+ return String.valueOf(change.legacy_id().get()) + ": " + change.subject();
+ }
+
+ @Override
+ public String getReplacementString() {
+ return String.valueOf(change.legacy_id().get());
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestBox.java
new file mode 100644
index 0000000000..50f991c71e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestBox.java
@@ -0,0 +1,137 @@
+// Copyright (C) 2014 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.client.ui;
+
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.TextBoxBase;
+
+public class RemoteSuggestBox extends Composite implements Focusable, HasText,
+ HasSelectionHandlers<String>, HasCloseHandlers<RemoteSuggestBox> {
+ private final RemoteSuggestOracle remoteSuggestOracle;
+ private final DefaultSuggestionDisplay display;
+ private final HintTextBox textBox;
+ private final SuggestBox suggestBox;
+ private boolean submitOnSelection;
+
+ public RemoteSuggestBox(SuggestOracle oracle) {
+ remoteSuggestOracle = new RemoteSuggestOracle(oracle);
+ display = new DefaultSuggestionDisplay();
+
+ textBox = new HintTextBox();
+ textBox.addKeyDownHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent e) {
+ submitOnSelection = false;
+
+ if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ CloseEvent.fire(RemoteSuggestBox.this, RemoteSuggestBox.this);
+ } else if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ if (display.isSuggestionListShowing()) {
+ if (textBox.getValue().equals(remoteSuggestOracle.getLast())) {
+ submitOnSelection = true;
+ } else {
+ display.hideSuggestions();
+ }
+ } else {
+ SelectionEvent.fire(RemoteSuggestBox.this, getText());
+ }
+ }
+ }
+ });
+
+ suggestBox = new SuggestBox(remoteSuggestOracle, textBox, display);
+ suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ @Override
+ public void onSelection(SelectionEvent<Suggestion> event) {
+ textBox.setFocus(true);
+ if (submitOnSelection) {
+ SelectionEvent.fire(RemoteSuggestBox.this, getText());
+ }
+ }
+ });
+ initWidget(suggestBox);
+ }
+
+ public void setHintText(String hint) {
+ textBox.setHintText(hint);
+ }
+
+ public void setVisibleLength(int len) {
+ textBox.setVisibleLength(len);
+ }
+
+ public void setEnabled(boolean enabled) {
+ suggestBox.setEnabled(enabled);
+ }
+
+ public TextBoxBase getTextBox() {
+ return textBox;
+ }
+
+ @Override
+ public String getText() {
+ return suggestBox.getText();
+ }
+
+ @Override
+ public void setText(String value) {
+ suggestBox.setText(value);
+ }
+
+ @Override
+ public void setFocus(boolean focus) {
+ suggestBox.setFocus(focus);
+ }
+
+ @Override
+ public int getTabIndex() {
+ return suggestBox.getTabIndex();
+ }
+
+ @Override
+ public void setAccessKey(char key) {
+ suggestBox.setAccessKey(key);
+ }
+
+ @Override
+ public void setTabIndex(int index) {
+ suggestBox.setTabIndex(index);
+ }
+
+ @Override
+ public HandlerRegistration addSelectionHandler(SelectionHandler<String> h) {
+ return addHandler(h, SelectionEvent.getType());
+ }
+
+ @Override
+ public HandlerRegistration addCloseHandler(CloseHandler<RemoteSuggestBox> h) {
+ return addHandler(h, CloseEvent.getType());
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java
new file mode 100644
index 0000000000..9554ac5f25
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2010 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.client.ui;
+
+import com.google.gwt.user.client.ui.SuggestOracle;
+
+/**
+ * Delegates to a slow SuggestOracle, such as a remote server API.
+ * <p>
+ * A response is only supplied to the UI if no requests were made after the
+ * oracle begin that request.
+ * <p>
+ * When a request is made while the delegate is still processing a prior request
+ * all intermediate requests are discarded and the most recent request is
+ * queued. The pending request's response is discarded and the most recent
+ * request is started.
+ */
+public class RemoteSuggestOracle extends SuggestOracle {
+ private final SuggestOracle oracle;
+ private Query query;
+ private String last;
+
+ public RemoteSuggestOracle(SuggestOracle src) {
+ oracle = src;
+ }
+
+ public String getLast() {
+ return last;
+ }
+
+ @Override
+ public void requestSuggestions(Request req, Callback cb) {
+ Query q = new Query(req, cb);
+ if (query == null) {
+ q.start();
+ }
+ query = q;
+ }
+
+ @Override
+ public boolean isDisplayStringHTML() {
+ return oracle.isDisplayStringHTML();
+ }
+
+ private class Query implements Callback {
+ final Request request;
+ final Callback callback;
+
+ Query(Request req, Callback cb) {
+ request = req;
+ callback = cb;
+ }
+
+ void start() {
+ oracle.requestSuggestions(request, this);
+ }
+
+ @Override
+ public void onSuggestionsReady(Request req, Response res) {
+ if (query == this) {
+ // No new request was started while this query was running.
+ // Propose this request's response as the suggestions.
+ query = null;
+ last = request.getQuery();
+ callback.onSuggestionsReady(req, res);
+ } else {
+ // Another query came in while this one was running. Skip
+ // this response and start the most recent query.
+ query.start();
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
deleted file mode 100644
index 12506daefc..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
+++ /dev/null
@@ -1,81 +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.client.ui;
-
-import com.google.gerrit.client.FormatUtil;
-import com.google.gerrit.client.RpcStatus;
-import com.google.gerrit.client.admin.Util;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.common.data.AccountInfo;
-import com.google.gerrit.common.data.ReviewerInfo;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.user.client.ui.SuggestOracle;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Suggestion Oracle for reviewers. */
-public class ReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
-
- private Change.Id changeId;
-
- @Override
- protected void _onRequestSuggestions(final Request req, final Callback callback) {
- RpcStatus.hide(new Runnable() {
- public void run() {
- SuggestUtil.SVC.suggestChangeReviewer(changeId, req.getQuery(),
- req.getLimit(), new GerritCallback<List<ReviewerInfo>>() {
- public void onSuccess(final List<ReviewerInfo> result) {
- final List<ReviewerSuggestion> r =
- new ArrayList<>(result.size());
- for (final ReviewerInfo reviewer : result) {
- r.add(new ReviewerSuggestion(reviewer));
- }
- callback.onSuggestionsReady(req, new Response(r));
- }
- });
- }
- });
- }
-
- public void setChange(Change.Id changeId) {
- this.changeId = changeId;
- }
-
- private static class ReviewerSuggestion implements SuggestOracle.Suggestion {
- private final ReviewerInfo reviewerInfo;
-
- ReviewerSuggestion(final ReviewerInfo reviewerInfo) {
- this.reviewerInfo = reviewerInfo;
- }
-
- public String getDisplayString() {
- final AccountInfo accountInfo = reviewerInfo.getAccountInfo();
- if (accountInfo != null) {
- return FormatUtil.nameEmail(FormatUtil.asInfo(accountInfo));
- }
- return reviewerInfo.getGroup().getName() + " ("
- + Util.C.suggestedGroupLabel() + ")";
- }
-
- public String getReplacementString() {
- final AccountInfo accountInfo = reviewerInfo.getAccountInfo();
- if (accountInfo != null) {
- return FormatUtil.nameEmail(FormatUtil.asInfo(accountInfo));
- }
- return reviewerInfo.getGroup().getName();
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
index 4f54ba512b..26c0ce62fd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
@@ -17,6 +17,9 @@ package com.google.gerrit.client.ui;
import com.google.gerrit.client.Gerrit;
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+import java.util.Collections;
+import java.util.List;
+
/**
* Suggest oracle that only provides suggestions if the user has typed at least
* as many characters as configured by 'suggest.from'. If 'suggest.from' is set
@@ -25,10 +28,12 @@ import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
public abstract class SuggestAfterTypingNCharsOracle extends HighlightSuggestOracle {
@Override
- protected void onRequestSuggestions(final Request request, final Callback done) {
- final int suggestFrom = Gerrit.getConfig().getSuggestFrom();
- if (suggestFrom == 0 || request.getQuery().length() >= suggestFrom) {
- _onRequestSuggestions(request, done);
+ protected void onRequestSuggestions(Request req, Callback cb) {
+ if (req.getQuery().length() >= Gerrit.getConfig().getSuggestFrom()) {
+ _onRequestSuggestions(req, cb);
+ } else {
+ List<Suggestion> none = Collections.emptyList();
+ cb.onSuggestionsReady(req, new Response(none));
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextAreaActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextAreaActionDialog.java
new file mode 100644
index 0000000000..d7d5d6a42a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextAreaActionDialog.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2015 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.client.ui;
+
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+
+public abstract class TextAreaActionDialog extends CommentedActionDialog
+ implements CloseHandler<PopupPanel> {
+ protected final NpTextArea message;
+
+ public TextAreaActionDialog(String title, String heading) {
+ super(title, heading);
+
+ message = new NpTextArea();
+ message.setCharacterWidth(60);
+ message.setVisibleLines(10);
+ message.getElement().setPropertyBoolean("spellcheck", true);
+ setFocusOn(message);
+
+ contentPanel.add(message);
+ }
+
+ public String getMessageText() {
+ return message.getText().trim();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
index bcfb394ec0..0f5e12ad76 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
@@ -25,4 +25,8 @@ public interface UIConstants extends Constants {
String projectItemHelp();
String projectStateAbbrev();
String projectStateHelp();
+
+ String dialogCreateChangeTitle();
+ String dialogCreateChangeHeading();
+ String newChangeBranchSuggestion();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
index 1e0e18563d..a0845d980c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
@@ -5,4 +5,8 @@ projectName = Project Name
projectDescription = Project Description
projectItemHelp = project
projectStateAbbrev = S
-projectStateHelp = State \ No newline at end of file
+projectStateHelp = State
+
+dialogCreateChangeTitle = Create Change
+dialogCreateChangeHeading = Description
+newChangeBranchSuggestion = Select branch for new change
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
index 24a0f57d57..add033fce0 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
+++ b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
@@ -20,4 +20,5 @@
<source path='lib'/>
<source path='keymap'/>
<source path='mode'/>
+ <source path='theme'/>
</module>
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
index 85b1fa67f8..1c9ad3c72f 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -14,10 +14,14 @@
package net.codemirror.lib;
+import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.diff.DisplaySide;
+import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import net.codemirror.lib.TextMarker.FromTo;
@@ -28,316 +32,343 @@ import net.codemirror.lib.TextMarker.FromTo;
* @see <a href="http://codemirror.net/doc/manual.html#api">CodeMirror API</a>
*/
public class CodeMirror extends JavaScriptObject {
+ public static void preload() {
+ initLibrary(CallbackGroup.<Void> emptyCallback());
+ }
+
public static void initLibrary(AsyncCallback<Void> cb) {
Loader.initLibrary(cb);
}
- public static native CodeMirror create(
- DisplaySide side,
- Element parent,
- Configuration cfg) /*-{
- var m = $wnd.CodeMirror(parent, cfg);
- m._sbs2_side = side;
- return m;
+ interface Style extends CssResource {
+ String activeLine();
+ String showTabs();
+ String margin();
+ }
+
+ static Style style() {
+ return Lib.I.style();
+ }
+
+ public static CodeMirror create(Element p, Configuration cfg) {
+ CodeMirror cm = newCM(p, cfg);
+ Extras.attach(cm);
+ return cm;
+ }
+
+ private static native CodeMirror newCM(Element p, Configuration cfg) /*-{
+ return $wnd.CodeMirror(p, cfg);
}-*/;
public final native void setOption(String option, boolean value) /*-{
- this.setOption(option, value);
+ this.setOption(option, value)
}-*/;
public final native void setOption(String option, double value) /*-{
- this.setOption(option, value);
+ this.setOption(option, value)
}-*/;
public final native void setOption(String option, String value) /*-{
- this.setOption(option, value);
+ this.setOption(option, value)
}-*/;
public final native void setOption(String option, JavaScriptObject val) /*-{
- this.setOption(option, val);
+ this.setOption(option, val)
}-*/;
public final native String getStringOption(String o) /*-{ return this.getOption(o) }-*/;
- public final native void setValue(String v) /*-{ this.setValue(v); }-*/;
- public final native void setWidth(double w) /*-{ this.setSize(w, null); }-*/;
- public final native void setWidth(String w) /*-{ this.setSize(w, null); }-*/;
- public final native void setHeight(double h) /*-{ this.setSize(null, h); }-*/;
- public final native void setHeight(String h) /*-{ this.setSize(null, h); }-*/;
- public final native String getLine(int n) /*-{ return this.getLine(n) }-*/;
+ public final native String getValue() /*-{ return this.getValue() }-*/;
+ public final native void setValue(String v) /*-{ this.setValue(v) }-*/;
+
+ public final native int changeGeneration(boolean closeEvent)
+ /*-{ return this.changeGeneration(closeEvent) }-*/;
+ public final native boolean isClean(int generation)
+ /*-{ return this.isClean(generation) }-*/;
- public final native void refresh() /*-{ this.refresh(); }-*/;
- public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/;
+ public final native void setWidth(double w) /*-{ this.setSize(w, null) }-*/;
+ public final native void setHeight(double h) /*-{ this.setSize(null, h) }-*/;
- public final native TextMarker markText(LineCharacter from, LineCharacter to,
+ public final int getHeight() {
+ return getWrapperElement().getClientHeight();
+ }
+
+ public final void adjustHeight(int localHeader) {
+ int rest = Gerrit.getHeaderFooterHeight()
+ + localHeader
+ + 5; // Estimate
+ setHeight(Window.getClientHeight() - rest);
+ }
+
+ public final native String getLine(int n) /*-{ return this.getLine(n) }-*/;
+ public final native double barHeight() /*-{ return this.display.barHeight }-*/;
+ public final native double barWidth() /*-{ return this.display.barWidth }-*/;
+ public final native int lastLine() /*-{ return this.lastLine() }-*/;
+ public final native void refresh() /*-{ this.refresh() }-*/;
+
+ public final native TextMarker markText(Pos from, Pos to,
Configuration options) /*-{
- return this.markText(from, to, options);
+ return this.markText(from, to, options)
}-*/;
public enum LineClassWhere {
- TEXT, BACKGROUND, WRAP
+ TEXT { @Override String value() { return "text"; } },
+ BACKGROUND { @Override String value() { return "background"; } },
+ WRAP { @Override String value() { return "wrap"; } };
+ abstract String value();
}
public final void addLineClass(int line, LineClassWhere where,
String className) {
- addLineClassNative(line, where.name().toLowerCase(), className);
+ addLineClassNative(line, where.value(), className);
}
private final native void addLineClassNative(int line, String where,
String lineClass) /*-{
- this.addLineClass(line, where, lineClass);
+ this.addLineClass(line, where, lineClass)
}-*/;
public final void addLineClass(LineHandle line, LineClassWhere where,
String className) {
- addLineClassNative(line, where.name().toLowerCase(), className);
+ addLineClassNative(line, where.value(), className);
}
private final native void addLineClassNative(LineHandle line, String where,
String lineClass) /*-{
- this.addLineClass(line, where, lineClass);
+ this.addLineClass(line, where, lineClass)
}-*/;
public final void removeLineClass(int line, LineClassWhere where,
String className) {
- removeLineClassNative(line, where.name().toLowerCase(), className);
+ removeLineClassNative(line, where.value(), className);
}
private final native void removeLineClassNative(int line, String where,
String lineClass) /*-{
- this.removeLineClass(line, where, lineClass);
+ this.removeLineClass(line, where, lineClass)
}-*/;
public final void removeLineClass(LineHandle line, LineClassWhere where,
String className) {
- removeLineClassNative(line, where.name().toLowerCase(), className);
+ removeLineClassNative(line, where.value(), className);
}
private final native void removeLineClassNative(LineHandle line, String where,
String lineClass) /*-{
- this.removeLineClass(line, where, lineClass);
+ this.removeLineClass(line, where, lineClass)
}-*/;
- public final native void addWidget(LineCharacter pos, Element node,
- boolean scrollIntoView) /*-{
- this.addWidget(pos, node, scrollIntoView);
+ public final native void addWidget(Pos pos, Element node) /*-{
+ this.addWidget(pos, node, false)
}-*/;
public final native LineWidget addLineWidget(int line, Element node,
Configuration options) /*-{
- return this.addLineWidget(line, node, options);
+ return this.addLineWidget(line, node, options)
}-*/;
public final native int lineAtHeight(double height) /*-{
- return this.lineAtHeight(height);
+ return this.lineAtHeight(height)
}-*/;
public final native int lineAtHeight(double height, String mode) /*-{
- return this.lineAtHeight(height, mode);
+ return this.lineAtHeight(height, mode)
}-*/;
public final native double heightAtLine(int line) /*-{
- return this.heightAtLine(line);
+ return this.heightAtLine(line)
}-*/;
public final native double heightAtLine(int line, String mode) /*-{
- return this.heightAtLine(line, mode);
+ return this.heightAtLine(line, mode)
}-*/;
- public final native Rect charCoords(LineCharacter pos, String mode) /*-{
- return this.charCoords(pos, mode);
+ public final native Rect charCoords(Pos pos, String mode) /*-{
+ return this.charCoords(pos, mode)
}-*/;
public final native CodeMirrorDoc getDoc() /*-{
- return this.getDoc();
+ return this.getDoc()
}-*/;
public final native void scrollTo(double x, double y) /*-{
- this.scrollTo(x, y);
+ this.scrollTo(x, y)
}-*/;
public final native void scrollToY(double y) /*-{
- this.scrollTo(null, y);
+ this.scrollTo(null, y)
}-*/;
+ public final void scrollToLine(int line) {
+ int height = getHeight();
+ if (lineAtHeight(height - 20) < line) {
+ scrollToY(heightAtLine(line, "local") - 0.5 * height);
+ }
+ setCursor(Pos.create(line, 0));
+ }
+
public final native ScrollInfo getScrollInfo() /*-{
- return this.getScrollInfo();
+ return this.getScrollInfo()
}-*/;
public final native Viewport getViewport() /*-{
- return this.getViewport();
+ return this.getViewport()
}-*/;
public final native void operation(Runnable thunk) /*-{
this.operation(function() {
thunk.@java.lang.Runnable::run()();
- });
+ })
}-*/;
- public final native void on(String event, Runnable thunk) /*-{
- this.on(event, $entry(function() {
- thunk.@java.lang.Runnable::run()();
- }));
+ public final native void off(String event, RegisteredHandler h) /*-{
+ this.off(event, h)
+ }-*/;
+
+ public final native RegisteredHandler on(String event, Runnable thunk) /*-{
+ var h = $entry(function() { thunk.@java.lang.Runnable::run()() });
+ this.on(event, h);
+ return h;
}-*/;
public final native void on(String event, EventHandler handler) /*-{
this.on(event, $entry(function(cm, e) {
handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(
- Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
- }));
+ Lnet/codemirror/lib/CodeMirror;
+ Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
+ }))
}-*/;
public final native void on(String event, RenderLineHandler handler) /*-{
- this.on(event, $entry(function(cm, h, ele) {
+ this.on(event, $entry(function(cm, h, e) {
handler.@net.codemirror.lib.CodeMirror.RenderLineHandler::handle(
- Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/CodeMirror$LineHandle;
- Lcom/google/gwt/dom/client/Element;)(cm, h, ele);
- }));
+ Lnet/codemirror/lib/CodeMirror;
+ Lnet/codemirror/lib/CodeMirror$LineHandle;
+ Lcom/google/gwt/dom/client/Element;)(cm, h, e);
+ }))
}-*/;
public final native void on(String event, GutterClickHandler handler) /*-{
this.on(event, $entry(function(cm, l, g, e) {
handler.@net.codemirror.lib.CodeMirror.GutterClickHandler::handle(
- Lnet/codemirror/lib/CodeMirror;ILjava/lang/String;
+ Lnet/codemirror/lib/CodeMirror;
+ I
+ Ljava/lang/String;
Lcom/google/gwt/dom/client/NativeEvent;)(cm, l, g, e);
- }));
+ }))
}-*/;
public final native void on(String event, BeforeSelectionChangeHandler handler) /*-{
- this.on(event, $entry(function(cm, e) {
+ this.on(event, $entry(function(cm, o) {
+ var e = o.ranges[o.ranges.length-1];
handler.@net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler::handle(
- Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/LineCharacter;
- Lnet/codemirror/lib/LineCharacter;)(cm,e.anchor,e.head);
- }));
+ Lnet/codemirror/lib/CodeMirror;
+ Lnet/codemirror/lib/Pos;
+ Lnet/codemirror/lib/Pos;)(cm, e.anchor, e.head);
+ }))
}-*/;
- public final native LineCharacter getCursor() /*-{
- return this.getCursor();
+ public final native void on(ChangesHandler handler) /*-{
+ this.on('changes', $entry(function(cm, o) {
+ handler.@net.codemirror.lib.CodeMirror.ChangesHandler::handle(
+ Lnet/codemirror/lib/CodeMirror;)(cm);
+ }))
}-*/;
- public final native LineCharacter getCursor(String start) /*-{
- return this.getCursor(start);
+ public final native void setCursor(Pos p) /*-{ this.setCursor(p) }-*/;
+ public final native Pos getCursor() /*-{ return this.getCursor() }-*/;
+ public final native Pos getCursor(String start) /*-{
+ return this.getCursor(start)
}-*/;
public final FromTo getSelectedRange() {
return FromTo.create(getCursor("start"), getCursor("end"));
}
- public final native void setSelection(LineCharacter anchor) /*-{
- this.setSelection(anchor);
- }-*/;
-
- public final native void setSelection(LineCharacter anchor, LineCharacter head) /*-{
- this.setSelection(anchor, head);
- }-*/;
-
- public final native void setCursor(LineCharacter lineCh) /*-{
- this.setCursor(lineCh);
+ public final native void setSelection(Pos p) /*-{ this.setSelection(p) }-*/;
+ public final native void setSelection(Pos anchor, Pos head) /*-{
+ this.setSelection(anchor, head)
}-*/;
public final native boolean somethingSelected() /*-{
- return this.somethingSelected();
- }-*/;
-
- public final native boolean hasActiveLine() /*-{
- return !!this.state.activeLine;
- }-*/;
-
- public final native LineHandle getActiveLine() /*-{
- return this.state.activeLine;
+ return this.somethingSelected()
}-*/;
- public final native void setActiveLine(LineHandle line) /*-{
- this.state.activeLine = line;
- }-*/;
-
- public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map); }-*/;
- public final native void removeKeyMap(KeyMap map) /*-{ this.removeKeyMap(map); }-*/;
-
- public static final native LineCharacter pos(int line, int ch) /*-{
- return $wnd.CodeMirror.Pos(line, ch);
- }-*/;
-
- public static final native LineCharacter pos(int line) /*-{
- return $wnd.CodeMirror.Pos(line);
- }-*/;
+ public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map) }-*/;
+ public final native void removeKeyMap(KeyMap map) /*-{ this.removeKeyMap(map) }-*/;
public final native LineHandle getLineHandle(int line) /*-{
- return this.getLineHandle(line);
+ return this.getLineHandle(line)
}-*/;
public final native LineHandle getLineHandleVisualStart(int line) /*-{
- return this.getLineHandleVisualStart(line);
+ return this.getLineHandleVisualStart(line)
}-*/;
public final native int getLineNumber(LineHandle handle) /*-{
- return this.getLineNumber(handle);
+ return this.getLineNumber(handle)
}-*/;
public final native void focus() /*-{
- this.focus();
+ this.focus()
}-*/;
- public final native Element getGutterElement() /*-{
- return this.getGutterElement();
+ public final native Element getWrapperElement() /*-{
+ return this.getWrapperElement()
}-*/;
- public final native Element getSizer() /*-{
- return this.display.sizer;
+ public final native Element getGutterElement() /*-{
+ return this.getGutterElement()
}-*/;
- public final native Element getMoverElement() /*-{
- return this.display.mover;
+ public final native Element sizer() /*-{
+ return this.display.sizer
}-*/;
- public final native Element getMeasureElement() /*-{
- return this.display.measure;
+ public final native Element mover() /*-{
+ return this.display.mover
}-*/;
- public final native Element getScrollbarV() /*-{
- return this.display.scrollbarV;
+ public final native Element measure() /*-{
+ return this.display.measure
}-*/;
- public static final native KeyMap cloneKeyMap(String name) /*-{
- var i = $wnd.CodeMirror.keyMap[name];
- var o = {};
- for (n in i)
- if (i.hasOwnProperty(n))
- o[n] = i[n];
- return o;
+ public final native Element scrollbarV() /*-{
+ return this.display.scrollbars.vert.node;
}-*/;
public final native void execCommand(String cmd) /*-{
- this.execCommand(cmd);
+ this.execCommand(cmd)
}-*/;
- public static final native void addKeyMap(String name, KeyMap km) /*-{
- $wnd.CodeMirror.keyMap[name] = km;
+ public static final native KeyMap getKeyMap(String name) /*-{
+ return $wnd.CodeMirror.keyMap[name];
}-*/;
- public static final native void handleVimKey(CodeMirror cm, String key) /*-{
- $wnd.CodeMirror.Vim.handleKey(cm, key);
+ public static final native void addKeyMap(String name, KeyMap km) /*-{
+ $wnd.CodeMirror.keyMap[name] = km
}-*/;
- public static final native void mapVimKey(String alias, String actual) /*-{
- $wnd.CodeMirror.Vim.map(alias, actual);
+ public final native Vim vim() /*-{
+ return this;
}-*/;
- public final native boolean hasVimSearchHighlight() /*-{
- return this.state.vim && this.state.vim.searchState_ &&
- !!this.state.vim.searchState_.getOverlay();
- }-*/;
+ public final DisplaySide side() {
+ return extras().side();
+ }
- public final native DisplaySide side() /*-{ return this._sbs2_side }-*/;
+ public final Extras extras() {
+ return Extras.get(this);
+ }
protected CodeMirror() {
}
public static class Viewport extends JavaScriptObject {
- public final native int getFrom() /*-{ return this.from; }-*/;
- public final native int getTo() /*-{ return this.to; }-*/;
+ public final native int from() /*-{ return this.from }-*/;
+ public final native int to() /*-{ return this.to }-*/;
public final boolean contains(int line) {
- return getFrom() <= line && line < getTo();
+ return from() <= line && line < to();
}
protected Viewport() {
@@ -349,6 +380,11 @@ public class CodeMirror extends JavaScriptObject {
}
}
+ public static class RegisteredHandler extends JavaScriptObject {
+ protected RegisteredHandler() {
+ }
+ }
+
public interface EventHandler {
public void handle(CodeMirror instance, NativeEvent event);
}
@@ -363,6 +399,10 @@ public class CodeMirror extends JavaScriptObject {
}
public interface BeforeSelectionChangeHandler {
- public void handle(CodeMirror instance, LineCharacter anchor, LineCharacter head);
+ public void handle(CodeMirror instance, Pos anchor, Pos head);
+ }
+
+ public interface ChangesHandler {
+ public void handle(CodeMirror instance);
}
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java
index ff5d230706..d04cc24981 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java
@@ -20,11 +20,11 @@ import com.google.gwt.core.client.JavaScriptObject;
public class CodeMirrorDoc extends JavaScriptObject {
public final native void replaceRange(String replacement,
- LineCharacter from, LineCharacter to) /*-{
+ Pos from, Pos to) /*-{
this.replaceRange(replacement, from, to);
}-*/;
- public final native void insertText(String insertion, LineCharacter at) /*-{
+ public final native void insertText(String insertion, Pos at) /*-{
this.replaceRange(insertion, at);
}-*/;
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
index 4ec3dea1ae..7a0bbeae4b 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
@@ -20,7 +20,7 @@ import com.google.gwt.core.client.JavaScriptObject;
* Simple map-like structure to pass configuration to CodeMirror.
*
* @see <a href="http://codemirror.net/doc/manual.html#config">CodeMirror config</a>
- * @see CodeMirror#create(com.google.gerrit.client.diff.DisplaySide, com.google.gwt.dom.client.Element, Configuration)
+ * @see CodeMirror#create(com.google.gwt.dom.client.Element, Configuration)
*/
public class Configuration extends JavaScriptObject {
public static Configuration create() {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Extras.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Extras.java
new file mode 100644
index 0000000000..13186d19a2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Extras.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2015 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 net.codemirror.lib;
+
+import static com.google.gwt.dom.client.Style.Display.INLINE_BLOCK;
+import static com.google.gwt.dom.client.Style.Unit.PX;
+import static net.codemirror.lib.CodeMirror.style;
+import static net.codemirror.lib.CodeMirror.LineClassWhere.WRAP;
+
+import com.google.gerrit.client.diff.DisplaySide;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.DOM;
+
+import net.codemirror.lib.CodeMirror.LineHandle;
+
+import java.util.Objects;
+
+/** Additional features added to CodeMirror by Gerrit Code Review. */
+public class Extras {
+ static final native Extras get(CodeMirror c) /*-{ return c.gerritExtras }-*/;
+ private static final native void set(CodeMirror c, Extras e)
+ /*-{ c.gerritExtras = e }-*/;
+
+ static void attach(CodeMirror c) {
+ set(c, new Extras(c));
+ }
+
+ private final CodeMirror cm;
+ private Element margin;
+ private DisplaySide side;
+ private double charWidthPx;
+ private double lineHeightPx;
+ private LineHandle activeLine;
+
+ private Extras(CodeMirror cm) {
+ this.cm = cm;
+ }
+
+ public DisplaySide side() {
+ return side;
+ }
+
+ public void side(DisplaySide s) {
+ side = s;
+ }
+
+ public double charWidthPx() {
+ if (charWidthPx <= 1) {
+ int len = 100;
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < len; i++) {
+ s.append('m');
+ }
+
+ Element e = DOM.createSpan();
+ e.getStyle().setDisplay(INLINE_BLOCK);
+ e.setInnerText(s.toString());
+
+ cm.measure().appendChild(e);
+ charWidthPx = ((double) e.getOffsetWidth()) / len;
+ e.removeFromParent();
+ }
+ return charWidthPx;
+ }
+
+ public double lineHeightPx() {
+ if (lineHeightPx <= 1) {
+ Element p = DOM.createDiv();
+ int lines = 1;
+ for (int i = 0; i < lines; i++) {
+ Element e = DOM.createDiv();
+ p.appendChild(e);
+
+ Element pre = DOM.createElement("pre");
+ pre.setInnerText("gqyŚŻŹŃ");
+ e.appendChild(pre);
+ }
+
+ cm.measure().appendChild(p);
+ lineHeightPx = ((double) p.getOffsetHeight()) / lines;
+ p.removeFromParent();
+ }
+ return lineHeightPx;
+ }
+
+ public void lineLength(int columns) {
+ if (margin == null) {
+ margin = DOM.createDiv();
+ margin.setClassName(style().margin());
+ cm.mover().appendChild(margin);
+ }
+ margin.getStyle().setMarginLeft(columns * charWidthPx(), PX);
+ }
+
+ public void showTabs(boolean show) {
+ Element e = cm.getWrapperElement();
+ if (show) {
+ e.addClassName(style().showTabs());
+ } else {
+ e.removeClassName(style().showTabs());
+ }
+ }
+
+ public final boolean hasActiveLine() {
+ return activeLine != null;
+ }
+
+ public final LineHandle activeLine() {
+ return activeLine;
+ }
+
+ public final boolean activeLine(LineHandle line) {
+ if (Objects.equals(activeLine, line)) {
+ return false;
+ }
+
+ if (activeLine != null) {
+ cm.removeLineClass(activeLine, WRAP, style().activeLine());
+ }
+ activeLine = line;
+ cm.addLineClass(activeLine, WRAP, style().activeLine());
+ return true;
+ }
+
+ public final void clearActiveLine() {
+ if (activeLine != null) {
+ cm.removeLineClass(activeLine, WRAP, style().activeLine());
+ activeLine = null;
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
index fb9f770627..946b44f196 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
@@ -27,12 +27,17 @@ public class KeyMap extends JavaScriptObject {
return this;
}-*/;
- public final native KeyMap on(String key, boolean b) /*-{
- this[key] = b;
+ /** Do not handle inside of CodeMirror; instead push up the DOM tree. */
+ public final native KeyMap propagate(String key) /*-{
+ this[key] = false;
return this;
}-*/;
- public final native KeyMap remove(String key) /*-{ delete this[key]; }-*/;
+ /** Delegate undefined keys to another KeyMap implementation. */
+ public final native KeyMap fallthrough(KeyMap m) /*-{
+ this.fallthrough = m;
+ return this;
+ }-*/;
protected KeyMap() {
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java
index bb60fe989d..a3b1cf453f 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java
@@ -23,10 +23,13 @@ import com.google.gwt.resources.client.ExternalTextResource;
interface Lib extends ClientBundle {
static final Lib I = GWT.create(Lib.class);
- @Source("cm3.css")
+ @Source("cm.css")
ExternalTextResource css();
- @Source("cm3.js")
+ @Source("cm.js")
@DoNotEmbed
DataResource js();
+
+ @Source("style.css")
+ CodeMirror.Style style();
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
deleted file mode 100644
index 5076aff950..0000000000
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
+++ /dev/null
@@ -1,44 +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 net.codemirror.lib;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-/** {line, ch} objects used within CodeMirror. */
-public class LineCharacter extends JavaScriptObject {
- public static LineCharacter create(int line, int ch) {
- return createImpl(line, ch);
- }
-
- public static LineCharacter create(int line) {
- return createImpl(line, 0);
- }
-
- private static LineCharacter createImpl(int line, int ch) {
- LineCharacter lineCh = createObject().cast();
- lineCh.setLine(line);
- lineCh.setCh(ch);
- return lineCh;
- }
-
- public final native void setLine(int line) /*-{ this.line = line; }-*/;
- public final native void setCh(int ch) /*-{ this.ch = ch; }-*/;
-
- public final native int getLine() /*-{ return this.line; }-*/;
- public final native int getCh() /*-{ return this.ch; }-*/;
-
- protected LineCharacter() {
- }
-}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
index 91547fbc49..62c219b24c 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
@@ -18,17 +18,13 @@ import com.google.gwt.core.client.JavaScriptObject;
/** LineWidget objects used within CodeMirror. */
public class LineWidget extends JavaScriptObject {
- public static LineWidget create() {
- return createObject().cast();
- }
-
- public final native void clear() /*-{ this.clear(); }-*/;
- public final native void changed() /*-{ this.changed(); }-*/;
+ public final native void clear() /*-{ this.clear() }-*/;
+ public final native void changed() /*-{ this.changed() }-*/;
public final native void onRedraw(Runnable thunk) /*-{
this.on("redraw", $entry(function() {
thunk.@java.lang.Runnable::run()();
- }));
+ }))
}-*/;
public final native void onFirstRedraw(Runnable thunk) /*-{
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
index 4d894c66ab..379cb3c966 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
@@ -14,7 +14,7 @@
package net.codemirror.lib;
-import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.dom.client.ScriptElement;
@@ -26,47 +26,54 @@ import com.google.gwt.resources.client.TextResource;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.user.client.rpc.AsyncCallback;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-class Loader {
+public class Loader {
private static native boolean isLibLoaded()
/*-{ return $wnd.hasOwnProperty('CodeMirror'); }-*/;
static void initLibrary(final AsyncCallback<Void> cb) {
if (isLibLoaded()) {
cb.onSuccess(null);
- } else {
- injectCss(Lib.I.css());
- injectScript(Lib.I.js().getSafeUri(), new GerritCallback<Void>(){
- @Override
- public void onSuccess(Void result) {
- initVimKeys();
- cb.onSuccess(null);
- }
- });
+ return;
}
+
+ CallbackGroup group = new CallbackGroup();
+ injectCss(Lib.I.css(), group.<Void> addEmpty());
+ injectScript(Lib.I.js().getSafeUri(), group.add(new AsyncCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ Vim.initKeyMap();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+ }));
+ group.addListener(cb);
+ group.done();
}
- private static void injectCss(ExternalTextResource css) {
+ private static void injectCss(ExternalTextResource css,
+ final AsyncCallback<Void> cb) {
try {
css.getText(new ResourceCallback<TextResource>() {
@Override
public void onSuccess(TextResource resource) {
StyleInjector.inject(resource.getText());
+ Lib.I.style().ensureInjected();
+ cb.onSuccess(null);
}
@Override
public void onError(ResourceException e) {
- error(e);
+ cb.onFailure(e);
}
});
} catch (ResourceException e) {
- error(e);
+ cb.onFailure(e);
}
}
- static void injectScript(SafeUri js, final AsyncCallback<Void> callback) {
+ public static void injectScript(SafeUri js, final AsyncCallback<Void> callback) {
final ScriptElement[] script = new ScriptElement[1];
script[0] = ScriptInjector.fromUrl(js.asString())
.setWindow(ScriptInjector.TOP_WINDOW)
@@ -79,7 +86,6 @@ class Loader {
@Override
public void onFailure(Exception reason) {
- error(reason);
callback.onFailure(reason);
}
})
@@ -87,33 +93,6 @@ class Loader {
.cast();
}
- private static void initVimKeys() {
- // TODO: Better custom keybindings, remove temporary navigation hacks.
- KeyMap km = CodeMirror.cloneKeyMap("vim");
- for (String s : new String[] {
- "A", "C", "I", "O", "R", "U",
- "Ctrl-C", "Ctrl-O", "Ctrl-P", "Ctrl-S",
- "Ctrl-F", "Ctrl-B", "Ctrl-R"}) {
- km.remove(s);
- }
- for (int i = 0; i <= 9; i++) {
- km.remove("Ctrl-" + i);
- }
- CodeMirror.addKeyMap("vim_ro", km);
-
- CodeMirror.mapVimKey("j", "gj");
- CodeMirror.mapVimKey("k", "gk");
- CodeMirror.mapVimKey("Down", "gj");
- CodeMirror.mapVimKey("Up", "gk");
- CodeMirror.mapVimKey("<PageUp>", "<C-u>");
- CodeMirror.mapVimKey("<PageDown>", "<C-d>");
- }
-
- private static void error(Exception e) {
- Logger log = Logger.getLogger("net.codemirror");
- log.log(Level.SEVERE, "Cannot load portions of CodeMirror", e);
- }
-
private Loader() {
}
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
deleted file mode 100644
index 3098c438d9..0000000000
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
+++ /dev/null
@@ -1,208 +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 net.codemirror.lib;
-
-import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.resources.client.DataResource;
-import com.google.gwt.safehtml.shared.SafeUri;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-import net.codemirror.mode.Modes;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class ModeInjector {
- /** Map of server content type to CodeMiror mode or content type. */
- private static final Map<String, String> mimeAlias;
-
- /** Map of content type "text/x-java" to mode name "clike". */
- private static final Map<String, String> mimeModes;
-
- /** Map of names such as "clike" to URI for code download. */
- private static final Map<String, SafeUri> modeUris;
-
- static {
- DataResource[] all = {
- Modes.I.clike(),
- Modes.I.clojure(),
- Modes.I.commonlisp(),
- Modes.I.coffeescript(),
- Modes.I.css(),
- Modes.I.d(),
- Modes.I.diff(),
- Modes.I.dtd(),
- Modes.I.erlang(),
- Modes.I.gas(),
- Modes.I.gerrit_commit(),
- Modes.I.gfm(),
- Modes.I.go(),
- Modes.I.groovy(),
- Modes.I.haskell(),
- Modes.I.htmlmixed(),
- Modes.I.javascript(),
- Modes.I.lua(),
- Modes.I.markdown(),
- Modes.I.perl(),
- Modes.I.php(),
- Modes.I.pig(),
- Modes.I.properties(),
- Modes.I.python(),
- Modes.I.r(),
- Modes.I.ruby(),
- Modes.I.scheme(),
- Modes.I.shell(),
- Modes.I.smalltalk(),
- Modes.I.sql(),
- Modes.I.velocity(),
- Modes.I.verilog(),
- Modes.I.xml(),
- Modes.I.yaml(),
- };
-
- mimeAlias = new HashMap<>();
- mimeModes = new HashMap<>();
- modeUris = new HashMap<>();
-
- for (DataResource m : all) {
- modeUris.put(m.getName(), m.getSafeUri());
- }
- parseModeMap();
- }
-
- private static void parseModeMap() {
- String mode = null;
- for (String line : Modes.I.mode_map().getText().split("\n")) {
- int eq = line.indexOf('=');
- if (0 < eq) {
- mimeAlias.put(
- line.substring(0, eq).trim(),
- line.substring(eq + 1).trim());
- } else if (line.endsWith(":")) {
- String n = line.substring(0, line.length() - 1);
- if (modeUris.containsKey(n)) {
- mode = n;
- }
- } else if (mode != null && line.contains("/")) {
- mimeModes.put(line, mode);
- } else {
- mode = null;
- }
- }
- }
-
- public static String getContentType(String mode) {
- String real = mode != null ? mimeAlias.get(mode) : null;
- return real != null ? real : mode;
- }
-
- public static Collection<String> getKnownMimeTypes() {
- return mimeModes.keySet();
- }
-
- private static native boolean isModeLoaded(String n)
- /*-{ return $wnd.CodeMirror.modes.hasOwnProperty(n); }-*/;
-
- private static native boolean isMimeLoaded(String n)
- /*-{ return $wnd.CodeMirror.mimeModes.hasOwnProperty(n); }-*/;
-
- private static native JsArrayString getDependencies(String n)
- /*-{ return $wnd.CodeMirror.modes[n].dependencies || []; }-*/;
-
- private final Set<String> loading = new HashSet<>(4);
- private int pending;
- private AsyncCallback<Void> appCallback;
-
- public ModeInjector add(String name) {
- if (name == null || isModeLoaded(name) || isMimeLoaded(name)) {
- return this;
- }
-
- String mode = mimeModes.get(name);
- if (mode == null) {
- mode = name;
- }
-
- SafeUri uri = modeUris.get(mode);
- if (uri == null) {
- Logger.getLogger("net.codemirror").log(
- Level.WARNING,
- "CodeMirror mode " + mode + " not configured.");
- return this;
- }
-
- loading.add(mode);
- return this;
- }
-
- public void inject(AsyncCallback<Void> appCallback) {
- this.appCallback = appCallback;
- for (String mode : loading) {
- beginLoading(mode);
- }
- if (pending == 0) {
- appCallback.onSuccess(null);
- }
- }
-
- private void beginLoading(final String mode) {
- pending++;
- Loader.injectScript(
- modeUris.get(mode),
- new AsyncCallback<Void>() {
- @Override
- public void onSuccess(Void result) {
- pending--;
- ensureDependenciesAreLoaded(mode);
- if (pending == 0) {
- appCallback.onSuccess(null);
- }
- }
-
- @Override
- public void onFailure(Throwable caught) {
- if (--pending == 0) {
- appCallback.onFailure(caught);
- }
- }
- });
- }
-
- private void ensureDependenciesAreLoaded(String mode) {
- JsArrayString deps = getDependencies(mode);
- for (int i = 0; i < deps.length(); i++) {
- String d = deps.get(i);
- if (loading.contains(d) || isModeLoaded(d)) {
- continue;
- }
-
- SafeUri uri = modeUris.get(d);
- if (uri == null) {
- Logger.getLogger("net.codemirror").log(
- Level.SEVERE,
- "CodeMirror mode " + mode + " needs " + d);
- continue;
- }
-
- loading.add(d);
- beginLoading(d);
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Pos.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Pos.java
new file mode 100644
index 0000000000..07ead4372b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Pos.java
@@ -0,0 +1,41 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** Pos (or {line, ch}) objects used within CodeMirror. */
+public class Pos extends JavaScriptObject {
+ public static final native Pos create(int line) /*-{
+ return $wnd.CodeMirror.Pos(line)
+ }-*/;
+
+ public static final native Pos create(int line, int ch) /*-{
+ return $wnd.CodeMirror.Pos(line, ch)
+ }-*/;
+
+ public final native void line(int l) /*-{ this.line = l }-*/;
+ public final native void ch(int c) /*-{ this.ch = c }-*/;
+
+ public final native int line() /*-{ return this.line }-*/;
+ public final native int ch() /*-{ return this.ch || 0 }-*/;
+
+ public final boolean equals(Pos o) {
+ return this == o || (line() == o.line() && ch() == o.ch());
+ }
+
+ protected Pos() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
index 096f1adcf8..26235302a5 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
@@ -18,20 +18,20 @@ import com.google.gwt.core.client.JavaScriptObject;
/** Returned by {@link CodeMirror#getScrollInfo()}. */
public class ScrollInfo extends JavaScriptObject {
- public final native double getLeft() /*-{ return this.left; }-*/;
- public final native double getTop() /*-{ return this.top; }-*/;
+ public final native double left() /*-{ return this.left }-*/;
+ public final native double top() /*-{ return this.top }-*/;
/**
* Pixel height of the full content being scrolled. This may only be an
* estimate given by CodeMirror. Line widgets further down in the document may
* not be measured, so line heights can be incorrect until drawn.
*/
- public final native double getHeight() /*-{ return this.height; }-*/;
- public final native double getWidth() /*-{ return this.width; }-*/;
+ public final native double height() /*-{ return this.height }-*/;
+ public final native double width() /*-{ return this.width }-*/;
/** Visible height of the viewport, excluding scrollbars. */
- public final native double getClientHeight() /*-{ return this.clientHeight; }-*/;
- public final native double getClientWidth() /*-{ return this.clientWidth; }-*/;
+ public final native double clientHeight() /*-{ return this.clientHeight }-*/;
+ public final native double clientWidth() /*-{ return this.clientWidth }-*/;
protected ScrollInfo() {
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
index 50db13c7ce..2d690153a4 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
@@ -19,10 +19,6 @@ import com.google.gwt.core.client.JavaScriptObject;
/** Object that represents a text marker within CodeMirror */
public class TextMarker extends JavaScriptObject {
- public static TextMarker create() {
- return createObject().cast();
- }
-
public final native void clear() /*-{ this.clear(); }-*/;
public final native void changed() /*-{ this.changed(); }-*/;
public final native FromTo find() /*-{ return this.find(); }-*/;
@@ -33,24 +29,21 @@ public class TextMarker extends JavaScriptObject {
}
public static class FromTo extends JavaScriptObject {
- public static FromTo create(LineCharacter from, LineCharacter to) {
- FromTo fromTo = createObject().cast();
- fromTo.setFrom(from);
- fromTo.setTo(to);
- return fromTo;
- }
+ public static final native FromTo create(Pos f, Pos t) /*-{
+ return {from: f, to: t}
+ }-*/;
public static FromTo create(CommentRange range) {
return create(
- LineCharacter.create(range.start_line() - 1, range.start_character()),
- LineCharacter.create(range.end_line() - 1, range.end_character()));
+ Pos.create(range.start_line() - 1, range.start_character()),
+ Pos.create(range.end_line() - 1, range.end_character()));
}
- public final native LineCharacter getFrom() /*-{ return this.from; }-*/;
- public final native LineCharacter getTo() /*-{ return this.to; }-*/;
+ public final native Pos from() /*-{ return this.from }-*/;
+ public final native Pos to() /*-{ return this.to }-*/;
- public final native void setFrom(LineCharacter from) /*-{ this.from = from; }-*/;
- public final native void setTo(LineCharacter to) /*-{ this.to = to; }-*/;
+ public final native void from(Pos f) /*-{ this.from = f }-*/;
+ public final native void to(Pos t) /*-{ this.to = t }-*/;
protected FromTo() {
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java
new file mode 100644
index 0000000000..f1dee69f3e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Vim.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2014 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Glue around the Vim emulation for {@link CodeMirror}.
+ *
+ * As an instance {@code this} is actually the {@link CodeMirror} object. Class
+ * Vim is providing a new namespace for Vim related methods that are associated
+ * with an editor.
+ */
+public class Vim extends JavaScriptObject {
+ static void initKeyMap() {
+ KeyMap km = KeyMap.create();
+ for (String key : new String[] {"A", "C", "I", "O", "R", "U"}) {
+ km.propagate(key);
+ km.propagate("'" + key.toLowerCase() + "'");
+ }
+ for (String key : new String[] {
+ "Ctrl-C", "Ctrl-O", "Ctrl-P", "Ctrl-S",
+ "Ctrl-F", "Ctrl-B", "Ctrl-R"}) {
+ km.propagate(key);
+ }
+ for (int i = 0; i <= 9; i++) {
+ km.propagate("Ctrl-" + i);
+ }
+ km.fallthrough(CodeMirror.getKeyMap("vim"));
+ CodeMirror.addKeyMap("vim_ro", km);
+
+ mapKey("j", "gj");
+ mapKey("k", "gk");
+ mapKey("Down", "gj");
+ mapKey("Up", "gk");
+ mapKey("<PageUp>", "<C-u>");
+ mapKey("<PageDown>", "<C-d>");
+ }
+
+ public static final native void mapKey(String alias, String actual) /*-{
+ $wnd.CodeMirror.Vim.map(alias, actual)
+ }-*/;
+
+ public final native void handleKey(String key) /*-{
+ $wnd.CodeMirror.Vim.handleKey(this, key)
+ }-*/;
+
+ public final native boolean hasSearchHighlight() /*-{
+ var v = this.state.vim;
+ return v && v.searchState_ && !!v.searchState_.getOverlay();
+ }-*/;
+
+ protected Vim() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css b/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css
new file mode 100644
index 0000000000..c5f79d3c56
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css
@@ -0,0 +1,83 @@
+/* Copyright (C) 2015 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.
+ */
+
+@external .CodeMirror;
+@external .CodeMirror-lines;
+@external .CodeMirror-linenumber;
+@external .CodeMirror-overlayscroll-horizontal;
+@external .CodeMirror-overlayscroll-vertical;
+@external .cm-tab;
+@external .cm-searching;
+@external .cm-trailingspace;
+
+/* Reduce margins around CodeMirror to save space. */
+.CodeMirror-lines {
+ padding: 0;
+}
+.CodeMirror pre {
+ padding: 0;
+ line-height: normal;
+}
+
+/* Minimum scrollbar bubble size even on large files. */
+.CodeMirror-overlayscroll-horizontal div {
+ min-width: 25px;
+}
+.CodeMirror-overlayscroll-vertical div {
+ min-height: 25px;
+}
+
+/* Stack the scrollbar so annotations can receive clicks. */
+.CodeMirror-overlayscroll-vertical {
+ z-index: inherit;
+}
+.CodeMirror-overlayscroll-horizontal div,
+.CodeMirror-overlayscroll-vertical div {
+ background-color: rgba(128, 128, 128, 0.50);
+ z-index: 8;
+}
+
+/* Highlight current line number in the line gutter. */
+.activeLine .CodeMirror-linenumber {
+ background-color: #bcf !important;
+ color: #000;
+}
+
+.showTabs .cm-tab:before {
+ position: absolute;
+ content: "\00bb";
+ color: #f00;
+}
+
+.cm-searching {
+ background-color: #ffa;
+}
+
+.cm-trailingspace {
+ background-color: red;
+}
+
+/* Line length margin displayed at NN columns to provide
+ * a visual guide for length of any single line of code.
+ */
+.margin {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 0;
+ border-right: 1px dashed #ffa500;
+ z-index: 2;
+ cursor: text;
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
new file mode 100644
index 0000000000..d6b194bc6f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
@@ -0,0 +1,198 @@
+// Copyright (C) 2014 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 net.codemirror.mode;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.safehtml.shared.SafeUri;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Description of a CodeMirror language mode. */
+public class ModeInfo extends JavaScriptObject {
+ private static NativeMap<ModeInfo> byMime;
+ private static NativeMap<ModeInfo> byExt;
+
+ /** Map of names such as "clike" to URI for code download. */
+ private static final Map<String, SafeUri> modeUris = new HashMap<>();
+
+ static {
+ indexModes(new DataResource[] {
+ Modes.I.clike(),
+ Modes.I.clojure(),
+ Modes.I.coffeescript(),
+ Modes.I.commonlisp(),
+ Modes.I.css(),
+ Modes.I.d(),
+ Modes.I.dart(),
+ Modes.I.diff(),
+ Modes.I.dockerfile(),
+ Modes.I.dtd(),
+ Modes.I.erlang(),
+ Modes.I.gas(),
+ Modes.I.gerrit_commit(),
+ Modes.I.gfm(),
+ Modes.I.go(),
+ Modes.I.groovy(),
+ Modes.I.haskell(),
+ Modes.I.htmlmixed(),
+ Modes.I.javascript(),
+ Modes.I.lua(),
+ Modes.I.markdown(),
+ Modes.I.perl(),
+ Modes.I.php(),
+ Modes.I.pig(),
+ Modes.I.properties(),
+ Modes.I.python(),
+ Modes.I.r(),
+ Modes.I.rst(),
+ Modes.I.ruby(),
+ Modes.I.scheme(),
+ Modes.I.shell(),
+ Modes.I.smalltalk(),
+ Modes.I.soy(),
+ Modes.I.sql(),
+ Modes.I.stex(),
+ Modes.I.velocity(),
+ Modes.I.verilog(),
+ Modes.I.xml(),
+ Modes.I.yaml(),
+ });
+
+ alias("application/x-httpd-php-open", "application/x-httpd-php");
+ alias("application/x-javascript", "application/javascript");
+ alias("application/x-shellscript", "text/x-sh");
+ alias("application/x-tcl", "text/x-tcl");
+ alias("text/typescript", "application/typescript");
+ alias("text/x-c", "text/x-csrc");
+ alias("text/x-c++hdr", "text/x-c++src");
+ alias("text/x-chdr", "text/x-csrc");
+ alias("text/x-h", "text/x-csrc");
+ alias("text/x-ini", "text/x-properties");
+ alias("text/x-java-source", "text/x-java");
+ alias("text/x-php", "application/x-httpd-php");
+ alias("text/x-scripttcl", "text/x-tcl");
+ }
+
+ /** All supported modes. */
+ public static native JsArray<ModeInfo> all() /*-{
+ return $wnd.CodeMirror.modeInfo
+ }-*/;
+
+ private static native void setAll(JsArray<ModeInfo> m) /*-{
+ $wnd.CodeMirror.modeInfo = m
+ }-*/;
+
+ /** Look up mode by primary or alternate MIME types. */
+ public static ModeInfo findModeByMIME(String mime) {
+ return byMime.get(mime);
+ }
+
+ public static SafeUri getModeScriptUri(String mode) {
+ return modeUris.get(mode);
+ }
+
+ /** Look up mode by MIME type or file extension from a path. */
+ public static ModeInfo findMode(String mime, String path) {
+ ModeInfo m = byMime.get(mime);
+ if (m != null) {
+ return m;
+ }
+
+ int s = path.lastIndexOf('/');
+ int d = path.lastIndexOf('.');
+ if (d == -1 || s > d) {
+ return null; // punt on "foo.src/bar" type paths.
+ }
+
+ if (byExt == null) {
+ byExt = NativeMap.create();
+ for (ModeInfo mode : Natives.asList(all())) {
+ for (String ext : Natives.asList(mode.ext())) {
+ byExt.put(ext, mode);
+ }
+ }
+ }
+ return byExt.get(path.substring(d + 1));
+ }
+
+ private static void alias(String serverMime, String toMime) {
+ ModeInfo mode = byMime.get(toMime);
+ if (mode != null) {
+ byMime.put(serverMime, mode);
+ }
+ }
+
+ private static void indexModes(DataResource[] all) {
+ for (DataResource r : all) {
+ modeUris.put(r.getName(), r.getSafeUri());
+ }
+
+ JsArray<ModeInfo> modeList = all();
+ modeList.push(gerrit_commit());
+
+ byMime = NativeMap.create();
+ JsArray<ModeInfo> filtered = JsArray.createArray().cast();
+ for (ModeInfo m : Natives.asList(modeList)) {
+ if (modeUris.containsKey(m.mode())) {
+ filtered.push(m);
+
+ for (String mimeType : Natives.asList(m.mimes())) {
+ byMime.put(mimeType, m);
+ }
+ byMime.put(m.mode(), m);
+ }
+ }
+ Collections.sort(Natives.asList(filtered), new Comparator<ModeInfo>() {
+ @Override
+ public int compare(ModeInfo a, ModeInfo b) {
+ return a.name().toLowerCase().compareTo(b.name().toLowerCase());
+ }
+ });
+ setAll(filtered);
+ }
+
+ /** Human readable name of the mode, such as "C++". */
+ public final native String name() /*-{ return this.name }-*/;
+
+ /** Internal CodeMirror name for {@code mode.js} file to load. */
+ public final native String mode() /*-{ return this.mode }-*/;
+
+ /** Primary MIME type to activate this mode. */
+ public final native String mime() /*-{ return this.mime }-*/;
+
+ /** Primary and additional MIME types that activate this mode. */
+ public final native JsArrayString mimes()
+ /*-{ return this.mimes || [this.mime] }-*/;
+
+ private final native JsArrayString ext()
+ /*-{ return this.ext || [] }-*/;
+
+ protected ModeInfo() {
+ }
+
+ private static native ModeInfo gerrit_commit() /*-{
+ return {name: "Git Commit Message",
+ mime: "text/x-gerrit-commit-message",
+ mode: "gerrit_commit"}
+ }-*/;
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInjector.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInjector.java
new file mode 100644
index 0000000000..2c171ac877
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInjector.java
@@ -0,0 +1,118 @@
+// 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 net.codemirror.mode;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import net.codemirror.lib.Loader;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ModeInjector {
+ private static boolean canLoad(String mode) {
+ return ModeInfo.getModeScriptUri(mode) != null;
+ }
+
+ private static native boolean isModeLoaded(String n)
+ /*-{ return $wnd.CodeMirror.modes.hasOwnProperty(n); }-*/;
+
+ private static native boolean isMimeLoaded(String n)
+ /*-{ return $wnd.CodeMirror.mimeModes.hasOwnProperty(n); }-*/;
+
+ private static native JsArrayString getDependencies(String n)
+ /*-{ return $wnd.CodeMirror.modes[n].dependencies || []; }-*/;
+
+ private final Set<String> loading = new HashSet<>(4);
+ private int pending;
+ private AsyncCallback<Void> appCallback;
+
+ public ModeInjector add(String name) {
+ if (name == null || isModeLoaded(name) || isMimeLoaded(name)) {
+ return this;
+ }
+
+ ModeInfo m = ModeInfo.findModeByMIME(name);
+ if (m != null) {
+ name = m.mode();
+ }
+
+ if (!canLoad(name)) {
+ Logger.getLogger("net.codemirror").log(
+ Level.WARNING,
+ "CodeMirror mode " + name + " not configured.");
+ return this;
+ }
+
+ loading.add(name);
+ return this;
+ }
+
+ public void inject(AsyncCallback<Void> appCallback) {
+ this.appCallback = appCallback;
+ for (String mode : loading) {
+ beginLoading(mode);
+ }
+ if (pending == 0) {
+ appCallback.onSuccess(null);
+ }
+ }
+
+ private void beginLoading(final String mode) {
+ pending++;
+ Loader.injectScript(
+ ModeInfo.getModeScriptUri(mode),
+ new AsyncCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ pending--;
+ ensureDependenciesAreLoaded(mode);
+ if (pending == 0) {
+ appCallback.onSuccess(null);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ if (--pending == 0) {
+ appCallback.onFailure(caught);
+ }
+ }
+ });
+ }
+
+ private void ensureDependenciesAreLoaded(String mode) {
+ JsArrayString deps = getDependencies(mode);
+ for (int i = 0; i < deps.length(); i++) {
+ String d = deps.get(i);
+ if (loading.contains(d) || isModeLoaded(d)) {
+ continue;
+ }
+
+ if (!canLoad(d)) {
+ Logger.getLogger("net.codemirror").log(
+ Level.SEVERE,
+ "CodeMirror mode " + d + " needs " + d);
+ continue;
+ }
+
+ loading.add(d);
+ beginLoading(d);
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
index d51479f51e..e511be5a2d 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
@@ -18,47 +18,50 @@ import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.DataResource.DoNotEmbed;
-import com.google.gwt.resources.client.TextResource;
public interface Modes extends ClientBundle {
public static final Modes I = GWT.create(Modes.class);
- @Source("mode_map") TextResource mode_map();
- @Source("clike/clike.js") @DoNotEmbed DataResource clike();
- @Source("clojure/clojure.js") @DoNotEmbed DataResource clojure();
- @Source("commonlisp/commonlisp.js") @DoNotEmbed DataResource commonlisp();
- @Source("coffeescript/coffeescript.js") @DoNotEmbed DataResource coffeescript();
- @Source("css/css.js") @DoNotEmbed DataResource css();
- @Source("d/d.js") @DoNotEmbed DataResource d();
- @Source("diff/diff.js") @DoNotEmbed DataResource diff();
- @Source("dtd/dtd.js") @DoNotEmbed DataResource dtd();
- @Source("erlang/erlang.js") @DoNotEmbed DataResource erlang();
- @Source("gas/gas.js") @DoNotEmbed DataResource gas();
+ @Source("clike.js") @DoNotEmbed DataResource clike();
+ @Source("clojure.js") @DoNotEmbed DataResource clojure();
+ @Source("coffeescript.js") @DoNotEmbed DataResource coffeescript();
+ @Source("commonlisp.js") @DoNotEmbed DataResource commonlisp();
+ @Source("css.js") @DoNotEmbed DataResource css();
+ @Source("d.js") @DoNotEmbed DataResource d();
+ @Source("dart.js") @DoNotEmbed DataResource dart();
+ @Source("diff.js") @DoNotEmbed DataResource diff();
+ @Source("dockerfile.js") @DoNotEmbed DataResource dockerfile();
+ @Source("dtd.js") @DoNotEmbed DataResource dtd();
+ @Source("erlang.js") @DoNotEmbed DataResource erlang();
+ @Source("gas.js") @DoNotEmbed DataResource gas();
@Source("gerrit/commit.js") @DoNotEmbed DataResource gerrit_commit();
- @Source("gfm/gfm.js") @DoNotEmbed DataResource gfm();
- @Source("go/go.js") @DoNotEmbed DataResource go();
- @Source("groovy/groovy.js") @DoNotEmbed DataResource groovy();
- @Source("haskell/haskell.js") @DoNotEmbed DataResource haskell();
- @Source("htmlmixed/htmlmixed.js") @DoNotEmbed DataResource htmlmixed();
- @Source("javascript/javascript.js") @DoNotEmbed DataResource javascript();
- @Source("lua/lua.js") @DoNotEmbed DataResource lua();
- @Source("markdown/markdown.js") @DoNotEmbed DataResource markdown();
- @Source("perl/perl.js") @DoNotEmbed DataResource perl();
- @Source("php/php.js") @DoNotEmbed DataResource php();
- @Source("pig/pig.js") @DoNotEmbed DataResource pig();
- @Source("properties/properties.js") @DoNotEmbed DataResource properties();
- @Source("python/python.js") @DoNotEmbed DataResource python();
- @Source("r/r.js") @DoNotEmbed DataResource r();
- @Source("ruby/ruby.js") @DoNotEmbed DataResource ruby();
- @Source("scheme/scheme.js") @DoNotEmbed DataResource scheme();
- @Source("shell/shell.js") @DoNotEmbed DataResource shell();
- @Source("smalltalk/smalltalk.js") @DoNotEmbed DataResource smalltalk();
- @Source("sql/sql.js") @DoNotEmbed DataResource sql();
- @Source("tcl/tcl.js") @DoNotEmbed DataResource tcl();
- @Source("velocity/velocity.js") @DoNotEmbed DataResource velocity();
- @Source("verilog/verilog.js") @DoNotEmbed DataResource verilog();
- @Source("xml/xml.js") @DoNotEmbed DataResource xml();
- @Source("yaml/yaml.js") @DoNotEmbed DataResource yaml();
+ @Source("gfm.js") @DoNotEmbed DataResource gfm();
+ @Source("go.js") @DoNotEmbed DataResource go();
+ @Source("groovy.js") @DoNotEmbed DataResource groovy();
+ @Source("haskell.js") @DoNotEmbed DataResource haskell();
+ @Source("htmlmixed.js") @DoNotEmbed DataResource htmlmixed();
+ @Source("javascript.js") @DoNotEmbed DataResource javascript();
+ @Source("lua.js") @DoNotEmbed DataResource lua();
+ @Source("markdown.js") @DoNotEmbed DataResource markdown();
+ @Source("perl.js") @DoNotEmbed DataResource perl();
+ @Source("php.js") @DoNotEmbed DataResource php();
+ @Source("pig.js") @DoNotEmbed DataResource pig();
+ @Source("properties.js") @DoNotEmbed DataResource properties();
+ @Source("python.js") @DoNotEmbed DataResource python();
+ @Source("r.js") @DoNotEmbed DataResource r();
+ @Source("rst.js") @DoNotEmbed DataResource rst();
+ @Source("ruby.js") @DoNotEmbed DataResource ruby();
+ @Source("scheme.js") @DoNotEmbed DataResource scheme();
+ @Source("shell.js") @DoNotEmbed DataResource shell();
+ @Source("smalltalk.js") @DoNotEmbed DataResource smalltalk();
+ @Source("soy.js") @DoNotEmbed DataResource soy();
+ @Source("sql.js") @DoNotEmbed DataResource sql();
+ @Source("stex.js") @DoNotEmbed DataResource stex();
+ @Source("tcl.js") @DoNotEmbed DataResource tcl();
+ @Source("velocity.js") @DoNotEmbed DataResource velocity();
+ @Source("verilog.js") @DoNotEmbed DataResource verilog();
+ @Source("xml.js") @DoNotEmbed DataResource xml();
+ @Source("yaml.js") @DoNotEmbed DataResource yaml();
- // When adding a resource, update static initializer in ModeInjector.
+ // When adding a resource, update static initializer in ModeInfo.
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/gerrit/commit.js b/gerrit-gwtui/src/main/java/net/codemirror/mode/gerrit/commit.js
index a846e50f6b..e1fe89833b 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/gerrit/commit.js
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/gerrit/commit.js
@@ -2,7 +2,7 @@ CodeMirror.defineMode('gerrit_commit', function() {
var header = /^(Parent|Author|AuthorDate|Commit|CommitDate):/;
var id = /^Change-Id: I[0-9a-f]{40}/;
var footer = /^[A-Z][A-Za-z0-9-]+:/;
- var sha1 = /[0-9a-f]{6,40}/;
+ var sha1 = /\b[0-9a-f]{6,40}/;
return {
token: function(stream) {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
deleted file mode 100644
index 2bff364ed9..0000000000
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
+++ /dev/null
@@ -1,137 +0,0 @@
-clike:
-text/x-csrc
-text/x-c
-text/x-chdr
-text/x-c++src
-text/x-c++hdr
-text/x-java
-text/x-csharp
-text/x-scala
-
-clojure:
-text/x-clojure
-
-coffeescript:
-text/x-coffeescript
-
-commonlisp:
-text/x-common-lisp
-
-css:
-text/css
-text/x-scss
-
-d:
-text/x-d
-
-diff:
-text/x-diff
-
-dtd:
-application/xml-dtd
-
-erlang:
-text/x-erlang
-
-gas:
-text/x-gas
-
-gerrit_commit:
-text/x-gerrit-commit-message
-
-gfm:
-text/x-github-markdown
-
-go:
-text/x-go
-
-groovy:
-text/x-groovy
-
-haskell:
-text/x-haskell
-
-htmlmixed:
-text/html
-
-javascript:
-text/javascript
-text/ecmascript
-application/javascript
-application/ecmascript
-application/json
-application/x-json
-text/typescript
-application/typescript
-
-lua:
-text/x-lua
-
-markdown:
-text/x-markdown
-
-perl:
-text/x-perl
-
-properties:
-text/x-ini
-text/x-properties
-
-perl:
-text/x-perl
-
-php:
-application/x-httpd-php
-application/x-httpd-php-open
-text/x-php
-
-pig:
-text/x-pig
-
-python:
-text/x-python
-
-r:
-text/r-src
-
-ruby:
-text/x-ruby
-
-scheme:
-text/x-scheme
-
-shell:
-text/x-sh
-application/x-shellscript
-
-smalltalk:
-text/x-stsrc
-
-sql:
-text/x-sql
-text/x-mariadb
-text/x-mysql
-text/x-plsql
-
-tcl:
-text/x-tcl
-
-velocity:
-text/velocity
-
-verilog:
-text/x-verilog
-
-xml:
-text/xml
-application/xml
-
-yaml:
-text/x-yaml
-
-application/x-javascript = application/javascript
-application/x-shellscript = text/x-sh
-application/x-tcl = text/x-tcl
-text/x-h = text/x-c++hdr
-text/x-java-source = text/x-java
-text/x-scripttcl = text/x-tcl
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/theme/ThemeLoader.java b/gerrit-gwtui/src/main/java/net/codemirror/theme/ThemeLoader.java
new file mode 100644
index 0000000000..c563c0a63e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/theme/ThemeLoader.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2014 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 net.codemirror.theme;
+
+import com.google.gerrit.extensions.client.Theme;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.resources.client.ExternalTextResource;
+import com.google.gwt.resources.client.ResourceCallback;
+import com.google.gwt.resources.client.ResourceException;
+import com.google.gwt.resources.client.TextResource;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import java.util.EnumSet;
+
+/** Dynamically loads a known CodeMirror theme's CSS */
+public class ThemeLoader {
+ private static final ExternalTextResource[] THEMES = {
+ Themes.I.eclipse(),
+ Themes.I.elegant(),
+ Themes.I.midnight(),
+ Themes.I.neat(),
+ Themes.I.night(),
+ Themes.I.twilight(),
+ };
+
+ private static final EnumSet<Theme> loaded = EnumSet.of(Theme.DEFAULT);
+
+ public static final void loadTheme(final Theme theme,
+ final AsyncCallback<Void> cb) {
+ if (loaded.contains(theme)) {
+ cb.onSuccess(null);
+ return;
+ }
+
+ ExternalTextResource resource = findTheme(theme);
+ if (resource == null) {
+ cb.onFailure(new Exception("unknown theme " + theme));
+ return;
+ }
+
+ try {
+ resource.getText(new ResourceCallback<TextResource>() {
+ @Override
+ public void onSuccess(TextResource resource) {
+ StyleInjector.inject(resource.getText());
+ loaded.add(theme);
+ cb.onSuccess(null);
+ }
+
+ @Override
+ public void onError(ResourceException e) {
+ cb.onFailure(e);
+ }
+ });
+ } catch (ResourceException e) {
+ cb.onFailure(e);
+ }
+ }
+
+ private static final ExternalTextResource findTheme(Theme theme) {
+ for (ExternalTextResource r : THEMES) {
+ if (theme.name().toLowerCase().equals(r.getName())) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ private ThemeLoader() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java b/gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java
new file mode 100644
index 0000000000..ed0ffcac24
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2014 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 net.codemirror.theme;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ExternalTextResource;
+
+public interface Themes extends ClientBundle {
+ public static final Themes I = GWT.create(Themes.class);
+
+ @Source("eclipse.css") ExternalTextResource eclipse();
+ @Source("elegant.css") ExternalTextResource elegant();
+ @Source("midnight.css") ExternalTextResource midnight();
+ @Source("neat.css") ExternalTextResource neat();
+ @Source("night.css") ExternalTextResource night();
+ @Source("twilight.css") ExternalTextResource twilight();
+
+ // When adding a resource, update:
+ // - static initializer in ThemeLoader
+ // - enum value in com.google.gerrit.extensions.common.Theme
+}
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
index 5be029cadd..6705e51d3a 100644
--- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
@@ -14,18 +14,18 @@
package com.google.gerrit.client;
-import static org.junit.Assert.assertEquals;
-import static com.google.gerrit.client.RelativeDateFormatter.YEAR_IN_MILLIS;
-import static com.google.gerrit.client.RelativeDateFormatter.SECOND_IN_MILLIS;
-import static com.google.gerrit.client.RelativeDateFormatter.MINUTE_IN_MILLIS;
-import static com.google.gerrit.client.RelativeDateFormatter.HOUR_IN_MILLIS;
import static com.google.gerrit.client.RelativeDateFormatter.DAY_IN_MILLIS;
-
-import java.util.Date;
+import static com.google.gerrit.client.RelativeDateFormatter.HOUR_IN_MILLIS;
+import static com.google.gerrit.client.RelativeDateFormatter.MINUTE_IN_MILLIS;
+import static com.google.gerrit.client.RelativeDateFormatter.SECOND_IN_MILLIS;
+import static com.google.gerrit.client.RelativeDateFormatter.YEAR_IN_MILLIS;
+import static org.junit.Assert.assertEquals;
import org.eclipse.jgit.util.RelativeDateFormatter;
import org.junit.Test;
+import java.util.Date;
+
public class RelativeDateFormatterTest {
private static void assertFormat(long ageFromNow, long timeUnit,
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
index 1ebf6bbde7..d751f34ba4 100644
--- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
@@ -22,18 +22,21 @@ import com.google.gwt.core.client.JsArrayString;
import com.googlecode.gwt.test.GwtModule;
import com.googlecode.gwt.test.GwtTest;
-import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.Pos;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
/** Unit tests for EditIterator */
@GwtModule("com.google.gerrit.GerritGwtUI")
+@Ignore
+// TODO(davido): Enable this again when gwt-test-utils lib is fixed.
public class EditIteratorTest extends GwtTest {
private JsArrayString lines;
- private void assertLineChsEqual(LineCharacter a, LineCharacter b) {
- assertEquals(a.getLine() + "," + a.getCh(), b.getLine() + "," + b.getCh());
+ private void assertLineChsEqual(Pos a, Pos b) {
+ assertEquals(a.line() + "," + a.ch(), b.line() + "," + b.ch());
}
@Before
@@ -47,57 +50,57 @@ public class EditIteratorTest extends GwtTest {
@Test
public void testNegativeAdvance() {
EditIterator i = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(1, 1), i.advance(5));
- assertLineChsEqual(LineCharacter.create(0, 3), i.advance(-2));
+ assertLineChsEqual(Pos.create(1, 1), i.advance(5));
+ assertLineChsEqual(Pos.create(0, 3), i.advance(-2));
}
@Test
public void testNoAdvance() {
EditIterator iter = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(0), iter.advance(0));
+ assertLineChsEqual(Pos.create(0), iter.advance(0));
}
@Test
public void testSimpleAdvance() {
EditIterator iter = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(0, 1), iter.advance(1));
+ assertLineChsEqual(Pos.create(0, 1), iter.advance(1));
}
@Test
public void testEndsBeforeNewline() {
EditIterator iter = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(0, 3), iter.advance(3));
+ assertLineChsEqual(Pos.create(0, 3), iter.advance(3));
}
@Test
public void testEndsOnNewline() {
EditIterator iter = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(1), iter.advance(4));
+ assertLineChsEqual(Pos.create(1), iter.advance(4));
}
@Test
public void testAcrossNewline() {
EditIterator iter = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(1, 1), iter.advance(5));
+ assertLineChsEqual(Pos.create(1, 1), iter.advance(5));
}
@Test
public void testContinueFromBeforeNewline() {
EditIterator iter = new EditIterator(lines, 0);
iter.advance(3);
- assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(7));
+ assertLineChsEqual(Pos.create(2, 2), iter.advance(7));
}
@Test
public void testContinueFromAfterNewline() {
EditIterator iter = new EditIterator(lines, 0);
iter.advance(4);
- assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(6));
+ assertLineChsEqual(Pos.create(2, 2), iter.advance(6));
}
@Test
public void testAcrossMultipleLines() {
EditIterator iter = new EditIterator(lines, 0);
- assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(10));
+ assertLineChsEqual(Pos.create(2, 2), iter.advance(10));
}
}
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
index 4ecc9e0c69..a4d127da47 100644
--- a/gerrit-httpd/BUCK
+++ b/gerrit-httpd/BUCK
@@ -18,6 +18,7 @@ java_library(
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-util-cli:cli',
+ '//gerrit-util-http:http',
'//lib:args4j',
'//lib:gson',
'//lib:guava',
@@ -25,6 +26,7 @@ java_library(
'//lib:gwtorm',
'//lib:jsch',
'//lib:mime-util',
+ '//lib/auto:auto-value',
'//lib/commons:codec',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
@@ -53,6 +55,7 @@ java_test(
'//gerrit-extension-api:api',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
+ '//gerrit-util-http:http',
'//lib:junit',
'//lib:gson',
'//lib:gwtorm',
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
index d594d777d0..4bf31023dc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
@@ -14,28 +14,16 @@
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;
-class AdvertisedObjectsCacheKey {
- private final Account.Id account;
- private final Project.NameKey project;
-
- AdvertisedObjectsCacheKey(Account.Id account, Project.NameKey project) {
- this.account = account;
- this.project = project;
- }
-
- @Override
- public int hashCode() {
- return account.hashCode();
+@AutoValue
+abstract class AdvertisedObjectsCacheKey {
+ static AdvertisedObjectsCacheKey create(Account.Id account, Project.NameKey project) {
+ return new AutoValue_AdvertisedObjectsCacheKey(account, project);
}
- public boolean equals(Object other) {
- if (other instanceof AdvertisedObjectsCacheKey) {
- AdvertisedObjectsCacheKey o = (AdvertisedObjectsCacheKey) other;
- return account.equals(o.account) && project.equals(o.project);
- }
- return false;
- }
+ public abstract Account.Id account();
+ public abstract Project.NameKey project();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java
index 992c70a4dd..901b180bdc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CanonicalWebUrl.java
@@ -14,12 +14,12 @@
package com.google.gerrit.httpd;
-import javax.servlet.http.HttpServletRequest;
-
import com.google.gerrit.common.Nullable;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import javax.servlet.http.HttpServletRequest;
+
public class CanonicalWebUrl {
private final Provider<String> configured;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index f41f67cbe2..4b0e46c77d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -15,12 +15,12 @@
package com.google.gerrit.httpd;
import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.GitwebConfig;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.change.ArchiveFormat;
import com.google.gerrit.server.change.GetArchive;
@@ -133,9 +133,6 @@ class GerritConfigProvider implements Provider<GerritConfig> {
config.setSuggestFrom(cfg.getInt("suggest", "from", 0));
config.setChangeUpdateDelay((int) ConfigUtil.getTimeUnit(
cfg, "change", null, "updateDelay", 30, TimeUnit.SECONDS));
- config.setChangeScreen(cfg.getEnum(
- "gerrit", null, "changeScreen",
- AccountGeneralPreferences.ChangeScreen.CHANGE_SCREEN2));
config.setLargeChangeSize(cfg.getInt("change", "largeChange", 500));
config.setArchiveFormats(Lists.newArrayList(Iterables.transform(
archiveFormats.getAllowed(),
@@ -146,11 +143,7 @@ class GerritConfigProvider implements Provider<GerritConfig> {
}
})));
- config.setNewFeatures(cfg.getBoolean("gerrit", "enableNewFeatures", true));
-
- final String reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
- config.setReportBugUrl(reportBugUrl != null ?
- reportBugUrl : "http://code.google.com/p/gerrit/issues/list");
+ config.setReportBugUrl(cfg.getString("gerrit", null, "reportBugUrl"));
config.setReportBugText(cfg.getString("gerrit", null, "reportBugText"));
final Set<Account.FieldName> fields = new HashSet<>();
@@ -174,6 +167,19 @@ class GerritConfigProvider implements Provider<GerritConfig> {
config.setSshdAddress(sshInfo.getHostKeys().get(0).getHost());
}
+ String replyTitle =
+ Optional.fromNullable(cfg.getString("change", null, "replyTooltip"))
+ .or("Reply and score")
+ + " (Shortcut: a)";
+ String replyLabel =
+ Optional.fromNullable(cfg.getString("change", null, "replyLabel"))
+ .or("Reply")
+ + "\u2026";
+ config.setReplyTitle(replyTitle);
+ config.setReplyLabel(replyLabel);
+
+ config.setAllowDraftChanges(cfg.getBoolean("change", "allowDrafts", true));
+
return config;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritUiOptions.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java
index cd07320a7a..adfe86c3d2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritUiOptions.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java
@@ -14,14 +14,20 @@
package com.google.gerrit.httpd;
-public class GerritUiOptions {
+public class GerritOptions {
private final boolean headless;
+ private final boolean slave;
- public GerritUiOptions(boolean headless) {
+ public GerritOptions(boolean headless, boolean slave) {
this.headless = headless;
+ this.slave = slave;
}
public boolean enableDefaultUi() {
return !headless;
}
+
+ public boolean enableMasterFeatures() {
+ return !slave;
+ }
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
index 4f35f1c139..94b8f2925c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.http.jetty;
+package com.google.gerrit.httpd;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -24,7 +25,6 @@ import com.google.inject.servlet.ServletModule;
import org.eclipse.jgit.lib.Config;
import java.io.IOException;
-import java.net.URI;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -34,28 +34,26 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
- * Stores as a request attribute, so the {@link HttpLog} can include the the
- * user for the request outside of the request scope.
+ * Stores user as a request attribute, so servlets can access it outside of the
+ * request scope.
*/
@Singleton
public class GetUserFilter implements Filter {
- static final String REQ_ATTR_KEY = CurrentUser.class.toString();
+ public static final String REQ_ATTR_KEY = "User";
public static class Module extends ServletModule {
- private boolean loggingEnabled;
+ private final boolean enabled;
@Inject
Module(@GerritServerConfig final Config cfg) {
- URI[] urls = JettyServer.listenURLs(cfg);
- boolean reverseProxy = JettyServer.isReverseProxied(urls);
- this.loggingEnabled = cfg.getBoolean("httpd", "requestLog", !reverseProxy);
+ enabled = cfg.getBoolean("http", "addUserAsRequestAttribute", true);
}
@Override
protected void configureServlets() {
- if (loggingEnabled) {
+ if (enabled) {
filter("/*").through(GetUserFilter.class);
}
}
@@ -72,7 +70,15 @@ public class GetUserFilter implements Filter {
public void doFilter(
ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
- req.setAttribute(REQ_ATTR_KEY, userProvider.get());
+ CurrentUser user = userProvider.get();
+ if (user != null && user.isIdentifiedUser()) {
+ IdentifiedUser who = (IdentifiedUser) user;
+ if (who.getUserName() != null && !who.getUserName().isEmpty()) {
+ req.setAttribute(REQ_ATTR_KEY, who.getUserName());
+ } else {
+ req.setAttribute(REQ_ATTR_KEY, "a/" + who.getAccountId());
+ }
+ }
chain.doFilter(req, resp);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 0c4f60cbfd..e73f80592c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -63,6 +63,8 @@ import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -98,12 +100,20 @@ public class GitOverHttpServlet extends GitServlet {
}
static class Module extends AbstractModule {
+
+ private final boolean enableReceive;
+
+ public Module(boolean enableReceive) {
+ this.enableReceive = enableReceive;
+ }
+
@Override
protected void configure() {
bind(Resolver.class);
bind(UploadFactory.class);
bind(UploadFilter.class);
- bind(ReceiveFactory.class);
+ bind(new TypeLiteral<ReceivePackFactory<HttpServletRequest>>() {}).to(
+ enableReceive ? ReceiveFactory.class : DisabledReceiveFactory.class);
bind(ReceiveFilter.class);
install(new CacheModule() {
@Override
@@ -119,9 +129,10 @@ public class GitOverHttpServlet extends GitServlet {
}
@Inject
- GitOverHttpServlet(Resolver resolver,
- UploadFactory upload, UploadFilter uploadFilter,
- ReceiveFactory receive, ReceiveFilter receiveFilter) {
+ GitOverHttpServlet(Resolver resolver, UploadFactory upload,
+ UploadFilter uploadFilter,
+ ReceivePackFactory<HttpServletRequest> receive,
+ ReceiveFilter receiveFilter) {
setRepositoryResolver(resolver);
setAsIsFileService(AsIsFileService.DISABLED);
@@ -147,6 +158,13 @@ public class GitOverHttpServlet extends GitServlet {
public Repository open(HttpServletRequest req, String projectName)
throws RepositoryNotFoundException, ServiceNotAuthorizedException,
ServiceNotEnabledException {
+ try {
+ // TODO: remove this code when Guice fixes its issue 745
+ projectName = URLDecoder.decode(projectName, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // leave it encoded
+ }
+
while (projectName.endsWith("/")) {
projectName = projectName.substring(0, projectName.length() - 1);
}
@@ -308,6 +326,15 @@ public class GitOverHttpServlet extends GitServlet {
}
}
+ static class DisabledReceiveFactory implements
+ ReceivePackFactory<HttpServletRequest> {
+ @Override
+ public ReceivePack create(HttpServletRequest req, Repository db)
+ throws ServiceNotEnabledException {
+ throw new ServiceNotEnabledException();
+ }
+ }
+
static class ReceiveFilter implements Filter {
private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
@@ -354,7 +381,7 @@ public class GitOverHttpServlet extends GitServlet {
return;
}
- AdvertisedObjectsCacheKey cacheKey = new AdvertisedObjectsCacheKey(
+ AdvertisedObjectsCacheKey cacheKey = AdvertisedObjectsCacheKey.create(
((IdentifiedUser) pc.getCurrentUser()).getAccountId(),
projectName);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
index b2228a98f3..ada3ebf3b4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
@@ -18,12 +18,11 @@ import com.google.common.base.Strings;
import com.google.gerrit.audit.AuditEvent;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -47,7 +46,6 @@ public class HttpLogoutServlet extends HttpServlet {
protected HttpLogoutServlet(final AuthConfig authConfig,
final DynamicItem<WebSession> webSession,
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
- final AccountManager accountManager,
final AuditService audit) {
this.webSession = webSession;
this.urlProvider = urlProvider;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
index 044c18ca23..6c17c87787 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
@@ -32,4 +32,7 @@ public class LoginUrlToken {
return CharMatcher.is('/').trimLeadingFrom(Url.decode(encodedToken));
}
}
+
+ private LoginUrlToken() {
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index b9b57319cd..8c469a99e4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -16,7 +16,7 @@ package com.google.gerrit.httpd;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.AccessPath;
@@ -183,7 +183,7 @@ class ProjectBasicAuthFilter implements Filter {
}
private String encoding(HttpServletRequest req) {
- return Objects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
+ return MoreObjects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
}
class Response extends HttpServletResponseWrapper {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java
new file mode 100644
index 0000000000..67b97c4ec2
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyProperties.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 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 java.net.URL;
+
+public interface ProxyProperties {
+ URL getProxyUrl();
+ String getUsername();
+ String getPassword();
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java
new file mode 100644
index 0000000000..0e51cc2c52
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProxyPropertiesProvider.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2014 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 com.google.common.base.Strings;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+@Singleton
+class ProxyPropertiesProvider implements Provider<ProxyProperties> {
+
+ private URL proxyUrl;
+ private String proxyUser;
+ private String proxyPassword;
+
+ @Inject
+ ProxyPropertiesProvider(@GerritServerConfig Config config)
+ throws MalformedURLException {
+ String proxyUrlStr = config.getString("http", null, "proxy");
+ if (!Strings.isNullOrEmpty(proxyUrlStr)) {
+ proxyUrl = new URL(proxyUrlStr);
+ proxyUser = config.getString("http", null, "proxyUsername");
+ proxyPassword = config.getString("http", null, "proxyPassword");
+ String userInfo = proxyUrl.getUserInfo();
+ if (userInfo != null) {
+ int c = userInfo.indexOf(':');
+ if (0 < c) {
+ proxyUser = userInfo.substring(0, c);
+ proxyPassword = userInfo.substring(c + 1);
+ } else {
+ proxyUser = userInfo;
+ }
+ }
+ }
+ }
+
+ @Override
+ public ProxyProperties get() {
+ return new ProxyProperties() {
+ @Override
+ public URL getProxyUrl() {
+ return proxyUrl;
+ }
+ @Override
+ public String getUsername() {
+ return proxyUser;
+ }
+ @Override
+ public String getPassword() {
+ return proxyPassword;
+ }
+ };
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
index 3418354284..614184a9d7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -14,11 +14,11 @@
package com.google.gerrit.httpd;
+import static com.google.gerrit.httpd.restapi.RestApiServlet.replyError;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.httpd.restapi.RestApiServlet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResolver;
@@ -77,17 +77,19 @@ class RunAsFilter implements Filter {
String runas = req.getHeader(RUN_AS);
if (runas != null) {
if (!enabled) {
- RestApiServlet.replyError(req, res,
+ replyError(req, res,
SC_FORBIDDEN,
- RUN_AS + " disabled by auth.enableRunAs = false");
+ RUN_AS + " disabled by auth.enableRunAs = false",
+ null);
return;
}
CurrentUser self = session.get().getCurrentUser();
if (!self.getCapabilities().canRunAs()) {
- RestApiServlet.replyError(req, res,
+ replyError(req, res,
SC_FORBIDDEN,
- "not permitted to use " + RUN_AS);
+ "not permitted to use " + RUN_AS,
+ null);
return;
}
@@ -96,15 +98,17 @@ class RunAsFilter implements Filter {
target = accountResolver.find(runas);
} catch (OrmException e) {
log.warn("cannot resolve account for " + RUN_AS, e);
- RestApiServlet.replyError(req, res,
+ replyError(req, res,
SC_INTERNAL_SERVER_ERROR,
- "cannot resolve " + RUN_AS);
+ "cannot resolve " + RUN_AS,
+ e);
return;
}
if (target == null) {
- RestApiServlet.replyError(req, res,
+ replyError(req, res,
SC_FORBIDDEN,
- "no account matches " + RUN_AS);
+ "no account matches " + RUN_AS,
+ null);
return;
}
session.get().setUserAccountId(target.getId());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 031e3a230f..86debdd2ce 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -28,7 +28,6 @@ import com.google.gerrit.httpd.raw.ToolServlet;
import com.google.gerrit.httpd.rpc.access.AccessRestApiServlet;
import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
-import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
import com.google.gerrit.httpd.rpc.doc.QueryDocumentationFilter;
import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
@@ -37,15 +36,12 @@ import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AuthConfig;
-import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtexpui.server.CacheControlFilter;
-import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.servlet.ServletModule;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import java.io.IOException;
@@ -55,22 +51,11 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
class UrlModule extends ServletModule {
- static class UrlConfig {
- private final boolean deprecatedQuery;
-
- @Inject
- UrlConfig(@GerritServerConfig Config cfg) {
- deprecatedQuery = cfg.getBoolean("site", "enableDeprecatedQuery", true);
- }
- }
-
- private final UrlConfig cfg;
- private GerritUiOptions uiOptions;
+ private GerritOptions options;
private AuthConfig authConfig;
- UrlModule(UrlConfig cfg, GerritUiOptions uiOptions, AuthConfig authConfig) {
- this.cfg = cfg;
- this.uiOptions = uiOptions;
+ UrlModule(GerritOptions options, AuthConfig authConfig) {
+ this.options = options;
this.authConfig = authConfig;
}
@@ -79,7 +64,7 @@ class UrlModule extends ServletModule {
filter("/*").through(Key.get(CacheControlFilter.class));
bind(Key.get(CacheControlFilter.class)).in(SINGLETON);
- if (uiOptions.enableDefaultUi()) {
+ if (options.enableDefaultUi()) {
serve("/").with(HostPageServlet.class);
serve("/Gerrit").with(LegacyGerritServlet.class);
serve("/Gerrit/*").with(legacyGerritScreen());
@@ -121,10 +106,6 @@ class UrlModule extends ServletModule {
filter("/Documentation/").through(QueryDocumentationFilter.class);
- if (cfg.deprecatedQuery) {
- serve("/query").with(DeprecatedChangeQueryServlet.class);
- }
-
serve("/robots.txt").with(RobotsServlet.class);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 76e1e414e7..783116180a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -15,7 +15,6 @@
package com.google.gerrit.httpd;
import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
-import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GerritConfig;
@@ -32,8 +31,6 @@ import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritRequestModule;
-import com.google.gerrit.server.contact.ContactStore;
-import com.google.gerrit.server.contact.ContactStoreProvider;
import com.google.gerrit.server.git.AsyncReceiveCommits;
import com.google.gerrit.server.util.GuiceRequestScopePropagator;
import com.google.gerrit.server.util.RequestScopePropagator;
@@ -47,21 +44,18 @@ import java.net.SocketAddress;
public class WebModule extends LifecycleModule {
private final AuthConfig authConfig;
- private final UrlModule.UrlConfig urlConfig;
private final boolean wantSSL;
private final GitWebConfig gitWebConfig;
- private final GerritUiOptions uiOptions;
+ private final GerritOptions options;
@Inject
WebModule(final AuthConfig authConfig,
- final UrlModule.UrlConfig urlConfig,
@CanonicalWebUrl @Nullable final String canonicalUrl,
- GerritUiOptions uiOptions,
+ GerritOptions options,
final Injector creatingInjector) {
this.authConfig = authConfig;
- this.urlConfig = urlConfig;
this.wantSSL = canonicalUrl != null && canonicalUrl.startsWith("https:");
- this.uiOptions = uiOptions;
+ this.options = options;
this.gitWebConfig =
creatingInjector.createChildInjector(new AbstractModule() {
@@ -82,6 +76,34 @@ public class WebModule extends LifecycleModule {
}
install(new RunAsFilter.Module());
+ installAuthModule();
+ if (options.enableMasterFeatures()) {
+ install(new UrlModule(options, authConfig));
+ install(new UiRpcModule());
+ }
+ install(new GerritRequestModule());
+ install(new GitOverHttpServlet.Module(options.enableMasterFeatures()));
+
+ bind(GitWebConfig.class).toInstance(gitWebConfig);
+ if (gitWebConfig.getGitwebCGI() != null) {
+ install(new GitWebModule());
+ }
+
+ bind(GerritConfigProvider.class);
+ bind(GerritConfig.class).toProvider(GerritConfigProvider.class);
+ DynamicSet.setOf(binder(), WebUiPlugin.class);
+
+ install(new AsyncReceiveCommits.Module());
+
+ bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
+ HttpRemotePeerProvider.class).in(RequestScoped.class);
+
+ bind(ProxyProperties.class).toProvider(ProxyPropertiesProvider.class);
+
+ listener().toInstance(registerInParentInjectors());
+ }
+
+ private void installAuthModule() {
switch (authConfig.getAuthType()) {
case HTTP:
case HTTP_LDAP:
@@ -111,28 +133,5 @@ public class WebModule extends LifecycleModule {
default:
throw new ProvisionException("Unsupported loginType: " + authConfig.getAuthType());
}
-
- install(new UrlModule(urlConfig, uiOptions, authConfig));
- install(new UiRpcModule());
- install(new GerritRequestModule());
- install(new GitOverHttpServlet.Module());
-
- bind(GitWebConfig.class).toInstance(gitWebConfig);
- if (gitWebConfig.getGitwebCGI() != null) {
- install(new GitWebModule());
- }
-
- bind(ContactStore.class).toProvider(ContactStoreProvider.class).in(
- SINGLETON);
- bind(GerritConfigProvider.class);
- bind(GerritConfig.class).toProvider(GerritConfigProvider.class);
- DynamicSet.setOf(binder(), WebUiPlugin.class);
-
- install(new AsyncReceiveCommits.Module());
-
- bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
- HttpRemotePeerProvider.class).in(RequestScoped.class);
-
- listener().toInstance(registerInParentInjectors());
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
index 24e2c56c09..5db620c834 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd;
+import static com.google.gerrit.common.TimeUtil.nowMs;
import static com.google.gerrit.httpd.CacheBasedWebSession.MAX_AGE_MINUTES;
import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
@@ -22,7 +23,6 @@ import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
-import static com.google.gerrit.server.util.TimeUtil.nowMs;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 8884b4d9c2..b8a8092bd8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -46,7 +46,6 @@ import java.io.Writer;
import java.util.List;
import java.util.UUID;
-import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -66,7 +65,6 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
BecomeAnyAccountLoginServlet(final DynamicItem<WebSession> ws,
final SchemaFactory<ReviewDb> sf,
final AccountManager am,
- final ServletContext servletContext,
SiteHeaderFooter shf) {
webSession = ws;
schema = sf;
@@ -90,13 +88,13 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
res = create();
} else if (req.getParameter("user_name") != null) {
- res = byUserName(rsp, req.getParameter("user_name"));
+ res = byUserName(req.getParameter("user_name"));
} else if (req.getParameter("preferred_email") != null) {
- res = byPreferredEmail(rsp, req.getParameter("preferred_email"));
+ res = byPreferredEmail(req.getParameter("preferred_email"));
} else if (req.getParameter("account_id") != null) {
- res = byAccountId(rsp, req.getParameter("account_id"));
+ res = byAccountId(req.getParameter("account_id"));
} else {
byte[] raw;
@@ -207,8 +205,7 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
return null;
}
- private AuthResult byUserName(final HttpServletResponse rsp,
- final String userName) {
+ private AuthResult byUserName(final String userName) {
try {
final ReviewDb db = schema.open();
try {
@@ -224,8 +221,7 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
}
}
- private AuthResult byPreferredEmail(final HttpServletResponse rsp,
- final String email) {
+ private AuthResult byPreferredEmail(final String email) {
try {
final ReviewDb db = schema.open();
try {
@@ -240,8 +236,7 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
}
}
- private AuthResult byAccountId(final HttpServletResponse rsp,
- final String idStr) {
+ private AuthResult byAccountId(final String idStr) {
final Account.Id id;
try {
id = Account.Id.parse(idStr);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
index b737cd7052..19c8342351 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd.auth.container;
-import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GERRIT;
@@ -63,6 +63,7 @@ class HttpAuthFilter implements Filter {
private final String loginHeader;
private final String displaynameHeader;
private final String emailHeader;
+ private final String externalIdHeader;
@Inject
HttpAuthFilter(final DynamicItem<WebSession> webSession,
@@ -82,6 +83,7 @@ class HttpAuthFilter implements Filter {
AUTHORIZATION);
displaynameHeader = emptyToNull(authConfig.getHttpDisplaynameHeader());
emailHeader = emptyToNull(authConfig.getHttpEmailHeader());
+ externalIdHeader = emptyToNull(authConfig.getHttpExternalIdHeader());
}
@Override
@@ -194,6 +196,14 @@ class HttpAuthFilter implements Filter {
}
}
+ String getRemoteExternalIdToken(HttpServletRequest req) {
+ if(externalIdHeader != null) {
+ return emptyToNull(req.getHeader(externalIdHeader));
+ } else {
+ return null;
+ }
+ }
+
String getLoginHeader() {
return loginHeader;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index 734addb09e..6d8a0cda07 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -14,18 +14,22 @@
package com.google.gerrit.httpd.auth.container;
+import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_EXTERNAL;
+
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtexpui.server.CacheHeaders;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -122,6 +126,20 @@ class HttpLoginServlet extends HttpServlet {
return;
}
+ String remoteExternalId = authFilter.getRemoteExternalIdToken(req);
+ if (remoteExternalId != null) {
+ try {
+ log.debug("Associating external identity \"{}\" to user \"{}\"",
+ remoteExternalId, user);
+ updateRemoteExternalId(arsp, remoteExternalId);
+ } catch (AccountException | OrmException e) {
+ log.error("Unable to associate external identity \"" + remoteExternalId
+ + "\" to user \"" + user + "\"", e);
+ rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+
final StringBuilder rdr = new StringBuilder();
if (arsp.isNew() && authConfig.getRegisterPageUrl() != null) {
rdr.append(authConfig.getRegisterPageUrl());
@@ -137,6 +155,15 @@ class HttpLoginServlet extends HttpServlet {
rsp.sendRedirect(rdr.toString());
}
+ private void updateRemoteExternalId(AuthResult arsp, String remoteAuthToken)
+ throws AccountException, OrmException {
+ AccountExternalId remoteAuthExtId =
+ new AccountExternalId(arsp.getAccountId(), new AccountExternalId.Key(
+ SCHEME_EXTERNAL, remoteAuthToken));
+ accountManager.updateLink(arsp.getAccountId(),
+ new AuthRequest(remoteAuthExtId.getExternalId()));
+ }
+
private void replace(Document doc, String name, String value) {
Element e = HtmlDomUtil.find(doc, name);
if (e != null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
index 2deb2ebdcb..f58a719d08 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd.auth.ldap;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -72,7 +72,7 @@ class LdapLoginServlet extends HttpServlet {
private void sendForm(HttpServletRequest req, HttpServletResponse res,
@Nullable String errorMessage) throws IOException {
String self = req.getRequestURI();
- String cancel = Objects.firstNonNull(urlProvider.get(req), "/");
+ String cancel = MoreObjects.firstNonNull(urlProvider.get(req), "/");
cancel += LoginUrlToken.getToken(req);
Document doc = headers.parse(LdapLoginServlet.class, "LoginForm.html");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java
index dcca106c83..4a39b97f6f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java
@@ -36,8 +36,8 @@ abstract class GitWebCssServlet extends HttpServlet {
@Singleton
static class Site extends GitWebCssServlet {
@Inject
- Site(SitePaths paths, GitWebConfig gwc) throws IOException {
- super(paths.site_css, gwc);
+ Site(SitePaths paths) throws IOException {
+ super(paths.site_css);
}
}
@@ -45,7 +45,7 @@ abstract class GitWebCssServlet extends HttpServlet {
static class Default extends GitWebCssServlet {
@Inject
Default(GitWebConfig gwc) throws IOException {
- super(gwc.getGitwebCSS(), gwc);
+ super(gwc.getGitwebCSS());
}
}
@@ -55,7 +55,7 @@ abstract class GitWebCssServlet extends HttpServlet {
private final byte[] raw_css;
private final byte[] gz_css;
- GitWebCssServlet(final File src, final GitWebConfig gitWebConfig)
+ GitWebCssServlet(final File src)
throws IOException {
if (src != null) {
final File dir = src.getParentFile();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 3b4c9985fe..573725c9dc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -160,6 +160,8 @@ class GitWebServlet extends HttpServlet {
myconf.setWritable(true, true /* owner only */);
myconf.setReadable(true, true /* owner only */);
+ myconf.deleteOnExit();
+
_env.set("GIT_DIR", ".");
_env.set("GITWEB_CONFIG", myconf.getAbsolutePath());
@@ -413,7 +415,7 @@ class GitWebServlet extends HttpServlet {
}
try {
CacheHeaders.setNotCacheable(rsp);
- exec(req, rsp, project, repo);
+ exec(req, rsp, project);
} finally {
repo.close();
}
@@ -451,8 +453,7 @@ class GitWebServlet extends HttpServlet {
}
private void exec(final HttpServletRequest req,
- final HttpServletResponse rsp, final ProjectControl project,
- final Repository repo) throws IOException {
+ final HttpServletResponse rsp, final ProjectControl project) throws IOException {
final Process proc =
Runtime.getRuntime().exec(new String[] {gitwebCgi.getAbsolutePath()},
makeEnv(req, project),
@@ -611,6 +612,7 @@ class GitWebServlet extends HttpServlet {
final int contentLength = req.getContentLength();
final InputStream src = req.getInputStream();
new Thread(new Runnable() {
+ @Override
public void run() {
try {
try {
@@ -637,6 +639,7 @@ class GitWebServlet extends HttpServlet {
private void copyStderrToLog(final InputStream in) {
new Thread(new Runnable() {
+ @Override
public void run() {
try {
final BufferedReader br =
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
index d1c617f069..0e81a0d9c8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
@@ -14,14 +14,18 @@
package com.google.gerrit.httpd.plugins;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.gerrit.extensions.annotations.Export;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.server.plugins.HttpModuleGenerator;
import com.google.gerrit.server.plugins.InvalidPluginException;
-import com.google.gerrit.server.plugins.ModuleGenerator;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
@@ -33,9 +37,10 @@ import java.util.Map;
import javax.servlet.http.HttpServlet;
class HttpAutoRegisterModuleGenerator extends ServletModule
- implements ModuleGenerator {
+ implements HttpModuleGenerator {
private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
+ private String javascript;
@Override
protected void configureServlets() {
@@ -53,6 +58,10 @@ class HttpAutoRegisterModuleGenerator extends ServletModule
Annotation n = calculateBindAnnotation(impl);
bind(type).annotatedWith(n).to(impl);
}
+ if (javascript != null) {
+ DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(
+ new JavaScriptPlugin(javascript));
+ }
}
@Override
@@ -80,6 +89,14 @@ class HttpAutoRegisterModuleGenerator extends ServletModule
}
@Override
+ public void export(String javascript) {
+ checkState(this.javascript == null,
+ "Multiple JavaScript plugins detected: %s, %s", this.javascript,
+ javascript);
+ this.javascript = javascript;
+ }
+
+ @Override
public void listen(TypeLiteral<?> tl, Class<?> clazz) {
listeners.put(tl, clazz);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
index 5dc7e2e74d..20169425c1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
@@ -14,8 +14,11 @@
package com.google.gerrit.httpd.plugins;
+import com.google.gerrit.httpd.resources.Resource;
+import com.google.gerrit.httpd.resources.ResourceKey;
+import com.google.gerrit.httpd.resources.ResourceWeigher;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.plugins.ModuleGenerator;
+import com.google.gerrit.server.plugins.HttpModuleGenerator;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.inject.internal.UniqueAnnotations;
@@ -37,7 +40,7 @@ public class HttpPluginModule extends ServletModule {
.annotatedWith(UniqueAnnotations.create())
.to(HttpPluginServlet.class);
- bind(ModuleGenerator.class)
+ bind(HttpModuleGenerator.class)
.to(HttpAutoRegisterModuleGenerator.class);
install(new CacheModule() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 588663689a..64b754d164 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -26,11 +26,13 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.httpd.resources.Resource;
+import com.google.gerrit.httpd.resources.ResourceKey;
+import com.google.gerrit.httpd.resources.SmallResource;
import com.google.gerrit.httpd.restapi.RestApiServlet;
-import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.documentation.MarkdownFormatter;
+import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.plugins.Plugin;
import com.google.gerrit.server.plugins.Plugin.ApiType;
import com.google.gerrit.server.plugins.PluginContentScanner;
@@ -39,6 +41,7 @@ import com.google.gerrit.server.plugins.PluginsCollection;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gerrit.util.http.RequestUtil;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -46,7 +49,6 @@ import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.servlet.GuiceFilter;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.slf4j.Logger;
@@ -105,7 +107,6 @@ class HttpPluginServlet extends HttpServlet
MimeUtilFileTypeRegistry mimeUtil,
@CanonicalWebUrl Provider<String> webUrl,
@Named(HttpPluginModule.PLUGIN_RESOURCES) Cache<ResourceKey, Resource> cache,
- @GerritServerConfig Config cfg,
SshInfo sshInfo,
RestApiServlet.Globals globals,
PluginsCollection plugins) {
@@ -205,7 +206,7 @@ class HttpPluginServlet extends HttpServlet
throws IOException, ServletException {
List<String> parts = Lists.newArrayList(
Splitter.on('/').limit(3).omitEmptyStrings()
- .split(Strings.nullToEmpty(req.getPathInfo())));
+ .split(Strings.nullToEmpty(RequestUtil.getEncodedPathInfo(req))));
if (isApiCall(req, parts)) {
managerApi.service(req, res);
@@ -252,14 +253,14 @@ class HttpPluginServlet extends HttpServlet
return;
}
- String pathInfo = req.getPathInfo();
+ String pathInfo = RequestUtil.getEncodedPathInfo(req);
if (pathInfo.length() < 1) {
Resource.NOT_FOUND.send(req, res);
return;
}
String file = pathInfo.substring(1);
- ResourceKey key = new ResourceKey(holder.plugin, file);
+ PluginResourceKey key = PluginResourceKey.create(holder.plugin, file);
Resource rsc = resourceCache.getIfPresent(key);
if (rsc != null && req.getHeader(HttpHeaders.IF_MODIFIED_SINCE) == null) {
rsc.send(req, res);
@@ -364,7 +365,7 @@ class HttpPluginServlet extends HttpServlet
private void sendAutoIndex(PluginContentScanner scanner,
String prefix, String pluginName,
- ResourceKey cacheKey, HttpServletResponse res,long lastModifiedTime)
+ PluginResourceKey cacheKey, HttpServletResponse res,long lastModifiedTime)
throws IOException {
List<PluginEntry> cmds = Lists.newArrayList();
List<PluginEntry> servlets = Lists.newArrayList();
@@ -437,7 +438,7 @@ class HttpPluginServlet extends HttpServlet
}
private void sendMarkdownAsHtml(String md, String pluginName,
- ResourceKey cacheKey, HttpServletResponse res, long lastModifiedTime)
+ PluginResourceKey cacheKey, HttpServletResponse res, long lastModifiedTime)
throws UnsupportedEncodingException, IOException {
Map<String, String> macros = Maps.newHashMap();
macros.put("PLUGIN", pluginName);
@@ -540,7 +541,7 @@ class HttpPluginServlet extends HttpServlet
}
private void sendMarkdownAsHtml(PluginContentScanner scanner, PluginEntry entry,
- String pluginName, ResourceKey key, HttpServletResponse res)
+ String pluginName, PluginResourceKey key, HttpServletResponse res)
throws IOException {
byte[] rawmd = readWholeEntry(scanner, entry);
String encoding = null;
@@ -560,7 +561,7 @@ class HttpPluginServlet extends HttpServlet
}
private void sendResource(PluginContentScanner scanner, PluginEntry entry,
- ResourceKey key, HttpServletResponse res)
+ PluginResourceKey key, HttpServletResponse res)
throws IOException {
byte[] data = null;
Optional<Long> size = entry.getSize();
@@ -608,7 +609,7 @@ class HttpPluginServlet extends HttpServlet
}
}
- private void sendJsPlugin(Plugin plugin, ResourceKey key,
+ private void sendJsPlugin(Plugin plugin, PluginResourceKey key,
HttpServletRequest req, HttpServletResponse res) throws IOException {
File pluginFile = plugin.getSrcFile();
if (req.getRequestURI().endsWith(getJsPluginPath(plugin))
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java
index 068d6b4fd6..0a15a4f23b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceKey.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java
@@ -14,32 +14,21 @@
package com.google.gerrit.httpd.plugins;
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.httpd.resources.ResourceKey;
import com.google.gerrit.server.plugins.Plugin;
-final class ResourceKey {
- private final Plugin.CacheKey plugin;
- private final String resource;
-
- ResourceKey(Plugin p, String r) {
- this.plugin = p.getCacheKey();
- this.resource = r;
- }
-
- int weigh() {
- return resource.length() * 2;
+@AutoValue
+abstract class PluginResourceKey implements ResourceKey {
+ static PluginResourceKey create(Plugin p, String r) {
+ return new AutoValue_PluginResourceKey(p.getCacheKey(), r);
}
- @Override
- public int hashCode() {
- return plugin.hashCode() * 31 + resource.hashCode();
- }
+ public abstract Plugin.CacheKey plugin();
+ public abstract String resource();
@Override
- public boolean equals(Object other) {
- if (other instanceof ResourceKey) {
- ResourceKey rk = (ResourceKey) other;
- return plugin == rk.plugin && resource.equals(rk.resource);
- }
- return false;
+ public int weigh() {
+ return resource().length() * 2;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
index 756c42762e..72fc008d75 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -14,17 +14,22 @@
package com.google.gerrit.httpd.raw;
+import com.google.common.base.Optional;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.AuthException;
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.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.FileTypeRegistry;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -73,16 +78,24 @@ public class CatServlet extends HttpServlet {
private final GitRepositoryManager repoManager;
private final SecureRandom rng;
private final FileTypeRegistry registry;
- private final ChangeControl.Factory changeControl;
+ private final Provider<CurrentUser> userProvider;
+ private final ChangeControl.GenericFactory changeControl;
+ private final ChangeEditUtil changeEditUtil;
@Inject
- CatServlet(final GitRepositoryManager grm, final Provider<ReviewDb> sf,
- final FileTypeRegistry ftr, final ChangeControl.Factory ccf) {
+ CatServlet(GitRepositoryManager grm,
+ Provider<ReviewDb> sf,
+ FileTypeRegistry ftr,
+ ChangeControl.GenericFactory ccf,
+ Provider<CurrentUser> usrprv,
+ ChangeEditUtil ceu) {
requestDb = sf;
repoManager = grm;
rng = new SecureRandom();
registry = ftr;
changeControl = ccf;
+ userProvider = usrprv;
+ changeEditUtil = ceu;
}
@Override
@@ -137,16 +150,35 @@ public class CatServlet extends HttpServlet {
final Change.Id changeId = patchKey.getParentKey().getParentKey();
final Project project;
- final PatchSet patchSet;
+ final String revision;
try {
final ReviewDb db = requestDb.get();
- final ChangeControl control = changeControl.validateFor(changeId);
+ final ChangeControl control = changeControl.validateFor(changeId,
+ userProvider.get());
project = control.getProject();
- patchSet = db.patchSets().get(patchKey.getParentKey());
- if (patchSet == null) {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
+
+ if (patchKey.getParentKey().get() == 0) {
+ // change edit
+ try {
+ Optional<ChangeEdit> edit = changeEditUtil.byChange(control.getChange());
+ if (edit.isPresent()) {
+ revision = edit.get().getRevision().get();
+ } else {
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ } catch (AuthException e) {
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ } else {
+ PatchSet patchSet = db.patchSets().get(patchKey.getParentKey());
+ if (patchSet == null) {
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ revision = patchSet.getRevision().get();
}
} catch (NoSuchChangeException e) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
@@ -170,32 +202,29 @@ public class CatServlet extends HttpServlet {
final RevCommit fromCommit;
final String suffix;
final String path = patchKey.getFileName();
- try {
- final ObjectReader reader = repo.newObjectReader();
- try {
- final RevWalk rw = new RevWalk(reader);
- final RevCommit c;
- final TreeWalk tw;
-
- c = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- if (side == 0) {
- fromCommit = c;
- suffix = "new";
-
- } else if (1 <= side && side - 1 < c.getParentCount()) {
- fromCommit = rw.parseCommit(c.getParent(side - 1));
- if (c.getParentCount() == 1) {
- suffix = "old";
- } else {
- suffix = "old" + side;
- }
-
+ try (ObjectReader reader = repo.newObjectReader();
+ RevWalk rw = new RevWalk(reader)) {
+ final RevCommit c;
+
+ c = rw.parseCommit(ObjectId.fromString(revision));
+ if (side == 0) {
+ fromCommit = c;
+ suffix = "new";
+
+ } else if (1 <= side && side - 1 < c.getParentCount()) {
+ fromCommit = rw.parseCommit(c.getParent(side - 1));
+ if (c.getParentCount() == 1) {
+ suffix = "old";
} else {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
+ suffix = "old" + side;
}
- tw = TreeWalk.forPath(reader, path, fromCommit.getTree());
+ } else {
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ try (TreeWalk tw = TreeWalk.forPath(reader, path, fromCommit.getTree())) {
if (tw == null) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
@@ -208,8 +237,6 @@ public class CatServlet extends HttpServlet {
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
- } finally {
- reader.close();
}
} catch (IOException e) {
getServletContext().log("Cannot read repository", e);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 18a2ae5187..90c5ff4ddf 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -32,6 +32,7 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.gwtjsonrpc.server.RPCServletUtils;
@@ -82,6 +83,7 @@ public class HostPageServlet extends HttpServlet {
private final String noCacheName;
private final boolean refreshHeaderFooter;
private final StaticServlet staticServlet;
+ private final boolean isNoteDbEnabled;
private volatile Page page;
@Inject
@@ -91,7 +93,8 @@ public class HostPageServlet extends HttpServlet {
final DynamicSet<WebUiPlugin> webUiPlugins,
final DynamicSet<MessageOfTheDay> motd,
@GerritServerConfig final Config cfg,
- final StaticServlet ss)
+ final StaticServlet ss,
+ final NotesMigration migration)
throws IOException, ServletException {
currentUser = cu;
session = w;
@@ -103,6 +106,7 @@ public class HostPageServlet extends HttpServlet {
site = sp;
refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
staticServlet = ss;
+ isNoteDbEnabled = migration.enabled();
final String pageName = "HostPage.html";
template = HtmlDomUtil.parseFile(getClass(), pageName);
@@ -314,6 +318,7 @@ public class HostPageServlet extends HttpServlet {
final HostPageData pageData = new HostPageData();
pageData.version = Version.getVersion();
pageData.config = config;
+ pageData.isNoteDbEnabled = isNoteDbEnabled;
final StringWriter w = new StringWriter();
w.write("var " + HPD_ID + "=");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
index 79c2aeb598..9c267a8d0e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
@@ -24,7 +24,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
-import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -45,7 +44,7 @@ public class LegacyGerritServlet extends HttpServlet {
private final byte[] compressed;
@Inject
- LegacyGerritServlet(final ServletContext servletContext) throws IOException {
+ LegacyGerritServlet() throws IOException {
final String pageName = "LegacyGerrit.html";
final String doc = HtmlDomUtil.readFile(getClass(), pageName);
if (doc == null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java
index 810cc2a016..16509ed82b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java
@@ -61,7 +61,7 @@ public class ToolServlet extends HttpServlet {
switch (ent.getType()) {
case FILE:
- doGetFile(ent, req, rsp);
+ doGetFile(ent, rsp);
break;
case DIR:
@@ -74,8 +74,7 @@ public class ToolServlet extends HttpServlet {
}
}
- private void doGetFile(Entry ent, HttpServletRequest req,
- HttpServletResponse rsp) throws IOException {
+ private void doGetFile(Entry ent, HttpServletResponse rsp) throws IOException {
byte[] tosend = ent.getBytes();
rsp.setDateHeader(HDR_EXPIRES, 0L);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/Resource.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/Resource.java
index b361fdc2cf..b6d9a75e71 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/Resource.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/Resource.java
@@ -12,39 +12,48 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.httpd.plugins;
+package com.google.gerrit.httpd.resources;
import com.google.gwtexpui.server.CacheHeaders;
import java.io.IOException;
+import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-abstract class Resource {
- static final Resource NOT_FOUND = new Resource() {
+public abstract class Resource implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public static final Resource NOT_FOUND = new Resource() {
+ private static final long serialVersionUID = 1L;
+
@Override
- int weigh() {
+ public int weigh() {
return 0;
}
@Override
- void send(HttpServletRequest req, HttpServletResponse res)
+ public void send(HttpServletRequest req, HttpServletResponse res)
throws IOException {
CacheHeaders.setNotCacheable(res);
res.sendError(HttpServletResponse.SC_NOT_FOUND);
}
@Override
- boolean isUnchanged(long latestModifiedDate) {
+ public boolean isUnchanged(long latestModifiedDate) {
return false;
}
+
+ protected Object readResolve() {
+ return NOT_FOUND;
+ }
};
- abstract boolean isUnchanged(long latestModifiedDate);
+ public abstract boolean isUnchanged(long latestModifiedDate);
- abstract int weigh();
+ public abstract int weigh();
- abstract void send(HttpServletRequest req, HttpServletResponse res)
+ public abstract void send(HttpServletRequest req, HttpServletResponse res)
throws IOException;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java
new file mode 100644
index 0000000000..ef5a1df7d3
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2014 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.resources;
+
+public interface ResourceKey {
+ public int weigh();
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceWeigher.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceWeigher.java
index 2514272666..8e997c77fd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceWeigher.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceWeigher.java
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.httpd.plugins;
+package com.google.gerrit.httpd.resources;
import com.google.common.cache.Weigher;
-class ResourceWeigher implements Weigher<ResourceKey, Resource> {
+public class ResourceWeigher implements Weigher<ResourceKey, Resource> {
@Override
public int weigh(ResourceKey key, Resource value) {
return key.weigh() + value.weigh();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/SmallResource.java
index 2a3da57093..d057269693 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/SmallResource.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.httpd.plugins;
+package com.google.gerrit.httpd.resources;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.common.Nullable;
@@ -22,38 +22,39 @@ import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-final class SmallResource extends Resource {
+public final class SmallResource extends Resource {
+ private static final long serialVersionUID = 1L;
private final byte[] data;
private String contentType;
private String characterEncoding;
private long lastModified;
- SmallResource(byte[] data) {
+ public SmallResource(byte[] data) {
this.data = data;
}
- SmallResource setLastModified(long when) {
+ public SmallResource setLastModified(long when) {
this.lastModified = when;
return this;
}
- SmallResource setContentType(String contentType) {
+ public SmallResource setContentType(String contentType) {
this.contentType = contentType;
return this;
}
- SmallResource setCharacterEncoding(@Nullable String enc) {
+ public SmallResource setCharacterEncoding(@Nullable String enc) {
this.characterEncoding = enc;
return this;
}
@Override
- int weigh() {
+ public int weigh() {
return contentType.length() * 2 + data.length;
}
@Override
- void send(HttpServletRequest req, HttpServletResponse res)
+ public void send(HttpServletRequest req, HttpServletResponse res)
throws IOException {
if (0 < lastModified) {
long ifModifiedSince = req.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
@@ -73,7 +74,7 @@ final class SmallResource extends Resource {
}
@Override
- boolean isUnchanged(long lastModified) {
+ public boolean isUnchanged(long lastModified) {
return this.lastModified == lastModified;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index 9183d5c3c2..46843fc417 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -68,7 +68,7 @@ class ParameterParser {
clp.parseOptionMap(in);
} catch (CmdLineException e) {
if (!clp.wasHelpRequestedByOption()) {
- replyError(req, res, SC_BAD_REQUEST, e.getMessage());
+ replyError(req, res, SC_BAD_REQUEST, e.getMessage(), e);
return false;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 45144d7179..6f4cc08de3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -25,13 +25,14 @@ 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 com.google.common.base.Function;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMultimap;
@@ -47,17 +48,21 @@ import com.google.common.net.HttpHeaders;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.audit.HttpAuditEvent;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AcceptsDelete;
import com.google.gerrit.extensions.restapi.AcceptsPost;
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.CacheControl;
import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.IdString;
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.RawInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -78,7 +83,7 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OptionUtil;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.CapabilityUtils;
-import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.util.http.RequestUtil;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.FieldNamingPolicy;
@@ -89,6 +94,7 @@ 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;
import com.google.gson.stream.MalformedJsonException;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
@@ -222,7 +228,7 @@ public class RestApiServlet extends HttpServlet {
try {
rsrc = rc.parse(rsrc, id);
if (path.isEmpty()) {
- checkPreconditions(req, rsrc);
+ checkPreconditions(req);
}
} catch (ResourceNotFoundException e) {
if (rc instanceof AcceptsCreate
@@ -255,6 +261,10 @@ public class RestApiServlet extends HttpServlet {
@SuppressWarnings("unchecked")
AcceptsPost<RestResource> ac = (AcceptsPost<RestResource>) c;
viewData = new ViewData(null, ac.post(rsrc));
+ } else if (c instanceof AcceptsDelete && "DELETE".equals(req.getMethod())) {
+ @SuppressWarnings("unchecked")
+ AcceptsDelete<RestResource> ac = (AcceptsDelete<RestResource>) c;
+ viewData = new ViewData(null, ac.delete(rsrc, null));
} else {
throw new MethodNotAllowedException();
}
@@ -263,7 +273,7 @@ public class RestApiServlet extends HttpServlet {
IdString id = path.remove(0);
try {
rsrc = c.parse(rsrc, id);
- checkPreconditions(req, rsrc);
+ checkPreconditions(req);
viewData = new ViewData(null, null);
} catch (ResourceNotFoundException e) {
if (c instanceof AcceptsCreate
@@ -274,6 +284,13 @@ public class RestApiServlet extends HttpServlet {
AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) c;
viewData = new ViewData(viewData.pluginName, ac.create(rsrc, id));
status = SC_CREATED;
+ } else if (c instanceof AcceptsDelete
+ && path.isEmpty()
+ && "DELETE".equals(req.getMethod())) {
+ @SuppressWarnings("unchecked")
+ AcceptsDelete<RestResource> ac = (AcceptsDelete<RestResource>) c;
+ viewData = new ViewData(viewData.pluginName, ac.delete(rsrc, id));
+ status = SC_NO_CONTENT;
} else {
throw e;
}
@@ -285,7 +302,7 @@ public class RestApiServlet extends HttpServlet {
checkRequiresCapability(viewData);
}
- if (notModified(req, rsrc)) {
+ if (notModified(req, rsrc, viewData.view)) {
res.sendError(SC_NOT_MODIFIED);
return;
}
@@ -331,28 +348,38 @@ public class RestApiServlet extends HttpServlet {
replyJson(req, res, config, result);
}
}
- } catch (AuthException e) {
- replyError(req, res, status = SC_FORBIDDEN, e.getMessage(), e.caching());
+ } catch (MalformedJsonException e) {
+ replyError(req, res, status = SC_BAD_REQUEST,
+ "Invalid " + JSON_TYPE + " in request", e);
+ } catch (JsonParseException e) {
+ replyError(req, res, status = SC_BAD_REQUEST,
+ "Invalid " + JSON_TYPE + " in request", e);
} catch (BadRequestException e) {
- replyError(req, res, status = SC_BAD_REQUEST, e.getMessage(), e.caching());
+ replyError(req, res, status = SC_BAD_REQUEST, messageOr(e, "Bad Request"),
+ e.caching(), e);
+ } catch (AuthException e) {
+ replyError(req, res, status = SC_FORBIDDEN, messageOr(e, "Forbidden"),
+ e.caching(), e);
+ } catch (AmbiguousViewException e) {
+ replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e);
+ } catch (ResourceNotFoundException e) {
+ replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Not Found"),
+ e.caching(), e);
} catch (MethodNotAllowedException e) {
- replyError(req, res, status = SC_METHOD_NOT_ALLOWED, "Method not allowed", e.caching());
+ replyError(req, res, status = SC_METHOD_NOT_ALLOWED,
+ messageOr(e, "Method Not Allowed"), e.caching(), e);
} catch (ResourceConflictException e) {
- replyError(req, res, status = SC_CONFLICT, e.getMessage(), e.caching());
+ replyError(req, res, status = SC_CONFLICT, messageOr(e, "Conflict"),
+ e.caching(), e);
} catch (PreconditionFailedException e) {
replyError(req, res, status = SC_PRECONDITION_FAILED,
- Objects.firstNonNull(e.getMessage(), "Precondition failed"), e.caching());
- } catch (ResourceNotFoundException e) {
- replyError(req, res, status = SC_NOT_FOUND, "Not found", e.caching());
+ messageOr(e, "Precondition Failed"), e.caching(), e);
} catch (UnprocessableEntityException e) {
- replyError(req, res, status = 422,
- Objects.firstNonNull(e.getMessage(), "Unprocessable Entity"), e.caching());
- } catch (AmbiguousViewException e) {
- replyError(req, res, status = SC_NOT_FOUND, e.getMessage());
- } catch (MalformedJsonException e) {
- replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
- } catch (JsonParseException e) {
- replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
+ replyError(req, res, status = 422, messageOr(e, "Unprocessable Entity"),
+ e.caching(), e);
+ } catch (NotImplementedException e) {
+ replyError(req, res, status = SC_NOT_IMPLEMENTED,
+ messageOr(e, "Not Implemented"), e);
} catch (Exception e) {
status = SC_INTERNAL_SERVER_ERROR;
handleException(e, req, res);
@@ -364,11 +391,27 @@ public class RestApiServlet extends HttpServlet {
}
}
- private static boolean notModified(HttpServletRequest req, RestResource rsrc) {
+ private static String messageOr(Throwable t, String defaultMessage) {
+ if (!Strings.isNullOrEmpty(t.getMessage())) {
+ return t.getMessage();
+ }
+ return defaultMessage;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static boolean notModified(HttpServletRequest req, RestResource rsrc,
+ RestView<RestResource> view) {
if (!isGetOrHead(req)) {
return false;
}
+ if (view instanceof ETagView) {
+ String have = req.getHeader(HttpHeaders.IF_NONE_MATCH);
+ if (have != null) {
+ return have.equals(((ETagView) view).getETag(rsrc));
+ }
+ }
+
if (rsrc instanceof RestResource.HasETag) {
String have = req.getHeader(HttpHeaders.IF_NONE_MATCH);
if (have != null) {
@@ -426,7 +469,7 @@ public class RestApiServlet extends HttpServlet {
}
}
- private void checkPreconditions(HttpServletRequest req, RestResource rsrc)
+ private void checkPreconditions(HttpServletRequest req)
throws PreconditionFailedException {
if ("*".equals(req.getHeader("If-None-Match"))) {
throw new PreconditionFailedException("Resource already exists");
@@ -692,6 +735,7 @@ public class RestApiServlet extends HttpServlet {
}
}
+ @SuppressWarnings("resource")
static void replyBinaryResult(
@Nullable HttpServletRequest req,
HttpServletResponse res,
@@ -704,7 +748,11 @@ public class RestApiServlet extends HttpServlet {
"attachment; filename=\"" + bin.getAttachmentName() + "\"");
}
if (bin.isBase64()) {
- bin = stackBase64(res, bin);
+ if (req != null && JSON_TYPE.equals(req.getHeader(HttpHeaders.ACCEPT))) {
+ bin = stackJsonString(res, bin);
+ } else {
+ bin = stackBase64(res, bin);
+ }
}
if (bin.canGzip() && acceptsGzip(req)) {
bin = stackGzip(res, bin);
@@ -731,6 +779,24 @@ public class RestApiServlet extends HttpServlet {
}
}
+ private static BinaryResult stackJsonString(HttpServletResponse res,
+ final BinaryResult src) throws IOException {
+ TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE);
+ buf.write(JSON_MAGIC);
+ try(Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
+ JsonWriter json = new JsonWriter(w)) {
+ json.setLenient(true);
+ json.setHtmlSafe(true);
+ json.value(src.asString());
+ w.write('\n');
+ }
+ res.setHeader("X-FYI-Content-Encoding", "json");
+ res.setHeader("X-FYI-Content-Type", src.getContentType());
+ return asBinaryResult(buf)
+ .setContentType(JSON_TYPE)
+ .setCharacterEncoding(UTF_8.name());
+ }
+
private static BinaryResult stackBase64(HttpServletResponse res,
final BinaryResult src) throws IOException {
BinaryResult b64;
@@ -865,7 +931,7 @@ public class RestApiServlet extends HttpServlet {
}
private static List<IdString> splitPath(HttpServletRequest req) {
- String path = req.getPathInfo();
+ String path = RequestUtil.getEncodedPathInfo(req);
if (Strings.isNullOrEmpty(path)) {
return Collections.emptyList();
}
@@ -921,20 +987,23 @@ public class RestApiServlet extends HttpServlet {
if (!res.isCommitted()) {
res.reset();
- replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error");
+ replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
}
}
- public static void replyError(HttpServletRequest req,
- HttpServletResponse res, int statusCode, String msg) throws IOException {
- replyError(req, res, statusCode, msg, CacheControl.NONE);
+ public static void replyError(HttpServletRequest req, HttpServletResponse res,
+ int statusCode, String msg, @Nullable Throwable err) throws IOException {
+ replyError(req, res, statusCode, msg, CacheControl.NONE, err);
}
public static void replyError(HttpServletRequest req,
HttpServletResponse res, int statusCode, String msg,
- CacheControl c) throws IOException {
- res.setStatus(statusCode);
+ CacheControl c, @Nullable Throwable err) throws IOException {
+ if (err != null) {
+ RequestUtil.setErrorTraceAttribute(req, err);
+ }
configureCaching(req, res, null, c);
+ res.setStatus(statusCode);
replyText(req, res, msg);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java
index c0c453508e..4696e8d3cf 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java
@@ -28,6 +28,7 @@ class AuditedHttpServletResponse
super(response);
}
+ @SuppressWarnings("all") // @Override for servlet API 3.0+ only.
public int getStatus() {
return status;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
index 32b4958575..01f2df31df 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.audit.RpcAuditEvent;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.audit.Audit;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.common.errors.NotSignedInException;
@@ -25,7 +26,6 @@ import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gson.GsonBuilder;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.server.ActiveCall;
@@ -131,7 +131,7 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
if (method == null) {
return;
}
- Audit note = (Audit) method.getAnnotation(Audit.class);
+ Audit note = method.getAnnotation(Audit.class);
if (note != null) {
final String sid = call.getWebSession().getSessionId();
final CurrentUser username = call.getWebSession().getCurrentUser();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/Handler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/Handler.java
index 911d1cc543..19a02a5be4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/Handler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/Handler.java
@@ -103,5 +103,6 @@ public abstract class Handler<T> implements Callable<T> {
* @throws Exception the operation failed. The caller will log the exception
* and the stack trace, if it is worth logging on the server side.
*/
+ @Override
public abstract T call() throws Exception;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index dcd181cf22..69db233156 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -17,173 +17,42 @@ package com.google.gerrit.httpd.rpc;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.ReviewerInfo;
import com.google.gerrit.common.data.SuggestService;
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountControl;
-import com.google.gerrit.server.account.AccountVisibility;
import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupMembers;
-import com.google.gerrit.server.change.PostReviewers;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.lib.Config;
-
-import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
class SuggestServiceImpl extends BaseServiceImplementation implements
SuggestService {
- private static final String MAX_SUFFIX = "\u9fa5";
-
- private final Provider<ReviewDb> reviewDbProvider;
- private final AccountCache accountCache;
- private final GroupMembers.Factory groupMembersFactory;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
- private final AccountControl.Factory accountControlFactory;
- private final ChangeControl.Factory changeControlFactory;
private final ProjectControl.Factory projectControlFactory;
- private final Config cfg;
private final GroupBackend groupBackend;
- private final boolean suggestAccounts;
@Inject
SuggestServiceImpl(final Provider<ReviewDb> schema,
- final AccountCache accountCache,
- final GroupMembers.Factory groupMembersFactory,
final Provider<CurrentUser> currentUser,
- final IdentifiedUser.GenericFactory identifiedUserFactory,
- final AccountControl.Factory accountControlFactory,
- final ChangeControl.Factory changeControlFactory,
final ProjectControl.Factory projectControlFactory,
- @GerritServerConfig final Config cfg, final GroupBackend groupBackend) {
+ final GroupBackend groupBackend) {
super(schema, currentUser);
- this.reviewDbProvider = schema;
- this.accountCache = accountCache;
- this.groupMembersFactory = groupMembersFactory;
- this.identifiedUserFactory = identifiedUserFactory;
- this.accountControlFactory = accountControlFactory;
- this.changeControlFactory = changeControlFactory;
this.projectControlFactory = projectControlFactory;
- this.cfg = cfg;
this.groupBackend = groupBackend;
-
- if ("OFF".equals(cfg.getString("suggest", null, "accounts"))) {
- this.suggestAccounts = false;
- } else {
- boolean suggestAccounts;
- try {
- AccountVisibility av =
- cfg.getEnum("suggest", null, "accounts", AccountVisibility.ALL);
- suggestAccounts = (av != AccountVisibility.NONE);
- } catch (IllegalArgumentException err) {
- suggestAccounts = cfg.getBoolean("suggest", null, "accounts", true);
- }
- this.suggestAccounts = suggestAccounts;
- }
- }
-
- private interface VisibilityControl {
- boolean isVisible(Account account) throws OrmException;
- }
-
- public void suggestAccount(final String query, final Boolean active,
- final int limit, final AsyncCallback<List<AccountInfo>> callback) {
- run(callback, new Action<List<AccountInfo>>() {
- public List<AccountInfo> run(final ReviewDb db) throws OrmException {
- return suggestAccount(db, query, active, limit, new VisibilityControl() {
- @Override
- public boolean isVisible(Account account) throws OrmException {
- return accountControlFactory.get().canSee(account);
- }
- });
- }
- });
- }
-
- private List<AccountInfo> suggestAccount(final ReviewDb db,
- final String query, final Boolean active, final int limit,
- VisibilityControl visibilityControl)
- throws OrmException {
- if (!suggestAccounts) {
- return Collections.emptyList();
- }
-
- final String a = query;
- final String b = a + MAX_SUFFIX;
- final int max = 10;
- final int n = limit <= 0 ? max : Math.min(limit, max);
-
- LinkedHashMap<Account.Id, AccountInfo> r = new LinkedHashMap<>();
- for (final Account p : db.accounts().suggestByFullName(a, b, n)) {
- addSuggestion(r, p, new AccountInfo(p), active, visibilityControl);
- }
- if (r.size() < n) {
- for (final Account p : db.accounts().suggestByPreferredEmail(a, b,
- n - r.size())) {
- addSuggestion(r, p, new AccountInfo(p), active, visibilityControl);
- }
- }
- if (r.size() < n) {
- for (final AccountExternalId e : db.accountExternalIds()
- .suggestByEmailAddress(a, b, n - r.size())) {
- if (!r.containsKey(e.getAccountId())) {
- final Account p = accountCache.get(e.getAccountId()).getAccount();
- final AccountInfo info = new AccountInfo(p);
- info.setPreferredEmail(e.getEmailAddress());
- addSuggestion(r, p, info, active, visibilityControl);
- }
- }
- }
- return new ArrayList<>(r.values());
- }
-
- private void addSuggestion(Map<Account.Id, AccountInfo> map, Account account,
- AccountInfo info, Boolean active, VisibilityControl visibilityControl)
- throws OrmException {
- if (map.containsKey(account.getId())) {
- return;
- }
- if (active != null && active != account.isActive()) {
- return;
- }
- if (visibilityControl.isVisible(account)) {
- map.put(account.getId(), info);
- }
- }
-
- public void suggestAccountGroup(final String query, final int limit,
- final AsyncCallback<List<GroupReference>> callback) {
- suggestAccountGroupForProject(null, query, limit, callback);
}
+ @Override
public void suggestAccountGroupForProject(final Project.NameKey project,
final String query, final int limit,
final AsyncCallback<List<GroupReference>> callback) {
run(callback, new Action<List<GroupReference>>() {
+ @Override
public List<GroupReference> run(final ReviewDb db) {
ProjectControl projectControl = null;
if (project != null) {
@@ -204,102 +73,4 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
groupBackend.suggest(query, projectControl),
limit <= 0 ? 10 : Math.min(limit, 10)));
}
-
- @Override
- public void suggestReviewer(Project.NameKey project, String query, int limit,
- AsyncCallback<List<ReviewerInfo>> callback) {
- // The RPC is deprecated, but return an empty list for RPC API compatibility.
- callback.onSuccess(Collections.<ReviewerInfo>emptyList());
- }
-
- @Override
- public void suggestChangeReviewer(final Change.Id change,
- final String query, final int limit,
- final AsyncCallback<List<ReviewerInfo>> callback) {
- run(callback, new Action<List<ReviewerInfo>>() {
- public List<ReviewerInfo> run(final ReviewDb db)
- throws OrmException, Failure {
- final ChangeControl changeControl;
- try {
- changeControl = changeControlFactory.controlFor(change);
- } catch (NoSuchChangeException e) {
- return Collections.emptyList();
- }
-
- VisibilityControl visibilityControl;
- if (changeControl.getRefControl().isVisibleByRegisteredUsers()) {
- visibilityControl = new VisibilityControl() {
- @Override
- public boolean isVisible(Account account) throws OrmException {
- return true;
- }
- };
- } else {
- visibilityControl = new VisibilityControl() {
- @Override
- public boolean isVisible(Account account) throws OrmException {
- IdentifiedUser who =
- identifiedUserFactory.create(reviewDbProvider, account.getId());
- // we can't use changeControl directly as it won't suggest reviewers
- // to drafts
- return changeControl.forUser(who).isRefVisible();
- }
- };
- }
-
- final List<AccountInfo> suggestedAccounts =
- suggestAccount(db, query, Boolean.TRUE, limit, visibilityControl);
- final List<ReviewerInfo> reviewer =
- new ArrayList<>(suggestedAccounts.size());
- for (final AccountInfo a : suggestedAccounts) {
- reviewer.add(new ReviewerInfo(a));
- }
- final List<GroupReference> suggestedAccountGroups =
- suggestAccountGroup(changeControl.getProjectControl(), query, limit);
- for (final GroupReference g : suggestedAccountGroups) {
- if (suggestGroupAsReviewer(changeControl.getProject().getNameKey(), g)) {
- reviewer.add(new ReviewerInfo(g));
- }
- }
-
- Collections.sort(reviewer);
- if (reviewer.size() <= limit) {
- return reviewer;
- } else {
- return reviewer.subList(0, limit);
- }
- }
- });
- }
-
- private boolean suggestGroupAsReviewer(final Project.NameKey project,
- final GroupReference group) throws OrmException, Failure {
- if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
- return false;
- }
-
- try {
- final Set<Account> members = groupMembersFactory.create(getCurrentUser())
- .listAccounts(group.getUUID(), project);
-
- if (members.isEmpty()) {
- return false;
- }
-
- final int maxAllowed =
- cfg.getInt("addreviewer", "maxAllowed",
- PostReviewers.DEFAULT_MAX_REVIEWERS);
- if (maxAllowed > 0 && members.size() > maxAllowed) {
- return false;
- }
- } catch (NoSuchGroupException e) {
- return false;
- } catch (NoSuchProjectException e) {
- return false;
- } catch (IOException e) {
- throw new Failure(e);
- }
-
- return true;
- }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java
index 56a6a507b9..5b28a614da 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java
@@ -59,6 +59,7 @@ class SystemInfoServiceImpl implements SystemInfoService {
projectCache = pc;
}
+ @Override
public void contributorAgreements(
final AsyncCallback<List<ContributorAgreement>> callback) {
Collection<ContributorAgreement> agreements =
@@ -71,6 +72,7 @@ class SystemInfoServiceImpl implements SystemInfoService {
callback.onSuccess(cas);
}
+ @Override
public void daemonHostKeys(final AsyncCallback<List<SshHostKey>> callback) {
final ArrayList<SshHostKey> r = new ArrayList<>(hostKeys.size());
for (final HostKey hk : hostKeys) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index b6549ea878..b2a6afce4a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -15,10 +15,13 @@
package com.google.gerrit.httpd.rpc.account;
import com.google.common.base.Strings;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.AccountSecurity;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.errors.ContactInformationStoreException;
+import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
@@ -27,7 +30,6 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.ContactInformation;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -42,7 +44,6 @@ import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.contact.ContactStore;
import com.google.gerrit.server.mail.EmailTokenVerifier;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtorm.server.OrmException;
@@ -71,6 +72,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
private final ChangeHooks hooks;
private final GroupCache groupCache;
+ private final AuditService auditService;
@Inject
AccountSecurityImpl(final Provider<ReviewDb> schema,
@@ -82,7 +84,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
final ChangeUserName.CurrentUser changeUserNameFactory,
final DeleteExternalIds.Factory deleteExternalIdsFactory,
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
- final ChangeHooks hooks, final GroupCache groupCache) {
+ final ChangeHooks hooks, final GroupCache groupCache,
+ final AuditService auditService) {
super(schema, currentUser);
contactStore = cs;
realm = r;
@@ -92,6 +95,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
byEmailCache = abec;
accountCache = uac;
accountManager = am;
+ this.auditService = auditService;
useContactInfo = contactStore != null && contactStore.isEnabled();
@@ -106,25 +110,32 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
public void changeUserName(final String newName,
final AsyncCallback<VoidResult> callback) {
if (realm.allowsEdit(Account.FieldName.USER_NAME)) {
+ if (newName == null || !newName.matches(Account.USER_NAME_PATTERN)) {
+ callback.onFailure(new InvalidUserNameException());
+ }
Handler.wrap(changeUserNameFactory.create(newName)).to(callback);
} else {
- callback.onFailure(new PermissionDeniedException("Not allowed to change"
- + " username"));
+ callback.onFailure(
+ new PermissionDeniedException("Not allowed to change username"));
}
}
+ @Override
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
externalIdDetailFactory.create().to(callback);
}
+ @Override
public void deleteExternalIds(final Set<AccountExternalId.Key> keys,
final AsyncCallback<Set<AccountExternalId.Key>> callback) {
deleteExternalIdsFactory.create(keys).to(callback);
}
+ @Override
public void updateContact(final String name, final String emailAddr,
final ContactInformation info, final AsyncCallback<Account> callback) {
run(callback, new Action<Account>() {
+ @Override
public Account run(ReviewDb db) throws OrmException, Failure {
IdentifiedUser self = user.get();
final Account me = db.accounts().get(self.getAccountId());
@@ -133,7 +144,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
me.setFullName(Strings.emptyToNull(name));
}
if (!Strings.isNullOrEmpty(emailAddr)
- && !self.getEmailAddresses().contains(emailAddr)) {
+ && !self.hasEmailAddress(emailAddr)) {
throw new Failure(new PermissionDeniedException("Email address must be verified"));
}
me.setPreferredEmail(Strings.emptyToNull(emailAddr));
@@ -168,9 +179,11 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
return a != null && a.equals(b);
}
+ @Override
public void enterAgreement(final String agreementName,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
+ @Override
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
ContributorAgreement ca = projectCache.getAllProjects().getConfig()
.getContributorAgreement(agreementName);
@@ -198,9 +211,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
AccountGroupMember m = db.accountGroupMembers().get(key);
if (m == null) {
m = new AccountGroupMember(key);
- db.accountGroupMembersAudit().insert(
- Collections.singleton(new AccountGroupMemberAudit(
- m, account.getId(), TimeUtil.nowTs())));
+ auditService.dispatchAddAccountsToGroup(account.getId(), Collections
+ .singleton(m));
db.accountGroupMembers().insert(Collections.singleton(m));
accountCache.evict(m.getAccountId());
}
@@ -210,6 +222,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void validateEmail(final String tokenString,
final AsyncCallback<VoidResult> callback) {
try {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index bdb0028d2a..e1c9e3c494 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -51,7 +51,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
private final AccountCache accountCache;
private final ProjectControl.Factory projectControlFactory;
private final AgreementInfoFactory.Factory agreementInfoFactory;
- private final ChangeQueryBuilder.Factory queryBuilder;
+ private final ChangeQueryBuilder queryBuilder;
@Inject
AccountServiceImpl(final Provider<ReviewDb> schema,
@@ -59,7 +59,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
final AccountCache accountCache,
final ProjectControl.Factory projectControlFactory,
final AgreementInfoFactory.Factory agreementInfoFactory,
- final ChangeQueryBuilder.Factory queryBuilder) {
+ final ChangeQueryBuilder queryBuilder) {
super(schema, identifiedUser);
this.currentUser = identifiedUser;
this.accountCache = accountCache;
@@ -68,6 +68,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
this.queryBuilder = queryBuilder;
}
+ @Override
public void myAccount(final AsyncCallback<Account> callback) {
run(callback, new Action<Account>() {
@Override
@@ -77,9 +78,11 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void changePreferences(final AccountGeneralPreferences pref,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
+ @Override
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Account a = db.accounts().get(getAccountId());
if (a == null) {
@@ -97,6 +100,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
public void changeDiffPreferences(final AccountDiffPreference diffPref,
AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>(){
+ @Override
public VoidResult run(ReviewDb db) throws OrmException {
if (!diffPref.getAccountId().equals(getAccountId())) {
throw new IllegalArgumentException("diffPref.getAccountId() "
@@ -109,9 +113,11 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void myProjectWatch(
final AsyncCallback<List<AccountProjectWatchInfo>> callback) {
run(callback, new Action<List<AccountProjectWatchInfo>>() {
+ @Override
public List<AccountProjectWatchInfo> run(ReviewDb db) throws OrmException {
List<AccountProjectWatchInfo> r = new ArrayList<>();
@@ -127,6 +133,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
r.add(new AccountProjectWatchInfo(w, ctl.getProject()));
}
Collections.sort(r, new Comparator<AccountProjectWatchInfo>() {
+ @Override
public int compare(final AccountProjectWatchInfo a,
final AccountProjectWatchInfo b) {
return a.getProject().getName().compareTo(b.getProject().getName());
@@ -137,9 +144,11 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void addProjectWatch(final String projectName, final String filter,
final AsyncCallback<AccountProjectWatchInfo> callback) {
run(callback, new Action<AccountProjectWatchInfo>() {
+ @Override
public AccountProjectWatchInfo run(ReviewDb db) throws OrmException,
NoSuchProjectException, InvalidQueryException {
final Project.NameKey nameKey = new Project.NameKey(projectName);
@@ -147,7 +156,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
if (filter != null) {
try {
- queryBuilder.create(currentUser.get()).parse(filter);
+ queryBuilder.parse(filter);
} catch (QueryParseException badFilter) {
throw new InvalidQueryException(badFilter.getMessage(), filter);
}
@@ -167,6 +176,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void updateProjectWatch(final AccountProjectWatch watch,
final AsyncCallback<VoidResult> callback) {
if (!getAccountId().equals(watch.getAccountId())) {
@@ -175,6 +185,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
}
run(callback, new Action<VoidResult>() {
+ @Override
public VoidResult run(ReviewDb db) throws OrmException {
db.accountProjectWatches().update(Collections.singleton(watch));
return VoidResult.INSTANCE;
@@ -182,9 +193,11 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void deleteProjectWatches(final Set<AccountProjectWatch.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
+ @Override
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Account.Id me = getAccountId();
for (final AccountProjectWatch.Key keyId : keys) {
@@ -198,6 +211,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
public void myAgreements(final AsyncCallback<AgreementInfo> callback) {
agreementInfoFactory.create().to(callback);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
deleted file mode 100644
index da21c5161a..0000000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (C) 2010 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.rpc.change;
-
-import com.google.gerrit.server.query.change.QueryProcessor;
-import com.google.gerrit.server.query.change.QueryProcessor.OutputFormat;
-import com.google.gson.Gson;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-import java.io.IOException;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-@Singleton
-public class DeprecatedChangeQueryServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
- private final Provider<QueryProcessor> processor;
-
- @Inject
- DeprecatedChangeQueryServlet(Provider<QueryProcessor> processor) {
- this.processor = processor;
- }
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
- throws IOException {
- rsp.setContentType("text/json");
- rsp.setCharacterEncoding("UTF-8");
-
- QueryProcessor p = processor.get();
- OutputFormat format = OutputFormat.JSON;
- try {
- format = OutputFormat.valueOf(get(req, "format", format.toString()));
- } catch (IllegalArgumentException err) {
- error(rsp, "invalid format");
- return;
- }
-
- switch (format) {
- case JSON:
- rsp.setContentType("text/json");
- rsp.setCharacterEncoding("UTF-8");
- break;
-
- case TEXT:
- rsp.setContentType("text/plain");
- rsp.setCharacterEncoding("UTF-8");
- break;
-
- default:
- error(rsp, "invalid format");
- return;
- }
-
- p.setIncludeComments(get(req, "comments", false));
- p.setIncludeCurrentPatchSet(get(req, "current-patch-set", false));
- p.setIncludePatchSets(get(req, "patch-sets", false));
- p.setIncludeApprovals(get(req, "all-approvals", false));
- p.setIncludeFiles(get(req, "files", false));
- p.setOutput(rsp.getOutputStream(), format);
- p.query(get(req, "q", "status:open"));
- }
-
- private static void error(HttpServletResponse rsp, String message)
- throws IOException {
- ErrorMessage em = new ErrorMessage();
- em.message = message;
-
- ServletOutputStream out = rsp.getOutputStream();
- try {
- out.write(new Gson().toJson(em).getBytes("UTF-8"));
- out.write('\n');
- out.flush();
- } finally {
- out.close();
- }
- }
-
- private static String get(HttpServletRequest req, String name, String val) {
- String v = req.getParameter(name);
- if (v == null || v.isEmpty()) {
- return val;
- }
- return v;
- }
-
- private static boolean get(HttpServletRequest req, String name, boolean val) {
- String v = req.getParameter(name);
- if (v == null || v.isEmpty()) {
- return val;
- }
- return "true".equalsIgnoreCase(v);
- }
-
- public static class ErrorMessage {
- public final String type = "error";
- public String message;
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java
index 538275fb06..d0c042c1ca 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java
@@ -30,11 +30,13 @@ class ChangeDetailServiceImpl implements ChangeDetailService {
this.patchSetDetail = patchSetDetail;
}
+ @Override
public void patchSetDetail(PatchSet.Id id,
AsyncCallback<PatchSetDetail> callback) {
patchSetDetail2(null, id, null, callback);
}
+ @Override
public void patchSetDetail2(PatchSet.Id baseId, PatchSet.Id id,
AccountDiffPreference diffPrefs, AsyncCallback<PatchSetDetail> callback) {
patchSetDetail.create(baseId, id, diffPrefs).to(callback);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index 73cc83a92e..9fc95b1ba4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -15,18 +15,21 @@
package com.google.gerrit.httpd.rpc.changedetail;
import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.common.data.UiCommandDetail;
import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.AccountPatchReview;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -38,6 +41,8 @@ import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Revisions;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchList;
@@ -50,6 +55,7 @@ import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.util.Providers;
@@ -57,6 +63,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -77,10 +84,12 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
private final PatchSetInfoFactory infoFactory;
private final ReviewDb db;
private final PatchListCache patchListCache;
- private final ChangeControl.Factory changeControlFactory;
+ private final Provider<CurrentUser> userProvider;
+ private final ChangeControl.GenericFactory changeControlFactory;
private final ChangesCollection changes;
private final Revisions revisions;
private final PatchLineCommentsUtil plcUtil;
+ private final ChangeEditUtil editUtil;
private Project.NameKey projectKey;
private final PatchSet.Id psIdBase;
@@ -96,20 +105,24 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
@Inject
PatchSetDetailFactory(final PatchSetInfoFactory psif, final ReviewDb db,
final PatchListCache patchListCache,
- final ChangeControl.Factory changeControlFactory,
+ final Provider<CurrentUser> userProvider,
+ final ChangeControl.GenericFactory changeControlFactory,
final ChangesCollection changes,
final Revisions revisions,
final PatchLineCommentsUtil plcUtil,
+ ChangeEditUtil editUtil,
@Assisted("psIdBase") @Nullable final PatchSet.Id psIdBase,
@Assisted("psIdNew") final PatchSet.Id psIdNew,
@Assisted @Nullable final AccountDiffPreference diffPrefs) {
this.infoFactory = psif;
this.db = db;
this.patchListCache = patchListCache;
+ this.userProvider = userProvider;
this.changeControlFactory = changeControlFactory;
this.changes = changes;
this.revisions = revisions;
this.plcUtil = plcUtil;
+ this.editUtil = editUtil;
this.psIdBase = psIdBase;
this.psIdNew = psIdNew;
@@ -118,10 +131,21 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
@Override
public PatchSetDetail call() throws OrmException, NoSuchEntityException,
- PatchSetInfoNotAvailableException, NoSuchChangeException {
+ PatchSetInfoNotAvailableException, NoSuchChangeException, AuthException,
+ IOException {
+ Optional<ChangeEdit> edit = null;
if (control == null || patchSet == null) {
- control = changeControlFactory.validateFor(psIdNew.getParentKey());
- patchSet = db.patchSets().get(psIdNew);
+ control = changeControlFactory.validateFor(psIdNew.getParentKey(),
+ userProvider.get());
+ if (psIdNew.get() == 0) {
+ Change change = db.changes().get(psIdNew.getParentKey());
+ edit = editUtil.byChange(change);
+ if (edit.isPresent()) {
+ patchSet = edit.get().getBasePatchSet();
+ }
+ } else {
+ patchSet = db.patchSets().get(psIdNew);
+ }
if (patchSet == null) {
throw new NoSuchEntityException();
}
@@ -132,7 +156,11 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
try {
if (psIdBase != null) {
oldId = toObjectId(psIdBase);
- newId = toObjectId(psIdNew);
+ if (edit != null && edit.isPresent()) {
+ newId = edit.get().getEditCommit().toObjectId();
+ } else {
+ newId = toObjectId(psIdNew);
+ }
list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
} else { // OK, means use base to compare
@@ -149,10 +177,12 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
}
ChangeNotes notes = control.getNotes();
- for (PatchLineComment c : plcUtil.publishedByPatchSet(db, notes, psIdNew)) {
- final Patch p = byKey.get(c.getKey().getParentKey());
- if (p != null) {
- p.setCommentCount(p.getCommentCount() + 1);
+ if (edit == null) {
+ for (PatchLineComment c : plcUtil.publishedByPatchSet(db, notes, psIdNew)) {
+ final Patch p = byKey.get(c.getKey().getParentKey());
+ if (p != null) {
+ p.setCommentCount(p.getCommentCount() + 1);
+ }
}
}
@@ -160,17 +190,18 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
detail.setPatchSet(patchSet);
detail.setProject(projectKey);
- detail.setInfo(infoFactory.get(db, psIdNew));
+ detail.setInfo(infoFactory.get(db, patchSet.getId()));
detail.setPatches(patches);
final CurrentUser user = control.getCurrentUser();
- if (user.isIdentifiedUser()) {
+ if (user.isIdentifiedUser() && edit == null) {
// If we are signed in, compute the number of draft comments by the
// current user on each of these patch files. This way they can more
// quickly locate where they have pending drafts, and review them.
//
final Account.Id me = ((IdentifiedUser) user).getAccountId();
- for (final PatchLineComment c : db.patchComments().draftByPatchSetAuthor(psIdNew, me)) {
+ for (PatchLineComment c
+ : plcUtil.draftByPatchSetAuthor(db, psIdNew, me, notes)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setDraftCount(p.getDraftCount() + 1);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index 64d5838117..9257271446 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -20,7 +20,6 @@ import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-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.server.ReviewDb;
@@ -34,19 +33,20 @@ import com.google.inject.Provider;
class PatchDetailServiceImpl extends BaseServiceImplementation implements
PatchDetailService {
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
- private final ChangeControl.Factory changeControlFactory;
+ private final ChangeControl.GenericFactory changeControlFactory;
@Inject
PatchDetailServiceImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser,
final PatchScriptFactory.Factory patchScriptFactoryFactory,
- final ChangeControl.Factory changeControlFactory) {
+ final ChangeControl.GenericFactory changeControlFactory) {
super(schema, currentUser);
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
this.changeControlFactory = changeControlFactory;
}
+ @Override
public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa,
final PatchSet.Id psb, final AccountDiffPreference dp,
final AsyncCallback<PatchScript> callback) {
@@ -58,8 +58,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
new Handler<PatchScript>() {
@Override
public PatchScript call() throws Exception {
- Change.Id changeId = patchKey.getParentKey().getParentKey();
- ChangeControl control = changeControlFactory.validateFor(changeId);
+ ChangeControl control = changeControlFactory.validateFor(
+ patchKey.getParentKey().getParentKey(),
+ getCurrentUser());
return patchScriptFactoryFactory.create(
control, patchKey.getFileName(), psa, psb, dp).call();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index d18c16a35a..e877d1ee97 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -79,9 +79,9 @@ class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
}
@Override
- protected ProjectAccess updateProjectConfig(ProjectConfig config,
- MetaDataUpdate md, boolean parentProjectUpdate) throws IOException,
- NoSuchProjectException, ConfigInvalidException {
+ protected ProjectAccess updateProjectConfig(ProjectControl ctl,
+ ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
+ throws IOException, NoSuchProjectException, ConfigInvalidException {
RevCommit commit = config.commit(md);
hooks.doRefUpdatedHook(
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 52283b2fe8..829035b35b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -16,7 +16,7 @@ package com.google.gerrit.httpd.rpc.project;
import static com.google.gerrit.common.ProjectAccessUtil.mergeSections;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
@@ -139,7 +139,7 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> {
parentProjectUpdate = true;
try {
setParent.get().validateParentUpdate(projectControl,
- Objects.firstNonNull(parentProjectName, allProjects.get()).get(),
+ MoreObjects.firstNonNull(parentProjectName, allProjects.get()).get(),
checkIfOwner);
} catch (AuthException e) {
throw new UpdateParentFailedException(
@@ -163,15 +163,17 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> {
md.setMessage("Modify access rules\n");
}
- return updateProjectConfig(config, md, parentProjectUpdate);
+ return updateProjectConfig(projectControl, config, md,
+ parentProjectUpdate);
} finally {
md.close();
}
}
- protected abstract T updateProjectConfig(ProjectConfig config,
- MetaDataUpdate md, boolean parentProjectUpdate) throws IOException,
- NoSuchProjectException, ConfigInvalidException, OrmException;
+ protected abstract T updateProjectConfig(ProjectControl ctl,
+ ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
+ throws IOException, NoSuchProjectException, ConfigInvalidException,
+ OrmException;
private void replace(ProjectConfig config, Set<String> toDelete,
AccessSection section) throws NoSuchGroupException {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index 51aa2c0cce..9437bbe907 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -14,8 +14,8 @@
package com.google.gerrit.httpd.rpc.project;
-import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRule;
@@ -24,29 +24,22 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.PatchSetAncestor;
-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.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
-import com.google.gerrit.server.change.MergeabilityChecker;
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.mail.CreateChangeSender;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SetParent;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -54,18 +47,11 @@ import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
- private static final Logger log =
- LoggerFactory.getLogger(ReviewProjectAccess.class);
-
interface Factory {
ReviewProjectAccess create(
@Assisted("projectName") Project.NameKey projectName,
@@ -77,25 +63,21 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
private final ReviewDb db;
private final IdentifiedUser user;
- private final PatchSetInfoFactory patchSetInfoFactory;
private final Provider<PostReviewers> reviewersProvider;
- private final MergeabilityChecker mergeabilityChecker;
- private final ChangeHooks hooks;
- private final CreateChangeSender.Factory createChangeSenderFactory;
private final ProjectCache projectCache;
private final ChangesCollection changes;
+ private final ChangeInserter.Factory changeInserterFactory;
@Inject
ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory, ReviewDb db,
- IdentifiedUser user, PatchSetInfoFactory patchSetInfoFactory,
+ IdentifiedUser user,
Provider<PostReviewers> reviewersProvider,
- MergeabilityChecker mergeabilityChecker, ChangeHooks hooks,
- CreateChangeSender.Factory createChangeSenderFactory,
ProjectCache projectCache,
AllProjectsNameProvider allProjects,
ChangesCollection changes,
+ ChangeInserter.Factory changeInserterFactory,
Provider<SetParent> setParent,
@Assisted("projectName") Project.NameKey projectName,
@@ -108,23 +90,20 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
parentProjectName, message, false);
this.db = db;
this.user = user;
- this.patchSetInfoFactory = patchSetInfoFactory;
this.reviewersProvider = reviewersProvider;
- this.mergeabilityChecker = mergeabilityChecker;
- this.hooks = hooks;
- this.createChangeSenderFactory = createChangeSenderFactory;
this.projectCache = projectCache;
this.changes = changes;
+ this.changeInserterFactory = changeInserterFactory;
}
@Override
- protected Change.Id updateProjectConfig(ProjectConfig config,
- MetaDataUpdate md, boolean parentProjectUpdate) throws IOException,
- OrmException {
+ protected Change.Id updateProjectConfig(ProjectControl ctl,
+ ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
+ throws IOException, OrmException {
Change.Id changeId = new Change.Id(db.nextChangeId());
- PatchSet ps =
- new PatchSet(new PatchSet.Id(changeId, Change.INITIAL_PATCH_SET_ID));
- RevCommit commit = config.commitToNewRef(md, ps.getRefName());
+ RevCommit commit =
+ config.commitToNewRef(md, new PatchSet.Id(changeId,
+ Change.INITIAL_PATCH_SET_ID).toRefName());
if (commit.getId().equals(base)) {
return null;
}
@@ -137,35 +116,10 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
config.getProject().getNameKey(),
RefNames.REFS_CONFIG),
TimeUtil.nowTs());
+ ChangeInserter ins =
+ changeInserterFactory.create(ctl, change, commit);
+ ins.insert();
- ps.setCreatedOn(change.getCreatedOn());
- ps.setUploader(change.getOwner());
- ps.setRevision(new RevId(commit.name()));
-
- PatchSetInfo info = patchSetInfoFactory.get(commit, ps.getId());
- change.setCurrentPatchSet(info);
- ChangeUtil.updated(change);
-
- db.changes().beginTransaction(changeId);
- try {
- insertAncestors(ps.getId(), commit);
- db.patchSets().insert(Collections.singleton(ps));
- db.changes().insert(Collections.singleton(change));
- db.commit();
- } finally {
- db.rollback();
- }
- mergeabilityChecker.newCheck().addChange(change).reindex().run();
- hooks.doPatchsetCreatedHook(change, ps, db);
- try {
- CreateChangeSender cm =
- createChangeSenderFactory.create(change);
- cm.setFrom(change.getOwner());
- cm.setPatchSet(ps, info);
- cm.send();
- } catch (Exception err) {
- log.error("Cannot send email for new change " + change.getId(), err);
- }
ChangeResource rsrc;
try {
rsrc = changes.parse(changeId);
@@ -179,20 +133,6 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
return changeId;
}
- private void insertAncestors(PatchSet.Id id, RevCommit src)
- throws OrmException {
- final int cnt = src.getParentCount();
- List<PatchSetAncestor> toInsert = new ArrayList<>(cnt);
- for (int p = 0; p < cnt; p++) {
- PatchSetAncestor a;
-
- a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
- a.setAncestorRevision(new RevId(src.getParent(p).name()));
- toInsert.add(a);
- }
- db.patchSetAncestors().insert(toInsert);
- }
-
private void addProjectOwnersAsReviewers(ChangeResource rsrc) {
final String projectOwners =
groupBackend.get(SystemGroupBackend.PROJECT_OWNERS).getName();
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
index 26cdd8aee0..57b089a094 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
@@ -14,11 +14,11 @@
package com.google.gerrit.httpd;
-import org.junit.Test;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
public class GitWebConfigTest {
private static final String VALID_CHARACTERS = "*()";
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
index 8533a9c303..af90585381 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.restapi;
+import static org.junit.Assert.assertEquals;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -22,7 +24,6 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
public class ParameterParserTest {
@Test
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index 474b284843..4ee9676ac9 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -294,8 +294,8 @@ public final class GerritLauncher {
return name;
}
- private volatile static File myArchive;
- private volatile static File myHome;
+ private static volatile File myArchive;
+ private static volatile File myHome;
/**
* Locate the JAR/WAR file we were launched from.
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java
index fd14cd9c06..e0c13ae03a 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java
@@ -82,7 +82,7 @@ class AutoCommitWriter extends IndexWriter {
}
@Override
- public void deleteDocuments(Term term) throws IOException {
+ public void deleteDocuments(Term... term) throws IOException {
super.deleteDocuments(term);
autoFlush();
}
@@ -98,18 +98,6 @@ class AutoCommitWriter extends IndexWriter {
}
@Override
- public void deleteDocuments(Term... terms) throws IOException {
- super.deleteDocuments(terms);
- autoFlush();
- }
-
- @Override
- public void deleteDocuments(Query query) throws IOException {
- super.deleteDocuments(query);
- autoFlush();
- }
-
- @Override
public void deleteDocuments(Query... queries) throws IOException {
super.deleteDocuments(queries);
autoFlush();
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/CustomMappingAnalyzer.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/CustomMappingAnalyzer.java
new file mode 100644
index 0000000000..3d7faeb443
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/CustomMappingAnalyzer.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2014 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.lucene;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.AnalyzerWrapper;
+import org.apache.lucene.analysis.charfilter.MappingCharFilter;
+import org.apache.lucene.analysis.charfilter.NormalizeCharMap;
+
+import java.io.Reader;
+import java.util.Map;
+
+/**
+ * This analyzer can be used to provide custom char mappings.
+ *
+ * <p>Example usage:
+ *
+ * <pre class="prettyprint">
+ * {@code
+ * Map<String,String> customMapping = new HashMap<>();
+ * customMapping.put("_", " ");
+ * customMapping.put(".", " ");
+ *
+ * CustomMappingAnalyzer analyzer =
+ * new CustomMappingAnalyzer(new StandardAnalyzer(version), customMapping);
+ * }
+ * </pre>
+ */
+public class CustomMappingAnalyzer extends AnalyzerWrapper {
+ private Analyzer delegate;
+ private Map<String, String> customMappings;
+
+ public CustomMappingAnalyzer(Analyzer delegate,
+ Map<String, String> customMappings) {
+ super(delegate.getReuseStrategy());
+ this.delegate = delegate;
+ this.customMappings = customMappings;
+ }
+
+ @Override
+ protected Analyzer getWrappedAnalyzer(String fieldName) {
+ return delegate;
+ }
+
+ @Override
+ protected Reader wrapReader(String fieldName, Reader reader) {
+ NormalizeCharMap.Builder builder = new NormalizeCharMap.Builder();
+ for (Map.Entry<String, String> e : customMappings.entrySet()) {
+ builder.add(e.getKey(), e.getValue());
+ }
+ return new MappingCharFilter(builder.build(), reader);
+ }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 3e3a7fadcc..e8825c56cf 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -15,6 +15,7 @@
package com.google.gerrit.lucene;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
import static com.google.gerrit.server.index.IndexRewriteImpl.CLOSED_STATUSES;
import static com.google.gerrit.server.index.IndexRewriteImpl.OPEN_STATUSES;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -50,15 +51,12 @@ import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.query.change.SortKeyPredicate;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
-import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.document.Document;
@@ -121,8 +119,12 @@ public class LuceneChangeIndex implements ChangeIndex {
private static final String CHANGE_FIELD = ChangeField.CHANGE.getName();
private static final String DELETED_FIELD = ChangeField.DELETED.getName();
private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
+ private static final String MERGEABLE_FIELD = ChangeField.MERGEABLE.getName();
private static final ImmutableSet<String> FIELDS = ImmutableSet.of(
- ADDED_FIELD, APPROVAL_FIELD, CHANGE_FIELD, DELETED_FIELD, ID_FIELD);
+ ADDED_FIELD, APPROVAL_FIELD, CHANGE_FIELD, DELETED_FIELD, ID_FIELD,
+ MERGEABLE_FIELD);
+ private static final Map<String, String> CUSTOM_CHAR_MAPPING = ImmutableMap.of(
+ "_", " ", ".", " ");
private static final Map<Schema<ChangeData>, Version> LUCENE_VERSIONS;
static {
@@ -136,6 +138,14 @@ public class LuceneChangeIndex implements ChangeIndex {
Version lucene46 = Version.LUCENE_46;
@SuppressWarnings("deprecation")
Version lucene47 = Version.LUCENE_47;
+ @SuppressWarnings("deprecation")
+ Version lucene48 = Version.LUCENE_48;
+ @SuppressWarnings("deprecation")
+ Version lucene410 = Version.LUCENE_4_10_0;
+ // We are using 4.10.2 but there is no difference in the index
+ // format since 4.10.1, so we reuse the version here.
+ @SuppressWarnings("deprecation")
+ Version lucene4101 = Version.LUCENE_4_10_1;
for (Map.Entry<Integer, Schema<ChangeData>> e
: ChangeSchemas.ALL.entrySet()) {
if (e.getKey() <= 3) {
@@ -146,8 +156,12 @@ public class LuceneChangeIndex implements ChangeIndex {
versions.put(e.getValue(), lucene46);
} else if (e.getKey() <= 10) {
versions.put(e.getValue(), lucene47);
+ } else if (e.getKey() <= 11) {
+ versions.put(e.getValue(), lucene48);
+ } else if (e.getKey() <= 13) {
+ versions.put(e.getValue(), lucene410);
} else {
- versions.put(e.getValue(), Version.LUCENE_48);
+ versions.put(e.getValue(), lucene4101);
}
}
LUCENE_VERSIONS = versions.build();
@@ -174,8 +188,10 @@ public class LuceneChangeIndex implements ChangeIndex {
private long commitWithinMs;
private GerritIndexWriterConfig(Version version, Config cfg, String name) {
- luceneConfig = new IndexWriterConfig(version,
- new StandardAnalyzer(version, CharArraySet.EMPTY_SET));
+ CustomMappingAnalyzer analyzer =
+ new CustomMappingAnalyzer(new StandardAnalyzer(
+ CharArraySet.EMPTY_SET), CUSTOM_CHAR_MAPPING);
+ luceneConfig = new IndexWriterConfig(version, analyzer);
luceneConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
double m = 1 << 20;
luceneConfig.setRAMBufferSizeMB(cfg.getLong(
@@ -217,7 +233,7 @@ public class LuceneChangeIndex implements ChangeIndex {
LuceneChangeIndex(
@GerritServerConfig Config cfg,
SitePaths sitePaths,
- @IndexExecutor ListeningExecutorService executor,
+ @IndexExecutor(INTERACTIVE) ListeningExecutorService executor,
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
FillArgs fillArgs,
@@ -238,10 +254,10 @@ public class LuceneChangeIndex implements ChangeIndex {
Version luceneVersion = checkNotNull(
LUCENE_VERSIONS.get(schema),
"unknown Lucene version for index schema: %s", schema);
-
- Analyzer analyzer =
- new StandardAnalyzer(luceneVersion, CharArraySet.EMPTY_SET);
- queryBuilder = new QueryBuilder(schema, analyzer);
+ CustomMappingAnalyzer analyzer =
+ new CustomMappingAnalyzer(new StandardAnalyzer(CharArraySet.EMPTY_SET),
+ CUSTOM_CHAR_MAPPING);
+ queryBuilder = new QueryBuilder(analyzer);
BooleanQuery.setMaxClauseCount(cfg.getInt("index", "defaultMaxClauseCount",
BooleanQuery.getMaxClauseCount()));
@@ -285,26 +301,6 @@ public class LuceneChangeIndex implements ChangeIndex {
@SuppressWarnings("unchecked")
@Override
- public void insert(ChangeData cd) throws IOException {
- Term id = QueryBuilder.idTerm(cd);
- Document doc = toDocument(cd);
- try {
- if (cd.change().getStatus().isOpen()) {
- Futures.allAsList(
- closedIndex.delete(id),
- openIndex.insert(doc)).get();
- } else {
- Futures.allAsList(
- openIndex.delete(id),
- closedIndex.insert(doc)).get();
- }
- } catch (OrmException | ExecutionException | InterruptedException e) {
- throw new IOException(e);
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
public void replace(ChangeData cd) throws IOException {
Term id = QueryBuilder.idTerm(cd);
Document doc = toDocument(cd);
@@ -325,20 +321,7 @@ public class LuceneChangeIndex implements ChangeIndex {
@SuppressWarnings("unchecked")
@Override
- public void delete(ChangeData cd) throws IOException {
- Term id = QueryBuilder.idTerm(cd);
- try {
- Futures.allAsList(
- openIndex.delete(id),
- closedIndex.delete(id)).get();
- } catch (ExecutionException | InterruptedException e) {
- throw new IOException(e);
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void delete(int id) throws IOException {
+ public void delete(Change.Id id) throws IOException {
Term idTerm = QueryBuilder.idTerm(id);
try {
Futures.allAsList(
@@ -367,7 +350,7 @@ public class LuceneChangeIndex implements ChangeIndex {
indexes.add(closedIndex);
}
return new QuerySource(indexes, queryBuilder.toQuery(p), start, limit,
- getSort(schema, p));
+ getSort());
}
@Override
@@ -375,22 +358,12 @@ public class LuceneChangeIndex implements ChangeIndex {
setReady(sitePaths, schema.getVersion(), ready);
}
- @SuppressWarnings("deprecation")
- private static Sort getSort(Schema<ChangeData> schema,
- Predicate<ChangeData> p) {
- // Standard order is descending by sort key, unless reversed due to a
- // sortkey_before predicate.
- if (SortKeyPredicate.hasSortKeyField(schema)) {
- boolean reverse = ChangeQueryBuilder.hasNonTrivialSortKeyAfter(schema, p);
- return new Sort(new SortField(
- ChangeField.SORTKEY.getName(), SortField.Type.LONG, !reverse));
- } else {
- return new Sort(
- new SortField(
- ChangeField.UPDATED.getName(), SortField.Type.LONG, true),
- new SortField(
- ChangeField.LEGACY_ID.getName(), SortField.Type.INT, true));
- }
+ private static Sort getSort() {
+ return new Sort(
+ new SortField(
+ ChangeField.UPDATED.getName(), SortField.Type.LONG, true),
+ new SortField(
+ ChangeField.LEGACY_ID.getName(), SortField.Type.INT, true));
}
private class QuerySource implements ChangeDataSource {
@@ -510,25 +483,28 @@ public class LuceneChangeIndex implements ChangeIndex {
deleted.numericValue().intValue());
}
+ // Mergeable.
+ String mergeable = doc.get(MERGEABLE_FIELD);
+ if ("1".equals(mergeable)) {
+ cd.setMergeable(true);
+ } else if ("0".equals(mergeable)) {
+ cd.setMergeable(false);
+ }
+
return cd;
}
- private Document toDocument(ChangeData cd) throws IOException {
- try {
- Document result = new Document();
- for (Values<ChangeData> vs : schema.buildFields(cd, fillArgs)) {
- if (vs.getValues() != null) {
- add(result, vs);
- }
+ private Document toDocument(ChangeData cd) {
+ Document result = new Document();
+ for (Values<ChangeData> vs : schema.buildFields(cd, fillArgs)) {
+ if (vs.getValues() != null) {
+ add(result, vs);
}
- return result;
- } catch (OrmException e) {
- throw new IOException(e);
}
+ return result;
}
- private void add(Document doc, Values<ChangeData> values)
- throws OrmException {
+ private void add(Document doc, Values<ChangeData> values) {
String name = values.getField().getName();
FieldType<?> type = values.getField().getType();
Store store = store(values.getField());
@@ -542,17 +518,8 @@ public class LuceneChangeIndex implements ChangeIndex {
doc.add(new LongField(name, (Long) value, store));
}
} else if (type == FieldType.TIMESTAMP) {
- @SuppressWarnings("deprecation")
- boolean legacy = values.getField() == ChangeField.LEGACY_UPDATED;
- if (legacy) {
- for (Object value : values.getValues()) {
- int t = queryBuilder.toIndexTimeInMinutes((Timestamp) value);
- doc.add(new IntField(name, (int) t, store));
- }
- } else {
- for (Object value : values.getValues()) {
- doc.add(new LongField(name, ((Timestamp) value).getTime(), store));
- }
+ for (Object value : values.getValues()) {
+ doc.add(new LongField(name, ((Timestamp) value).getTime(), store));
}
} else if (type == FieldType.EXACT
|| type == FieldType.PREFIX) {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 583e54fca1..672a7c9d6e 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -16,9 +16,9 @@ package com.google.gerrit.lucene;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.ChangeSchemas;
import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.query.change.ChangeData;
@@ -44,6 +44,7 @@ public class LuceneIndexModule extends LifecycleModule {
@Override
protected void configure() {
+ bind(IndexConfig.class).toInstance(IndexConfig.createDefault());
factory(LuceneChangeIndex.Factory.class);
install(new IndexModule(threads));
if (singleVersion == null && base == null) {
@@ -69,8 +70,7 @@ public class LuceneIndexModule extends LifecycleModule {
@Provides
@Singleton
- LuceneChangeIndex getIndex(LuceneChangeIndex.Factory factory,
- SitePaths sitePaths) {
+ LuceneChangeIndex getIndex(LuceneChangeIndex.Factory factory) {
Schema<ChangeData> schema = singleVersion != null
? ChangeSchemas.get(singleVersion)
: ChangeSchemas.getLatest();
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
index 41c83ebe7c..edded44f40 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
@@ -17,9 +17,9 @@ package com.google.gerrit.lucene;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists;
-import com.google.gerrit.server.index.ChangeBatchIndexer;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.SiteIndexer;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -39,14 +39,14 @@ public class OnlineReindexer {
}
private final IndexCollection indexes;
- private final ChangeBatchIndexer batchIndexer;
+ private final SiteIndexer batchIndexer;
private final ProjectCache projectCache;
private final int version;
@Inject
OnlineReindexer(
IndexCollection indexes,
- ChangeBatchIndexer batchIndexer,
+ SiteIndexer batchIndexer,
ProjectCache projectCache,
@Assisted int version) {
this.indexes = indexes;
@@ -76,8 +76,8 @@ public class OnlineReindexer {
"not an active write schema version: %s", version);
log.info("Starting online reindex from schema version {} to {}",
version(indexes.getSearchIndex()), version(index));
- ChangeBatchIndexer.Result result = batchIndexer.indexAll(
- index, projectCache.all(), -1, -1, null, null);
+ SiteIndexer.Result result =
+ batchIndexer.indexAll(index, projectCache.all());
if (!result.success()) {
log.error("Online reindex of schema version {} failed. Successfully"
+ " indexed {} changes, failed to index {} changes",
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index 2365929531..28af0570a1 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -19,12 +19,12 @@ import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.index.IntegerRangePredicate;
import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.TimestampRangePredicate;
import com.google.gerrit.server.query.AndPredicate;
import com.google.gerrit.server.query.NotPredicate;
@@ -32,7 +32,6 @@ import com.google.gerrit.server.query.OrPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.SortKeyPredicate;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
@@ -43,7 +42,7 @@ import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.NumericUtils;
import java.util.Date;
@@ -56,15 +55,13 @@ public class QueryBuilder {
return intTerm(ID_FIELD, cd.getId().get());
}
- public static Term idTerm(int id) {
- return intTerm(ID_FIELD, id);
+ public static Term idTerm(Change.Id id) {
+ return intTerm(ID_FIELD, id.get());
}
- private final Schema<ChangeData> schema;
private final org.apache.lucene.util.QueryBuilder queryBuilder;
- public QueryBuilder(Schema<ChangeData> schema, Analyzer analyzer) {
- this.schema = schema;
+ public QueryBuilder(Analyzer analyzer) {
queryBuilder = new org.apache.lucene.util.QueryBuilder(analyzer);
}
@@ -150,17 +147,15 @@ public class QueryBuilder {
return prefixQuery(p);
} else if (p.getType() == FieldType.FULL_TEXT) {
return fullTextQuery(p);
- } else if (p instanceof SortKeyPredicate) {
- return sortKeyQuery((SortKeyPredicate) p);
} else {
throw badFieldType(p.getType());
}
}
private static Term intTerm(String name, int value) {
- BytesRef bytes = new BytesRef(NumericUtils.BUF_SIZE_INT);
- NumericUtils.intToPrefixCodedBytes(value, 0, bytes);
- return new Term(name, bytes);
+ BytesRefBuilder builder = new BytesRefBuilder();
+ NumericUtils.intToPrefixCodedBytes(value, 0, builder);
+ return new Term(name, builder.get());
}
private Query intQuery(IndexPredicate<ChangeData> p)
@@ -198,56 +193,28 @@ public class QueryBuilder {
throw new QueryParseException("not an integer range: " + p);
}
- private Query sortKeyQuery(SortKeyPredicate p) {
- long min = p.getMinValue(schema);
- long max = p.getMaxValue(schema);
- return NumericRangeQuery.newLongRange(
- p.getField().getName(),
- min != Long.MIN_VALUE ? min : null,
- max != Long.MAX_VALUE ? max : null,
- false, false);
- }
-
- @SuppressWarnings("deprecation")
private Query timestampQuery(IndexPredicate<ChangeData> p)
throws QueryParseException {
if (p instanceof TimestampRangePredicate) {
TimestampRangePredicate<ChangeData> r =
(TimestampRangePredicate<ChangeData>) p;
- if (r.getField() == ChangeField.LEGACY_UPDATED) {
- return NumericRangeQuery.newIntRange(
- r.getField().getName(),
- toIndexTimeInMinutes(r.getMinTimestamp()),
- toIndexTimeInMinutes(r.getMaxTimestamp()),
- true, true);
- } else {
- return NumericRangeQuery.newLongRange(
- r.getField().getName(),
- r.getMinTimestamp().getTime(),
- r.getMaxTimestamp().getTime(),
- true, true);
- }
+ return NumericRangeQuery.newLongRange(
+ r.getField().getName(),
+ r.getMinTimestamp().getTime(),
+ r.getMaxTimestamp().getTime(),
+ true, true);
}
throw new QueryParseException("not a timestamp: " + p);
}
- @SuppressWarnings("deprecation")
private Query notTimestamp(TimestampRangePredicate<ChangeData> r)
throws QueryParseException {
if (r.getMinTimestamp().getTime() == 0) {
- if (r.getField() == ChangeField.LEGACY_UPDATED) {
- return NumericRangeQuery.newIntRange(
- r.getField().getName(),
- toIndexTimeInMinutes(r.getMaxTimestamp()),
- null,
- true, true);
- } else {
- return NumericRangeQuery.newLongRange(
- r.getField().getName(),
- r.getMaxTimestamp().getTime(),
- null,
- true, true);
- }
+ return NumericRangeQuery.newLongRange(
+ r.getField().getName(),
+ r.getMaxTimestamp().getTime(),
+ null,
+ true, true);
}
throw new QueryParseException("cannot negate: " + r);
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
index 4ee3bf4c88..e024f769c5 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
@@ -108,7 +108,7 @@ class SubIndex {
notDoneNrtFutures = Sets.newConcurrentHashSet();
- reopenThread = new ControlledRealTimeReopenThread<IndexSearcher>(
+ reopenThread = new ControlledRealTimeReopenThread<>(
writer, searcherManager,
0.500 /* maximum stale age (seconds) */,
0.010 /* minimum stale age (seconds) */);
@@ -159,7 +159,7 @@ class SubIndex {
try {
writer.getIndexWriter().commit();
try {
- writer.getIndexWriter().close(true);
+ writer.getIndexWriter().close();
} catch (AlreadyClosedException e) {
// Ignore.
}
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java
index 35253a1dfc..36bca15e40 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java
@@ -19,7 +19,6 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.HttpLogoutServlet;
import com.google.gerrit.httpd.WebSession;
-import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
@@ -41,10 +40,9 @@ class OAuthLogoutServlet extends HttpLogoutServlet {
OAuthLogoutServlet(AuthConfig authConfig,
DynamicItem<WebSession> webSession,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
- AccountManager accountManager,
AuditService audit,
Provider<OAuthSession> oauthSession) {
- super(authConfig, webSession, urlProvider, accountManager, audit);
+ super(authConfig, webSession, urlProvider, audit);
this.oauthSession = oauthSession;
}
@@ -57,3 +55,4 @@ class OAuthLogoutServlet extends HttpLogoutServlet {
}
}
}
+
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
index 91c3e33b7f..2d736346a5 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd.auth.oauth;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.Nullable;
@@ -133,7 +133,7 @@ class OAuthWebFilter implements Filter {
@Nullable String errorMessage)
throws IOException {
String self = req.getRequestURI();
- String cancel = Objects.firstNonNull(
+ String cancel = MoreObjects.firstNonNull(
urlProvider != null ? urlProvider.get() : "/", "/");
cancel += LoginUrlToken.getToken(req);
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index aea816e7c2..b8080c9988 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd.auth.openid;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -248,7 +248,8 @@ class LoginForm extends HttpServlet {
private void sendForm(HttpServletRequest req, HttpServletResponse res,
boolean link, @Nullable String errorMessage) throws IOException {
String self = req.getRequestURI();
- String cancel = Objects.firstNonNull(urlProvider != null ? urlProvider.get() : "/", "/");
+ String cancel = MoreObjects.firstNonNull(
+ urlProvider != null ? urlProvider.get() : "/", "/");
cancel += LoginUrlToken.getToken(req);
Document doc = header.parse(LoginForm.class, "LoginForm.html");
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java
index 8fad0ad3c9..02f428e62d 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java
@@ -19,7 +19,6 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.HttpLogoutServlet;
import com.google.gerrit.httpd.WebSession;
-import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
@@ -40,11 +39,10 @@ class OAuthOverOpenIDLogoutServlet extends HttpLogoutServlet {
@Inject
OAuthOverOpenIDLogoutServlet(AuthConfig authConfig,
DynamicItem<WebSession> webSession,
- AccountManager accountManager,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
AuditService audit,
Provider<OAuthSessionOverOpenID> oauthSession) {
- super(authConfig, webSession, urlProvider, accountManager, audit);
+ super(authConfig, webSession, urlProvider, audit);
this.oauthSession = oauthSession;
}
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
index ff02419e92..c17079de8f 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
@@ -82,8 +82,7 @@ class OAuthWebFilterOverOpenID implements Filter {
}
}
- private void pickSSOServiceProvider()
- throws ServletException {
+ private void pickSSOServiceProvider() {
SortedSet<String> plugins = oauthServiceProviders.plugins();
if (plugins.size() == 1) {
SortedMap<String, Provider<OAuthServiceProvider>> services =
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 44b58c39ed..ba144b6960 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -19,6 +19,7 @@ import com.google.gerrit.common.auth.openid.OpenIdUrls;
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;
@@ -55,12 +56,10 @@ import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.util.HttpClientFactory;
-import org.openid4java.util.ProxyProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -108,29 +107,17 @@ class OpenIdServiceImpl {
final Provider<IdentifiedUser> iu,
CanonicalWebUrl up,
@GerritServerConfig final Config config, final AuthConfig ac,
- final AccountManager am) throws ConsumerException, MalformedURLException {
-
- if (config.getString("http", null, "proxy") != null) {
- final URL proxyUrl = new URL(config.getString("http", null, "proxy"));
- String username = config.getString("http", null, "proxyUsername");
- String password = config.getString("http", null, "proxyPassword");
-
- final String userInfo = proxyUrl.getUserInfo();
- if (userInfo != null) {
- int c = userInfo.indexOf(':');
- if (0 < c) {
- username = userInfo.substring(0, c);
- password = userInfo.substring(c + 1);
- } else {
- username = userInfo;
- }
- }
-
- final ProxyProperties proxy = new ProxyProperties();
- proxy.setProxyHostName(proxyUrl.getHost());
- proxy.setProxyPort(proxyUrl.getPort());
- proxy.setUserName(username);
- proxy.setPassword(password);
+ final AccountManager am,
+ ProxyProperties proxyProperties) {
+
+ if (proxyProperties.getProxyUrl() != null) {
+ final org.openid4java.util.ProxyProperties proxy =
+ new org.openid4java.util.ProxyProperties();
+ URL url = proxyProperties.getProxyUrl();
+ proxy.setProxyHostName(url.getHost());
+ proxy.setProxyPort(url.getPort());
+ proxy.setUserName(proxyProperties.getUsername());
+ proxy.setPassword(proxyProperties.getPassword());
HttpClientFactory.setProxyProperties(proxy);
}
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
index 5a9b9359a8..a99b3607cc 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
+++ b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
@@ -30,6 +30,7 @@ import java.util.List;
public class EditDeserializer implements JsonDeserializer<Edit>,
JsonSerializer<Edit> {
+ @Override
public Edit deserialize(final JsonElement json, final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) {
@@ -73,6 +74,7 @@ public class EditDeserializer implements JsonDeserializer<Edit>,
return p.getAsInt();
}
+ @Override
public JsonElement serialize(final Edit src, final Type typeOfSrc,
final JsonSerializationContext context) {
if (src == null) {
diff --git a/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java b/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
index f0ad62a1b3..a2c3dae931 100644
--- a/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
+++ b/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
@@ -14,9 +14,10 @@
package org.eclipse.jgit.diff;
-import org.junit.Test;
import static org.junit.Assert.assertNotNull;
+import org.junit.Test;
+
public class EditDeserializerTest {
@Test
public void testDiffDeserializer() {
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 8b5fdc173a..9a317cd7ca 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -1,29 +1,30 @@
SRCS = 'src/main/java/com/google/gerrit/pgm/'
+RSRCS = 'src/main/resources/com/google/gerrit/pgm/'
-INIT_API_SRCS = [SRCS + n for n in [
- 'init/AllProjectsConfig.java',
- 'init/AllProjectsNameOnInitProvider.java',
- 'util/ConsoleUI.java',
- 'init/InitFlags.java',
- 'init/InitStep.java',
- 'init/InitStep.java',
- 'init/InstallPlugins.java',
- 'init/Section.java',
-]]
+INIT_API_SRCS = glob([SRCS + 'init/api/*.java'])
-java_library(
- name = 'init-api',
- srcs = INIT_API_SRCS,
- deps = [
- '//gerrit-common:annotations',
+DEPS = [
'//gerrit-common:server',
- '//gerrit-reviewdb:server',
+ '//gerrit-extension-api:api',
+ '//gerrit-gwtexpui:linker_server',
+ '//gerrit-gwtexpui:server',
+ '//gerrit-httpd:httpd',
'//gerrit-server:server',
+ '//gerrit-sshd:sshd',
+ '//gerrit-reviewdb:server',
'//lib:guava',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
+ '//lib/guice:guice-servlet',
'//lib/jgit:jgit',
- ],
+ '//lib/log:api',
+ '//lib/log:log4j',
+]
+
+java_library(
+ name = 'init-api',
+ srcs = INIT_API_SRCS,
+ deps = DEPS + ['//gerrit-common:annotations'],
visibility = ['PUBLIC'],
)
@@ -33,101 +34,85 @@ java_sources(
visibility = ['PUBLIC'],
)
-INIT_BASE_SRCS = [SRCS + 'BaseInit.java'] + glob(
- [SRCS + n for n in [
- 'init/**/*.java',
- 'util/**/*.java',
- ]],
- excludes = INIT_API_SRCS +
- [SRCS + n for n in [
- 'init/Browser.java',
- 'util/ErrorLogFile.java',
- 'util/GarbageCollectionLogFile.java',
- 'util/LogFileCompressor.java',
- 'util/RuntimeShutdown.java',
- ]]
- )
-
-INIT_BASE_RSRCS = ['src/main/resources/com/google/gerrit/pgm/libraries.config']
-
java_library(
- name = 'init-base',
- srcs = INIT_BASE_SRCS,
- resources = INIT_BASE_RSRCS,
- deps = [
+ name = 'init',
+ srcs = glob([SRCS + 'init/*.java']),
+ resources = glob([RSRCS + 'init/*']),
+ deps = DEPS + [
':init-api',
- '//gerrit-common:server',
- '//gerrit-extension-api:api',
+ ':util',
+ '//gerrit-common:annotations',
'//gerrit-lucene:lucene',
- '//gerrit-reviewdb:server',
- '//gerrit-server:server',
- '//gerrit-util-cli:cli',
- '//lib/commons:dbcp',
- '//lib/guice:guice',
- '//lib/guice:guice-assistedinject',
- '//lib/jgit:jgit',
- '//lib/mina:sshd',
'//lib:args4j',
- '//lib:guava',
'//lib:gwtjsonrpc',
'//lib:gwtorm',
- '//lib/log:api',
+ '//lib:h2',
+ '//lib/commons:validator',
+ '//lib/mina:sshd',
],
provided_deps = ['//gerrit-launcher:launcher'],
visibility = [
+ '//gerrit-acceptance-tests/...',
'//gerrit-war:',
+ ],
+)
+
+java_library(
+ name = 'util',
+ srcs = glob([SRCS + 'util/*.java']),
+ deps = DEPS + [
+ '//gerrit-cache-h2:cache-h2',
+ '//gerrit-util-cli:cli',
+ '//lib:args4j',
+ '//lib:gwtorm',
+ '//lib/commons:dbcp',
+ ],
+ visibility = [
'//gerrit-acceptance-tests/...',
+ '//gerrit-gwtdebug:gwtdebug',
+ '//gerrit-war:',
],
)
java_library(
+ name = 'http',
+ srcs = glob([SRCS + 'http/**/*.java']),
+ deps = DEPS + [
+ '//lib/jetty:jmx',
+ '//lib/jetty:server',
+ '//lib/jetty:servlet',
+ ],
+ provided_deps = [
+ '//gerrit-launcher:launcher',
+ '//lib:servlet-api-3_1',
+ ],
+ visibility = ['//gerrit-war:'],
+)
+
+java_library(
name = 'pgm',
- srcs = glob(
- ['src/main/java/**/*.java'],
- excludes = INIT_API_SRCS + INIT_BASE_SRCS
- ),
- resources = glob(
- ['src/main/resources/**/*'],
- excludes = INIT_BASE_RSRCS),
- deps = [
+ srcs = glob([SRCS + '*.java']),
+ resources = glob([RSRCS + '*']),
+ deps = DEPS + [
+ ':http',
+ ':init',
':init-api',
- ':init-base',
+ ':util',
'//gerrit-cache-h2:cache-h2',
- '//gerrit-common:server',
- '//gerrit-extension-api:api',
- '//gerrit-gwtexpui:linker_server',
- '//gerrit-gwtexpui:server',
- '//gerrit-httpd:httpd',
'//gerrit-lucene:lucene',
'//gerrit-oauth:oauth',
'//gerrit-openid:openid',
- '//gerrit-reviewdb:server',
- '//gerrit-server:server',
- '//gerrit-server/src/main/prolog:common',
'//gerrit-solr:solr',
- '//gerrit-sshd:sshd',
- '//gerrit-util-cli:cli',
'//lib:args4j',
- '//lib:guava',
'//lib:gwtorm',
- '//lib:h2',
'//lib:servlet-api-3_1',
- '//lib/guice:guice',
- '//lib/guice:guice-assistedinject',
- '//lib/guice:guice-servlet',
- '//lib/jetty:server',
- '//lib/jetty:servlet',
- '//lib/jetty:jmx',
- '//lib/jgit:jgit',
- '//lib/log:api',
- '//lib/log:log4j',
- '//lib/lucene:core',
'//lib/prolog:prolog-cafe',
],
provided_deps = ['//gerrit-launcher:launcher'],
visibility = [
'//:',
'//gerrit-acceptance-tests/...',
+ '//gerrit-gwtdebug:gwtdebug',
'//tools/eclipse:classpath',
'//Documentation:licenses.txt',
],
@@ -137,8 +122,8 @@ java_test(
name = 'pgm_tests',
srcs = glob(['src/test/java/**/*.java']),
deps = [
+ ':init',
':init-api',
- ':init-base',
':pgm',
'//gerrit-server:server',
'//lib:junit',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 1c667e3ad0..2b9af2f9a4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -17,10 +17,11 @@ package com.google.gerrit.pgm;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.httpd.AllRequestFilter;
-import com.google.gerrit.httpd.GerritUiOptions;
+import com.google.gerrit.httpd.GerritOptions;
+import com.google.gerrit.httpd.GetUserFilter;
import com.google.gerrit.httpd.GitOverHttpModule;
import com.google.gerrit.httpd.H2CacheBasedWebSession;
import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
@@ -32,20 +33,16 @@ import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
-import com.google.gerrit.pgm.http.jetty.GetUserFilter;
import com.google.gerrit.pgm.http.jetty.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule;
import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
-import com.google.gerrit.pgm.shell.JythonShell;
import com.google.gerrit.pgm.util.ErrorLogFile;
-import com.google.gerrit.pgm.util.GarbageCollectionLogFile;
import com.google.gerrit.pgm.util.LogFileCompressor;
import com.google.gerrit.pgm.util.RuntimeShutdown;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.account.InternalAccountDirectory;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
@@ -54,8 +51,10 @@ import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.config.RestCacheAdminModule;
+import com.google.gerrit.server.contact.ContactStoreModule;
import com.google.gerrit.server.contact.HttpContactStoreConnection;
-import com.google.gerrit.server.git.GarbageCollectionRunner;
+import com.google.gerrit.server.git.ChangeCacheImplModule;
+import com.google.gerrit.server.git.GarbageCollectionModule;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.DummyIndexModule;
@@ -63,11 +62,16 @@ import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
+import com.google.gerrit.server.mime.MimeUtil2Module;
import com.google.gerrit.server.patch.DiffExecutorModule;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.SchemaVersionCheck;
+import com.google.gerrit.server.securestore.DefaultSecureStore;
+import com.google.gerrit.server.securestore.SecureStore;
+import com.google.gerrit.server.securestore.SecureStoreClassName;
+import com.google.gerrit.server.securestore.SecureStoreProvider;
import com.google.gerrit.server.ssh.NoSshKeyCache;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.ssh.SshAddressesModule;
@@ -106,7 +110,7 @@ public class Daemon extends SiteProgram {
private Boolean httpd;
@Option(name = "--disable-httpd", usage = "Disable the internal HTTP daemon")
- void setDisableHttpd(final boolean arg) {
+ void setDisableHttpd(@SuppressWarnings("unused") boolean arg) {
httpd = false;
}
@@ -114,11 +118,11 @@ public class Daemon extends SiteProgram {
private boolean sshd = true;
@Option(name = "--disable-sshd", usage = "Disable the internal SSH daemon")
- void setDisableSshd(final boolean arg) {
+ void setDisableSshd(@SuppressWarnings("unused") boolean arg) {
sshd = false;
}
- @Option(name = "--slave", usage = "Support fetch only; implies --disable-httpd")
+ @Option(name = "--slave", usage = "Support fetch only")
private boolean slave;
@Option(name = "--console-log", usage = "Log to console (not $site_path/logs)")
@@ -190,11 +194,7 @@ public class Daemon extends SiteProgram {
if (!httpd && !sshd) {
throw die("No services enabled, nothing to do");
}
- if (slave && httpd) {
- throw die("Cannot combine --slave and --enable-httpd");
- }
- manager.add(GarbageCollectionLogFile.start(getSitePath()));
if (consoleLog) {
} else {
manager.add(ErrorLogFile.start(getSitePath()));
@@ -277,7 +277,7 @@ public class Daemon extends SiteProgram {
cfgInjector = createCfgInjector();
sysInjector = createSysInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class)
- .setCfgInjector(cfgInjector);
+ .setDbCfgInjector(dbInjector, cfgInjector);
manager.add(dbInjector, cfgInjector, sysInjector);
sshd &= !sshdOff();
@@ -285,7 +285,7 @@ public class Daemon extends SiteProgram {
initSshd();
}
- if (Objects.firstNonNull(httpd, true)) {
+ if (MoreObjects.firstNonNull(httpd, true)) {
initHttpd();
}
@@ -319,9 +319,10 @@ public class Daemon extends SiteProgram {
modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
- modules.add(new MergeabilityChecksExecutorModule());
modules.add(new DiffExecutorModule());
+ modules.add(new MimeUtil2Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
+ modules.add(new ChangeCacheImplModule(slave));
modules.add(new InternalAccountDirectory.Module());
modules.add(new DefaultCacheFactory.Module());
modules.add(new SmtpEmailSender.Module());
@@ -329,7 +330,7 @@ public class Daemon extends SiteProgram {
modules.add(new PluginRestApiModule());
modules.add(new RestCacheAdminModule());
modules.add(createIndexModule());
- if (Objects.firstNonNull(httpd, true)) {
+ if (MoreObjects.firstNonNull(httpd, true)) {
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
@@ -355,10 +356,15 @@ public class Daemon extends SiteProgram {
modules.add(new AbstractModule() {
@Override
protected void configure() {
- bind(GerritUiOptions.class).toInstance(new GerritUiOptions(headless));
+ bind(GerritOptions.class).toInstance(new GerritOptions(headless, slave));
+ if (test) {
+ bind(String.class).annotatedWith(SecureStoreClassName.class)
+ .toInstance(DefaultSecureStore.class.getName());
+ bind(SecureStore.class).toProvider(SecureStoreProvider.class);
+ }
}
});
- modules.add(GarbageCollectionRunner.module());
+ modules.add(new GarbageCollectionModule());
return cfgInjector.createChildInjector(modules);
}
@@ -421,6 +427,7 @@ public class Daemon extends SiteProgram {
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
modules.add(sysInjector.getInstance(WebModule.class));
modules.add(new HttpPluginModule());
+ modules.add(new ContactStoreModule());
if (sshd) {
modules.add(sshInjector.getInstance(WebSshGlueModule.class));
} else {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index c30507f4da..38b1824d90 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -17,20 +17,23 @@ package com.google.gerrit.pgm;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.IoUtil;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.PluginData;
+import com.google.gerrit.pgm.init.BaseInit;
import com.google.gerrit.pgm.init.Browser;
import com.google.gerrit.pgm.init.InitPlugins;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.util.ErrorLogFile;
-import com.google.gerrit.pgm.util.IoUtil;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.gerrit.server.util.HostPlatform;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Module;
+import com.google.inject.util.Providers;
import org.kohsuke.args4j.Option;
@@ -47,7 +50,7 @@ public class Init extends BaseInit {
@Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init")
private boolean noAutoStart;
- @Option(name = "--skip-plugins", usage = "Don't install plugin")
+ @Option(name = "--skip-plugins", usage = "Don't install plugins")
private boolean skipPlugins = false;
@Option(name = "--list-plugins", usage = "List available plugins")
@@ -56,6 +59,10 @@ public class Init extends BaseInit {
@Option(name = "--install-plugin", usage = "Install given plugin without asking")
private List<String> installPlugins;
+ @Option(name = "--secure-store-lib",
+ usage = "Path to jar providing SecureStore implementation class")
+ private String secureStoreLib;
+
@Inject
Browser browser;
@@ -101,6 +108,8 @@ public class Init extends BaseInit {
protected void configure() {
bind(File.class).annotatedWith(SitePath.class).toInstance(getSitePath());
bind(Browser.class);
+ bind(String.class).annotatedWith(SecureStoreClassName.class)
+ .toProvider(Providers.of(getConfiguredSecureStoreClass()));
}
});
modules.add(new GerritServerConfigModule());
@@ -128,6 +137,11 @@ public class Init extends BaseInit {
return skipPlugins;
}
+ @Override
+ protected String getSecureStoreLib() {
+ return secureStoreLib;
+ }
+
void start(SiteRun run) throws Exception {
if (run.flags.autoStart) {
if (HostPlatform.isWin32()) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/JythonShell.java
index 6efda29b2d..ff157cec28 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/JythonShell.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.shell;
+package com.google.gerrit.pgm;
import com.google.gerrit.launcher.GerritLauncher;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
new file mode 100644
index 0000000000..ab20f531f5
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
@@ -0,0 +1,296 @@
+// Copyright (C) 2014 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.util.BatchProgramModule;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.pgm.util.ThreadLimiter;
+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.server.ReviewDb;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MultiProgressMonitor;
+import com.google.gerrit.server.git.MultiProgressMonitor.Task;
+import com.google.gerrit.server.git.SearchingChangeCacheImpl;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.index.DummyIndexModule;
+import com.google.gerrit.server.index.ReindexAfterUpdate;
+import com.google.gerrit.server.notedb.ChangeRebuilder;
+import com.google.gerrit.server.notedb.NoteDbModule;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+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.ReceiveCommand;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class RebuildNotedb extends SiteProgram {
+ private static final Logger log =
+ LoggerFactory.getLogger(RebuildNotedb.class);
+
+ @Option(name = "--threads", usage = "Number of threads to use for indexing")
+ private int threads = Runtime.getRuntime().availableProcessors();
+
+ private Injector dbInjector;
+ private Injector sysInjector;
+
+ @Override
+ public int run() throws Exception {
+ mustHaveValidSite();
+ dbInjector = createDbInjector(MULTI_USER);
+ threads = ThreadLimiter.limitThreads(dbInjector, threads);
+
+ LifecycleManager dbManager = new LifecycleManager();
+ dbManager.add(dbInjector);
+ dbManager.start();
+
+ sysInjector = createSysInjector();
+ NotesMigration notesMigration = sysInjector.getInstance(
+ NotesMigration.class);
+ if (!notesMigration.enabled()) {
+ die("Notedb is not enabled.");
+ }
+ LifecycleManager sysManager = new LifecycleManager();
+ sysManager.add(sysInjector);
+ sysManager.start();
+
+ ListeningExecutorService executor = newExecutor();
+ System.out.println("Rebuilding the notedb");
+ ChangeRebuilder rebuilder = sysInjector.getInstance(ChangeRebuilder.class);
+
+ Multimap<Project.NameKey, Change> changesByProject = getChangesByProject();
+ final AtomicBoolean ok = new AtomicBoolean(true);
+ Stopwatch sw = Stopwatch.createStarted();
+ GitRepositoryManager repoManager =
+ sysInjector.getInstance(GitRepositoryManager.class);
+ final Project.NameKey allUsersName =
+ sysInjector.getInstance(AllUsersName.class);
+ final Repository allUsersRepo =
+ repoManager.openMetadataRepository(allUsersName);
+ try {
+ deleteDraftRefs(allUsersRepo);
+ for (final Project.NameKey project : changesByProject.keySet()) {
+ final Repository repo = repoManager.openMetadataRepository(project);
+ try {
+ final BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ final BatchRefUpdate bruForDrafts =
+ allUsersRepo.getRefDatabase().newBatchUpdate();
+ List<ListenableFuture<?>> futures = Lists.newArrayList();
+
+ // Here, we truncate the project name to 50 characters to ensure that
+ // the whole monitor line for a project fits on one line (<80 chars).
+ final MultiProgressMonitor mpm = new MultiProgressMonitor(System.out,
+ truncateProjectName(project.get()));
+ final Task doneTask =
+ mpm.beginSubTask("done", changesByProject.get(project).size());
+ final Task failedTask =
+ mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
+
+ for (final Change c : changesByProject.get(project)) {
+ final ListenableFuture<?> future = rebuilder.rebuildAsync(c,
+ executor, bru, bruForDrafts, repo, allUsersRepo);
+ futures.add(future);
+ future.addListener(
+ new RebuildListener(c.getId(), future, ok, doneTask, failedTask),
+ MoreExecutors.directExecutor());
+ }
+
+ mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+ new AsyncFunction<List<?>, Void>() {
+ @Override
+ public ListenableFuture<Void> apply(List<?> input)
+ throws Exception {
+ execute(bru, repo);
+ execute(bruForDrafts, allUsersRepo);
+ mpm.end();
+ return Futures.immediateFuture(null);
+ }
+ }));
+ } catch (Exception e) {
+ log.error("Error rebuilding notedb", e);
+ ok.set(false);
+ break;
+ } finally {
+ repo.close();
+ }
+ }
+ } finally {
+ allUsersRepo.close();
+ }
+
+ double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
+ System.out.format("Rebuild %d changes in %.01fs (%.01f/s)\n",
+ changesByProject.size(), t, changesByProject.size() / t);
+ return ok.get() ? 0 : 1;
+ }
+
+ private static String truncateProjectName(String projectName) {
+ int monitorStringMaxLength = 50;
+ String monitorString = (projectName.length() > monitorStringMaxLength)
+ ? projectName.substring(0, monitorStringMaxLength)
+ : projectName;
+ if (projectName.length() > monitorString.length()) {
+ monitorString = monitorString + "...";
+ }
+ return monitorString;
+ }
+
+ private static void execute(BatchRefUpdate bru, Repository repo)
+ throws IOException {
+ try (RevWalk rw = new RevWalk(repo)) {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+ }
+
+ private void deleteDraftRefs(Repository allUsersRepo) throws IOException {
+ RefDatabase refDb = allUsersRepo.getRefDatabase();
+ Map<String, Ref> allRefs = refDb.getRefs(RefNames.REFS_DRAFT_COMMENTS);
+ BatchRefUpdate bru = refDb.newBatchUpdate();
+ for (Map.Entry<String, Ref> ref : allRefs.entrySet()) {
+ bru.addCommand(new ReceiveCommand(ref.getValue().getObjectId(),
+ ObjectId.zeroId(), RefNames.REFS_DRAFT_COMMENTS + ref.getKey()));
+ }
+ execute(bru, allUsersRepo);
+ }
+
+ private Injector createSysInjector() {
+ return dbInjector.createChildInjector(new AbstractModule() {
+ @Override
+ public void configure() {
+ install(dbInjector.getInstance(BatchProgramModule.class));
+ install(SearchingChangeCacheImpl.module());
+ install(new NoteDbModule());
+ DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(
+ ReindexAfterUpdate.class);
+ install(new DummyIndexModule());
+ }
+ });
+ }
+
+ private ListeningExecutorService newExecutor() {
+ if (threads > 0) {
+ return MoreExecutors.listeningDecorator(
+ dbInjector.getInstance(WorkQueue.class)
+ .createQueue(threads, "RebuildChange"));
+ } else {
+ return MoreExecutors.newDirectExecutorService();
+ }
+ }
+
+ private Multimap<Project.NameKey, Change> getChangesByProject()
+ throws OrmException {
+ // Memorize all changes so we can close the db connection and allow
+ // rebuilder threads to use the full connection pool.
+ SchemaFactory<ReviewDb> schemaFactory = sysInjector.getInstance(Key.get(
+ new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
+ ReviewDb db = schemaFactory.open();
+ Multimap<Project.NameKey, Change> changesByProject =
+ ArrayListMultimap.create();
+ try {
+ for (Change c : db.changes().all()) {
+ changesByProject.put(c.getProject(), c);
+ }
+ return changesByProject;
+ } finally {
+ db.close();
+ }
+ }
+
+ private class RebuildListener implements Runnable {
+ private Change.Id changeId;
+ private ListenableFuture<?> future;
+ private AtomicBoolean ok;
+ private Task doneTask;
+ private Task failedTask;
+
+
+ private RebuildListener(Change.Id changeId, ListenableFuture<?> future,
+ AtomicBoolean ok, Task doneTask, Task failedTask) {
+ this.changeId = changeId;
+ this.future = future;
+ this.ok = ok;
+ this.doneTask = doneTask;
+ this.failedTask = failedTask;
+ }
+
+ @Override
+ public void run() {
+ try {
+ future.get();
+ doneTask.update(1);
+ } catch (ExecutionException | InterruptedException e) {
+ fail(e);
+ } catch (RuntimeException e) {
+ failAndThrow(e);
+ } catch (Error e) {
+ // Can't join with RuntimeException because "RuntimeException
+ // | Error" becomes Throwable, which messes with signatures.
+ failAndThrow(e);
+ }
+ }
+
+ private void fail(Throwable t) {
+ log.error("Failed to rebuild change " + changeId, t);
+ ok.set(false);
+ failedTask.update(1);
+ }
+
+ private void failAndThrow(RuntimeException e) {
+ fail(e);
+ throw e;
+ }
+
+ private void failAndThrow(Error e) {
+ fail(e);
+ throw e;
+ }
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index 161efc1ed6..44f80f27a1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -15,103 +15,42 @@
package com.google.gerrit.pgm;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
-import static com.google.inject.Scopes.SINGLETON;
-import com.google.common.cache.Cache;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.Die;
-import com.google.gerrit.common.DisabledChangeHooks;
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule;
+import com.google.gerrit.pgm.util.BatchProgramModule;
import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.pgm.util.ThreadLimiter;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.rules.PrologModule;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCacheImpl;
-import com.google.gerrit.server.account.AccountCacheImpl;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupCacheImpl;
-import com.google.gerrit.server.account.GroupIncludeCacheImpl;
-import com.google.gerrit.server.cache.CacheRemovalListener;
-import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.ChangeKindCacheImpl;
-import com.google.gerrit.server.change.MergeabilityChecker;
-import com.google.gerrit.server.change.MergeabilityChecksExecutor;
-import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
-import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.CanonicalWebUrlProvider;
-import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.GitReceivePackGroups;
-import com.google.gerrit.server.config.GitUploadPackGroups;
-import com.google.gerrit.server.git.GitModule;
-import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.git.validators.CommitValidationListener;
-import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.group.GroupModule;
-import com.google.gerrit.server.index.ChangeBatchIndexer;
+import com.google.gerrit.server.git.ScanningChangeCacheImpl;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.ChangeSchemas;
import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
-import com.google.gerrit.server.notedb.NoteDbModule;
-import com.google.gerrit.server.patch.DiffExecutorModule;
-import com.google.gerrit.server.patch.PatchListCacheImpl;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.CommentLinkInfo;
-import com.google.gerrit.server.project.CommentLinkProvider;
-import com.google.gerrit.server.project.ProjectCacheImpl;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.SectionSortCache;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DataSourceType;
+import com.google.gerrit.server.index.SiteIndexer;
import com.google.gerrit.solr.SolrIndexModule;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
-import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
-import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.kohsuke.args4j.Option;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class Reindex extends SiteProgram {
- private static final Logger log = LoggerFactory.getLogger(Reindex.class);
-
@Option(name = "--threads", usage = "Number of threads to use for indexing")
private int threads = Runtime.getRuntime().availableProcessors();
@@ -122,9 +61,6 @@ public class Reindex extends SiteProgram {
@Option(name = "--output", usage = "Prefix for output; path for local disk index, or prefix for remote index")
private String outputBase;
- @Option(name = "--recheck-mergeable", usage = "Recheck mergeable flag on all changes")
- private boolean recheckMergeable;
-
@Option(name = "--verbose", usage = "Output debug information for each change")
private boolean verbose;
@@ -132,19 +68,20 @@ public class Reindex extends SiteProgram {
private boolean dryRun;
private Injector dbInjector;
- private Config cfg;
private Injector sysInjector;
+ private Config globalConfig;
private ChangeIndex index;
@Override
public int run() throws Exception {
mustHaveValidSite();
dbInjector = createDbInjector(MULTI_USER);
- cfg = dbInjector.getInstance(
- Key.get(Config.class, GerritServerConfig.class));
+ globalConfig =
+ dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+ threads = ThreadLimiter.limitThreads(dbInjector, threads);
checkNotSlaveMode();
- limitThreads();
disableLuceneAutomaticCommit();
+ disableChangeCache();
if (version == null) {
version = ChangeSchemas.getLatest().getVersion();
}
@@ -173,28 +110,14 @@ public class Reindex extends SiteProgram {
}
private void checkNotSlaveMode() throws Die {
- if (cfg.getBoolean("container", "slave", false)) {
+ if (globalConfig.getBoolean("container", "slave", false)) {
throw die("Cannot run reindex in slave mode");
}
}
- private void limitThreads() {
- boolean usePool = cfg.getBoolean("database", "connectionpool",
- dbInjector.getInstance(DataSourceType.class).usePool());
- int poolLimit = cfg.getInt("database", "poollimit",
- DataSourceProvider.DEFAULT_POOL_LIMIT);
- if (usePool && threads > poolLimit) {
- log.warn("Limiting reindexing to " + poolLimit
- + " threads due to database.poolLimit");
- threads = poolLimit;
- }
- }
-
private Injector createSysInjector() {
List<Module> modules = Lists.newArrayList();
- modules.add(new DiffExecutorModule());
- modules.add(PatchListCacheImpl.module());
- AbstractModule changeIndexModule;
+ Module changeIndexModule;
switch (IndexModule.getIndexType(dbInjector)) {
case LUCENE:
changeIndexModule = new LuceneIndexModule(version, threads, outputBase);
@@ -206,142 +129,23 @@ public class Reindex extends SiteProgram {
throw new IllegalStateException("unsupported index.type");
}
modules.add(changeIndexModule);
- modules.add(new ReviewDbModule());
- modules.add(new FactoryModule() {
- @SuppressWarnings("rawtypes")
- @Override
- protected void configure() {
- // Plugins are not loaded and we're just running through each change
- // once, so don't worry about cache removal.
- bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
- .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
- bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
- .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
- bind(new TypeLiteral<List<CommentLinkInfo>>() {})
- .toProvider(CommentLinkProvider.class).in(SINGLETON);
- bind(String.class).annotatedWith(CanonicalWebUrl.class)
- .toProvider(CanonicalWebUrlProvider.class);
- bind(IdentifiedUser.class)
- .toProvider(Providers. <IdentifiedUser>of(null));
- bind(CurrentUser.class).to(IdentifiedUser.class);
-
- bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
- .annotatedWith(GitUploadPackGroups.class)
- .toInstance(Collections.<AccountGroup.UUID> emptySet());
- bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
- .annotatedWith(GitReceivePackGroups.class)
- .toInstance(Collections.<AccountGroup.UUID> emptySet());
- factory(ChangeControl.AssistedFactory.class);
- factory(ProjectControl.AssistedFactory.class);
-
- install(new DefaultCacheFactory.Module());
- install(new GroupModule());
- install(new PrologModule());
- install(AccountByEmailCacheImpl.module());
- install(AccountCacheImpl.module());
- install(GroupCacheImpl.module());
- install(GroupIncludeCacheImpl.module());
- install(ProjectCacheImpl.module());
- install(SectionSortCache.module());
- install(ChangeKindCacheImpl.module());
- factory(CapabilityControl.Factory.class);
- factory(ChangeData.Factory.class);
- factory(ProjectState.Factory.class);
-
- if (recheckMergeable) {
- install(new MergeabilityModule());
- } else {
- bind(MergeabilityChecker.class)
- .toProvider(Providers.<MergeabilityChecker> of(null));
- }
- }
- });
+ // Scan changes from git instead of relying on the secondary index, as we
+ // will have just deleted the old (possibly corrupt) index.
+ modules.add(ScanningChangeCacheImpl.module());
+ modules.add(dbInjector.getInstance(BatchProgramModule.class));
return dbInjector.createChildInjector(modules);
}
private void disableLuceneAutomaticCommit() {
- Config cfg =
- dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
if (IndexModule.getIndexType(dbInjector) == IndexType.LUCENE) {
- cfg.setLong("index", "changes_open", "commitWithin", -1);
- cfg.setLong("index", "changes_closed", "commitWithin", -1);
- }
- }
-
- private class ReviewDbModule extends LifecycleModule {
- @Override
- protected void configure() {
- final SchemaFactory<ReviewDb> schema = dbInjector.getInstance(
- Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
- final List<ReviewDb> dbs = Collections.synchronizedList(
- Lists.<ReviewDb> newArrayListWithCapacity(threads + 1));
- final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
-
- bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
- @Override
- public ReviewDb get() {
- ReviewDb db = localDb.get();
- if (db == null) {
- try {
- db = schema.open();
- dbs.add(db);
- localDb.set(db);
- } catch (OrmException e) {
- throw new ProvisionException("unable to open ReviewDb", e);
- }
- }
- return db;
- }
- });
- listener().toInstance(new LifecycleListener() {
- @Override
- public void start() {
- // Do nothing.
- }
-
- @Override
- public void stop() {
- for (ReviewDb db : dbs) {
- db.close();
- }
- }
- });
+ globalConfig.setLong("index", "changes_open", "commitWithin", -1);
+ globalConfig.setLong("index", "changes_closed", "commitWithin", -1);
}
}
- private static class MergeabilityModule extends FactoryModule {
- @Override
- public void configure() {
- factory(PatchSetInserter.Factory.class);
- bind(ChangeHooks.class).to(DisabledChangeHooks.class);
- bind(ReplacePatchSetSender.Factory.class).toProvider(
- Providers.<ReplacePatchSetSender.Factory>of(null));
-
- factory(MergeUtil.Factory.class);
- DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
- DynamicSet.setOf(binder(), CommitValidationListener.class);
- factory(CommitValidators.Factory.class);
- install(new GitModule());
- install(new NoteDbModule());
- }
-
- @Provides
- @Singleton
- @MergeabilityChecksExecutor(Priority.BACKGROUND)
- public WorkQueue.Executor createMergeabilityChecksExecutor(
- WorkQueue queues) {
- return queues.createQueue(1, "MergeabilityChecks");
- }
-
- @Provides
- @Singleton
- @MergeabilityChecksExecutor(Priority.INTERACTIVE)
- public WorkQueue.Executor createInteractiveMergeabilityChecksExecutor(
- @MergeabilityChecksExecutor(Priority.BACKGROUND)
- WorkQueue.Executor bg) {
- return bg;
- }
+ private void disableChangeCache() {
+ globalConfig.setLong("cache", "changes", "maximumWeight", 0);
}
private int indexAll() throws Exception {
@@ -363,11 +167,12 @@ public class Reindex extends SiteProgram {
}
pm.endTask();
- ChangeBatchIndexer batchIndexer =
- sysInjector.getInstance(ChangeBatchIndexer.class);
- ChangeBatchIndexer.Result result = batchIndexer.indexAll(
- index, projects, projects.size(), changeCount, System.err,
- verbose ? System.out : NullOutputStream.INSTANCE);
+ SiteIndexer batchIndexer =
+ sysInjector.getInstance(SiteIndexer.class);
+ SiteIndexer.Result result = batchIndexer.setNumChanges(changeCount)
+ .setProgressOut(System.err)
+ .setVerboseOut(verbose ? System.out : NullOutputStream.INSTANCE)
+ .indexAll(index, projects);
int n = result.doneCount() + result.failedCount();
double t = result.elapsed(TimeUnit.MILLISECONDS) / 1000d;
System.out.format("Reindexed %d changes in %.01fs (%.01f/s)\n", n, t, n/t);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
new file mode 100644
index 0000000000..f2feae1861
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -0,0 +1,217 @@
+// Copyright (C) 2014 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.common.SiteLibraryLoaderUtil;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugins.JarScanner;
+import com.google.gerrit.server.securestore.DefaultSecureStore;
+import com.google.gerrit.server.securestore.SecureStore;
+import com.google.gerrit.server.securestore.SecureStore.EntryKey;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+public class SwitchSecureStore extends SiteProgram {
+ private static String getSecureStoreClassFromGerritConfig(SitePaths sitePaths) {
+ FileBasedConfig cfg =
+ new FileBasedConfig(sitePaths.gerrit_config, FS.DETECTED);
+ try {
+ cfg.load();
+ } catch (IOException | ConfigInvalidException e) {
+ throw new RuntimeException("Cannot read gerrit.config file", e);
+ }
+ return cfg.getString("gerrit", null, "secureStoreClass");
+ }
+
+ private static final Logger log = LoggerFactory
+ .getLogger(SwitchSecureStore.class);
+
+ @Option(name = "--new-secure-store-lib",
+ usage = "Path to new SecureStore implementation",
+ required = true)
+ private String newSecureStoreLib;
+
+ @Override
+ public int run() throws Exception {
+ SitePaths sitePaths = new SitePaths(getSitePath());
+ File newSecureStoreFile = new File(newSecureStoreLib);
+ if (!newSecureStoreFile.exists()) {
+ log.error(String.format("File %s doesn't exists",
+ newSecureStoreFile.getAbsolutePath()));
+ return -1;
+ }
+
+ String newSecureStore = getNewSecureStoreClassName(newSecureStoreFile);
+ String currentSecureStoreName = getCurrentSecureStoreClassName(sitePaths);
+
+ if (currentSecureStoreName.equals(newSecureStore)) {
+ log.error("Old and new SecureStore implementation names "
+ + "are the same. Migration will not work");
+ return -1;
+ }
+
+ IoUtil.loadJARs(newSecureStoreFile);
+ SiteLibraryLoaderUtil.loadSiteLib(sitePaths.lib_dir);
+
+ log.info("Current secureStoreClass property ({}) will be replaced with {}",
+ currentSecureStoreName, newSecureStore);
+ Injector dbInjector = createDbInjector(SINGLE_USER);
+ SecureStore currentStore =
+ getSecureStore(currentSecureStoreName, dbInjector);
+ SecureStore newStore = getSecureStore(newSecureStore, dbInjector);
+
+ migrateProperties(currentStore, newStore);
+
+ removeOldLib(sitePaths, currentSecureStoreName);
+ copyNewLib(sitePaths, newSecureStoreFile);
+
+ updateGerritConfig(sitePaths, newSecureStore);
+
+ return 0;
+ }
+
+ private void migrateProperties(SecureStore currentStore, SecureStore newStore) {
+ log.info("Migrate entries");
+ for (EntryKey key : currentStore.list()) {
+ String[] value =
+ currentStore.getList(key.section, key.subsection, key.name);
+ if (value != null) {
+ newStore.setList(key.section, key.subsection, key.name,
+ Arrays.asList(value));
+ } else {
+ String msg =
+ String.format("Cannot migrate entry for %s", key.section);
+ if (key.subsection != null) {
+ msg = msg + String.format(".%s", key.subsection);
+ }
+ msg = msg + String.format(".%s", key.name);
+ throw new RuntimeException(msg);
+ }
+ }
+ }
+
+ private void removeOldLib(SitePaths sitePaths, String currentSecureStoreName) {
+ File oldSecureStore =
+ findJarWithSecureStore(sitePaths, currentSecureStoreName);
+ if (oldSecureStore != null) {
+ log.info("Removing old SecureStore ({}) from lib/ directory",
+ oldSecureStore.getName());
+ if (!oldSecureStore.delete()) {
+ log.error("Cannot remove {}", oldSecureStore.getAbsolutePath());
+ }
+ } else {
+ log.info("Cannot find jar with old SecureStore ({}) in lib/ directory",
+ currentSecureStoreName);
+ }
+ }
+
+ private void copyNewLib(SitePaths sitePaths, File newSecureStoreFile)
+ throws IOException {
+ log.info("Copy new SecureStore ({}) into lib/ directory",
+ newSecureStoreFile.getName());
+ Files.copy(newSecureStoreFile, new File(sitePaths.lib_dir,
+ newSecureStoreFile.getName()));
+ }
+
+ private void updateGerritConfig(SitePaths sitePaths, String newSecureStore)
+ throws IOException, ConfigInvalidException {
+ log.info("Set gerrit.secureStoreClass property of gerrit.config to {}",
+ newSecureStore);
+ FileBasedConfig config =
+ new FileBasedConfig(sitePaths.gerrit_config, FS.DETECTED);
+ config.load();
+ config.setString("gerrit", null, "secureStoreClass", newSecureStore);
+ config.save();
+ }
+
+ private String getNewSecureStoreClassName(File secureStore)
+ throws IOException {
+ JarScanner scanner = new JarScanner(secureStore);
+ List<String> newSecureStores =
+ scanner.findSubClassesOf(SecureStore.class);
+ if (newSecureStores.isEmpty()) {
+ throw new RuntimeException(String.format(
+ "Cannot find implementation of SecureStore interface in %s",
+ secureStore.getAbsolutePath()));
+ }
+ if (newSecureStores.size() > 1) {
+ throw new RuntimeException(String.format(
+ "Found too many implementations of SecureStore:\n%s\nin %s", Joiner
+ .on("\n").join(newSecureStores), secureStore.getAbsolutePath()));
+ }
+ return Iterables.getOnlyElement(newSecureStores);
+ }
+
+ private String getCurrentSecureStoreClassName(SitePaths sitePaths) {
+ String current = getSecureStoreClassFromGerritConfig(sitePaths);
+ if (!Strings.isNullOrEmpty(current)) {
+ return current;
+ }
+ return DefaultSecureStore.class.getName();
+ }
+
+ private SecureStore getSecureStore(String className, Injector injector) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<? extends SecureStore> clazz =
+ (Class<? extends SecureStore>) Class.forName(className);
+ return injector.getInstance(clazz);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(
+ String.format("Cannot load SecureStore implementation: %s", className), e);
+ }
+ }
+
+ private File findJarWithSecureStore(SitePaths sitePaths,
+ String secureStoreClass) {
+ File[] jars = SiteLibraryLoaderUtil.listJars(sitePaths.lib_dir);
+ if (jars == null || jars.length == 0) {
+ return null;
+ }
+ String secureStoreClassPath = secureStoreClass.replace('.', '/') + ".class";
+ for (File jar : jars) {
+ try (JarFile jarFile = new JarFile(jar)) {
+ ZipEntry entry = jarFile.getEntry(secureStoreClassPath);
+ if (entry != null) {
+ return jar;
+ }
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
index 2c34711559..3328a54f9c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
@@ -49,8 +49,9 @@ public class WarDistribution implements PluginsDistribution {
String pluginJarName = new File(ze.getName()).getName();
String pluginName = pluginJarName.substring(0,
pluginJarName.length() - JAR.length());
- final InputStream in = zf.getInputStream(ze);
- processor.process(pluginName, in);
+ try (InputStream in = zf.getInputStream(ze)) {
+ processor.process(pluginName, in);
+ }
}
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
index 5ca53dfeee..3b0a590ad7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
@@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletResponse;
class HiddenErrorHandler extends ErrorHandler {
private static final Logger log = LoggerFactory.getLogger(HiddenErrorHandler.class);
+ @Override
public void handle(String target, Request baseRequest,
HttpServletRequest req, HttpServletResponse res) throws IOException {
HttpConnection conn = HttpConnection.getCurrentConnection();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index ba97d4a1c0..a326919b74 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -14,10 +14,9 @@
package com.google.gerrit.pgm.http.jetty;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.httpd.GetUserFilter;
import com.google.gerrit.server.util.SystemLog;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.Inject;
import org.apache.log4j.AsyncAppender;
@@ -66,11 +65,6 @@ class HttpLog extends AbstractLifeCycle implements RequestLog {
@Override
public void log(final Request req, final Response rsp) {
- CurrentUser user = (CurrentUser) req.getAttribute(GetUserFilter.REQ_ATTR_KEY);
- doLog(req, rsp, user);
- }
-
- private void doLog(Request req, Response rsp, CurrentUser user) {
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
log, // logger
@@ -90,13 +84,9 @@ class HttpLog extends AbstractLifeCycle implements RequestLog {
uri = uri + "?" + qs;
}
- if (user != null && user.isIdentifiedUser()) {
- IdentifiedUser who = (IdentifiedUser) user;
- if (who.getUserName() != null && !who.getUserName().isEmpty()) {
- event.setProperty(P_USER, who.getUserName());
- } else {
- event.setProperty(P_USER, "a/" + who.getAccountId());
- }
+ String user = (String) req.getAttribute(GetUserFilter.REQ_ATTR_KEY);
+ if (user != null) {
+ event.setProperty(P_USER, user);
}
set(event, P_HOST, req.getRemoteAddr());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 1d6d2bb1eb..907624deec 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -18,19 +18,18 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Charsets;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.escape.Escaper;
import com.google.common.html.HtmlEscapers;
import com.google.common.io.ByteStreams;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.pgm.http.jetty.HttpLog.HttpLogFactory;
import com.google.gerrit.reviewdb.client.AuthType;
-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.util.TimeUtil;
import com.google.gwtexpui.linker.server.UserAgentRule;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
@@ -198,7 +197,7 @@ public class JettyServer {
final URI[] listenUrls = listenURLs(cfg);
final boolean reuseAddress = cfg.getBoolean("httpd", "reuseaddress", true);
final int acceptors = cfg.getInt("httpd", "acceptorThreads", 2);
- final AuthType authType = ConfigUtil.getEnum(cfg, "auth", null, "type", AuthType.OPENID);
+ final AuthType authType = cfg.getEnum("auth", null, "type", AuthType.OPENID);
reverseProxy = isReverseProxied(listenUrls);
final Connector[] connectors = new Connector[listenUrls.length];
@@ -579,7 +578,10 @@ public class JettyServer {
p.deleteOnExit();
app.addFilter(new FilterHolder(new Filter() {
+ private final boolean gwtuiRecompile =
+ System.getProperty("gerrit.disable-gwtui-recompile") == null;
private final UserAgentRule rule = new UserAgentRule();
+ private final Set<String> uaInitialized = new HashSet<>();
private String lastTarget;
private long lastTime;
@@ -588,30 +590,32 @@ public class JettyServer {
FilterChain chain) throws IOException, ServletException {
String pkg = "gerrit-gwtui";
String target = "ui_" + rule.select((HttpServletRequest) request);
- String rule = "//" + pkg + ":" + target;
- // TODO(davido): instead of assuming specific Buck's internal
- // target directory for gwt_binary() artifacts, ask Buck for
- // the location of user agent permutation GWT zip, e. g.:
- // $ buck targets --show_output //gerrit-gwtui:ui_safari \
- // | awk '{print $2}'
- String child = String.format("%s/__gwt_binary_%s__", pkg, target);
- File zip = new File(new File(gen, child), target + ".zip");
-
- synchronized (this) {
- try {
- build(root, gen, rule);
- } catch (BuildFailureException e) {
- displayFailure(rule, e.why, (HttpServletResponse) res);
- return;
- }
+ if (gwtuiRecompile || !uaInitialized.contains(target)) {
+ String rule = "//" + pkg + ":" + target;
+ // TODO(davido): instead of assuming specific Buck's internal
+ // target directory for gwt_binary() artifacts, ask Buck for
+ // the location of user agent permutation GWT zip, e. g.:
+ // $ buck targets --show_output //gerrit-gwtui:ui_safari \
+ // | awk '{print $2}'
+ String child = String.format("%s/__gwt_binary_%s__", pkg, target);
+ File zip = new File(new File(gen, child), target + ".zip");
+
+ synchronized (this) {
+ try {
+ build(root, gen, rule);
+ } catch (BuildFailureException e) {
+ displayFailure(rule, e.why, (HttpServletResponse) res);
+ return;
+ }
- if (!target.equals(lastTarget) || lastTime != zip.lastModified()) {
- lastTarget = target;
- lastTime = zip.lastModified();
- unpack(zip, dstwar);
+ if (!target.equals(lastTarget) || lastTime != zip.lastModified()) {
+ lastTarget = target;
+ lastTime = zip.lastModified();
+ unpack(zip, dstwar);
+ }
}
+ uaInitialized.add(target);
}
-
chain.doFilter(request, res);
}
@@ -648,7 +652,7 @@ public class JettyServer {
throws IOException, BuildFailureException {
log.info("buck build " + target);
Properties properties = loadBuckProperties(gen);
- String buck = Objects.firstNonNull(properties.getProperty("buck"), "buck");
+ String buck = MoreObjects.firstNonNull(properties.getProperty("buck"), "buck");
ProcessBuilder proc = new ProcessBuilder(buck, "build", target)
.directory(root)
.redirectErrorStream(true);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
index 4c65218a99..29e61660fc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
@@ -14,8 +14,9 @@
package com.google.gerrit.pgm.init;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -26,10 +27,11 @@ public class AllUsersNameOnInitProvider implements Provider<String> {
@Inject
AllUsersNameOnInitProvider(Section.Factory sections) {
String n = sections.get("gerrit", null).get("allUsers");
- name = Objects.firstNonNull(
+ name = MoreObjects.firstNonNull(
Strings.emptyToNull(n), AllUsersNameProvider.DEFAULT);
}
+ @Override
public String get() {
return name;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index 9638db6a42..c9e76c83e0 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -12,27 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm;
+package com.google.gerrit.pgm.init;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Die;
-import com.google.gerrit.pgm.init.InitFlags;
-import com.google.gerrit.pgm.init.InitModule;
-import com.google.gerrit.pgm.init.InstallPlugins;
-import com.google.gerrit.pgm.init.PluginsDistribution;
-import com.google.gerrit.pgm.init.SitePathInitializer;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InstallPlugins;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.plugins.JarScanner;
import com.google.gerrit.server.schema.SchemaUpdater;
import com.google.gerrit.server.schema.UpdateUI;
+import com.google.gerrit.server.securestore.SecureStore;
+import com.google.gerrit.server.securestore.SecureStoreClassName;
+import com.google.gerrit.server.securestore.SecureStoreProvider;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
@@ -47,12 +51,14 @@ import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Message;
+import com.google.inject.util.Providers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -135,10 +141,26 @@ public class BaseInit extends SiteProgram {
return false;
}
+ protected String getSecureStoreLib() {
+ return null;
+ }
+
+ /**
+ * Invoked before site init is called.
+ *
+ * @param init initializer instance.
+ * @throws Exception
+ */
protected boolean beforeInit(SiteInit init) throws Exception {
return false;
}
+ /**
+ * Invoked after site init is called.
+ *
+ * @param run completed run instance.
+ * @throws Exception
+ */
protected void afterInit(SiteRun run) throws Exception {
}
@@ -168,8 +190,8 @@ public class BaseInit extends SiteProgram {
return false;
}
- static class SiteInit {
- final SitePaths site;
+ public static class SiteInit {
+ public final SitePaths site;
final InitFlags flags;
final ConsoleUI ui;
final SitePathInitializer initializer;
@@ -188,6 +210,18 @@ public class BaseInit extends SiteProgram {
final ConsoleUI ui = getConsoleUI();
final File sitePath = getSitePath();
final List<Module> m = new ArrayList<>();
+ final SecureStoreInitData secureStoreInitData = discoverSecureStoreClass();
+ final String currentSecureStoreClassName = getConfiguredSecureStoreClass();
+
+ if (secureStoreInitData != null && currentSecureStoreClassName != null
+ && !currentSecureStoreClassName.equals(secureStoreInitData.className)) {
+ String err =
+ String.format(
+ "Different secure store was previously configured: %s. "
+ + "Use SwitchSecureStore program to switch between implementations.",
+ currentSecureStoreClassName);
+ die(err, new RuntimeException("secure store mismatch"));
+ }
m.add(new InitModule(standalone, initDb));
m.add(new AbstractModule() {
@@ -196,10 +230,26 @@ public class BaseInit extends SiteProgram {
bind(ConsoleUI.class).toInstance(ui);
bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
List<String> plugins =
- Objects.firstNonNull(getInstallPlugins(), Lists.<String> newArrayList());
+ MoreObjects.firstNonNull(
+ getInstallPlugins(), Lists.<String> newArrayList());
bind(new TypeLiteral<List<String>>() {}).annotatedWith(
InstallPlugins.class).toInstance(plugins);
bind(PluginsDistribution.class).toInstance(pluginsDistribution);
+
+ String secureStoreClassName;
+ if (secureStoreInitData != null) {
+ secureStoreClassName = secureStoreInitData.className;
+ } else {
+ secureStoreClassName = currentSecureStoreClassName;
+ }
+ if (secureStoreClassName != null) {
+ ui.message("Using secure store: %s\n", secureStoreClassName);
+ }
+ bind(SecureStoreInitData.class).toProvider(
+ Providers.of(secureStoreInitData));
+ bind(String.class).annotatedWith(SecureStoreClassName.class)
+ .toProvider(Providers.of(secureStoreClassName));
+ bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
}
});
@@ -230,10 +280,43 @@ public class BaseInit extends SiteProgram {
return ConsoleUI.getInstance(false);
}
- static class SiteRun {
- final ConsoleUI ui;
- final SitePaths site;
- final InitFlags flags;
+ private SecureStoreInitData discoverSecureStoreClass() {
+ String secureStore = getSecureStoreLib();
+ if (Strings.isNullOrEmpty(secureStore)) {
+ return null;
+ }
+
+ try {
+ File secureStoreLib = new File(secureStore);
+ if (!secureStoreLib.exists()) {
+ throw new InvalidSecureStoreException(String.format(
+ "File %s doesn't exist", secureStore));
+ }
+ JarScanner scanner = new JarScanner(secureStoreLib);
+ List<String> secureStores =
+ scanner.findSubClassesOf(SecureStore.class);
+ if (secureStores.isEmpty()) {
+ throw new InvalidSecureStoreException(String.format(
+ "Cannot find class implementing %s interface in %s",
+ SecureStore.class.getName(), secureStore));
+ }
+ if (secureStores.size() > 1) {
+ throw new InvalidSecureStoreException(String.format(
+ "%s has more that one implementation of %s interface",
+ secureStore, SecureStore.class.getName()));
+ }
+ IoUtil.loadJARs(secureStoreLib);
+ return new SecureStoreInitData(secureStoreLib, secureStores.get(0));
+ } catch (IOException e) {
+ throw new InvalidSecureStoreException(String.format("%s is not a valid jar",
+ secureStore));
+ }
+ }
+
+ public static class SiteRun {
+ public final ConsoleUI ui;
+ public final SitePaths site;
+ public final InitFlags flags;
final SchemaUpdater schemaUpdater;
final SchemaFactory<ReviewDb> schema;
final GitRepositoryManager repositoryManager;
@@ -295,18 +378,11 @@ public class BaseInit extends SiteProgram {
System.err.flush();
} else if (ui.yesno(true, "%s\nExecute now", msg)) {
- final JdbcSchema db = (JdbcSchema) schema.open();
- try {
- final JdbcExecutor e = new JdbcExecutor(db);
- try {
- for (String sql : pruneList) {
- e.execute(sql);
- }
- } finally {
- e.close();
+ try (JdbcSchema db = (JdbcSchema) schema.open();
+ JdbcExecutor e = new JdbcExecutor(db)) {
+ for (String sql : pruneList) {
+ e.execute(sql);
}
- } finally {
- db.close();
}
}
}
@@ -319,7 +395,7 @@ public class BaseInit extends SiteProgram {
private Injector createSysInjector(final SiteInit init) {
if (sysInjector == null) {
- final List<Module> modules = new ArrayList<Module>();
+ final List<Module> modules = new ArrayList<>();
modules.add(new AbstractModule() {
@Override
protected void configure() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java
index 96ab1030e4..5a1eab242c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java
@@ -15,6 +15,7 @@
package com.google.gerrit.pgm.init;
import com.google.common.base.Strings;
+import com.google.gerrit.pgm.init.api.InitUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
index 7ac1ed6775..c30edf8d46 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm.init;
+import com.google.gerrit.pgm.init.api.Section;
+
/** Abstraction of initializer for the database section */
interface DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
index 0ea3ff0ded..e20346a6d8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm.init;
+import com.google.gerrit.pgm.init.api.InitUtil;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
new file mode 100644
index 0000000000..c1f00904cd
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2014 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.pgm.init;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.TimeUtil;
+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.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.apache.commons.validator.routines.EmailValidator;
+
+import java.util.Collections;
+
+public class InitAdminUser implements InitStep {
+ private final ConsoleUI ui;
+ private final InitFlags flags;
+ private SchemaFactory<ReviewDb> dbFactory;
+
+ @Inject
+ InitAdminUser(
+ InitFlags flags,
+ ConsoleUI ui) {
+ this.flags = flags;
+ this.ui = ui;
+ }
+
+ @Override
+ public void run() {
+ }
+
+ @Inject(optional = true)
+ void set(SchemaFactory<ReviewDb> dbFactory) {
+ this.dbFactory = dbFactory;
+ }
+
+ @Override
+ public void postRun() throws Exception {
+ AuthType authType =
+ flags.cfg.getEnum(AuthType.values(), "auth", null, "type", null);
+ if (authType != AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) {
+ return;
+ }
+
+ ReviewDb db = dbFactory.open();
+ try {
+ if (db.accounts().anyAccounts().toList().isEmpty()) {
+ ui.header("Gerrit Administrator");
+ if (ui.yesno(true, "Create administrator user")) {
+ Account.Id id = new Account.Id(db.nextAccountId());
+ String username = ui.readString("admin", "username");
+ String name = ui.readString("Administrator", "name");
+ String email = readEmail();
+ String httpPassword = ui.readString("secret", "HTTP password");
+
+ AccountExternalId extUser =
+ new AccountExternalId(id, new AccountExternalId.Key(
+ AccountExternalId.SCHEME_USERNAME, username));
+ if (!Strings.isNullOrEmpty(httpPassword)) {
+ extUser.setPassword(httpPassword);
+ }
+ db.accountExternalIds().insert(Collections.singleton(extUser));
+
+ if (email != null) {
+ AccountExternalId extMailto =
+ new AccountExternalId(id, new AccountExternalId.Key(
+ AccountExternalId.SCHEME_MAILTO, email));
+ extMailto.setEmailAddress(email);
+ db.accountExternalIds().insert(Collections.singleton(extMailto));
+ }
+
+ Account a = new Account(id, TimeUtil.nowTs());
+ a.setFullName(name);
+ a.setPreferredEmail(email);
+ db.accounts().insert(Collections.singleton(a));
+
+ AccountGroupMember m =
+ new AccountGroupMember(new AccountGroupMember.Key(id,
+ new AccountGroup.Id(1)));
+ db.accountGroupMembers().insert(Collections.singleton(m));
+ }
+ }
+ } finally {
+ db.close();
+ }
+ }
+
+ private String readEmail() {
+ String email = ui.readString("admin@example.com", "email");
+ if (email != null && !EmailValidator.getInstance().isValid(email)) {
+ ui.message("error: invalid email address\n");
+ return readEmail();
+ }
+ return email;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index 74884a443b..5c716ef823 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -14,9 +14,11 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.dnOf;
+import static com.google.gerrit.pgm.init.api.InitUtil.dnOf;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
@@ -36,6 +38,7 @@ class InitAuth implements InitStep {
this.ldap = sections.get("ldap", null);
}
+ @Override
public void run() {
ui.header("User Authentication");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
index 7230c9dde2..8da4a033ba 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
@@ -14,8 +14,10 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+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.inject.Inject;
import com.google.inject.Singleton;
@@ -34,6 +36,7 @@ class InitCache implements InitStep {
this.cache = sections.get("cache", null);
}
+ @Override
public void run() {
String path = cache.get("directory");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
index 92cd46d31a..f830854711 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -14,11 +14,13 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.die;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
import com.google.gerrit.launcher.GerritLauncher;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+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.inject.Inject;
import com.google.inject.Singleton;
@@ -47,6 +49,7 @@ class InitContainer implements InitStep {
this.container = sections.get("container", null);
}
+ @Override
public void run() throws FileNotFoundException, IOException {
ui.header("Container Process");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index 0c645527f2..3fcc911245 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -18,7 +18,9 @@ import static com.google.inject.Stage.PRODUCTION;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+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.inject.Binding;
import com.google.inject.Guice;
@@ -51,6 +53,7 @@ class InitDatabase implements InitStep {
this.database = sections.get("database", null);
}
+ @Override
public void run() {
ui.header("SQL Database");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
index dc8a440c7e..067b1031de 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
@@ -14,9 +14,11 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -34,6 +36,7 @@ class InitGitManager implements InitStep {
this.gerrit = sections.get("gerrit", null);
}
+ @Override
public void run() {
ui.header("Git Repositories");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
index 8929a7bc9f..c8f1cd70db 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -15,12 +15,15 @@
package com.google.gerrit.pgm.init;
import static com.google.gerrit.common.FileUtil.chmod;
-import static com.google.gerrit.pgm.init.InitUtil.die;
-import static com.google.gerrit.pgm.init.InitUtil.domainOf;
-import static com.google.gerrit.pgm.init.InitUtil.isAnyAddress;
-import static com.google.gerrit.pgm.init.InitUtil.toURI;
-
-import com.google.gerrit.pgm.util.ConsoleUI;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.domainOf;
+import static com.google.gerrit.pgm.init.api.InitUtil.isAnyAddress;
+import static com.google.gerrit.pgm.init.api.InitUtil.toURI;
+
+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.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
@@ -50,6 +53,7 @@ class InitHttpd implements InitStep {
this.gerrit = sections.get("gerrit", null);
}
+ @Override
public void run() throws IOException, InterruptedException {
ui.header("HTTP Daemon");
@@ -150,10 +154,10 @@ class InitHttpd implements InitStep {
return;
}
- String ssl_pass = flags.sec.getString("http", null, "sslKeyPassword");
+ String ssl_pass = flags.sec.get("http", null, "sslKeyPassword");
if (ssl_pass == null || ssl_pass.isEmpty()) {
ssl_pass = SignedToken.generateRandomKey();
- flags.sec.setString("httpd", null, "sslKeyPassword", ssl_pass);
+ flags.sec.set("httpd", null, "sslKeyPassword", ssl_pass);
}
hostname = ui.readString(hostname, "Certificate server name");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index 9966fdaa6a..acb8a6b9ac 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -15,7 +15,10 @@
package com.google.gerrit.pgm.init;
import com.google.gerrit.lucene.LuceneChangeIndex;
-import com.google.gerrit.pgm.util.ConsoleUI;
+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.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.ChangeSchemas;
import com.google.gerrit.server.index.IndexModule.IndexType;
@@ -43,6 +46,7 @@ class InitIndex implements InitStep {
this.initFlags = initFlags;
}
+ @Override
public void run() throws IOException {
ui.header("Index");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
index 78cfa3b1d1..8fb05ca47e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
@@ -14,7 +14,9 @@
package com.google.gerrit.pgm.init;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.AllProjectsConfig;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -42,7 +44,7 @@ public class InitLabels implements InitStep {
@Override
public void run() throws Exception {
- Config cfg = allProjectsConfig.load();
+ Config cfg = allProjectsConfig.load().getConfig();
if (cfg == null || !cfg.getSubsections(KEY_LABEL).contains(LABEL_VERIFIED)) {
ui.header("Review Labels");
installVerified = ui.yesno(false, "Install Verified label");
@@ -51,7 +53,7 @@ public class InitLabels implements InitStep {
@Override
public void postRun() throws Exception {
- Config cfg = allProjectsConfig.load();
+ Config cfg = allProjectsConfig.load().getConfig();
if (installVerified) {
cfg.setString(KEY_LABEL, LABEL_VERIFIED, KEY_FUNCTION, "MaxWithBlock");
cfg.setStringList(KEY_LABEL, LABEL_VERIFIED, KEY_VALUE,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
index 64960d7e51..8d08520021 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
@@ -14,6 +14,9 @@
package com.google.gerrit.pgm.init;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.binder.LinkedBindingBuilder;
@@ -48,9 +51,9 @@ public class InitModule extends FactoryModule {
if (initDb) {
step().to(InitDatabase.class);
}
- step().to(UpdatePrimaryKeys.class);
step().to(InitIndex.class);
step().to(InitAuth.class);
+ step().to(InitAdminUser.class);
step().to(InitLabels.class);
step().to(InitSendEmail.class);
if (standalone) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index 1372c31e02..fa9f710c9c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -14,9 +14,10 @@
package com.google.gerrit.pgm.init;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.plugins.JarPluginProvider;
import com.google.gerrit.server.plugins.PluginLoader;
@@ -84,13 +85,18 @@ public class InitPluginStepsLoader {
return getPluginInjector(jar).getInstance(initStepClass);
} catch (ClassCastException e) {
ui.message(
- "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
+ "WARN: InitStep from plugin %s does not implement %s (Exception: %s)\n",
jar.getName(), InitStep.class.getName(), e.getMessage());
return null;
+ } catch (NoClassDefFoundError e) {
+ ui.message(
+ "WARN: Failed to run InitStep from plugin %s (Missing class: %s)\n",
+ jar.getName(), e.getMessage());
+ return null;
}
} catch (Exception e) {
ui.message(
- "WARN: Cannot load and get plugin init step for %s (Exception: %s)",
+ "WARN: Cannot load and get plugin init step for %s (Exception: %s)\n",
jar, e.getMessage());
return null;
}
@@ -98,7 +104,8 @@ public class InitPluginStepsLoader {
private Injector getPluginInjector(final File jarFile) throws IOException {
final String pluginName =
- Objects.firstNonNull(JarPluginProvider.getJarPluginName(jarFile),
+ MoreObjects.firstNonNull(
+ JarPluginProvider.getJarPluginName(jarFile),
PluginLoader.nameOf(jarFile));
return initInjector.createChildInjector(new AbstractModule() {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index 2326e48480..72400a42b5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -16,7 +16,9 @@ package com.google.gerrit.pgm.init;
import com.google.common.collect.Lists;
import com.google.gerrit.common.PluginData;
-import com.google.gerrit.pgm.util.ConsoleUI;
+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.server.config.SitePaths;
import com.google.gerrit.server.plugins.JarPluginProvider;
import com.google.inject.Inject;
@@ -26,6 +28,7 @@ import com.google.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Collection;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@@ -102,6 +105,7 @@ public class InitPlugins implements InitStep {
}
private void installPlugins() throws IOException {
+ ui.message("Installing plugins.\n");
List<PluginData> plugins = listPlugins(site, pluginsDistribution);
for (PluginData plugin : plugins) {
String pluginName = plugin.name;
@@ -140,13 +144,19 @@ public class InitPlugins implements InitStep {
}
}
if (plugins.isEmpty()) {
- ui.message("No plugins found.");
+ ui.message("No plugins found to install.\n");
}
}
private void initPlugins() throws Exception {
- for (InitStep initStep : pluginLoader.getInitSteps()) {
- initStep.run();
+ ui.message("Initializing plugins.\n");
+ Collection<InitStep> initSteps = pluginLoader.getInitSteps();
+ if (initSteps.isEmpty()) {
+ ui.message("No plugins found with init steps.\n");
+ } else {
+ for (InitStep initStep : initSteps) {
+ initStep.run();
+ }
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index ae621aeb5b..51eaa222b8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -14,10 +14,12 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.isLocal;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.isLocal;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+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.mail.SmtpEmailSender.Encryption;
import com.google.inject.Inject;
@@ -38,6 +40,7 @@ class InitSendEmail implements InitStep {
this.site = site;
}
+ @Override
public void run() {
ui.header("Email Delivery");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index 9003b305c0..ed18d737a6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -15,10 +15,12 @@
package com.google.gerrit.pgm.init;
import static com.google.gerrit.common.FileUtil.chmod;
-import static com.google.gerrit.pgm.init.InitUtil.die;
-import static com.google.gerrit.pgm.init.InitUtil.hostname;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.hostname;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+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.util.SocketUtil;
import com.google.inject.Inject;
@@ -48,6 +50,7 @@ class InitSshd implements InitStep {
this.sshd = sections.get("sshd", null);
}
+ @Override
public void run() throws Exception {
ui.header("SSH Daemon");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InvalidSecureStoreException.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InvalidSecureStoreException.java
new file mode 100644
index 0000000000..52f794df59
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InvalidSecureStoreException.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2014 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.pgm.init;
+
+public class InvalidSecureStoreException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidSecureStoreException(String message) {
+ super(message);
+ }
+
+ public InvalidSecureStoreException(String message, Throwable why) {
+ super(message, why);
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
index 20034c121e..dfd61717db 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
@@ -14,9 +14,10 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
import com.google.common.base.Strings;
+import com.google.gerrit.pgm.init.api.Section;
class JDBCInitializer implements DatabaseConfigInitializer {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index 7209990d04..ea8f0f11a2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -33,7 +33,7 @@ import java.lang.reflect.Modifier;
@Singleton
class Libraries {
private static final String RESOURCE_FILE =
- "com/google/gerrit/pgm/libraries.config";
+ "com/google/gerrit/pgm/init/libraries.config";
private final Provider<LibraryDownloader> downloadProvider;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index b943ca3a6d..4bf1c886d7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -17,8 +17,8 @@ package com.google.gerrit.pgm.init;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gerrit.common.Die;
-import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.pgm.util.IoUtil;
+import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
index 4d746cce57..0f696b731a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
@@ -14,7 +14,9 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
+
+import com.google.gerrit.pgm.init.api.Section;
public class MaxDbInitializer implements DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
index fe6a4d9335..037b52be74 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
@@ -14,7 +14,9 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
+
+import com.google.gerrit.pgm.init.api.Section;
class MySqlInitializer implements DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java
index 180beb0a07..e58c6ad7ee 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/OracleInitializer.java
@@ -14,7 +14,9 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
+
+import com.google.gerrit.pgm.init.api.Section;
public class OracleInitializer implements DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
index 14256634e2..ffb001714f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
@@ -14,7 +14,9 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.username;
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
+
+import com.google.gerrit.pgm.init.api.Section;
class PostgreSQLInitializer implements DatabaseConfigInitializer {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java
new file mode 100644
index 0000000000..8926759d90
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2014 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.pgm.init;
+
+import java.io.File;
+
+class SecureStoreInitData {
+ final File jarFile;
+ final String className;
+
+ SecureStoreInitData(File jar, String className) {
+ this.className = className;
+ this.jarFile = jar;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 800f2c3177..10c9badf62 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -15,15 +15,19 @@
package com.google.gerrit.pgm.init;
import static com.google.gerrit.common.FileUtil.chmod;
-import static com.google.gerrit.pgm.init.InitUtil.die;
-import static com.google.gerrit.pgm.init.InitUtil.extract;
-import static com.google.gerrit.pgm.init.InitUtil.mkdir;
-import static com.google.gerrit.pgm.init.InitUtil.savePublic;
-import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
-import static com.google.gerrit.pgm.init.InitUtil.version;
-
-import com.google.gerrit.pgm.BaseInit;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.extract;
+import static com.google.gerrit.pgm.init.api.InitUtil.mkdir;
+import static com.google.gerrit.pgm.init.api.InitUtil.savePublic;
+import static com.google.gerrit.pgm.init.api.InitUtil.version;
+
+import com.google.common.io.Files;
+import com.google.gerrit.common.Nullable;
+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.Section;
+import com.google.gerrit.pgm.init.api.Section.Factory;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.mail.OutgoingEmail;
import com.google.inject.Binding;
@@ -32,6 +36,7 @@ import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -41,13 +46,19 @@ public class SitePathInitializer {
private final InitFlags flags;
private final SitePaths site;
private final List<InitStep> steps;
+ private final Factory sectionFactory;
+ private final SecureStoreInitData secureStoreInitData;
@Inject
public SitePathInitializer(final Injector injector, final ConsoleUI ui,
- final InitFlags flags, final SitePaths site) {
+ final InitFlags flags, final SitePaths site,
+ final Section.Factory sectionFactory,
+ final @Nullable SecureStoreInitData secureStoreInitData) {
this.ui = ui;
this.flags = flags;
this.site = site;
+ this.sectionFactory = sectionFactory;
+ this.secureStoreInitData = secureStoreInitData;
this.steps = stepsOf(injector);
}
@@ -82,10 +93,10 @@ public class SitePathInitializer {
step.run();
}
+ saveSecureStore();
savePublic(flags.cfg);
- saveSecure(flags.sec);
- extract(site.gerrit_sh, BaseInit.class, "gerrit.sh");
+ extract(site.gerrit_sh, getClass(), "gerrit.sh");
chmod(0755, site.gerrit_sh);
chmod(0700, site.tmp_dir);
@@ -119,6 +130,15 @@ public class SitePathInitializer {
}
}
+ private void saveSecureStore() throws IOException {
+ if (secureStoreInitData != null) {
+ File dst = new File(site.lib_dir, secureStoreInitData.jarFile.getName());
+ Files.copy(secureStoreInitData.jarFile, dst);
+ Section gerritSection = sectionFactory.get("gerrit", null);
+ gerritSection.set("secureStoreClass", secureStoreInitData.className);
+ }
+ }
+
private void extractMailExample(String orig) throws Exception {
File ex = new File(site.mail_dir, orig + ".example");
extract(ex, OutgoingEmail.class, orig);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
index 97be0c571a..8c1354004c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -14,12 +14,15 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.die;
-import static com.google.gerrit.pgm.init.InitUtil.savePublic;
-import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+import static com.google.gerrit.pgm.init.api.InitUtil.savePublic;
-import com.google.gerrit.pgm.util.ConsoleUI;
+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.Section;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.securestore.SecureStore;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -60,7 +63,7 @@ class UpgradeFrom2_0_x implements InitStep {
private final ConsoleUI ui;
private final FileBasedConfig cfg;
- private final FileBasedConfig sec;
+ private final SecureStore sec;
private final File site_path;
private final File etc_dir;
private final Section.Factory sections;
@@ -86,6 +89,7 @@ class UpgradeFrom2_0_x implements InitStep {
return false;
}
+ @Override
public void run() throws IOException, ConfigInvalidException {
if (!isNeedUpgrade()) {
return;
@@ -113,7 +117,6 @@ class UpgradeFrom2_0_x implements InitStep {
// believed to be empty) file.
//
cfg.load();
- sec.load();
final Properties oldprop = readGerritServerProperties();
if (oldprop != null) {
@@ -136,7 +139,7 @@ class UpgradeFrom2_0_x implements InitStep {
String password = oldprop.getProperty("password");
if (password != null && !password.isEmpty()) {
- sec.setString("database", null, "password", password);
+ sec.set("database", null, "password", password);
}
}
@@ -144,13 +147,12 @@ class UpgradeFrom2_0_x implements InitStep {
values = cfg.getStringList("ldap", null, "password");
cfg.unset("ldap", null, "password");
- sec.setStringList("ldap", null, "password", Arrays.asList(values));
+ sec.setList("ldap", null, "password", Arrays.asList(values));
values = cfg.getStringList("sendemail", null, "smtpPass");
cfg.unset("sendemail", null, "smtpPass");
- sec.setStringList("sendemail", null, "smtpPass", Arrays.asList(values));
+ sec.setList("sendemail", null, "smtpPass", Arrays.asList(values));
- saveSecure(sec);
savePublic(cfg);
}
@@ -247,7 +249,7 @@ class UpgradeFrom2_0_x implements InitStep {
database.set("username", username);
}
if (password != null && !password.isEmpty()) {
- sec.setString("database", null, "password", password);
+ sec.set("database", null, "password", password);
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
index 03f6d1c672..f45a970c67 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GroupList;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.VersionedMetaData;
import com.google.inject.Inject;
@@ -28,21 +30,28 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
public class AllProjectsConfig extends VersionedMetaData {
+
+ private static final Logger log = LoggerFactory.getLogger(AllProjectsConfig.class);
+
private final String project;
private final SitePaths site;
private final InitFlags flags;
private Config cfg;
private ObjectId revision;
+ private GroupList groupList;
@Inject
AllProjectsConfig(AllProjectsNameOnInitProvider allProjects, SitePaths site,
@@ -66,34 +75,53 @@ public class AllProjectsConfig extends VersionedMetaData {
return FileKey.resolve(new File(basePath, project), FS.DETECTED);
}
- public Config load() throws IOException, ConfigInvalidException {
+ public AllProjectsConfig load() throws IOException, ConfigInvalidException {
File path = getPath();
- if (path == null) {
- return null;
+ if (path != null) {
+ Repository repo = new FileRepository(path);
+ try {
+ load(repo);
+ } finally {
+ repo.close();
+ }
}
+ return this;
+ }
- Repository repo = new FileRepository(path);
- try {
- load(repo);
- } finally {
- repo.close();
- }
+ public Config getConfig() {
return cfg;
}
+ public GroupList getGroups() {
+ return groupList;
+ }
+
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
+ groupList = readGroupList();
cfg = readConfig(ProjectConfig.PROJECT_CONFIG);
revision = getRevision();
}
+ private GroupList readGroupList() throws IOException {
+ ValidationError.Sink errors = new ValidationError.Sink() {
+ @Override
+ public void error(ValidationError error) {
+ log.error("Error parsing file " + GroupList.FILE_NAME + ": " + error.getMessage());
+ }
+ };
+ String text = readUTF8(GroupList.FILE_NAME);
+
+ return GroupList.parse(text, errors);
+ }
+
@Override
protected boolean onSave(CommitBuilder commit) throws IOException,
ConfigInvalidException {
throw new UnsupportedOperationException();
}
- void save(String message) throws IOException {
+ public void save(String message) throws IOException {
save(new PersonIdent("Gerrit Initialization", "init@gerrit"), message);
}
@@ -108,36 +136,31 @@ public class AllProjectsConfig extends VersionedMetaData {
throw new IOException("All-Projects does not exist.");
}
- Repository repo = new FileRepository(path);
- try {
+ try (Repository repo = new FileRepository(path)) {
inserter = repo.newObjectInserter();
reader = repo.newObjectReader();
- try {
- RevWalk rw = new RevWalk(reader);
- try {
- RevTree srcTree = revision != null ? rw.parseTree(revision) : null;
- newTree = readTree(srcTree);
- saveConfig(ProjectConfig.PROJECT_CONFIG, cfg);
- ObjectId res = newTree.writeTree(inserter);
- if (res.equals(srcTree)) {
- // If there are no changes to the content, don't create the commit.
- return;
- }
-
- CommitBuilder commit = new CommitBuilder();
- commit.setAuthor(ident);
- commit.setCommitter(ident);
- commit.setMessage(msg);
- commit.setTreeId(res);
- if (revision != null) {
- commit.addParentId(revision);
- }
- ObjectId newRevision = inserter.insert(commit);
- updateRef(repo, ident, newRevision, "commit: " + msg);
- revision = newRevision;
- } finally {
- rw.close();
+ try (RevWalk rw = new RevWalk(reader)) {
+ RevTree srcTree = revision != null ? rw.parseTree(revision) : null;
+ newTree = readTree(srcTree);
+ saveConfig(ProjectConfig.PROJECT_CONFIG, cfg);
+ saveGroupList();
+ ObjectId res = newTree.writeTree(inserter);
+ if (res.equals(srcTree)) {
+ // If there are no changes to the content, don't create the commit.
+ return;
}
+
+ CommitBuilder commit = new CommitBuilder();
+ commit.setAuthor(ident);
+ commit.setCommitter(ident);
+ commit.setMessage(msg);
+ commit.setTreeId(res);
+ if (revision != null) {
+ commit.addParentId(revision);
+ }
+ ObjectId newRevision = inserter.insert(commit);
+ updateRef(repo, ident, newRevision, "commit: " + msg);
+ revision = newRevision;
} finally {
if (inserter != null) {
inserter.close();
@@ -148,9 +171,15 @@ public class AllProjectsConfig extends VersionedMetaData {
reader = null;
}
}
- } finally {
- repo.close();
}
+
+ // we need to invalidate the JGit cache if the group list is invalidated in
+ // an unattended init step
+ RepositoryCache.clear();
+ }
+
+ private void saveGroupList() throws IOException {
+ saveUTF8(GroupList.FILE_NAME, groupList.asText());
}
private void updateRef(Repository repo, PersonIdent ident,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsNameOnInitProvider.java
index 1c9415ade0..9a6faeaf78 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/AllProjectsNameOnInitProvider.java
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.inject.Inject;
@@ -26,10 +26,11 @@ public class AllProjectsNameOnInitProvider implements Provider<String> {
@Inject
AllProjectsNameOnInitProvider(Section.Factory sections) {
String n = sections.get("gerrit", null).get("allProjects");
- name = Objects.firstNonNull(
+ name = MoreObjects.firstNonNull(
Strings.emptyToNull(n), AllProjectsNameProvider.DEFAULT);
}
+ @Override
public String get() {
return name;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
index 3cbf047096..fb0ef2820f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.pgm.init.api;
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
index 267b41a147..2a8155eeee 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
+import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.securestore.SecureStore;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -38,19 +40,19 @@ public class InitFlags {
public boolean skipPlugins;
public final FileBasedConfig cfg;
- public final FileBasedConfig sec;
+ public final SecureStore sec;
public final List<String> installPlugins;
-
+ @VisibleForTesting
@Inject
- InitFlags(final SitePaths site,
+ public InitFlags(final SitePaths site,
+ final SecureStore secureStore,
final @InstallPlugins List<String> installPlugins) throws IOException,
ConfigInvalidException {
+ sec = secureStore;
this.installPlugins = installPlugins;
cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
- sec = new FileBasedConfig(site.secure_config, FS.DETECTED);
cfg.load();
- sec.load();
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
index 5a9a3340e9..250cf5904c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
/** A single step in the site initialization process. */
public interface InitStep {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java
index 7e06b5a156..881208d552 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java
@@ -12,15 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
-import static com.google.gerrit.common.FileUtil.chmod;
import static com.google.gerrit.common.FileUtil.modified;
import com.google.gerrit.common.Die;
import org.eclipse.jgit.internal.storage.file.LockFile;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
@@ -38,60 +36,40 @@ import java.net.UnknownHostException;
import java.nio.ByteBuffer;
/** Utility functions to help initialize a site. */
-class InitUtil {
- static Die die(String why) {
+public class InitUtil {
+ public static Die die(String why) {
return new Die(why);
}
- static Die die(String why, Throwable cause) {
+ public static Die die(String why, Throwable cause) {
return new Die(why, cause);
}
- static void savePublic(final FileBasedConfig sec) throws IOException {
+ public static void savePublic(final FileBasedConfig sec) throws IOException {
if (modified(sec)) {
sec.save();
}
}
- static void saveSecure(final FileBasedConfig sec) throws IOException {
- if (modified(sec)) {
- final byte[] out = Constants.encode(sec.toText());
- final File path = sec.getFile();
- final LockFile lf = new LockFile(path, FS.DETECTED);
- if (!lf.lock()) {
- throw new IOException("Cannot lock " + path);
- }
- try {
- chmod(0600, new File(path.getParentFile(), path.getName() + ".lock"));
- lf.write(out);
- if (!lf.commit()) {
- throw new IOException("Cannot commit write to " + path);
- }
- } finally {
- lf.unlock();
- }
- }
- }
-
- static void mkdir(final File path) {
+ public static void mkdir(final File path) {
if (!path.isDirectory() && !path.mkdir()) {
throw die("Cannot make directory " + path);
}
}
- static String version() {
+ public static String version() {
return com.google.gerrit.common.Version.getVersion();
}
- static String username() {
+ public static String username() {
return System.getProperty("user.name");
}
- static String hostname() {
+ public static String hostname() {
return SystemReader.getInstance().getHostname();
}
- static boolean isLocal(final String hostname) {
+ public static boolean isLocal(final String hostname) {
try {
return InetAddress.getByName(hostname).isLoopbackAddress();
} catch (UnknownHostException e) {
@@ -99,7 +77,7 @@ class InitUtil {
}
}
- static String dnOf(String name) {
+ public static String dnOf(String name) {
if (name != null) {
int p = name.indexOf("://");
if (0 < p) {
@@ -117,7 +95,7 @@ class InitUtil {
return name;
}
- static String domainOf(String name) {
+ public static String domainOf(String name) {
if (name != null) {
int p = name.indexOf("://");
if (0 < p) {
@@ -131,7 +109,7 @@ class InitUtil {
return name;
}
- static void extract(final File dst, final Class<?> sibling,
+ public static void extract(final File dst, final Class<?> sibling,
final String name) throws IOException {
try (InputStream in = open(sibling, name)) {
if (in != null) {
@@ -158,7 +136,7 @@ class InitUtil {
return in;
}
- static void copy(final File dst, final ByteBuffer buf)
+ public static void copy(final File dst, final ByteBuffer buf)
throws FileNotFoundException, IOException {
// If the file already has the content we want to put there,
// don't attempt to overwrite the file.
@@ -196,7 +174,7 @@ class InitUtil {
}
}
- static URI toURI(String url) throws URISyntaxException {
+ public static URI toURI(String url) throws URISyntaxException {
final URI u = new URI(url);
if (isAnyAddress(u)) {
// If the URL uses * it means all addresses on this system, use the
@@ -208,12 +186,12 @@ class InitUtil {
return new URI(url);
}
- static boolean isAnyAddress(final URI u) {
+ public static boolean isAnyAddress(final URI u) {
return u.getHost() == null
&& (u.getAuthority().equals("*") || u.getAuthority().startsWith("*:"));
}
- static int portOf(final URI uri) {
+ public static int portOf(final URI uri) {
int port = uri.getPort();
if (port < 0) {
port = "https".equals(uri.getScheme()) ? 443 : 80;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InstallPlugins.java
index 73db6f5e5c..256892a799 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InstallPlugins.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.inject.BindingAnnotation;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
index d0a89e72fc..fbd8ecde0b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
@@ -12,12 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.pgm.init.api;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.securestore.SecureStore;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -38,19 +37,22 @@ public class Section {
private final ConsoleUI ui;
private final String section;
private final String subsection;
+ private final SecureStore secureStore;
@Inject
public Section(final InitFlags flags, final SitePaths site,
- final ConsoleUI ui, @Assisted("section") final String section,
+ final SecureStore secureStore, final ConsoleUI ui,
+ @Assisted("section") final String section,
@Assisted("subsection") @Nullable final String subsection) {
this.flags = flags;
this.site = site;
this.ui = ui;
this.section = section;
this.subsection = subsection;
+ this.secureStore = secureStore;
}
- String get(String name) {
+ public String get(String name) {
return flags.cfg.getString(section, subsection, name);
}
@@ -116,7 +118,7 @@ public class Section {
public <T extends Enum<?>> T select(final String title, final String name,
final T defValue, final boolean nullIfDefault) {
final boolean set = get(name) != null;
- T oldValue = ConfigUtil.getEnum(flags.cfg, section, subsection, name, defValue);
+ T oldValue = flags.cfg.getEnum(section, subsection, name, defValue);
T newValue = ui.readEnum(oldValue, "%s", title);
if (nullIfDefault && newValue == defValue) {
newValue = null;
@@ -144,7 +146,7 @@ public class Section {
public String password(final String username, final String password) {
final String ov = getSecure(password);
- String user = flags.sec.getString(section, subsection, username);
+ String user = flags.sec.get(section, subsection, username);
if (user == null) {
user = get(username);
}
@@ -189,14 +191,14 @@ public class Section {
}
public String getSecure(String name) {
- return flags.sec.getString(section, subsection, name);
+ return flags.sec.get(section, subsection, name);
}
public void setSecure(String name, String value) {
if (value != null) {
- flags.sec.setString(section, subsection, name, value);
+ secureStore.set(section, subsection, name, value);
} else {
- flags.sec.unset(section, subsection, name);
+ secureStore.unset(section, subsection, name);
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
new file mode 100644
index 0000000000..11ab073e28
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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.pgm.util;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.DisabledChangeHooks;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.GitModule;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidators;
+
+/** Module for batch programs that need git access. */
+public class BatchGitModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ bind(ChangeHooks.class).to(DisabledChangeHooks.class);
+ DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
+ DynamicSet.setOf(binder(), CommitValidationListener.class);
+ factory(CommitValidators.Factory.class);
+ install(new GitModule());
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
new file mode 100644
index 0000000000..23983b769d
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -0,0 +1,138 @@
+// Copyright (C) 2014 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.pgm.util;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+import com.google.common.cache.Cache;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.rules.PrologModule;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountByEmailCacheImpl;
+import com.google.gerrit.server.account.AccountCacheImpl;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.FakeRealm;
+import com.google.gerrit.server.account.GroupCacheImpl;
+import com.google.gerrit.server.account.GroupIncludeCacheImpl;
+import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.change.MergeabilityCacheImpl;
+import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.DisableReverseDnsLookup;
+import com.google.gerrit.server.config.DisableReverseDnsLookupProvider;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GitReceivePackGroups;
+import com.google.gerrit.server.config.GitUploadPackGroups;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.group.GroupModule;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.NoteDbModule;
+import com.google.gerrit.server.patch.DiffExecutorModule;
+import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.CommentLinkInfo;
+import com.google.gerrit.server.project.CommentLinkProvider;
+import com.google.gerrit.server.project.ProjectCacheImpl;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SectionSortCache;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.util.Providers;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Module for programs that perform batch operations on a site.
+ * <p>
+ * Any program that requires this module likely also requires using
+ * {@link ThreadLimiter} to limit the number of threads accessing the database
+ * concurrently.
+ */
+public class BatchProgramModule extends FactoryModule {
+ private final Module reviewDbModule;
+
+ @Inject
+ BatchProgramModule(PerThreadReviewDbModule reviewDbModule) {
+ this.reviewDbModule = reviewDbModule;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected void configure() {
+ install(reviewDbModule);
+ install(new DiffExecutorModule());
+ install(PatchListCacheImpl.module());
+ // Plugins are not loaded and we're just running through each change
+ // once, so don't worry about cache removal.
+ bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
+ .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+ bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
+ .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
+ bind(new TypeLiteral<List<CommentLinkInfo>>() {})
+ .toProvider(CommentLinkProvider.class).in(SINGLETON);
+ bind(String.class).annotatedWith(CanonicalWebUrl.class)
+ .toProvider(CanonicalWebUrlProvider.class);
+ bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class)
+ .toProvider(DisableReverseDnsLookupProvider.class).in(SINGLETON);
+ bind(Realm.class).to(FakeRealm.class);
+ bind(IdentifiedUser.class)
+ .toProvider(Providers.<IdentifiedUser> of(null));
+ bind(ReplacePatchSetSender.Factory.class).toProvider(
+ Providers.<ReplacePatchSetSender.Factory>of(null));
+ bind(CurrentUser.class).to(IdentifiedUser.class);
+ factory(MergeUtil.Factory.class);
+ factory(PatchSetInserter.Factory.class);
+
+ bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
+ .annotatedWith(GitUploadPackGroups.class)
+ .toInstance(Collections.<AccountGroup.UUID> emptySet());
+ bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
+ .annotatedWith(GitReceivePackGroups.class)
+ .toInstance(Collections.<AccountGroup.UUID> emptySet());
+ factory(ChangeControl.AssistedFactory.class);
+ factory(ProjectControl.AssistedFactory.class);
+
+ install(new BatchGitModule());
+ install(new DefaultCacheFactory.Module());
+ install(new GroupModule());
+ install(new NoteDbModule());
+ install(new PrologModule());
+ install(AccountByEmailCacheImpl.module());
+ install(AccountCacheImpl.module());
+ install(GroupCacheImpl.module());
+ install(GroupIncludeCacheImpl.module());
+ install(ProjectCacheImpl.module());
+ install(SectionSortCache.module());
+ install(ChangeKindCacheImpl.module());
+ install(MergeabilityCacheImpl.module());
+ install(TagCache.module());
+ factory(CapabilityControl.Factory.class);
+ factory(ChangeData.Factory.class);
+ factory(ProjectState.Factory.class);
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java
index d807af6f93..6776ca9e7c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java
@@ -25,6 +25,7 @@ public class GuiceLogger {
private static final Handler HANDLER;
static {
HANDLER = new StreamHandler(System.out, new Formatter() {
+ @Override
public String format(LogRecord record) {
return String.format("[Guice %s] %s%n", record.getLevel().getName(),
record.getMessage());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
new file mode 100644
index 0000000000..eb12937931
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2014 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.pgm.util;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Module to bind a single {@link ReviewDb} instance per thread.
+ * <p>
+ * New instances are opened on demand, but are closed only at shutdown.
+ */
+class PerThreadReviewDbModule extends LifecycleModule {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ PerThreadReviewDbModule(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ protected void configure() {
+ final List<ReviewDb> dbs = Collections.synchronizedList(
+ Lists.<ReviewDb> newArrayList());
+ final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
+
+ bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
+ @Override
+ public ReviewDb get() {
+ ReviewDb db = localDb.get();
+ if (db == null) {
+ try {
+ db = schema.open();
+ dbs.add(db);
+ localDb.set(db);
+ } catch (OrmException e) {
+ throw new ProvisionException("unable to open ReviewDb", e);
+ }
+ }
+ return db;
+ }
+ });
+ listener().toInstance(new LifecycleListener() {
+ @Override
+ public void start() {
+ // Do nothing.
+ }
+
+ @Override
+ public void stop() {
+ for (ReviewDb db : dbs) {
+ db.close();
+ }
+ }
+ });
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreException.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SecureStoreException.java
index 01450f8809..9693399531 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreException.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SecureStoreException.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.securestore;
+package com.google.gerrit.pgm.util;
public class SecureStoreException extends RuntimeException {
private static final long serialVersionUID = 5581700510568485065L;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
index 0614b5319c..c713b79869 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
@@ -14,6 +14,7 @@
package com.google.gerrit.pgm.util;
+import com.google.gerrit.common.SiteLibraryLoaderUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.schema.DataSourceProvider;
@@ -24,9 +25,6 @@ import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import java.io.File;
-import java.io.FileFilter;
-import java.util.Arrays;
-import java.util.Comparator;
import javax.sql.DataSource;
@@ -41,40 +39,16 @@ public class SiteLibraryBasedDataSourceProvider extends DataSourceProvider {
@GerritServerConfig Config cfg,
DataSourceProvider.Context ctx,
DataSourceType dst) {
- super(site, cfg, ctx, dst);
+ super(cfg, ctx, dst);
libdir = site.lib_dir;
}
+ @Override
public synchronized DataSource get() {
if (!init) {
- loadSiteLib();
+ SiteLibraryLoaderUtil.loadSiteLib(libdir);
init = true;
}
return super.get();
}
-
- private void loadSiteLib() {
- File[] jars = libdir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File path) {
- String name = path.getName();
- return (name.endsWith(".jar") || name.endsWith(".zip"))
- && path.isFile();
- }
- });
- if (jars != null && 0 < jars.length) {
- Arrays.sort(jars, new Comparator<File>() {
- @Override
- public int compare(File a, File b) {
- // Sort by reverse last-modified time so newer JARs are first.
- int cmp = Long.compare(b.lastModified(), a.lastModified());
- if (cmp != 0) {
- return cmp;
- }
- return a.getName().compareTo(b.getName());
- }
- });
- IoUtil.loadJARs(jars);
- }
- }
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index 3c605108b9..9763b1a3f6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -14,6 +14,7 @@
package com.google.gerrit.pgm.util;
+import static com.google.gerrit.server.config.GerritServerConfigModule.getSecureStoreClassName;
import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
@@ -30,6 +31,7 @@ import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.server.schema.DatabaseModule;
import com.google.gerrit.server.schema.SchemaModule;
+import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.gwtorm.server.OrmException;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
@@ -43,6 +45,7 @@ import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Message;
+import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
@@ -96,6 +99,8 @@ public abstract class SiteProgram extends AbstractProgram {
@Override
protected void configure() {
bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+ bind(String.class).annotatedWith(SecureStoreClassName.class)
+ .toProvider(Providers.of(getConfiguredSecureStoreClass()));
}
};
modules.add(sitePathModule);
@@ -177,15 +182,14 @@ public abstract class SiteProgram extends AbstractProgram {
}
}
+ protected final String getConfiguredSecureStoreClass() {
+ return getSecureStoreClassName(sitePath);
+ }
+
private String getDbType(Provider<DataSource> dsProvider) {
String dbProductName;
- try {
- Connection conn = dsProvider.get().getConnection();
- try {
- dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase();
- } finally {
- conn.close();
- }
+ try (Connection conn = dsProvider.get().getConnection()) {
+ dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase();
} catch (SQLException e) {
throw new RuntimeException(e);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java
new file mode 100644
index 0000000000..44361aa55b
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2014 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.pgm.util;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.schema.DataSourceProvider;
+import com.google.gerrit.server.schema.DataSourceType;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// TODO(dborowitz): Not necessary once we switch to notedb.
+/** Utility to limit threads used by a batch program. */
+public class ThreadLimiter {
+ private static final Logger log =
+ LoggerFactory.getLogger(ThreadLimiter.class);
+
+ public static int limitThreads(Injector dbInjector, int threads) {
+ return limitThreads(
+ dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)),
+ dbInjector.getInstance(DataSourceType.class),
+ threads);
+ }
+
+ private static int limitThreads(Config cfg, DataSourceType dst, int threads) {
+ boolean usePool = cfg.getBoolean("database", "connectionpool",
+ dst.usePool());
+ int poolLimit = cfg.getInt("database", "poollimit",
+ DataSourceProvider.DEFAULT_POOL_LIMIT);
+ if (usePool && threads > poolLimit) {
+ log.warn("Limiting program to " + poolLimit
+ + " threads due to database.poolLimit");
+ return poolLimit;
+ }
+ return threads;
+ }
+
+ private ThreadLimiter() {
+ }
+}
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py
index 92d6e56856..cf6fac99be 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py
@@ -18,7 +18,7 @@
import sys
-def help():
+def print_help():
for (n, v) in vars(sys.modules['__main__']).items():
if not n.startswith("__") and not n in ['help', 'reload'] \
and str(type(v)) != "<type 'javapackage'>" \
@@ -28,4 +28,4 @@ def help():
print "Welcome to the Gerrit Inspector"
print "Enter help() to see the above again, EOF to quit and stop Gerrit"
-help()
+print_help()
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
index 4b9b3ef05f..7e6f943b14 100755
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
@@ -47,7 +47,7 @@
usage() {
me=`basename "$0"`
- echo >&2 "Usage: $me {start|stop|restart|check|status|run|supervise} [-d site]"
+ echo >&2 "Usage: $me {start|stop|restart|check|status|run|supervise|threads} [-d site]"
exit 1
}
@@ -63,6 +63,13 @@ running() {
return 0
}
+thread_dump() {
+ test -f $1 || return 1
+ PID=`cat $1`
+ $JSTACK $PID || return 1
+ return 0;
+}
+
get_config() {
if test -f "$GERRIT_CONFIG" ; then
if test "x$1" = x--int ; then
@@ -258,6 +265,10 @@ if test -z "$JAVA" ; then
exit 1
fi
+if test -z "$JSTACK"; then
+ JSTACK="$JAVA_HOME/bin/jstack"
+fi
+
#####################################################
# Add Gerrit properties to Java VM options.
#####################################################
@@ -326,6 +337,11 @@ RUN_ARGS="-jar $GERRIT_WAR daemon -d $GERRIT_SITE"
if test "`get_config --bool container.slave`" = "true" ; then
RUN_ARGS="$RUN_ARGS --slave"
fi
+DAEMON_OPTS=`get_config --get-all container.daemonOpt`
+if test -n "$DAEMON_OPTS" ; then
+ RUN_ARGS="$RUN_ARGS $DAEMON_OPTS"
+fi
+
if test -n "$JAVA_OPTIONS" ; then
RUN_ARGS="$JAVA_OPTIONS $RUN_ARGS"
fi
@@ -531,6 +547,16 @@ case "$ACTION" in
exit 3
;;
+ threads)
+ if running "$GERRIT_PID" ; then
+ thread_dump "$GERRIT_PID"
+ exit 0
+ else
+ echo "Gerrit not running?"
+ fi
+ exit 3
+ ;;
+
*)
usage
;;
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
index 16bceeeb51..16bceeeb51 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
index 37eeda5d6c..a37c97d27d 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
@@ -17,12 +17,13 @@ package com.google.gerrit.pgm.init;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertNotNull;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Provider;
+
import org.junit.Test;
-import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.FileNotFoundException;
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
index 26c4382758..720d1082cd 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
@@ -24,10 +24,14 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.securestore.SecureStore;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
@@ -38,6 +42,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
+import java.util.List;
public class UpgradeFrom2_0_xTest extends InitTestCase {
@@ -69,13 +74,14 @@ public class UpgradeFrom2_0_xTest extends InitTestCase {
old.setString("sendemail", null, "smtpPass", "email.s3kr3t");
old.save();
+ final InMemorySecureStore secureStore = new InMemorySecureStore();
final InitFlags flags =
- new InitFlags(site, Collections.<String> emptyList());
+ new InitFlags(site, secureStore, Collections.<String> emptyList());
final ConsoleUI ui = createStrictMock(ConsoleUI.class);
Section.Factory sections = new Section.Factory() {
@Override
public Section get(String name, String subsection) {
- return new Section(flags, site, ui, name, subsection);
+ return new Section(flags, site, secureStore, ui, name, subsection);
}
};
@@ -97,18 +103,41 @@ public class UpgradeFrom2_0_xTest extends InitTestCase {
}
FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
- FileBasedConfig sec = new FileBasedConfig(site.secure_config, FS.DETECTED);
cfg.load();
- sec.load();
assertEquals("email.user", cfg.getString("sendemail", null, "smtpUser"));
assertNull(cfg.getString("sendemail", null, "smtpPass"));
- assertEquals("email.s3kr3t", sec.getString("sendemail", null, "smtpPass"));
+ assertEquals("email.s3kr3t", secureStore.get("sendemail", null, "smtpPass"));
assertEquals("ldap.user", cfg.getString("ldap", null, "username"));
assertNull(cfg.getString("ldap", null, "password"));
- assertEquals("ldap.s3kr3t", sec.getString("ldap", null, "password"));
+ assertEquals("ldap.s3kr3t", secureStore.get("ldap", null, "password"));
u.run();
}
+
+ private static class InMemorySecureStore extends SecureStore {
+ private final Config cfg = new Config();
+
+ @Override
+ public String[] getList(String section, String subsection, String name) {
+ return cfg.getStringList(section, subsection, name);
+ }
+
+ @Override
+ public void setList(String section, String subsection, String name,
+ List<String> values) {
+ cfg.setStringList(section, subsection, name, values);
+ }
+
+ @Override
+ public void unset(String section, String subsection, String name) {
+ cfg.unset(section, subsection, name);
+ }
+
+ @Override
+ public Iterable<EntryKey> list() {
+ throw new UnsupportedOperationException("not used by tests");
+ }
+ }
}
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index a6afafb365..80ab9a39c5 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -25,12 +25,16 @@ java_library(
'//gerrit-common:annotations',
'//gerrit-common:server',
'//gerrit-extension-api:api',
+ '//gerrit-gwtexpui:server',
'//gerrit-reviewdb:server',
'//lib:args4j',
'//lib:guava',
'//lib:gwtorm',
'//lib:jsch',
+ '//lib:mime-util',
'//lib:servlet-api-3_1',
+ '//lib/commons:io',
+ '//lib/commons:lang',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet',
@@ -52,7 +56,7 @@ java_binary(
java_doc(
name = 'plugin-api-javadoc',
title = 'Gerrit Review Plugin API Documentation',
- pkg = 'com.google.gerrit',
+ pkgs = ['com.google.gerrit'],
paths = [n for n in SRCS],
srcs = glob([n + '**/*.java' for n in SRCS]),
deps = [
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index c68423465a..17cf79ae9d 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/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>2.10.5</version>
+ <version>2.11.1</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin API</name>
<description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index f62a0dd91e..8f9684a285 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-archetype</artifactId>
- <version>2.10.5</version>
+ <version>2.11.1</version>
<name>Gerrit Code Review - Plugin Archetype</name>
<description>Maven Archetype for Gerrit Plugins</description>
<url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
index cdfd0da490..f384bc1d41 100644
--- a/gerrit-plugin-gwt-archetype/pom.xml
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-gwt-archetype</artifactId>
- <version>2.10.5</version>
+ <version>2.11.1</version>
<name>Gerrit Code Review - Web UI GWT Plugin Archetype</name>
<description>Maven Archetype for Gerrit Web UI GWT Plugins</description>
<url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index b443c4d924..fa86ab40e2 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -25,7 +25,7 @@ limitations under the License.
<defaultValue>http://code.google.com/p/gerrit/</defaultValue>
</requiredProperty>
<requiredProperty key="Gwt-Version">
- <defaultValue>2.6.1</defaultValue>
+ <defaultValue>2.7.0</defaultValue>
</requiredProperty>
<requiredProperty key="gerritApiVersion">
@@ -51,6 +51,18 @@ limitations under the License.
</includes>
</fileSet>
+ <fileSet filtered="true">
+ <directory></directory>
+ <include>.buckconfig</include>
+ <include>BUCK</include>
+ <include>VERSION</include>
+ <include>lib/gerrit/BUCK</include>
+ <include>lib/gwt/BUCK</include>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </fileSet>
+
<fileSet>
<directory></directory>
<includes>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.buckconfig b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.buckconfig
new file mode 100644
index 0000000000..1044c124a9
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.buckconfig
@@ -0,0 +1,14 @@
+[alias]
+ ${pluginName} = //:${pluginName}
+ plugin = //:${pluginName}
+
+[java]
+ src_roots = java, resources
+
+[project]
+ ignore = .git
+
+[cache]
+ mode = dir
+ dir = buck-out/cache
+
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore
index 80d62575a1..43838b01c6 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore
@@ -1,3 +1,7 @@
+/.buckversion
+/.buckd
+/buck-out
+/bucklets
/target
/.classpath
/.project
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
new file mode 100644
index 0000000000..b19312ce35
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
@@ -0,0 +1,23 @@
+include_defs('//bucklets/gerrit_plugin.bucklet')
+
+gerrit_plugin(
+ name = '${pluginName}',
+ srcs = glob(['src/main/java/**/*.java']),
+ resources = glob(['src/main/**/*']),
+ gwt_module = '${package}.HelloPlugin',
+ manifest_entries = [
+ 'Gerrit-PluginName: ${pluginName}',
+ 'Gerrit-ApiType: plugin',
+ 'Gerrit-ApiVersion: ${gerritApiVersion}',
+ 'Gerrit-Module: ${package}.Module',
+ 'Gerrit-SshModule: ${package}.SshModule',
+ 'Gerrit-HttpModule: ${package}.HttpModule',
+ ],
+)
+
+# this is required for bucklets/tools/eclipse/project.py to work
+java_library(
+ name = 'classpath',
+ deps = [':${pluginName}__plugin'],
+)
+
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/VERSION b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/VERSION
new file mode 100644
index 0000000000..8bbb460c0f
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/VERSION
@@ -0,0 +1,5 @@
+# Used by BUCK to include "Implementation-Version" in plugin Manifest.
+# If this file doesn't exist the output of 'git describe' is used
+# instead.
+PLUGIN_VERSION = '${version}'
+
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gerrit/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gerrit/BUCK
new file mode 100644
index 0000000000..0a0d8b93bc
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gerrit/BUCK
@@ -0,0 +1,20 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+VER = '${gerritApiVersion}'
+REPO = MAVEN_LOCAL
+
+maven_jar(
+ name = 'plugin-api',
+ id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
+ attach_source = False,
+ repository = REPO,
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'gwtui-api',
+ id = 'com.google.gerrit:gerrit-plugin-gwtui:' + VER,
+ attach_source = False,
+ repository = REPO,
+ license = 'Apache2.0',
+)
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK
new file mode 100644
index 0000000000..511a8ec303
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK
@@ -0,0 +1,32 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+VERSION = '${Gwt-Version}'
+
+maven_jar(
+ name = 'user',
+ id = 'com.google.gwt:gwt-user:' + VERSION,
+ license = 'Apache2.0',
+ attach_source = False,
+)
+
+maven_jar(
+ name = 'dev',
+ id = 'com.google.gwt:gwt-dev:' + VERSION,
+ license = 'Apache2.0',
+ deps = [
+ ':javax-validation',
+ ':javax-validation_src',
+ ],
+ attach_source = False,
+ exclude = ['org/eclipse/jetty/*'],
+)
+
+maven_jar(
+ name = 'javax-validation',
+ id = 'javax.validation:validation-api:1.0.0.GA',
+ bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e',
+ src_sha1 = '7a561191db2203550fbfa40d534d4997624cd369',
+ license = 'Apache2.0',
+ visibility = [],
+)
+
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md
new file mode 100644
index 0000000000..4c56ed6f5b
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md
@@ -0,0 +1,77 @@
+Build
+=====
+
+This plugin can be built with Buck or Maven.
+
+Buck
+----
+
+Two build modes are supported: Standalone and in Gerrit tree.
+The standalone build mode is recommended, as this mode doesn't require
+the Gerrit tree to exist locally.
+
+
+
+Clone bucklets library:
+
+```
+ git clone https://gerrit.googlesource.com/bucklets
+
+```
+and link it to @PLUGIN@ plugin directory:
+
+```
+ cd @PLUGIN@ && ln -s ../bucklets .
+```
+
+Add link to the .buckversion file:
+
+```
+ cd @PLUGIN@ && ln -s bucklets/buckversion .buckversion
+```
+
+To build the plugin, issue the following command:
+
+
+```
+ buck build plugin
+```
+
+The output is created in
+
+```
+ buck-out/gen/@PLUGIN@.jar
+```
+
+
+Clone or link this plugin to the plugins directory of Gerrit's source
+tree, and issue the command:
+
+```
+ buck build plugins/@PLUGIN@
+```
+
+The output is created in
+
+```
+ buck-out/gen/plugins/@PLUGIN@/@PLUGIN@.jar
+```
+
+This project can be imported into the Eclipse IDE:
+
+```
+ ./tools/eclipse/project.py
+```
+
+Maven
+-----
+
+Note that the Maven build is provided for compatibility reasons, but
+it is considered to be deprecated and will be removed in a future
+version of this plugin.
+
+To build with Maven, run
+
+```
+mvn clean package
+```
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK
index 419176d4a5..3b695ce411 100644
--- a/gerrit-plugin-gwtui/BUCK
+++ b/gerrit-plugin-gwtui/BUCK
@@ -1,4 +1,5 @@
COMMON = ['gerrit-gwtui-common/src/main/java/']
+GWTEXPUI = ['gerrit-gwtexpui/src/main/java/']
SRC = 'src/main/java/com/google/gerrit/'
SRCS = glob([SRC + '**/*.java'])
@@ -11,6 +12,7 @@ java_binary(
name = 'gwtui-api',
deps = [
':gwtui-api-lib',
+ '//gerrit-extension-api:client-lib',
'//gerrit-gwtui-common:client-lib',
],
visibility = ['PUBLIC'],
@@ -26,7 +28,14 @@ java_library(
name = 'gwtui-api-lib2',
srcs = SRCS,
resources = glob(['src/main/**/*']),
- exported_deps = ['//gerrit-gwtui-common:client-lib2'],
+ exported_deps = [
+ '//gerrit-extension-api:client-lib',
+ '//gerrit-gwtexpui:Clippy',
+ '//gerrit-gwtexpui:GlobalKey',
+ '//gerrit-gwtexpui:SafeHtml',
+ '//gerrit-gwtexpui:UserAgent',
+ '//gerrit-gwtui-common:client-lib2',
+ ],
provided_deps = DEPS,
visibility = ['PUBLIC'],
)
@@ -35,6 +44,7 @@ java_binary(
name = 'gwtui-api-src',
deps = [
':gwtui-api-src-lib',
+ '//gerrit-gwtexpui:client-src-lib',
'//gerrit-gwtui-common:client-src-lib',
],
visibility = ['PUBLIC'],
@@ -50,9 +60,16 @@ java_library(
java_doc(
name = 'gwtui-api-javadoc',
title = 'Gerrit Review GWT Extension API Documentation',
- pkg = 'com.google.gerrit',
- paths = ['src/main/java'] + COMMON,
+ pkgs = [
+ 'com.google.gerrit',
+ 'com.google.gwtexpui.clippy',
+ 'com.google.gwtexpui.globalkey',
+ 'com.google.gwtexpui.safehtml',
+ 'com.google.gwtexpui.user',
+ ],
+ paths = COMMON + GWTEXPUI,
srcs = SRCS,
deps = DEPS + ['//gerrit-gwtui-common:client-lib2'],
visibility = ['PUBLIC'],
+ do_it_wrong = True,
)
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
index 170985fc4d..e4fc192c61 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-gwtui</artifactId>
- <version>2.10.5</version>
+ <version>2.11.1</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin GWT UI</name>
<description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
index 046488d969..bf193529e1 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
@@ -54,6 +54,10 @@ public final class Plugin extends JavaScriptObject {
public final native void refreshMenuBar()
/*-{ return this.refreshMenuBar() }-*/;
+ /** Check if user is signed in. */
+ public final native boolean isSignedIn()
+ /*-{ return this.isSignedIn() }-*/;
+
/** Show message in Gerrit's ErrorDialog. */
public final native void showError(String message)
/*-{ return this.showError(message) }-*/;
@@ -92,7 +96,7 @@ public final class Plugin extends JavaScriptObject {
native void _initialized() /*-{ this._success = true }-*/;
native void _loaded() /*-{ this._loadedGwt() }-*/;
- private static native final Plugin install(String u)
+ private static final native Plugin install(String u)
/*-{ return $wnd.Gerrit.installGwt(u) }-*/;
private static final native JavaScriptObject wrap(Screen.EntryPoint b) /*-{
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
index ca1cdbf85f..0f8a16aa84 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
@@ -32,6 +32,7 @@ public abstract class PluginEntryPoint implements EntryPoint {
*/
public abstract void onPluginLoad();
+ @Override
public final void onModuleLoad() {
Plugin self = Plugin.get();
try {
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
index cfbc8a5f2e..8b2a6b8786 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
@@ -109,7 +109,7 @@ public class RestApi {
get(NativeString.unwrap(cb));
}
- private native static void get(String p, JavaScriptObject r)
+ private static native void get(String p, JavaScriptObject r)
/*-{ $wnd.Gerrit.get(p, r) }-*/;
public <T extends JavaScriptObject>
@@ -117,7 +117,7 @@ public class RestApi {
put(path(), wrap(cb));
}
- private native static void put(String p, JavaScriptObject r)
+ private static native void put(String p, JavaScriptObject r)
/*-{ $wnd.Gerrit.put(p, r) }-*/;
public <T extends JavaScriptObject>
@@ -125,7 +125,7 @@ public class RestApi {
put(path(), content, wrap(cb));
}
- private native static
+ private static native
void put(String p, String c, JavaScriptObject r)
/*-{ $wnd.Gerrit.put(p, c, r) }-*/;
@@ -134,7 +134,7 @@ public class RestApi {
put(path(), content, wrap(cb));
}
- private native static
+ private static native
void put(String p, JavaScriptObject c, JavaScriptObject r)
/*-{ $wnd.Gerrit.put(p, c, r) }-*/;
@@ -143,7 +143,7 @@ public class RestApi {
post(path(), content, wrap(cb));
}
- private native static
+ private static native
void post(String p, String c, JavaScriptObject r)
/*-{ $wnd.Gerrit.post(p, c, r) }-*/;
@@ -152,7 +152,7 @@ public class RestApi {
post(path(), content, wrap(cb));
}
- private native static
+ private static native
void post(String p, JavaScriptObject c, JavaScriptObject r)
/*-{ $wnd.Gerrit.post(p, c, r) }-*/;
@@ -160,10 +160,10 @@ public class RestApi {
delete(path(), wrap(cb));
}
- private native static void delete(String p, JavaScriptObject r)
+ private static native void delete(String p, JavaScriptObject r)
/*-{ $wnd.Gerrit.del(p, r) }-*/;
- private native static <T extends JavaScriptObject>
+ private static native <T extends JavaScriptObject>
JavaScriptObject wrap(AsyncCallback<T> b) /*-{
return function(r) {
b.@com.google.gwt.user.client.rpc.AsyncCallback::onSuccess(Ljava/lang/Object;)(r)
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
index 8278280d2f..89bb026368 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
@@ -15,8 +15,6 @@
package com.google.gerrit.plugin.rebind;
-import java.io.PrintWriter;
-
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
@@ -27,6 +25,8 @@ import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
+import java.io.PrintWriter;
+
/**
* Write the top layer in the Gadget bootstrap sandwich and generate a stub
* manifest that will be completed by the linker.
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
index fd7611afe1..559d9de6a1 100644
--- a/gerrit-plugin-js-archetype/pom.xml
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@ limitations under the License.
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-js-archetype</artifactId>
- <version>2.10.5</version>
+ <version>2.11.1</version>
<name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name>
<description>Maven Archetype for Gerrit Web UI JavaScript Plugins</description>
<url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
index ed42f10f0e..cdf800c92a 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
@@ -28,7 +28,7 @@ import java.util.List;
import java.util.Set;
public abstract class PrettyFormatter implements SparseHtmlFile {
- public static abstract class EditFilter {
+ public abstract static class EditFilter {
abstract String getStyleName();
abstract int getBegin(Edit edit);
@@ -82,10 +82,12 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
private Tag lastTag;
private StringBuilder buf;
+ @Override
public SafeHtml getSafeHtmlLine(int lineNo) {
return SafeHtml.asis(content.get(lineNo));
}
+ @Override
public int size() {
return content.size();
}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java
index 1d23009895..6a42a8e651 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java
@@ -39,14 +39,17 @@ public class EditList {
public Iterable<Hunk> getHunks() {
return new Iterable<Hunk>() {
+ @Override
public Iterator<Hunk> iterator() {
return new Iterator<Hunk>() {
private int curIdx;
+ @Override
public boolean hasNext() {
return curIdx < edits.size();
}
+ @Override
public Hunk next() {
final int c = curIdx;
final int e = findCombinedEnd(c);
@@ -54,6 +57,7 @@ public class EditList {
return new Hunk(c, e);
}
+ @Override
public void remove() {
throw new UnsupportedOperationException();
}
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK
index faf80a8c5a..ca2c18c33d 100644
--- a/gerrit-reviewdb/BUCK
+++ b/gerrit-reviewdb/BUCK
@@ -1,4 +1,5 @@
SRC = 'src/main/java/com/google/gerrit/reviewdb/'
+TESTS = 'src/test/java/com/google/gerrit/reviewdb/'
gwt_module(
name = 'client',
@@ -22,3 +23,17 @@ java_library(
],
visibility = ['PUBLIC'],
)
+
+java_test(
+ name = 'client_tests',
+ srcs = glob([TESTS + 'client/**/*.java']),
+ deps = [
+ ':client',
+ '//lib:guava',
+ '//lib:gwtorm',
+ '//lib:junit',
+ '//lib:truth',
+ ],
+ source_under_test = [':client'],
+ visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
index e131f7a319..d4fb3114c5 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
@@ -14,6 +14,8 @@
package com.google.gerrit.reviewdb.client;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_USER;
+
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.IntKey;
@@ -104,6 +106,65 @@ public final class Account {
r.fromString(str);
return r;
}
+
+ public static Id fromRef(String name) {
+ if (name == null) {
+ return null;
+ }
+ if (name.startsWith(REFS_USER)) {
+ return fromRefPart(name.substring(REFS_USER.length()));
+ }
+ return null;
+ }
+
+ /**
+ * Parse an Account.Id out of a part of a ref-name.
+ *
+ * @param name a ref name with the following syntax: {@code "34/1234..."}.
+ * We assume that the caller has trimmed any prefix.
+ */
+ public static Id fromRefPart(String name) {
+ if (name == null) {
+ return null;
+ }
+
+ String[] parts = name.split("/");
+ int n = parts.length;
+ if (n < 2) {
+ return null;
+ }
+
+ // Last 2 digits.
+ int le;
+ for (le = 0; le < parts[0].length(); le++) {
+ if (!Character.isDigit(parts[0].charAt(le))) {
+ return null;
+ }
+ }
+ if (le != 2) {
+ return null;
+ }
+
+ // Full ID.
+ int ie;
+ for (ie = 0; ie < parts[1].length(); ie++) {
+ if (!Character.isDigit(parts[1].charAt(ie))) {
+ if (ie == 0) {
+ return null;
+ } else {
+ break;
+ }
+ }
+ }
+
+ int shard = Integer.parseInt(parts[0]);
+ int id = Integer.parseInt(parts[1].substring(0, ie));
+
+ if (id % 100 != shard) {
+ return null;
+ }
+ return new Account.Id(id);
+ }
}
@Column(id = 1)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
index cf951c1e20..7948080b76 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
@@ -14,6 +14,7 @@
package com.google.gerrit.reviewdb.client;
+import com.google.gerrit.extensions.client.Theme;
import com.google.gwtorm.client.Column;
/** Diff formatting preferences of an account */
@@ -41,6 +42,7 @@ public class AccountDiffPreference {
code = c;
}
+ @Override
public char getCode() {
return code;
}
@@ -55,29 +57,6 @@ public class AccountDiffPreference {
}
}
- public static enum Theme {
- // Light themes
- DEFAULT,
- ECLIPSE,
- ELEGANT,
- NEAT,
- // Dark themes
- MIDNIGHT,
- NIGHT,
- TWILIGHT;
-
- public boolean isDark() {
- switch (this) {
- case MIDNIGHT:
- case NIGHT:
- case TWILIGHT:
- return true;
- default:
- return false;
- }
- }
- }
-
public static AccountDiffPreference createDefault(Account.Id accountId) {
AccountDiffPreference p = new AccountDiffPreference(accountId);
p.setIgnoreWhitespace(Whitespace.IGNORE_NONE);
@@ -92,6 +71,7 @@ public class AccountDiffPreference {
p.setContext(DEFAULT_CONTEXT);
p.setManualReview(false);
p.setHideEmptyPane(false);
+ p.setAutoHideDiffTableHeader(true);
return p;
}
@@ -156,6 +136,9 @@ public class AccountDiffPreference {
@Column(id = 20)
protected boolean hideEmptyPane;
+ @Column(id = 21)
+ protected boolean autoHideDiffTableHeader;
+
protected AccountDiffPreference() {
}
@@ -183,6 +166,7 @@ public class AccountDiffPreference {
this.hideLineNumbers = p.hideLineNumbers;
this.renderEntireFile = p.renderEntireFile;
this.hideEmptyPane = p.hideEmptyPane;
+ this.autoHideDiffTableHeader = p.autoHideDiffTableHeader;
}
public Account.Id getAccountId() {
@@ -343,4 +327,12 @@ public class AccountDiffPreference {
public void setHideEmptyPane(boolean hideEmptyPane) {
this.hideEmptyPane = hideEmptyPane;
}
+
+ public void setAutoHideDiffTableHeader(boolean hide) {
+ autoHideDiffTableHeader = hide;
+ }
+
+ public boolean isAutoHideDiffTableHeader() {
+ return autoHideDiffTableHeader;
+ }
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
index 8181d502af..8f9c726a9e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
@@ -36,6 +36,9 @@ public final class AccountExternalId {
/** Scheme for the username used to authenticate an account, e.g. over SSH. */
public static final String SCHEME_USERNAME = "username:";
+ /** Scheme for external auth used during authentication, e.g. OAuth Token */
+ public static final String SCHEME_EXTERNAL = "external:";
+
public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
@@ -65,6 +68,11 @@ public final class AccountExternalId {
protected void set(String newValue) {
externalId = newValue;
}
+
+ public String getScheme() {
+ int c = externalId.indexOf(':');
+ return 0 < c ? externalId.substring(0, c) : null;
+ }
}
@Column(id = 1, name = Column.NONE)
@@ -126,9 +134,10 @@ public final class AccountExternalId {
}
public String getSchemeRest() {
- String id = getExternalId();
- int c = id.indexOf(':');
- return 0 < c ? id.substring(c + 1) : null;
+ String scheme = key.getScheme();
+ return null != scheme
+ ? getExternalId().substring(scheme.length() + 1)
+ : null;
}
public String getPassword() {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index c6b3089f32..31494bac8a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -68,13 +68,6 @@ public final class AccountGeneralPreferences {
}
}
- public static enum CommentVisibilityStrategy {
- COLLAPSE_ALL,
- EXPAND_MOST_RECENT,
- EXPAND_RECENT,
- EXPAND_ALL
- }
-
public static enum ReviewCategoryStrategy {
NONE,
NAME,
@@ -88,11 +81,6 @@ public final class AccountGeneralPreferences {
UNIFIED_DIFF
}
- public static enum ChangeScreen {
- OLD_UI,
- CHANGE_SCREEN2
- }
-
public static enum TimeFormat {
/** 12-hour clock: 1:15 am, 2:13 pm */
HHMM_12("h:mm a"),
@@ -147,24 +135,18 @@ public final class AccountGeneralPreferences {
@Column(id = 9, length = 10, notNull = false)
protected String timeFormat;
- /**
- * If true display the patch sets in the ChangeScreen in reverse order
- * (show latest patch set on top).
- */
- @Column(id = 10)
- protected boolean reversePatchSetOrder;
+ // DELETED: id = 10 (reversePatchSetOrder)
+ // DELETED: id = 11 (showUserInReview)
@Column(id = 12)
protected boolean relativeDateInChangeTable;
- @Column(id = 13, length = 20, notNull = false)
- protected String commentVisibilityStrategy;
+ // DELETED: id = 13 (commentVisibilityStrategy)
@Column(id = 14, length = 20, notNull = false)
protected String diffView;
- @Column(id = 15, length = 20, notNull = false)
- protected String changeScreen;
+ // DELETED: id = 15 (changeScreen)
@Column(id = 16)
protected boolean sizeBarInChangeTable;
@@ -175,6 +157,9 @@ public final class AccountGeneralPreferences {
@Column(id = 18, length = 20, notNull = false)
protected String reviewCategoryStrategy;
+ @Column(id = 19)
+ protected boolean muteCommonPathPrefixes;
+
public AccountGeneralPreferences() {
}
@@ -240,14 +225,6 @@ public final class AccountGeneralPreferences {
copySelfOnEmail = includeSelfOnEmail;
}
- public boolean isReversePatchSetOrder() {
- return reversePatchSetOrder;
- }
-
- public void setReversePatchSetOrder(final boolean reversePatchSetOrder) {
- this.reversePatchSetOrder = reversePatchSetOrder;
- }
-
public boolean isShowInfoInReviewCategory() {
return getReviewCategoryStrategy() != ReviewCategoryStrategy.NONE;
}
@@ -294,18 +271,6 @@ public final class AccountGeneralPreferences {
reviewCategoryStrategy = strategy.name();
}
- public CommentVisibilityStrategy getCommentVisibilityStrategy() {
- if (commentVisibilityStrategy == null) {
- return CommentVisibilityStrategy.EXPAND_RECENT;
- }
- return CommentVisibilityStrategy.valueOf(commentVisibilityStrategy);
- }
-
- public void setCommentVisibilityStrategy(
- CommentVisibilityStrategy strategy) {
- commentVisibilityStrategy = strategy.name();
- }
-
public DiffView getDiffView() {
if (diffView == null) {
return DiffView.SIDE_BY_SIDE;
@@ -317,14 +282,6 @@ public final class AccountGeneralPreferences {
this.diffView = diffView.name();
}
- public ChangeScreen getChangeScreen() {
- return changeScreen != null ? ChangeScreen.valueOf(changeScreen) : null;
- }
-
- public void setChangeScreen(ChangeScreen ui) {
- changeScreen = ui != null ? ui.name() : null;
- }
-
public boolean isSizeBarInChangeTable() {
return sizeBarInChangeTable;
}
@@ -341,22 +298,29 @@ public final class AccountGeneralPreferences {
this.legacycidInChangeTable = legacycidInChangeTable;
}
+ public boolean isMuteCommonPathPrefixes() {
+ return muteCommonPathPrefixes;
+ }
+
+ public void setMuteCommonPathPrefixes(
+ boolean muteCommonPathPrefixes) {
+ this.muteCommonPathPrefixes = muteCommonPathPrefixes;
+ }
+
public void resetToDefaults() {
maximumPageSize = DEFAULT_PAGESIZE;
showSiteHeader = true;
useFlashClipboard = true;
copySelfOnEmail = false;
- reversePatchSetOrder = false;
reviewCategoryStrategy = null;
downloadUrl = null;
downloadCommand = null;
dateFormat = null;
timeFormat = null;
relativeDateInChangeTable = false;
- commentVisibilityStrategy = null;
diffView = null;
- changeScreen = null;
sizeBarInChangeTable = true;
legacycidInChangeTable = false;
+ muteCommonPathPrefixes = true;
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
index 46f78738e9..284ae0a66a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
@@ -124,13 +124,20 @@ public final class AccountGroup {
@Column(id = 2)
protected Id groupId;
+ // DELETED: id = 3 (ownerGroupId)
+
/** A textual description of the group's purpose. */
@Column(id = 4, length = Integer.MAX_VALUE, notNull = false)
protected String description;
+ // DELETED: id = 5 (groupType)
+ // DELETED: id = 6 (externalName)
+
@Column(id = 7)
protected boolean visibleToAll;
+ // DELETED: id = 8 (emailOnlyAuthors)
+
/** Globally unique identifier name for this group. */
@Column(id = 9)
protected UUID groupUUID;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index dbce048037..fe2cd968ff 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -14,12 +14,16 @@
package com.google.gerrit.reviewdb.client;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.IntKey;
import com.google.gwtorm.client.RowVersion;
import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
+import java.util.Arrays;
/**
* A change proposed to be merged into a {@link Branch}.
@@ -128,8 +132,64 @@ public final class Change {
return r;
}
- public static Id fromRef(final String ref) {
- return PatchSet.Id.fromRef(ref).getParentKey();
+ public static Id fromRef(String ref) {
+ int cs = startIndex(ref);
+ if (cs < 0) {
+ return null;
+ }
+ int ce = nextNonDigit(ref, cs);
+ if (ref.substring(ce).equals(RefNames.META_SUFFIX)
+ || PatchSet.Id.fromRef(ref, ce) >= 0) {
+ return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
+ }
+ return null;
+ }
+
+ static int startIndex(String ref) {
+ if (ref == null || !ref.startsWith(REFS_CHANGES)) {
+ return -1;
+ }
+
+ // Last 2 digits.
+ int ls = REFS_CHANGES.length();
+ int le = nextNonDigit(ref, ls);
+ if (le - ls != 2 || le >= ref.length() || ref.charAt(le) != '/') {
+ return -1;
+ }
+
+ // Change ID.
+ int cs = le + 1;
+ if (cs >= ref.length() || ref.charAt(cs) == '0') {
+ return -1;
+ }
+ int ce = nextNonDigit(ref, cs);
+ if (ce >= ref.length() || ref.charAt(ce) != '/') {
+ return -1;
+ }
+ switch (ce - cs) {
+ case 0:
+ return -1;
+ case 1:
+ if (ref.charAt(ls) != '0'
+ || ref.charAt(ls + 1) != ref.charAt(cs)) {
+ return -1;
+ }
+ break;
+ default:
+ if (ref.charAt(ls) != ref.charAt(ce - 2)
+ || ref.charAt(ls + 1) != ref.charAt(ce - 1)) {
+ return -1;
+ }
+ break;
+ }
+ return cs;
+ }
+
+ static int nextNonDigit(String s, int i) {
+ while (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9') {
+ i++;
+ }
+ return i;
}
}
@@ -221,7 +281,7 @@ public final class Change {
* <li>{@link #ABANDONED} - when the Abandon action is used.
* </ul>
*/
- NEW(STATUS_NEW),
+ NEW(STATUS_NEW, ChangeStatus.NEW),
/**
* Change is open, but has been submitted to the merge queue.
@@ -248,7 +308,7 @@ public final class Change {
* <li>{@link #ABANDONED} - when the Abandon action is used.
* </ul>
*/
- SUBMITTED(STATUS_SUBMITTED),
+ SUBMITTED(STATUS_SUBMITTED, ChangeStatus.SUBMITTED),
/**
* Change is a draft change that only consists of draft patchsets.
@@ -266,7 +326,7 @@ public final class Change {
* <li>{@link #NEW} - when the change is published, it becomes a new change;
* </ul>
*/
- DRAFT(STATUS_DRAFT),
+ DRAFT(STATUS_DRAFT, ChangeStatus.DRAFT),
/**
* Change is closed, and submitted to its destination branch.
@@ -276,7 +336,7 @@ public final class Change {
* replacement patch set. Draft comments however may be published,
* supporting a post-submit review.
*/
- MERGED(STATUS_MERGED),
+ MERGED(STATUS_MERGED, ChangeStatus.MERGED),
/**
* Change is closed, but was not submitted to its destination branch.
@@ -286,14 +346,31 @@ public final class Change {
* a replacement patch set, and it cannot be merged. Draft comments however
* may be published, permitting reviewers to send constructive feedback.
*/
- ABANDONED('A');
+ ABANDONED('A', ChangeStatus.ABANDONED);
+
+ static {
+ boolean ok = true;
+ if (Status.values().length != ChangeStatus.values().length) {
+ ok = false;
+ }
+ for (Status s : Status.values()) {
+ ok &= s.name().equals(s.changeStatus.name());
+ }
+ if (!ok) {
+ throw new IllegalStateException("Mismatched status mapping: "
+ + Arrays.asList(Status.values()) + " != "
+ + Arrays.asList(ChangeStatus.values()));
+ }
+ }
private final char code;
private final boolean closed;
+ private final ChangeStatus changeStatus;
- private Status(final char c) {
+ private Status(char c, ChangeStatus cs) {
code = c;
closed = !(MIN_OPEN <= c && c <= MAX_OPEN);
+ changeStatus = cs;
}
public char getCode() {
@@ -308,6 +385,10 @@ public final class Change {
return closed;
}
+ public ChangeStatus asChangeStatus() {
+ return changeStatus;
+ }
+
public static Status forCode(final char c) {
for (final Status s : Status.values()) {
if (s.code == c) {
@@ -316,6 +397,15 @@ public final class Change {
}
return null;
}
+
+ public static Status forChangeStatus(ChangeStatus cs) {
+ for (Status s : Status.values()) {
+ if (s.changeStatus == cs) {
+ return s;
+ }
+ }
+ return null;
+ }
}
/** Locally assigned unique identifier of the change */
@@ -343,9 +433,7 @@ public final class Change {
@Column(id = 5)
protected Timestamp lastUpdatedOn;
- /** A {@link #lastUpdatedOn} ASC,{@link #changeId} ASC for sorting. */
- @Column(id = 6, length = 16)
- protected String sortKey;
+ // DELETED: id = 6 (sortkey)
@Column(id = 7, name = "owner_account_id")
protected Account.Id owner;
@@ -354,14 +442,14 @@ public final class Change {
@Column(id = 8)
protected Branch.NameKey dest;
- /** Is the change currently open? Set to {@link #status}.isOpen(). */
- @Column(id = 9)
- protected boolean open;
+ // DELETED: id = 9 (open)
/** Current state code; see {@link Status}. */
@Column(id = 10)
protected char status;
+ // DELETED: id = 11 (nbrPatchSets)
+
/** The current patch set. */
@Column(id = 12)
protected int currentPatchSetId;
@@ -374,16 +462,17 @@ public final class Change {
@Column(id = 14, notNull = false)
protected String topic;
+ // DELETED: id = 15 (lastSha1MergeTested)
+ // DELETED: id = 16 (mergeable)
+
/**
- * Null if the change has never been tested.
- * Empty if it has been tested but against a branch that does
- * not exist.
+ * First line of first patch set's commit message.
+ *
+ * Unlike {@link #subject}, this string does not change if future patch sets
+ * change the first line.
*/
- @Column(id = 15, notNull = false)
- protected RevId lastSha1MergeTested;
-
- @Column(id = 16)
- protected boolean mergeable;
+ @Column(id = 17, notNull = false)
+ protected String originalSubject;
protected Change() {
}
@@ -397,7 +486,6 @@ public final class Change {
owner = ownedBy;
dest = forBranch;
setStatus(Status.NEW);
- setLastSha1MergeTested(null);
}
public Change(Change other) {
@@ -406,16 +494,13 @@ public final class Change {
rowVersion = other.rowVersion;
createdOn = other.createdOn;
lastUpdatedOn = other.lastUpdatedOn;
- sortKey = other.sortKey;
owner = other.owner;
dest = other.dest;
- open = other.open;
status = other.status;
currentPatchSetId = other.currentPatchSetId;
subject = other.subject;
+ originalSubject = other.originalSubject;
topic = other.topic;
- mergeable = other.mergeable;
- lastSha1MergeTested = other.lastSha1MergeTested;
}
/** Legacy 32 bit integer identity for a change. */
@@ -453,14 +538,6 @@ public final class Change {
return rowVersion;
}
- public String getSortKey() {
- return sortKey;
- }
-
- public void setSortKey(final String newSortKey) {
- sortKey = newSortKey;
- }
-
public Account.Id getOwner() {
return owner;
}
@@ -477,6 +554,10 @@ public final class Change {
return subject;
}
+ public String getOriginalSubject() {
+ return originalSubject != null ? originalSubject : subject;
+ }
+
/** Get the id of the most current {@link PatchSet} in this change. */
public PatchSet.Id currentPatchSetId() {
if (currentPatchSetId > 0) {
@@ -486,16 +567,27 @@ public final class Change {
}
public void setCurrentPatchSet(final PatchSetInfo ps) {
+ if (originalSubject == null && subject != null) {
+ // Change was created before schema upgrade. Use the last subject
+ // associated with this change, as the most recent discussion will
+ // be under that thread in an email client such as GMail.
+ originalSubject = subject;
+ }
+
currentPatchSetId = ps.getKey().get();
subject = ps.getSubject();
+
+ if (originalSubject == null) {
+ // Newly created changes remember the first commit's subject.
+ originalSubject = subject;
+ }
}
public Status getStatus() {
return Status.forCode(status);
}
- public void setStatus(final Status newStatus) {
- open = newStatus.isOpen();
+ public void setStatus(Status newStatus) {
status = newStatus.getCode();
}
@@ -507,19 +599,13 @@ public final class Change {
this.topic = topic;
}
- public RevId getLastSha1MergeTested() {
- return lastSha1MergeTested;
- }
-
- public void setLastSha1MergeTested(RevId lastSha1MergeTested) {
- this.lastSha1MergeTested = lastSha1MergeTested;
- }
-
- public boolean isMergeable() {
- return mergeable;
- }
-
- public void setMergeable(boolean mergeable) {
- this.mergeable = mergeable;
+ @Override
+ public String toString() {
+ return new StringBuilder(getClass().getSimpleName())
+ .append('{').append(changeId)
+ .append(" (").append(changeKey).append("), ")
+ .append("dest=").append(dest).append(", ")
+ .append("status=").append(status).append('}')
+ .toString();
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java
new file mode 100644
index 0000000000..9a159664be
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2015 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.Column;
+import com.google.gwtorm.client.StringKey;
+
+public class LabelId extends StringKey<com.google.gwtorm.client.Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ public static final LabelId SUBMIT = new LabelId("SUBM");
+
+ @Column(id = 1)
+ protected String id;
+
+ protected LabelId() {
+ }
+
+ public LabelId(final String n) {
+ id = n;
+ }
+
+ @Override
+ public String get() {
+ return id;
+ }
+
+ @Override
+ protected void set(String newValue) {
+ id = newValue;
+ }
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
index f5ecd2e71d..3637914582 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
@@ -93,6 +93,7 @@ public final class Patch {
code = c;
}
+ @Override
public char getCode() {
return code;
}
@@ -151,6 +152,7 @@ public final class Patch {
code = c;
}
+ @Override
public char getCode() {
return code;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
index 08c3f52d00..acf8b453f8 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
@@ -14,6 +14,7 @@
package com.google.gerrit.reviewdb.client;
+import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.StringKey;
@@ -214,6 +215,16 @@ public final class PatchLineComment {
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;
}
@@ -249,6 +260,11 @@ public final class PatchLineComment {
}
@Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PatchLineComment{");
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
index 613978acb0..48623bdfd1 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
@@ -24,28 +24,8 @@ import java.sql.Timestamp;
/** A single revision of a {@link Change}. */
public final class PatchSet {
/** Is the reference name a change reference? */
- public static boolean isRef(final String name) {
- if (name == null || !name.startsWith(REFS_CHANGES)) {
- return false;
- }
- boolean accepted = false;
- int numsFound = 0;
- for (int i = name.length() - 1; i >= REFS_CHANGES.length() - 1; i--) {
- char c = name.charAt(i);
- if (c >= '0' && c <= '9') {
- accepted = (c != '0');
- } else if (c == '/') {
- if (accepted) {
- if (++numsFound == 2) {
- return true;
- }
- accepted = false;
- }
- } else {
- return false;
- }
- }
- return false;
+ public static boolean isRef(String name) {
+ return Id.fromRef(name) != null;
}
public static class Id extends IntKey<Change.Id> {
@@ -105,19 +85,43 @@ public final class PatchSet {
}
/** Parse a PatchSet.Id from a {@link PatchSet#getRefName()} result. */
- public static Id fromRef(String name) {
- if (!name.startsWith(REFS_CHANGES)) {
- throw new IllegalArgumentException("Not a PatchSet.Id: " + name);
+ public static Id fromRef(String ref) {
+ int cs = Change.Id.startIndex(ref);
+ if (cs < 0) {
+ return null;
}
- final String[] parts = name.substring(REFS_CHANGES.length()).split("/");
- final int n = parts.length;
- if (n < 2) {
- throw new IllegalArgumentException("Not a PatchSet.Id: " + name);
+ int ce = Change.Id.nextNonDigit(ref, cs);
+ int patchSetId = fromRef(ref, ce);
+ if (patchSetId < 0) {
+ return null;
}
- final int changeId = Integer.parseInt(parts[n - 2]);
- final int patchSetId = Integer.parseInt(parts[n - 1]);
+ 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);
+ }
}
@Column(id = 1, name = Column.NONE)
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
index 83a9b67d20..ddfc8c6aff 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
@@ -16,53 +16,12 @@ package com.google.gerrit.reviewdb.client;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
-import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
import java.util.Objects;
/** An approval (or negative approval) on a patch set. */
public final class PatchSetApproval {
- public static class LabelId extends
- StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- public static final LabelId SUBMIT = new LabelId("SUBM");
-
- @Column(id = 1)
- protected String id;
-
- protected LabelId() {
- }
-
- public LabelId(final String n) {
- id = n;
- }
-
- @Override
- public String get() {
- return id;
- }
-
- @Override
- protected void set(String newValue) {
- id = newValue;
- }
-
- @Override
- public int hashCode() {
- return get().hashCode();
- }
-
- @Override
- public boolean equals(Object b) {
- if (b instanceof LabelId) {
- return get().equals(((LabelId) b).get());
- }
- return false;
- }
- }
-
public static class Key extends CompoundKey<PatchSet.Id> {
private static final long serialVersionUID = 1L;
@@ -130,6 +89,9 @@ public final class PatchSetApproval {
@Column(id = 3)
protected Timestamp granted;
+ // DELETED: id = 4 (changeOpen)
+ // DELETED: id = 5 (changeSortKey)
+
protected PatchSetApproval() {
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index 1114813d76..209998ac73 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -14,9 +14,9 @@
package com.google.gerrit.reviewdb.client;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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.Column;
import com.google.gwtorm.client.StringKey;
@@ -94,6 +94,8 @@ public final class Project {
protected String themeName;
+ protected InheritableBoolean createNewChangeForAllNotInTarget;
+
protected Project() {
}
@@ -105,6 +107,7 @@ public final class Project {
useSignedOffBy = InheritableBoolean.INHERIT;
requireChangeID = InheritableBoolean.INHERIT;
useContentMerge = InheritableBoolean.INHERIT;
+ createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
}
public Project.NameKey getNameKey() {
@@ -159,6 +162,15 @@ public final class Project {
requireChangeID = cid;
}
+ public InheritableBoolean getCreateNewChangeForAllNotInTarget() {
+ return createNewChangeForAllNotInTarget;
+ }
+
+ public void setCreateNewChangeForAllNotInTarget(
+ InheritableBoolean useAllNotInTarget) {
+ this.createNewChangeForAllNotInTarget = useAllNotInTarget;
+ }
+
public void setMaxObjectSizeLimit(final String limit) {
maxObjectSizeLimit = limit;
}
@@ -212,6 +224,7 @@ public final class Project {
submitType = update.submitType;
state = update.state;
maxObjectSizeLimit = update.maxObjectSizeLimit;
+ createNewChangeForAllNotInTarget = update.createNewChangeForAllNotInTarget;
}
/**
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index 968cfdec1b..072982aead 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -31,6 +31,8 @@ public class RefNames {
/** Configurations of project-specific dashboards (canned search queries). */
public static final String REFS_DASHBOARDS = "refs/meta/dashboards/";
+ public static final String REFS_DRAFT_COMMENTS = "refs/draft-comments/";
+
/**
* Prefix applied to merge commit base nodes.
* <p>
@@ -43,6 +45,9 @@ public class RefNames {
*/
public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
+ /** Suffix of a meta ref in the notedb. */
+ public static final String META_SUFFIX = "/meta";
+
public static String refsUsers(Account.Id accountId) {
StringBuilder r = new StringBuilder();
r.append(REFS_USER);
@@ -57,6 +62,52 @@ public class RefNames {
return r.toString();
}
+ public static String refsDraftComments(Account.Id accountId,
+ Change.Id changeId) {
+ StringBuilder r = new StringBuilder();
+ r.append(REFS_DRAFT_COMMENTS);
+ int n = accountId.get() % 100;
+ if (n < 10) {
+ r.append('0');
+ }
+ r.append(n);
+ r.append('/');
+ r.append(accountId.get());
+ r.append('-');
+ r.append(changeId.get());
+ return r.toString();
+ }
+
+ /**
+ * Returns reference for this change edit with sharded user and change number:
+ * refs/users/UU/UUUU/edit-CCCC/P.
+ *
+ * @param accountId account id
+ * @param changeId change number
+ * @param psId patch set number
+ * @return reference for this change edit
+ */
+ public static String refsEdit(Account.Id accountId, Change.Id changeId,
+ PatchSet.Id psId) {
+ return refsEditPrefix(accountId, changeId) + psId.get();
+ }
+
+ /**
+ * Returns reference prefix for this change edit with sharded user and
+ * change number: refs/users/UU/UUUU/edit-CCCC/.
+ *
+ * @param accountId account id
+ * @param changeId change number
+ * @return reference prefix for this change edit
+ */
+ public static String refsEditPrefix(Account.Id accountId, Change.Id changeId) {
+ return new StringBuilder(refsUsers(accountId))
+ .append("/edit-")
+ .append(changeId.get())
+ .append("/")
+ .toString();
+ }
+
private RefNames() {
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java
index 6603c17767..de81a909cf 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.ResultSet;
/** Access interface for {@link Account}. */
public interface AccountAccess extends Access<Account, Account.Id> {
/** Locate an account by our locally generated identity. */
+ @Override
@PrimaryKey("accountId")
Account get(Account.Id key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountDiffPreferenceAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountDiffPreferenceAccess.java
index fffcf9e89f..499cb77936 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountDiffPreferenceAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountDiffPreferenceAccess.java
@@ -22,6 +22,7 @@ import com.google.gwtorm.server.PrimaryKey;
public interface AccountDiffPreferenceAccess extends Access<AccountDiffPreference, Account.Id> {
+ @Override
@PrimaryKey("accountId")
AccountDiffPreference get(Account.Id key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java
index bf6c0ef8e4..12bd80fa14 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountExternalIdAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountExternalIdAccess extends
Access<AccountExternalId, AccountExternalId.Key> {
+ @Override
@PrimaryKey("key")
AccountExternalId get(AccountExternalId.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
index 1de80f3cab..3b8277364f 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
@@ -23,6 +23,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountGroupAccess extends
Access<AccountGroup, AccountGroup.Id> {
+ @Override
@PrimaryKey("groupId")
AccountGroup get(AccountGroup.Id id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAccess.java
index d1eaed87bc..4fce0cd337 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountGroupByIdAccess extends
Access<AccountGroupById, AccountGroupById.Key> {
+ @Override
@PrimaryKey("key")
AccountGroupById get(AccountGroupById.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAudAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAudAccess.java
index e772c8cf6b..d16d286270 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAudAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupByIdAudAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountGroupByIdAudAccess extends
Access<AccountGroupByIdAud, AccountGroupByIdAud.Key> {
+ @Override
@PrimaryKey("key")
AccountGroupByIdAud get(AccountGroupByIdAud.Key key)
throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAccess.java
index e070d69639..b3296d961b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountGroupMemberAccess extends
Access<AccountGroupMember, AccountGroupMember.Key> {
+ @Override
@PrimaryKey("key")
AccountGroupMember get(AccountGroupMember.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAuditAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAuditAccess.java
index 48c4e2d910..236d1c1e36 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAuditAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupMemberAuditAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountGroupMemberAuditAccess extends
Access<AccountGroupMemberAudit, AccountGroupMemberAudit.Key> {
+ @Override
@PrimaryKey("key")
AccountGroupMemberAudit get(AccountGroupMemberAudit.Key key)
throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java
index 30e685c85c..0a06d07f40 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountGroupNameAccess extends
Access<AccountGroupName, AccountGroup.NameKey> {
+ @Override
@PrimaryKey("name")
AccountGroupName get(AccountGroup.NameKey name) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java
index 80b2dc4ce1..6b9e16097b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountPatchReviewAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountPatchReviewAccess
extends Access<AccountPatchReview, AccountPatchReview.Key> {
+ @Override
@PrimaryKey("key")
AccountPatchReview get(AccountPatchReview.Key id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountProjectWatchAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountProjectWatchAccess.java
index c07346833e..c6f47755f3 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountProjectWatchAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountProjectWatchAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountProjectWatchAccess extends
Access<AccountProjectWatch, AccountProjectWatch.Key> {
+ @Override
@PrimaryKey("key")
AccountProjectWatch get(AccountProjectWatch.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountSshKeyAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountSshKeyAccess.java
index b170a3dd47..6f71ba40b0 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountSshKeyAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountSshKeyAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.ResultSet;
public interface AccountSshKeyAccess extends
Access<AccountSshKey, AccountSshKey.Id> {
+ @Override
@PrimaryKey("id")
AccountSshKey get(AccountSshKey.Id id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
index 54cdbfad77..4e46efbc8b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
@@ -14,9 +14,7 @@
package com.google.gerrit.reviewdb.server;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtorm.server.Access;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.PrimaryKey;
@@ -24,41 +22,10 @@ import com.google.gwtorm.server.Query;
import com.google.gwtorm.server.ResultSet;
public interface ChangeAccess extends Access<Change, Change.Id> {
+ @Override
@PrimaryKey("changeId")
Change get(Change.Id id) throws OrmException;
- @Query("WHERE changeKey = ?")
- ResultSet<Change> byKey(Change.Key key) throws OrmException;
-
- @Query("WHERE changeKey >= ? AND changeKey <= ?")
- ResultSet<Change> byKeyRange(Change.Key reva, Change.Key revb)
- throws OrmException;
-
- @Query("WHERE dest = ? AND changeKey = ?")
- ResultSet<Change> byBranchKey(Branch.NameKey p, Change.Key key)
- throws OrmException;
-
- @Query("WHERE dest.projectName = ?")
- ResultSet<Change> byProject(Project.NameKey p) throws OrmException;
-
- @Query("WHERE dest = ? AND status = '" + Change.STATUS_SUBMITTED
- + "' ORDER BY lastUpdatedOn")
- ResultSet<Change> submitted(Branch.NameKey dest) throws OrmException;
-
- @Query("WHERE status = '" + Change.STATUS_SUBMITTED + "'")
- ResultSet<Change> allSubmitted() throws OrmException;
-
- @Query("WHERE open = true AND dest.projectName = ?")
- ResultSet<Change> byProjectOpenAll(Project.NameKey p) throws OrmException;
-
- @Query("WHERE open = true AND dest = ?")
- ResultSet<Change> byBranchOpenAll(Branch.NameKey p) throws OrmException;
-
- @Query("WHERE open = true AND dest.projectName = ? AND sortKey < ?"
- + " ORDER BY sortKey DESC LIMIT ?")
- ResultSet<Change> byProjectOpenNext(Project.NameKey p, String sortKey,
- int limit) throws OrmException;
-
@Query
ResultSet<Change> all() throws OrmException;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeMessageAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeMessageAccess.java
index 0126a31063..d291fd507a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeMessageAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeMessageAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface ChangeMessageAccess extends
Access<ChangeMessage, ChangeMessage.Key> {
+ @Override
@PrimaryKey("key")
ChangeMessage get(ChangeMessage.Key id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchLineCommentAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchLineCommentAccess.java
index a5842deccc..81f3e5739b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchLineCommentAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchLineCommentAccess.java
@@ -26,6 +26,7 @@ import com.google.gwtorm.server.ResultSet;
public interface PatchLineCommentAccess extends
Access<PatchLineComment, PatchLineComment.Key> {
+ @Override
@PrimaryKey("key")
PatchLineComment get(PatchLineComment.Key id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java
index 703edbb391..36e969e282 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.Query;
import com.google.gwtorm.server.ResultSet;
public interface PatchSetAccess extends Access<PatchSet, PatchSet.Id> {
+ @Override
@PrimaryKey("id")
PatchSet get(PatchSet.Id id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java
index ac2c8491f8..02459d97f9 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAncestorAccess.java
@@ -14,7 +14,7 @@
package com.google.gerrit.reviewdb.server;
-import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.client.RevId;
@@ -26,6 +26,7 @@ import com.google.gwtorm.server.ResultSet;
public interface PatchSetAncestorAccess extends
Access<PatchSetAncestor, PatchSetAncestor.Id> {
+ @Override
@PrimaryKey("key")
PatchSetAncestor get(PatchSetAncestor.Id key) throws OrmException;
@@ -33,7 +34,7 @@ public interface PatchSetAncestorAccess extends
ResultSet<PatchSetAncestor> ancestorsOf(PatchSet.Id id) throws OrmException;
@Query("WHERE key.patchSetId.changeId = ?")
- ResultSet<PatchSetAncestor> byChange(Id id) throws OrmException;
+ ResultSet<PatchSetAncestor> byChange(Change.Id id) throws OrmException;
@Query("WHERE key.patchSetId = ?")
ResultSet<PatchSetAncestor> byPatchSet(PatchSet.Id id) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java
index e90e05a13b..cddee73626 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java
@@ -26,6 +26,7 @@ import com.google.gwtorm.server.ResultSet;
public interface PatchSetApprovalAccess extends
Access<PatchSetApproval, PatchSetApproval.Key> {
+ @Override
@PrimaryKey("key")
PatchSetApproval get(PatchSetApproval.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SchemaVersionAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SchemaVersionAccess.java
index 470f8c6819..dda8d5c12a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SchemaVersionAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SchemaVersionAccess.java
@@ -22,6 +22,7 @@ import com.google.gwtorm.server.PrimaryKey;
/** Access interface for {@link CurrentSchemaVersion}. */
public interface SchemaVersionAccess extends
Access<CurrentSchemaVersion, CurrentSchemaVersion.Key> {
+ @Override
@PrimaryKey("singleton")
CurrentSchemaVersion get(CurrentSchemaVersion.Key key) throws OrmException;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/StarredChangeAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/StarredChangeAccess.java
index 4010dae367..5f57fe73b6 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/StarredChangeAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/StarredChangeAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface StarredChangeAccess extends
Access<StarredChange, StarredChange.Key> {
+ @Override
@PrimaryKey("key")
StarredChange get(StarredChange.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java
index 8c67009c71..b25e4066c0 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java
@@ -25,6 +25,7 @@ import com.google.gwtorm.server.ResultSet;
public interface SubmoduleSubscriptionAccess extends
Access<SubmoduleSubscription, SubmoduleSubscription.Key> {
+ @Override
@PrimaryKey("key")
SubmoduleSubscription get(SubmoduleSubscription.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SystemConfigAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SystemConfigAccess.java
index 4b2ed74157..3bc49dd756 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SystemConfigAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SystemConfigAccess.java
@@ -24,6 +24,7 @@ import com.google.gwtorm.server.ResultSet;
/** Access interface for {@link SystemConfig}. */
public interface SystemConfigAccess extends
Access<SystemConfig, SystemConfig.Key> {
+ @Override
@PrimaryKey("singleton")
SystemConfig get(SystemConfig.Key key) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
index 0b7f2c14f1..a62c762f5a 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
@@ -68,24 +68,6 @@ ON account_project_watches (project_name);
-- *********************************************************************
--- ChangeAccess
--- covers: submitted, allSubmitted
-CREATE INDEX changes_submitted
-ON changes (status, dest_project_name, dest_branch_name, last_updated_on);
-
--- covers: byProjectOpenAll
-CREATE INDEX changes_byProjectOpen
-ON changes (open, dest_project_name, sort_key);
-
--- covers: byProject
-CREATE INDEX changes_byProject
-ON changes (dest_project_name);
-
-CREATE INDEX changes_key
-ON changes (change_key);
-
-
--- *********************************************************************
-- ChangeMessageAccess
-- @PrimaryKey covers: byChange
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
index 5faa71b34d..f88c169656 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
@@ -75,28 +75,6 @@ ON account_project_watches (project_name)
-- *********************************************************************
--- ChangeAccess
--- covers: submitted, allSubmitted
-CREATE INDEX changes_submitted
-ON changes (status, dest_project_name, dest_branch_name, last_updated_on)
-#
-
--- covers: byProjectOpenPrev, byProjectOpenNext
-CREATE INDEX changes_byProjectOpen
-ON changes (open, dest_project_name, sort_key)
-#
-
--- covers: byProject
-CREATE INDEX changes_byProject
-ON changes (dest_project_name)
-#
-
-CREATE INDEX changes_key
-ON changes (change_key)
-#
-
-
--- *********************************************************************
-- ChangeMessageAccess
-- @PrimaryKey covers: byChange
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index 25e3fae86d..a6b21ee78e 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -116,26 +116,6 @@ ON account_project_watches (project_name);
-- *********************************************************************
--- ChangeAccess
--- covers: submitted, allSubmitted
-CREATE INDEX changes_submitted
-ON changes (dest_project_name, dest_branch_name, last_updated_on)
-WHERE status = 's';
-
--- covers: byProjectOpenAll
-CREATE INDEX changes_byProjectOpen
-ON changes (dest_project_name, sort_key)
-WHERE open = 'Y';
-
--- covers: byProject
-CREATE INDEX changes_byProject
-ON changes (dest_project_name);
-
-CREATE INDEX changes_key
-ON changes (change_key);
-
-
--- *********************************************************************
-- ChangeMessageAccess
-- @PrimaryKey covers: byChange
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
new file mode 100644
index 0000000000..e8dc5e01e7
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2014 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class AccountTest {
+ @Test
+ public void parseRefName() {
+ assertRef(1, "refs/users/01/1");
+ assertRef(1, "refs/users/01/1-drafts");
+ assertRef(1, "refs/users/01/1-drafts/2");
+ assertRef(1, "refs/users/01/1/edit/2");
+
+ assertNotRef(null);
+ assertNotRef("");
+
+ // Invalid characters.
+ assertNotRef("refs/users/01a/1");
+ assertNotRef("refs/users/01/a1");
+
+ // Mismatched shard.
+ assertNotRef("refs/users/01/23");
+
+ // Shard too short.
+ assertNotRef("refs/users/1/1");
+ }
+
+ @Test
+ public void parseRefNameParts() {
+ assertRefPart(1, "01/1");
+ assertRefPart(1, "01/1-drafts");
+ assertRefPart(1, "01/1-drafts/2");
+
+ assertNotRefPart(null);
+ assertNotRefPart("");
+
+ // This method assumes that the common prefix "refs/users/" will be removed.
+ assertNotRefPart("refs/users/01/1");
+
+ // Invalid characters.
+ assertNotRefPart("01a/1");
+ assertNotRefPart("01/a1");
+
+ // Mismatched shard.
+ assertNotRefPart("01/23");
+
+ // Shard too short.
+ assertNotRefPart("1/1");
+ }
+
+ private static void assertRef(int accountId, String refName) {
+ assertEquals(new Account.Id(accountId), Account.Id.fromRef(refName));
+ }
+
+ private static void assertNotRef(String refName) {
+ assertNull(Account.Id.fromRef(refName));
+ }
+
+ private static void assertRefPart(int accountId, String refName) {
+ assertEquals(new Account.Id(accountId), Account.Id.fromRefPart(refName));
+ }
+
+ private static void assertNotRefPart(String refName) {
+ assertNull(Account.Id.fromRefPart(refName));
+ }
+}
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
new file mode 100644
index 0000000000..218d04f9a3
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2014 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class ChangeTest {
+ @Test
+ public void parseInvalidRefNames() {
+ assertNotRef(null);
+ assertNotRef("");
+ assertNotRef("01/1/1");
+ assertNotRef("HEAD");
+ assertNotRef("refs/tags/v1");
+ }
+
+ @Test
+ public void parsePatchSetRefNames() {
+ assertRef(1, "refs/changes/01/1/1");
+ assertRef(1234, "refs/changes/34/1234/56");
+
+ // Invalid characters.
+ assertNotRef("refs/changes/0x/1/1");
+ assertNotRef("refs/changes/01/x/1");
+ assertNotRef("refs/changes/01/1/x");
+
+ // Truncations.
+ assertNotRef("refs/changes/");
+ assertNotRef("refs/changes/1");
+ assertNotRef("refs/changes/01");
+ assertNotRef("refs/changes/01/");
+ assertNotRef("refs/changes/01/1/");
+ assertNotRef("refs/changes/01/1/1/");
+ assertNotRef("refs/changes/01//1/1");
+
+ // Leading zeroes.
+ assertNotRef("refs/changes/01/01/1");
+ assertNotRef("refs/changes/01/1/01");
+
+ // Mismatched last 2 digits.
+ assertNotRef("refs/changes/35/1234/56");
+
+ // Something other than patch set after change.
+ assertNotRef("refs/changes/34/1234/0");
+ assertNotRef("refs/changes/34/1234/foo");
+ assertNotRef("refs/changes/34/1234|56");
+ assertNotRef("refs/changes/34/1234foo");
+ }
+
+ @Test
+ public void parseChangeMetaRefNames() {
+ assertRef(1, "refs/changes/01/1/meta");
+ assertRef(1234, "refs/changes/34/1234/meta");
+
+ assertNotRef("refs/changes/01/1/met");
+ assertNotRef("refs/changes/01/1/META");
+ assertNotRef("refs/changes/01/1/1/meta");
+ }
+
+ private static void assertRef(int changeId, String refName) {
+ assertEquals(new Change.Id(changeId), Change.Id.fromRef(refName));
+ }
+
+ private static void assertNotRef(String refName) {
+ assertNull(Change.Id.fromRef(refName));
+ }
+}
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java
new file mode 100644
index 0000000000..eba08c80bd
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2015 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 static com.google.common.truth.Truth.assertThat;
+
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PatchSetApprovalTest {
+ static {
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+ }
+
+ @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 k2 = new PatchSetApproval.Key(
+ new PatchSet.Id(new Change.Id(1), 2),
+ new Account.Id(3),
+ new LabelId("My-Label"));
+ PatchSetApproval.Key k3 = new PatchSetApproval.Key(
+ new PatchSet.Id(new Change.Id(1), 2),
+ new Account.Id(3),
+ new LabelId("Other-Label"));
+
+ assertThat(k2).isEqualTo(k1);
+ assertThat(k3).isNotEqualTo(k1);
+ assertThat(k2.hashCode()).isEqualTo(k1.hashCode());
+ assertThat(k3.hashCode()).isNotEqualTo(k1.hashCode());
+
+ Map<PatchSetApproval.Key, String> map = new HashMap<>();
+ map.put(k1, "k1");
+ map.put(k2, "k2");
+ map.put(k3, "k3");
+ assertThat(map).containsKey(k1);
+ assertThat(map).containsKey(k2);
+ assertThat(map).containsKey(k3);
+ assertThat(map).containsEntry(k1, "k2");
+ assertThat(map).containsEntry(k2, "k2");
+ assertThat(map).containsEntry(k3, "k3");
+ }
+}
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java
new file mode 100644
index 0000000000..33da24a6ef
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/PatchSetTest.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2014 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class PatchSetTest {
+ @Test
+ public void parseRefNames() {
+ assertRef(1, 1, "refs/changes/01/1/1");
+ assertRef(1234, 56, "refs/changes/34/1234/56");
+
+ // Not even close.
+ assertNotRef(null);
+ assertNotRef("");
+ assertNotRef("01/1/1");
+ assertNotRef("HEAD");
+ assertNotRef("refs/tags/v1");
+
+ // Invalid characters.
+ assertNotRef("refs/changes/0x/1/1");
+ assertNotRef("refs/changes/01/x/1");
+ assertNotRef("refs/changes/01/1/x");
+
+ // Truncations.
+ assertNotRef("refs/changes/");
+ assertNotRef("refs/changes/1");
+ assertNotRef("refs/changes/01");
+ assertNotRef("refs/changes/01/");
+ assertNotRef("refs/changes/01/1/");
+ assertNotRef("refs/changes/01/1/1/");
+ assertNotRef("refs/changes/01//1/1");
+
+ // Leading zeroes.
+ assertNotRef("refs/changes/01/01/1");
+ assertNotRef("refs/changes/01/1/01");
+
+ // Mismatched last 2 digits.
+ assertNotRef("refs/changes/35/1234/56");
+
+ // Something other than patch set after change.
+ assertNotRef("refs/changes/34/1234/0");
+ assertNotRef("refs/changes/34/1234/foo");
+ assertNotRef("refs/changes/34/1234|56");
+ assertNotRef("refs/changes/34/1234foo");
+ }
+
+ private static void assertRef(int changeId, int psId, String refName) {
+ assertTrue(PatchSet.isRef(refName));
+ assertEquals(new PatchSet.Id(new Change.Id(changeId), psId),
+ PatchSet.Id.fromRef(refName));
+ }
+
+ private static void assertNotRef(String refName) {
+ assertFalse(PatchSet.isRef(refName));
+ assertNull(PatchSet.Id.fromRef(refName));
+ }
+}
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 7384cfd101..7202dc3639 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -41,14 +41,12 @@ java_library(
'//lib:jsch',
'//lib:juniversalchardet',
'//lib:mime-util',
- '//lib/ow2:ow2-asm',
- '//lib/ow2:ow2-asm-tree',
- '//lib/ow2:ow2-asm-util',
'//lib:parboiled-core',
'//lib:pegdown',
'//lib:protobuf',
'//lib:velocity',
'//lib/antlr:java_runtime',
+ '//lib/auto:auto-value',
'//lib/commons:codec',
'//lib/commons:dbcp',
'//lib/commons:lang',
@@ -62,10 +60,13 @@ java_library(
'//lib/joda:joda-time',
'//lib/log:api',
'//lib/log:log4j',
- '//lib/prolog:prolog-cafe',
'//lib/lucene:analyzers-common',
'//lib/lucene:core',
'//lib/lucene:query-parser',
+ '//lib/ow2:ow2-asm',
+ '//lib/ow2:ow2-asm-tree',
+ '//lib/ow2:ow2-asm-util',
+ '//lib/prolog:prolog-cafe',
],
provided_deps = [
'//lib:servlet-api-3_1',
@@ -130,6 +131,7 @@ java_library(
srcs = PROLOG_TEST_CASE,
deps = [
':server',
+ '//gerrit-common:server',
'//lib:junit',
'//lib/guice:guice',
'//lib/prolog:prolog-cafe',
@@ -147,8 +149,10 @@ java_test(
'//gerrit-common:server',
'//gerrit-reviewdb:server',
'//gerrit-server/src/main/prolog:common',
+ '//lib:guava',
'//lib:gwtorm',
'//lib:junit',
+ '//lib:truth',
'//lib/jgit:jgit',
'//lib/guice:guice',
'//lib/prolog:prolog-cafe',
@@ -175,6 +179,7 @@ java_test(
'//lib:guava',
'//lib:gwtorm',
'//lib:junit',
+ '//lib:truth',
'//lib/antlr:java_runtime',
'//lib/guice:guice',
'//lib/jgit:jgit',
@@ -186,6 +191,7 @@ java_test(
java_test(
name = 'server_tests',
+ labels = ['server'],
srcs = glob(
['src/test/java/**/*.java'],
excludes = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE + QUERY_TESTS
@@ -203,11 +209,14 @@ java_test(
'//lib:guava',
'//lib:gwtorm',
'//lib:junit',
+ '//lib:truth',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/jgit:jgit',
'//lib/jgit:junit',
'//lib/joda:joda-time',
+ '//lib:parboiled-core',
+ '//lib:parboiled-java',
'//lib/prolog:prolog-cafe',
],
source_under_test = [':server'],
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
index cdb24e76b0..e25b7cb924 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
@@ -14,12 +14,13 @@
package com.google.gerrit.audit;
-import com.google.common.base.Objects;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.util.TimeUtil;
public class AuditEvent {
@@ -36,41 +37,14 @@ public class AuditEvent {
public final long elapsed;
public final UUID uuid;
- public static class UUID {
-
- protected final String uuid;
-
- protected UUID() {
- uuid = String.format("audit:%s", java.util.UUID.randomUUID().toString());
- }
-
- public UUID(final String n) {
- uuid = n;
- }
-
- public String get() {
- return uuid;
- }
-
- @Override
- public int hashCode() {
- return uuid.hashCode();
+ @AutoValue
+ public abstract static class UUID {
+ private static UUID create() {
+ return new AutoValue_AuditEvent_UUID(
+ String.format("audit:%s", java.util.UUID.randomUUID().toString()));
}
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof UUID)) {
- return false;
- }
-
- return uuid.equals(((UUID) obj).uuid);
- }
+ public abstract String uuid();
}
/**
@@ -87,13 +61,13 @@ public class AuditEvent {
Multimap<String, ?> params, Object result) {
Preconditions.checkNotNull(what, "what is a mandatory not null param !");
- this.sessionId = Objects.firstNonNull(sessionId, UNKNOWN_SESSION_ID);
+ this.sessionId = MoreObjects.firstNonNull(sessionId, UNKNOWN_SESSION_ID);
this.who = who;
this.what = what;
this.when = when;
this.timeAtStart = this.when;
- this.params = Objects.firstNonNull(params, EMPTY_PARAMS);
- this.uuid = new UUID();
+ this.params = MoreObjects.firstNonNull(params, EMPTY_PARAMS);
+ this.uuid = UUID.create();
this.result = result;
this.elapsed = TimeUtil.nowMs() - timeAtStart;
}
@@ -116,6 +90,6 @@ public class AuditEvent {
@Override
public String toString() {
return String.format("AuditEvent UUID:%s, SID:%s, TS:%d, who:%s, what:%s",
- uuid.get(), sessionId, when, who, what);
+ uuid.uuid(), sessionId, when, who, what);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
index dc870acfc5..89b51f81f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
@@ -22,6 +22,7 @@ public class AuditModule extends AbstractModule {
@Override
protected void configure() {
DynamicSet.setOf(binder(), AuditListener.class);
+ DynamicSet.setOf(binder(), GroupMemberAuditListener.class);
bind(AuditService.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
index a992aa130a..4844045af7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
@@ -15,16 +15,29 @@
package com.google.gerrit.audit;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
@Singleton
public class AuditService {
+ private static final Logger log = LoggerFactory.getLogger(AuditService.class);
+
private final DynamicSet<AuditListener> auditListeners;
+ private final DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners;
@Inject
- public AuditService(DynamicSet<AuditListener> auditListeners) {
+ public AuditService(DynamicSet<AuditListener> auditListeners,
+ DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners) {
this.auditListeners = auditListeners;
+ this.groupMemberAuditListeners = groupMemberAuditListeners;
}
public void dispatch(AuditEvent action) {
@@ -32,4 +45,48 @@ public class AuditService {
auditListener.onAuditableAction(action);
}
}
+
+ public void dispatchAddAccountsToGroup(Account.Id actor,
+ Collection<AccountGroupMember> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onAddAccountsToGroup(actor, added);
+ } catch (RuntimeException e) {
+ log.error("failed to log add accounts to group event", e);
+ }
+ }
+ }
+
+ public void dispatchDeleteAccountsFromGroup(Account.Id actor,
+ Collection<AccountGroupMember> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onDeleteAccountsFromGroup(actor, removed);
+ } catch (RuntimeException e) {
+ log.error("failed to log delete accounts from group event", e);
+ }
+ }
+ }
+
+ public void dispatchAddGroupsToGroup(Account.Id actor,
+ Collection<AccountGroupById> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onAddGroupsToGroup(actor, added);
+ } catch (RuntimeException e) {
+ log.error("failed to log add groups to group event", e);
+ }
+ }
+ }
+
+ public void dispatchDeleteGroupsFromGroup(Account.Id actor,
+ Collection<AccountGroupById> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onDeleteGroupsFromGroup(actor, removed);
+ } catch (RuntimeException e) {
+ log.error("failed to log delete groups from group event", e);
+ }
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java
new file mode 100644
index 0000000000..1269f4ab97
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 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.audit;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+
+import java.util.Collection;
+
+@ExtensionPoint
+public interface GroupMemberAuditListener {
+
+ void onAddAccountsToGroup(Account.Id actor,
+ Collection<AccountGroupMember> added);
+
+ void onDeleteAccountsFromGroup(Account.Id actor,
+ Collection<AccountGroupMember> removed);
+
+ void onAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added);
+
+ void onDeleteGroupsFromGroup(Account.Id actor,
+ Collection<AccountGroupById> deleted);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index b8d45f6ecb..8bd082d03e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -14,6 +14,8 @@
package com.google.gerrit.common;
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.LabelType;
@@ -27,7 +29,7 @@ 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.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.AnonymousCowardName;
@@ -35,12 +37,13 @@ import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.data.ApprovalAttribute;
import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeEvent;
import com.google.gerrit.server.events.ChangeMergedEvent;
import com.google.gerrit.server.events.ChangeRestoredEvent;
import com.google.gerrit.server.events.CommentAddedEvent;
import com.google.gerrit.server.events.DraftPublishedEvent;
+import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.EventFactory;
+import com.google.gerrit.server.events.HashtagsChangedEvent;
import com.google.gerrit.server.events.MergeFailedEvent;
import com.google.gerrit.server.events.PatchSetCreatedEvent;
import com.google.gerrit.server.events.RefUpdatedEvent;
@@ -73,6 +76,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
@@ -83,7 +87,8 @@ import java.util.concurrent.TimeoutException;
/** Spawns local executables when a hook action occurs. */
@Singleton
-public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
+public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
+ EventSource, LifecycleListener {
/** A logger for this class. */
private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class);
@@ -92,15 +97,17 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
protected void configure() {
bind(ChangeHookRunner.class);
bind(ChangeHooks.class).to(ChangeHookRunner.class);
+ bind(EventDispatcher.class).to(ChangeHookRunner.class);
+ bind(EventSource.class).to(ChangeHookRunner.class);
listener().to(ChangeHookRunner.class);
}
}
- private static class ChangeListenerHolder {
- final ChangeListener listener;
- final IdentifiedUser user;
+ private static class EventListenerHolder {
+ final EventListener listener;
+ final CurrentUser user;
- ChangeListenerHolder(ChangeListener l, IdentifiedUser u) {
+ EventListenerHolder(EventListener l, CurrentUser u) {
listener = l;
user = u;
}
@@ -134,6 +141,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
return output;
}
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -155,11 +163,11 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
/** Listeners to receive changes as they happen (limited by visibility
* of holder's user). */
- private final Map<ChangeListener, ChangeListenerHolder> listeners =
+ private final Map<EventListener, EventListenerHolder> listeners =
new ConcurrentHashMap<>();
/** Listeners to receive all changes as they happen. */
- private final DynamicSet<ChangeListener> unrestrictedListeners;
+ private final DynamicSet<EventListener> unrestrictedListeners;
/** Filename of the new patchset hook. */
private final File patchsetCreatedHook;
@@ -197,6 +205,9 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
/** Filename of the update hook. */
private final File refUpdateHook;
+ /** Filename of the hashtags changed hook */
+ private final File hashtagsChangedHook;
+
private final String anonymousCowardName;
/** Repository Manager. */
@@ -237,8 +248,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
final ProjectCache projectCache,
final AccountCache accountCache,
final EventFactory eventFactory,
- final SitePaths sitePaths,
- final DynamicSet<ChangeListener> unrestrictedListeners) {
+ final DynamicSet<EventListener> unrestrictedListeners) {
this.anonymousCowardName = anonymousCowardName;
this.repoManager = repoManager;
this.hookQueue = queue.createQueue(1, "hook");
@@ -254,7 +264,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
draftPublishedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "draftPublishedHook", "draft-published")).getPath());
commentAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "commentAddedHook", "comment-added")).getPath());
changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath());
- mergeFailedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "mergeFailed", "merge-failed")).getPath());
+ mergeFailedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "mergeFailedHook", "merge-failed")).getPath());
changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath());
changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath());
refUpdatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdatedHook", "ref-updated")).getPath());
@@ -262,6 +272,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
topicChangedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "topicChangedHook", "topic-changed")).getPath());
claSignedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "claSignedHook", "cla-signed")).getPath());
refUpdateHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdateHook", "ref-update")).getPath());
+ hashtagsChangedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "hashtagsChangedHook", "hashtags-changed")).getPath());
syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
syncHookThreadPool = Executors.newCachedThreadPool(
new ThreadFactoryBuilder()
@@ -269,11 +280,13 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
.build());
}
- public void addChangeListener(ChangeListener listener, IdentifiedUser user) {
- listeners.put(listener, new ChangeListenerHolder(listener, user));
+ @Override
+ public void addEventListener(EventListener listener, CurrentUser user) {
+ listeners.put(listener, new EventListenerHolder(listener, user));
}
- public void removeChangeListener(ChangeListener listener) {
+ @Override
+ public void removeEventListener(EventListener listener) {
listeners.remove(listener);
}
@@ -288,7 +301,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
*/
private String getValue(final Config config, final String section, final String setting, final String fallback) {
final String result = config.getString(section, null, setting);
- return (result == null) ? fallback : result;
+ return Strings.isNullOrEmpty(result) ? fallback : result;
}
/**
@@ -317,6 +330,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
* Fire the update hook
*
*/
+ @Override
public HookResult doRefUpdateHook(final Project project, final String refname,
final Account uploader, final ObjectId oldId, final ObjectId newId) {
@@ -327,15 +341,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
addArg(args, "--oldrev", oldId.getName());
addArg(args, "--newrev", newId.getName());
- HookResult hookResult;
-
- try {
- hookResult = runSyncHook(project.getNameKey(), refUpdateHook, args);
- } catch (TimeoutException e) {
- hookResult = new HookResult(-1, "Synchronous hook timed out");
- }
-
- return hookResult;
+ return runSyncHook(project.getNameKey(), refUpdateHook, args);
}
/**
@@ -345,6 +351,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
* @param patchSet The Patchset that was created.
* @throws OrmException
*/
+ @Override
public void doPatchsetCreatedHook(final Change change, final PatchSet patchSet,
final ReviewDb db) throws OrmException {
final PatchSetCreatedEvent event = new PatchSetCreatedEvent();
@@ -372,6 +379,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), patchsetCreatedHook, args);
}
+ @Override
public void doDraftPublishedHook(final Change change, final PatchSet patchSet,
final ReviewDb db) throws OrmException {
final DraftPublishedEvent event = new DraftPublishedEvent();
@@ -397,6 +405,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), draftPublishedHook, args);
}
+ @Override
public void doCommentAddedHook(final Change change, final Account account,
final PatchSet patchSet, final String comment, final Map<String, Short> approvals,
final ReviewDb db) throws OrmException {
@@ -440,14 +449,17 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), commentAddedHook, args);
}
- public void doChangeMergedHook(final Change change, final Account account,
- final PatchSet patchSet, final ReviewDb db) throws OrmException {
+ @Override
+ public void doChangeMergedHook(final Change change, final Account account,
+ final PatchSet patchSet, final ReviewDb db, String mergeResultRev)
+ throws OrmException {
final ChangeMergedEvent event = new ChangeMergedEvent();
final AccountState owner = accountCache.get(change.getOwner());
event.change = eventFactory.asChangeAttribute(change);
event.submitter = eventFactory.asAccountAttribute(account);
event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+ event.newRev = mergeResultRev;
fireEvent(change, event, db);
final List<String> args = new ArrayList<>();
@@ -459,10 +471,12 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
addArg(args, "--topic", event.change.topic);
addArg(args, "--submitter", getDisplayName(account));
addArg(args, "--commit", event.patchSet.revision);
+ addArg(args, "--newrev", mergeResultRev);
runHook(change.getProject(), changeMergedHook, args);
}
+ @Override
public void doMergeFailedHook(final Change change, final Account account,
final PatchSet patchSet, final String reason,
final ReviewDb db) throws OrmException {
@@ -489,6 +503,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), mergeFailedHook, args);
}
+ @Override
public void doChangeAbandonedHook(final Change change, final Account account,
final PatchSet patchSet, final String reason, final ReviewDb db)
throws OrmException {
@@ -515,6 +530,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), changeAbandonedHook, args);
}
+ @Override
public void doChangeRestoredHook(final Change change, final Account account,
final PatchSet patchSet, final String reason, final ReviewDb db)
throws OrmException {
@@ -541,10 +557,12 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), changeRestoredHook, args);
}
+ @Override
public void doRefUpdatedHook(final Branch.NameKey refName, final RefUpdate refUpdate, final Account account) {
doRefUpdatedHook(refName, refUpdate.getOldObjectId(), refUpdate.getNewObjectId(), account);
}
+ @Override
public void doRefUpdatedHook(final Branch.NameKey refName, final ObjectId oldId, final ObjectId newId, final Account account) {
final RefUpdatedEvent event = new RefUpdatedEvent();
@@ -566,6 +584,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(refName.getParentKey(), refUpdatedHook, args);
}
+ @Override
public void doReviewerAddedHook(final Change change, final Account account,
final PatchSet patchSet, final ReviewDb db) throws OrmException {
final ReviewerAddedEvent event = new ReviewerAddedEvent();
@@ -587,6 +606,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), reviewerAddedHook, args);
}
+ @Override
public void doTopicChangedHook(final Change change, final Account account,
final String oldTopic, final ReviewDb db)
throws OrmException {
@@ -610,6 +630,54 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
runHook(change.getProject(), topicChangedHook, args);
}
+ String[] hashtagArray(Set<String> hashtags) {
+ if (hashtags != null && hashtags.size() > 0) {
+ return Sets.newHashSet(hashtags).toArray(
+ new String[hashtags.size()]);
+ }
+ return null;
+ }
+
+ @Override
+ public void doHashtagsChangedHook(Change change, Account account,
+ Set<String> added, Set<String> removed, Set<String> hashtags, ReviewDb db)
+ throws OrmException {
+ HashtagsChangedEvent event = new HashtagsChangedEvent();
+ AccountState owner = accountCache.get(change.getOwner());
+
+ event.change = eventFactory.asChangeAttribute(change);
+ event.editor = eventFactory.asAccountAttribute(account);
+ event.hashtags = hashtagArray(hashtags);
+ event.added = hashtagArray(added);
+ event.removed = hashtagArray(removed);
+
+ fireEvent(change, event, db);
+
+ final List<String> args = new ArrayList<>();
+ addArg(args, "--change", event.change.id);
+ addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+ addArg(args, "--project", event.change.project);
+ addArg(args, "--branch", event.change.branch);
+ addArg(args, "--editor", getDisplayName(account));
+ if (hashtags != null) {
+ for (String hashtag : hashtags) {
+ addArg(args, "--hashtag", hashtag);
+ }
+ }
+ if (added != null) {
+ for (String hashtag : added) {
+ addArg(args, "--added", hashtag);
+ }
+ }
+ if (removed != null) {
+ for (String hashtag : removed) {
+ addArg(args, "--removed", hashtag);
+ }
+ }
+ runHook(change.getProject(), hashtagsChangedHook, args);
+ }
+
+ @Override
public void doClaSignupHook(Account account, ContributorAgreement cla) {
if (account != null) {
final List<String> args = new ArrayList<>();
@@ -622,44 +690,45 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
}
@Override
- public void postEvent(final Change change, final ChangeEvent event,
+ public void postEvent(final Change change, final Event event,
final ReviewDb db) throws OrmException {
fireEvent(change, event, db);
}
@Override
public void postEvent(final Branch.NameKey branchName,
- final ChangeEvent event) {
+ final Event event) {
fireEvent(branchName, event);
}
- private void fireEventForUnrestrictedListeners(final ChangeEvent event) {
- for (ChangeListener listener : unrestrictedListeners) {
- listener.onChangeEvent(event);
+ private void fireEventForUnrestrictedListeners(final Event event) {
+ for (EventListener listener : unrestrictedListeners) {
+ listener.onEvent(event);
}
}
- private void fireEvent(final Change change, final ChangeEvent event, final ReviewDb db) throws OrmException {
- for (ChangeListenerHolder holder : listeners.values()) {
+ private void fireEvent(final Change change, final Event event,
+ final ReviewDb db) throws OrmException {
+ for (EventListenerHolder holder : listeners.values()) {
if (isVisibleTo(change, holder.user, db)) {
- holder.listener.onChangeEvent(event);
+ holder.listener.onEvent(event);
}
}
fireEventForUnrestrictedListeners( event );
}
- private void fireEvent(Branch.NameKey branchName, final ChangeEvent event) {
- for (ChangeListenerHolder holder : listeners.values()) {
+ private void fireEvent(Branch.NameKey branchName, final Event event) {
+ for (EventListenerHolder holder : listeners.values()) {
if (isVisibleTo(branchName, holder.user)) {
- holder.listener.onChangeEvent(event);
+ holder.listener.onEvent(event);
}
}
fireEventForUnrestrictedListeners( event );
}
- private boolean isVisibleTo(Change change, IdentifiedUser user, ReviewDb db) throws OrmException {
+ private boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db) throws OrmException {
final ProjectState pe = projectCache.get(change.getProject());
if (pe == null) {
return false;
@@ -668,7 +737,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
return pc.controlFor(change).isVisible(db);
}
- private boolean isVisibleTo(Branch.NameKey branchName, IdentifiedUser user) {
+ private boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) {
final ProjectState pe = projectCache.get(branchName.getParentKey());
if (pe == null) {
return false;
@@ -733,7 +802,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
}
private HookResult runSyncHook(Project.NameKey project,
- File hook, List<String> args) throws TimeoutException {
+ File hook, List<String> args) {
if (!hook.exists()) {
return null;
@@ -859,8 +928,7 @@ public class ChangeHookRunner implements ChangeHooks, LifecycleListener {
while ((line = br.readLine()) != null) {
log.info("hook[" + getName() + "] output: " + line);
}
- }
- catch(IOException iox) {
+ } catch (IOException iox) {
log.error("Error writing hook output", iox);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
index 339927272e..7f7e8b24a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -22,21 +22,16 @@ 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.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.events.ChangeEvent;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import java.util.Map;
+import java.util.Set;
/** Invokes hooks on server actions. */
public interface ChangeHooks {
- public void addChangeListener(ChangeListener listener, IdentifiedUser user);
-
- public void removeChangeListener(ChangeListener listener);
-
/**
* Fire the Patchset Created Hook.
*
@@ -78,10 +73,11 @@ public interface ChangeHooks {
* @param change The change itself.
* @param account The gerrit user who submitted the change.
* @param patchSet The patchset that was merged.
+ * @param mergeResultRev The SHA-1 of the merge result revision.
* @throws OrmException
*/
public void doChangeMergedHook(Change change, Account account,
- PatchSet patchSet, ReviewDb db) throws OrmException;
+ PatchSet patchSet, ReviewDb db, String mergeResultRev) throws OrmException;
/**
* Fire the Merge Failed Hook.
@@ -173,21 +169,16 @@ public interface ChangeHooks {
Account uploader, ObjectId oldId, ObjectId newId);
/**
- * Post a stream event that is related to a change
- *
- * @param change The change that the event is related to
- * @param event The event to post
+ * Fire the hashtags changed Hook.
+ * @param change The change
+ * @param account The gerrit user changing the hashtags
+ * @param added List of hashtags that were added to the change
+ * @param removed List of hashtags that were removed from the change
+ * @param hashtags List of hashtags on the change after adding or removing
* @param db The database
* @throws OrmException
*/
- public void postEvent(Change change, ChangeEvent event, ReviewDb db)
- throws OrmException;
-
- /**
- * Post a stream event that is related to a branch
- *
- * @param branchName The branch that the event is related to
- * @param event The event to post
- */
- public void postEvent(Branch.NameKey branchName, ChangeEvent event);
+ public void doHashtagsChangedHook(Change change, Account account,
+ Set<String>added, Set<String> removed, Set<String> hashtags,
+ ReviewDb db) throws OrmException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index dd68296d65..156672eb7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -17,24 +17,25 @@ package com.google.gerrit.common;
import com.google.gerrit.common.ChangeHookRunner.HookResult;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch.NameKey;
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.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.events.Event;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import java.util.Map;
+import java.util.Set;
/** Does not invoke hooks. */
-public final class DisabledChangeHooks implements ChangeHooks {
+public final class DisabledChangeHooks implements ChangeHooks, EventDispatcher,
+ EventSource {
@Override
- public void addChangeListener(ChangeListener listener, IdentifiedUser user) {
+ public void addEventListener(EventListener listener, CurrentUser user) {
}
@Override
@@ -44,7 +45,7 @@ public final class DisabledChangeHooks implements ChangeHooks {
@Override
public void doChangeMergedHook(Change change, Account account,
- PatchSet patchSet, ReviewDb db) {
+ PatchSet patchSet, ReviewDb db, String mergeResultRev) {
}
@Override
@@ -78,13 +79,13 @@ public final class DisabledChangeHooks implements ChangeHooks {
}
@Override
- public void doRefUpdatedHook(NameKey refName, RefUpdate refUpdate,
+ public void doRefUpdatedHook(Branch.NameKey refName, RefUpdate refUpdate,
Account account) {
}
@Override
- public void doRefUpdatedHook(NameKey refName, ObjectId oldId, ObjectId newId,
- Account account) {
+ public void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId,
+ ObjectId newId, Account account) {
}
@Override
@@ -98,7 +99,12 @@ public final class DisabledChangeHooks implements ChangeHooks {
}
@Override
- public void removeChangeListener(ChangeListener listener) {
+ public void doHashtagsChangedHook(Change change, Account account, Set<String> added,
+ Set<String> removed, Set<String> hashtags, ReviewDb db) {
+ }
+
+ @Override
+ public void removeEventListener(EventListener listener) {
}
@Override
@@ -108,10 +114,10 @@ public final class DisabledChangeHooks implements ChangeHooks {
}
@Override
- public void postEvent(Change change, ChangeEvent event, ReviewDb db) {
+ public void postEvent(Change change, Event event, ReviewDb db) {
}
@Override
- public void postEvent(Branch.NameKey branchName, ChangeEvent event) {
+ public void postEvent(Branch.NameKey branchName, Event event) {
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java
new file mode 100644
index 0000000000..b74771f818
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2015 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.common;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.events.Event;
+import com.google.gwtorm.server.OrmException;
+
+
+/** Interface for posting (dispatching) Events */
+public interface EventDispatcher {
+ /**
+ * Post a stream event that is related to a change
+ *
+ * @param change The change that the event is related to
+ * @param event The event to post
+ * @param db The database
+ * @throws OrmException
+ */
+ public void postEvent(Change change, Event event, ReviewDb db)
+ throws OrmException;
+
+ /**
+ * Post a stream event that is related to a branch
+ *
+ * @param branchName The branch that the event is related to
+ * @param event The event to post
+ */
+ public void postEvent(Branch.NameKey branchName, Event event);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java
index b34305ec34..7e8a794d20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java
@@ -15,9 +15,9 @@
package com.google.gerrit.common;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.Event;
@ExtensionPoint
-public interface ChangeListener {
- public void onChangeEvent(ChangeEvent event);
+public interface EventListener {
+ public void onEvent(Event event);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java
new file mode 100644
index 0000000000..e2c4b34958
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2014 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.common;
+
+import com.google.gerrit.server.CurrentUser;
+
+/** Distributes Events to ChangeListeners. Register listeners here. */
+public interface EventSource {
+ public void addEventListener(EventListener listener, CurrentUser user);
+
+ public void removeEventListener(EventListener listener);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/FooterConstants.java b/gerrit-server/src/main/java/com/google/gerrit/common/FooterConstants.java
new file mode 100644
index 0000000000..3ec809c226
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/FooterConstants.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2014 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.common;
+
+import org.eclipse.jgit.revwalk.FooterKey;
+
+public class FooterConstants {
+ /** The change ID as used to track patch sets. */
+ public static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
+
+ /** The footer telling us who reviewed the change. */
+ public static final FooterKey REVIEWED_BY = new FooterKey("Reviewed-by");
+
+ /** The footer telling us the URL where the review took place. */
+ public static final FooterKey REVIEWED_ON = new FooterKey("Reviewed-on");
+
+ /** The footer telling us who tested the change. */
+ public static final FooterKey TESTED_BY = new FooterKey("Tested-by");
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
index e69360a29d..641ba03c68 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
@@ -37,7 +37,7 @@ public class Version {
private static String loadVersion() {
try (InputStream in = Version.class.getResourceAsStream("Version")) {
if (in == null) {
- return null;
+ return "(dev)";
}
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
String vs = r.readLine();
@@ -51,7 +51,7 @@ public class Version {
}
} catch (IOException e) {
log.error(e.getMessage(), e);
- return null;
+ return "(unknown version)";
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
index 7f6bff4d57..4724bc26d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
@@ -14,11 +14,11 @@
package com.google.gerrit.rules;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.Version;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -78,6 +78,7 @@ public class PrologCompiler implements Callable<PrologCompiler.Status> {
git = gitRepository;
}
+ @Override
public Status call() throws IOException, CompileException {
ObjectId metaConfig = git.resolve(RefNames.REFS_CONFIG);
if (metaConfig == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
index 029a5d7dfb..2afdffc7ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
@@ -16,6 +16,7 @@ package com.google.gerrit.rules;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -26,9 +27,11 @@ import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.Predicate;
import com.googlecode.prolog_cafe.lang.Prolog;
import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,6 +68,8 @@ public class PrologEnvironment extends BufferingPrologControl {
private final Args args;
private final Map<StoredValue<Object>, Object> storedValues;
+ private int reductionLimit;
+ private int reductionsRemaining;
private List<Runnable> cleanup;
@Inject
@@ -74,6 +79,8 @@ public class PrologEnvironment extends BufferingPrologControl {
setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
args = a;
storedValues = new HashMap<>();
+ reductionLimit = a.reductionLimit;
+ reductionsRemaining = reductionLimit;
cleanup = new LinkedList<>();
}
@@ -81,6 +88,28 @@ public class PrologEnvironment extends BufferingPrologControl {
return args;
}
+ @Override
+ public boolean isEngineStopped() {
+ if (super.isEngineStopped()) {
+ return true;
+ } else if (--reductionsRemaining <= 0) {
+ throw new ReductionLimitException(reductionLimit);
+ }
+ return false;
+ }
+
+ @Override
+ public void setPredicate(Predicate goal) {
+ super.setPredicate(goal);
+ reductionLimit = args.reductionLimit(goal);
+ reductionsRemaining = reductionLimit;
+ }
+
+ /** @return number of reductions during execution. */
+ public int getReductions() {
+ return reductionLimit - reductionsRemaining;
+ }
+
/**
* Lookup a stored value in the interpreter's hash manager.
*
@@ -154,6 +183,8 @@ public class PrologEnvironment extends BufferingPrologControl {
private final PatchSetInfoFactory patchSetInfoFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<AnonymousUser> anonymousUser;
+ private final int reductionLimit;
+ private final int compileLimit;
@Inject
Args(ProjectCache projectCache,
@@ -161,13 +192,29 @@ public class PrologEnvironment extends BufferingPrologControl {
PatchListCache patchListCache,
PatchSetInfoFactory patchSetInfoFactory,
IdentifiedUser.GenericFactory userFactory,
- Provider<AnonymousUser> anonymousUser) {
+ Provider<AnonymousUser> anonymousUser,
+ @GerritServerConfig Config config) {
this.projectCache = projectCache;
this.repositoryManager = repositoryManager;
this.patchListCache = patchListCache;
this.patchSetInfoFactory = patchSetInfoFactory;
this.userFactory = userFactory;
this.anonymousUser = anonymousUser;
+
+ int limit = config.getInt("rules", null, "reductionLimit", 100000);
+ reductionLimit = limit <= 0 ? Integer.MAX_VALUE : limit;
+
+ limit = config.getInt("rules", null, "compileReductionLimit",
+ (int) Math.min(10L * limit, Integer.MAX_VALUE));
+ compileLimit = limit <= 0 ? Integer.MAX_VALUE : limit;
+ }
+
+ private int reductionLimit(Predicate goal) {
+ if ("com.googlecode.prolog_cafe.builtin.PRED_consult_stream_2"
+ .equals(goal.getClass().getName())) {
+ return compileLimit;
+ }
+ return reductionLimit;
}
public ProjectCache getProjectCache() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/ReductionLimitException.java b/gerrit-server/src/main/java/com/google/gerrit/rules/ReductionLimitException.java
new file mode 100644
index 0000000000..2c272408c3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/ReductionLimitException.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2014 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.rules;
+
+/** Thrown by {@link PrologEnvironment} if a script runs too long. */
+public class ReductionLimitException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ ReductionLimitException(int limit) {
+ super(String.format("exceeded reduction limit of %d", limit));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
index 132360b890..d454e409d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
@@ -84,7 +84,12 @@ public class StoredValue<T> {
env.set(this, obj);
}
- /** Creates a value to store, returns null by default. */
+ /**
+ * Creates a value to store, returns null by default.
+ *
+ * @param engine Prolog engine.
+ * @return new value.
+ */
protected T createValue(Prolog engine) {
return null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index 356b7d5b92..6136742a6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
@@ -49,8 +50,13 @@ import java.util.Map;
public final class StoredValues {
public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
public static final StoredValue<ChangeData> CHANGE_DATA = create(ChangeData.class);
- public static final StoredValue<PatchSet> PATCH_SET = create(PatchSet.class);
+
+ // Note: no guarantees are made about the user passed in the ChangeControl; do
+ // not depend on this directly. Either use .forUser(otherUser) to get a
+ // control for a specific known user, or use CURRENT_USER, which may be null
+ // for rule types that may not depend on the current user.
public static final StoredValue<ChangeControl> CHANGE_CONTROL = create(ChangeControl.class);
+ public static final StoredValue<CurrentUser> CURRENT_USER = create(CurrentUser.class);
public static Change getChange(Prolog engine) throws SystemException {
ChangeData cd = CHANGE_DATA.get(engine);
@@ -61,11 +67,20 @@ public final class StoredValues {
}
}
+ public static PatchSet getPatchSet(Prolog engine) throws SystemException {
+ ChangeData cd = CHANGE_DATA.get(engine);
+ try {
+ return cd.currentPatchSet();
+ } catch (OrmException e) {
+ throw new SystemException(e.getMessage());
+ }
+ }
+
public static final StoredValue<PatchSetInfo> PATCH_SET_INFO = new StoredValue<PatchSetInfo>() {
@Override
public PatchSetInfo createValue(Prolog engine) {
Change change = getChange(engine);
- PatchSet ps = StoredValues.PATCH_SET.get(engine);
+ PatchSet ps = getPatchSet(engine);
PrologEnvironment env = (PrologEnvironment) engine.control;
PatchSetInfoFactory patchInfoFactory =
env.getArgs().getPatchSetInfoFactory();
@@ -81,12 +96,12 @@ public final class StoredValues {
@Override
public PatchList createValue(Prolog engine) {
PrologEnvironment env = (PrologEnvironment) engine.control;
- PatchSetInfo psInfo = StoredValues.PATCH_SET_INFO.get(engine);
+ PatchSet ps = getPatchSet(engine);
PatchListCache plCache = env.getArgs().getPatchListCache();
Change change = getChange(engine);
Project.NameKey projectKey = change.getProject();
ObjectId a = null;
- ObjectId b = ObjectId.fromString(psInfo.getRevId());
+ ObjectId b = ObjectId.fromString(ps.getRevision().get());
Whitespace ws = Whitespace.IGNORE_NONE;
PatchListKey plKey = new PatchListKey(projectKey, a, b, ws);
PatchList patchList;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index b74eb776ae..bd175a9c67 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.server.change.ChangeKind.NO_CHANGE;
import static com.google.gerrit.server.change.ChangeKind.NO_CODE_CHANGE;
import static com.google.gerrit.server.change.ChangeKind.TRIVIAL_REBASE;
-import com.google.common.base.Objects;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
@@ -47,6 +47,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.NavigableSet;
+import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeMap;
@@ -150,14 +151,13 @@ public class ApprovalCopier {
}
private static boolean canCopy(ProjectState project, PatchSetApproval psa,
- PatchSet.Id psId, NavigableSet<Integer> allPsIds, ChangeKind kind)
- throws OrmException {
+ PatchSet.Id psId, NavigableSet<Integer> allPsIds, ChangeKind kind) {
int n = psa.getKey().getParentKey().get();
checkArgument(n != psId.get());
LabelType type = project.getLabelTypes().byLabel(psa.getLabelId());
if (type == null) {
return false;
- } else if (Objects.equal(n, previous(allPsIds, psId.get())) && (
+ } else if (Objects.equals(n, previous(allPsIds, psId.get())) && (
type.isCopyMinScore() && type.isMaxNegative(psa)
|| type.isCopyMaxScore() && type.isMaxPositive(psa))) {
// Copy min/max score only from the immediately preceding patch set (which
@@ -165,7 +165,8 @@ public class ApprovalCopier {
return true;
}
return (type.isCopyAllScoresOnTrivialRebase() && kind == TRIVIAL_REBASE)
- || (type.isCopyAllScoresIfNoCodeChange() && kind == NO_CODE_CHANGE);
+ || (type.isCopyAllScoresIfNoCodeChange() && kind == NO_CODE_CHANGE)
+ || (type.isCopyAllScoresIfNoChange() && kind == NO_CHANGE);
}
private static PatchSetApproval copy(PatchSetApproval src, PatchSet.Id psId) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 900bbdd828..726519606c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
-import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
@@ -30,15 +29,16 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
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.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -46,7 +46,6 @@ import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.ReviewerState;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -57,6 +56,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -91,7 +91,7 @@ public class ApprovalsUtil {
return Iterables.filter(psas, new Predicate<PatchSetApproval>() {
@Override
public boolean apply(PatchSetApproval input) {
- return Objects.equal(input.getAccountId(), accountId);
+ return Objects.equals(input.getAccountId(), accountId);
}
});
}
@@ -119,7 +119,7 @@ public class ApprovalsUtil {
*/
public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
ReviewDb db, ChangeNotes notes) throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return getReviewers(db.patchSetApprovals().byChange(notes.getChangeId()));
}
return notes.load().getReviewers();
@@ -137,7 +137,7 @@ public class ApprovalsUtil {
public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
ChangeNotes notes, Iterable<PatchSetApproval> allApprovals)
throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return getReviewers(allApprovals);
}
return notes.load().getReviewers();
@@ -221,11 +221,12 @@ public class ApprovalsUtil {
return Collections.unmodifiableList(cells);
}
- public void addApprovals(ReviewDb db, ChangeUpdate update, LabelTypes labelTypes,
- PatchSet ps, PatchSetInfo info, Change change, ChangeControl changeCtl,
- Map<String, Short> approvals) throws OrmException {
+ public void addApprovals(ReviewDb db, ChangeUpdate update,
+ LabelTypes labelTypes, PatchSet ps, PatchSetInfo info,
+ ChangeControl changeCtl, Map<String, Short> approvals)
+ throws OrmException {
if (!approvals.isEmpty()) {
- checkApprovals(approvals, labelTypes, change, changeCtl);
+ checkApprovals(approvals, changeCtl);
List<PatchSetApproval> cells = new ArrayList<>(approvals.size());
Timestamp ts = TimeUtil.nowTs();
for (Map.Entry<String, Short> vote : approvals.entrySet()) {
@@ -254,8 +255,8 @@ public class ApprovalsUtil {
}
}
- private static void checkApprovals(Map<String, Short> approvals, LabelTypes labelTypes,
- Change change, ChangeControl changeCtl) {
+ private static void checkApprovals(Map<String, Short> approvals,
+ ChangeControl changeCtl) {
for (Map.Entry<String, Short> vote : approvals.entrySet()) {
String name = vote.getKey();
Short value = vote.getValue();
@@ -269,7 +270,7 @@ public class ApprovalsUtil {
public ListMultimap<PatchSet.Id, PatchSetApproval> byChange(ReviewDb db,
ChangeNotes notes) throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
ImmutableListMultimap.Builder<PatchSet.Id, PatchSetApproval> result =
ImmutableListMultimap.builder();
for (PatchSetApproval psa
@@ -283,7 +284,7 @@ public class ApprovalsUtil {
public Iterable<PatchSetApproval> byPatchSet(ReviewDb db, ChangeControl ctl,
PatchSet.Id psId) throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return sortApprovals(db.patchSetApprovals().byPatchSet(psId));
}
return copier.getForPatchSet(db, ctl, psId);
@@ -292,7 +293,7 @@ public class ApprovalsUtil {
public Iterable<PatchSetApproval> byPatchSetUser(ReviewDb db,
ChangeControl ctl, PatchSet.Id psId, Account.Id accountId)
throws OrmException {
- if (!migration.readPatchSetApprovals()) {
+ if (!migration.readChanges()) {
return sortApprovals(
db.patchSetApprovals().byPatchSetUser(psId, accountId));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 72fd1a1ccf..6a4421992f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -50,7 +50,7 @@ public class ChangeMessagesUtil {
}
public List<ChangeMessage> byChange(ReviewDb db, ChangeNotes notes) throws OrmException {
- if (!migration.readChangeMessages()) {
+ if (!migration.readChanges()) {
return
sortChangeMessages(db.changeMessages().byChange(notes.getChangeId()));
} else {
@@ -60,7 +60,7 @@ public class ChangeMessagesUtil {
public List<ChangeMessage> byPatchSet(ReviewDb db, ChangeNotes notes,
PatchSet.Id psId) throws OrmException {
- if (!migration.readChangeMessages()) {
+ if (!migration.readChanges()) {
return sortChangeMessages(db.changeMessages().byPatchSet(psId));
}
return notes.load().getChangeMessages().get(psId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 249bd3867b..9faa516995 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -15,21 +15,23 @@
package com.google.gerrit.server;
import static com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy.RECEIVE_COMMITS;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static com.google.gerrit.server.query.change.ChangeData.asChanges;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.primitives.Ints;
-import com.google.gerrit.common.errors.EmailException;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.PatchSetAncestor;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeMessages;
+import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -38,15 +40,14 @@ import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.RevertedSender;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.MagicBranch;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmConcurrencyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -82,15 +83,6 @@ import java.util.Map;
@Singleton
public class ChangeUtil {
- /**
- * Epoch for sort key calculations, Tue Sep 30 2008 17:00:00.
- * <p>
- * We overrun approximately 4,083 years later, so ~6092.
- */
- @VisibleForTesting
- private static final long SORT_KEY_EPOCH_MINS =
- MINUTES.convert(1222819200L, SECONDS);
-
private static final Object uuidLock = new Object();
private static final int SEED = 0x2418e6f9;
private static int uuidPrefix;
@@ -147,7 +139,6 @@ public class ChangeUtil {
public static void updated(Change c) {
c.setLastUpdatedOn(TimeUtil.nowTs());
- computeSortKey(c);
}
public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
@@ -163,29 +154,6 @@ public class ChangeUtil {
db.patchSetAncestors().insert(toInsert);
}
- public static String sortKey(long lastUpdatedMs, int id) {
- long lastUpdatedMins = MINUTES.convert(lastUpdatedMs, MILLISECONDS);
- long minsSinceEpoch = lastUpdatedMins - SORT_KEY_EPOCH_MINS;
- StringBuilder r = new StringBuilder(16);
- r.setLength(16);
- formatHexInt(r, 0, Ints.checkedCast(minsSinceEpoch));
- formatHexInt(r, 8, id);
- return r.toString();
- }
-
- public static long parseSortKey(String sortKey) {
- if ("z".equals(sortKey)) {
- return Long.MAX_VALUE;
- }
- return Long.parseLong(sortKey, 16);
- }
-
- public static void computeSortKey(Change c) {
- long lastUpdatedMs = c.getLastUpdatedOn().getTime();
- int id = c.getId().get();
- c.setSortKey(sortKey(lastUpdatedMs, id));
- }
-
public static PatchSet.Id nextPatchSetId(Map<String, Ref> allRefs,
PatchSet.Id id) {
PatchSet.Id next = nextPatchSetId(id);
@@ -217,6 +185,7 @@ public class ChangeUtil {
private final Provider<CurrentUser> userProvider;
private final CommitValidators.Factory commitValidatorsFactory;
private final Provider<ReviewDb> db;
+ private final Provider<InternalChangeQuery> queryProvider;
private final RevertedSender.Factory revertedSenderFactory;
private final ChangeInserter.Factory changeInserterFactory;
private final PatchSetInserter.Factory patchSetInserterFactory;
@@ -228,6 +197,7 @@ public class ChangeUtil {
ChangeUtil(Provider<CurrentUser> userProvider,
CommitValidators.Factory commitValidatorsFactory,
Provider<ReviewDb> db,
+ Provider<InternalChangeQuery> queryProvider,
RevertedSender.Factory revertedSenderFactory,
ChangeInserter.Factory changeInserterFactory,
PatchSetInserter.Factory patchSetInserterFactory,
@@ -237,6 +207,7 @@ public class ChangeUtil {
this.userProvider = userProvider;
this.commitValidatorsFactory = commitValidatorsFactory;
this.db = db;
+ this.queryProvider = queryProvider;
this.revertedSenderFactory = revertedSenderFactory;
this.changeInserterFactory = changeInserterFactory;
this.patchSetInserterFactory = patchSetInserterFactory;
@@ -247,7 +218,7 @@ public class ChangeUtil {
public Change.Id revert(ChangeControl ctl, PatchSet.Id patchSetId,
String message, PersonIdent myIdent, SshInfo sshInfo)
- throws NoSuchChangeException, EmailException, OrmException,
+ throws NoSuchChangeException, OrmException,
MissingObjectException, IncorrectObjectTypeException, IOException,
InvalidChangeOperationException {
Change.Id changeId = patchSetId.getParentKey();
@@ -257,215 +228,200 @@ public class ChangeUtil {
}
Change changeToRevert = db.get().changes().get(changeId);
- Repository git;
- try {
- git = gitManager.openRepository(ctl.getChange().getProject());
- } catch (RepositoryNotFoundException e) {
- throw new NoSuchChangeException(changeId, e);
- }
- try {
- RevWalk revWalk = new RevWalk(git);
- try {
- RevCommit commitToRevert =
- revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+ Project.NameKey project = ctl.getChange().getProject();
+ try (Repository git = gitManager.openRepository(project);
+ RevWalk revWalk = new RevWalk(git)) {
+ RevCommit commitToRevert =
+ revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
- PersonIdent authorIdent =
- user().newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
+ PersonIdent authorIdent =
+ user().newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
- RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
- revWalk.parseHeaders(parentToCommitToRevert);
+ 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);
+ CommitBuilder revertCommitBuilder = new CommitBuilder();
+ revertCommitBuilder.addParentId(commitToRevert);
+ revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
+ revertCommitBuilder.setAuthor(authorIdent);
+ revertCommitBuilder.setCommitter(authorIdent);
- if (message == null) {
- message = MessageFormat.format(
- ChangeMessages.get().revertChangeDefaultMessage,
- changeToRevert.getSubject(), patch.getRevision().get());
- }
+ if (message == null) {
+ message = MessageFormat.format(
+ ChangeMessages.get().revertChangeDefaultMessage,
+ changeToRevert.getSubject(), patch.getRevision().get());
+ }
- ObjectId computedChangeId =
- ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
- commitToRevert, authorIdent, myIdent, message);
- revertCommitBuilder.setMessage(
- ChangeIdUtil.insertId(message, computedChangeId, true));
-
- RevCommit revertCommit;
- ObjectInserter oi = git.newObjectInserter();
- try {
- ObjectId id = oi.insert(revertCommitBuilder);
- oi.flush();
- revertCommit = revWalk.parseCommit(id);
- } finally {
- oi.close();
- }
+ ObjectId computedChangeId =
+ ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
+ commitToRevert, authorIdent, myIdent, message);
+ revertCommitBuilder.setMessage(
+ ChangeIdUtil.insertId(message, computedChangeId, true));
+
+ RevCommit revertCommit;
+ try (ObjectInserter oi = git.newObjectInserter()) {
+ ObjectId id = oi.insert(revertCommitBuilder);
+ oi.flush();
+ revertCommit = revWalk.parseCommit(id);
+ }
- RefControl refControl = ctl.getRefControl();
- Change change = new Change(
- new Change.Key("I" + computedChangeId.name()),
- new Change.Id(db.get().nextChangeId()),
- user().getAccountId(),
- changeToRevert.getDest(),
- TimeUtil.nowTs());
- change.setTopic(changeToRevert.getTopic());
- ChangeInserter ins =
- changeInserterFactory.create(refControl, change, revertCommit);
- PatchSet ps = ins.getPatchSet();
-
- String ref = refControl.getRefName();
- String cmdRef = MagicBranch.NEW_PUBLISH_CHANGE
- + ref.substring(ref.lastIndexOf('/') + 1);
- CommitReceivedEvent commitReceivedEvent = new CommitReceivedEvent(
- new ReceiveCommand(ObjectId.zeroId(), revertCommit.getId(), cmdRef),
- refControl.getProjectControl().getProject(),
- refControl.getRefName(), revertCommit, user());
-
- try {
- commitValidatorsFactory.create(refControl, sshInfo, git)
- .validateForGerritCommits(commitReceivedEvent);
- } catch (CommitValidationException e) {
- throw new InvalidChangeOperationException(e.getMessage());
- }
+ RefControl refControl = ctl.getRefControl();
+ Change change = new Change(
+ new Change.Key("I" + computedChangeId.name()),
+ new Change.Id(db.get().nextChangeId()),
+ user().getAccountId(),
+ changeToRevert.getDest(),
+ TimeUtil.nowTs());
+ change.setTopic(changeToRevert.getTopic());
+ ChangeInserter ins =
+ changeInserterFactory.create(refControl.getProjectControl(),
+ change, revertCommit);
+ PatchSet ps = ins.getPatchSet();
+
+ String ref = refControl.getRefName();
+ String cmdRef = MagicBranch.NEW_PUBLISH_CHANGE
+ + ref.substring(ref.lastIndexOf('/') + 1);
+ CommitReceivedEvent commitReceivedEvent = new CommitReceivedEvent(
+ new ReceiveCommand(ObjectId.zeroId(), revertCommit.getId(), cmdRef),
+ refControl.getProjectControl().getProject(),
+ refControl.getRefName(), revertCommit, user());
- RefUpdate ru = git.updateRef(ps.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(revertCommit);
- ru.disableRefLog();
- if (ru.update(revWalk) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", ps.getRefName(),
- change.getDest().getParentKey().get(), ru.getResult()));
- }
+ try {
+ commitValidatorsFactory.create(refControl, sshInfo, git)
+ .validateForGerritCommits(commitReceivedEvent);
+ } catch (CommitValidationException e) {
+ throw new InvalidChangeOperationException(e.getMessage());
+ }
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(changeId, messageUUID(db.get())),
- user().getAccountId(), TimeUtil.nowTs(), patchSetId);
- StringBuilder msgBuf = new StringBuilder();
- msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
- msgBuf.append("\n\n");
- msgBuf.append("This patchset was reverted in change: ")
- .append(change.getKey().get());
- cmsg.setMessage(msgBuf.toString());
-
- ins.setMessage(cmsg).insert();
-
- try {
- RevertedSender cm = revertedSenderFactory.create(change);
- cm.setFrom(user().getAccountId());
- cm.setChangeMessage(cmsg);
- cm.send();
- } catch (Exception err) {
- log.error("Cannot send email for revert change " + change.getId(),
- err);
- }
+ RefUpdate ru = git.updateRef(ps.getRefName());
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(revertCommit);
+ ru.disableRefLog();
+ if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+ throw new IOException(String.format(
+ "Failed to create ref %s in %s: %s", ps.getRefName(),
+ change.getDest().getParentKey().get(), ru.getResult()));
+ }
+
+ ChangeMessage cmsg = new ChangeMessage(
+ new ChangeMessage.Key(changeId, messageUUID(db.get())),
+ user().getAccountId(), TimeUtil.nowTs(), patchSetId);
+ StringBuilder msgBuf = new StringBuilder();
+ msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
+ msgBuf.append("\n\n");
+ msgBuf.append("This patchset was reverted in change: ")
+ .append(change.getKey().get());
+ cmsg.setMessage(msgBuf.toString());
+
+ ins.setMessage(cmsg).insert();
- return change.getId();
- } finally {
- revWalk.close();
+ try {
+ RevertedSender cm = revertedSenderFactory.create(change);
+ cm.setFrom(user().getAccountId());
+ cm.setChangeMessage(cmsg);
+ cm.send();
+ } catch (Exception err) {
+ log.error("Cannot send email for revert change " + change.getId(),
+ err);
}
- } finally {
- git.close();
+
+ return change.getId();
+ } catch (RepositoryNotFoundException e) {
+ throw new NoSuchChangeException(changeId, e);
}
}
- public Change.Id editCommitMessage(ChangeControl ctl, PatchSet.Id patchSetId,
- String message, PersonIdent myIdent)
- throws NoSuchChangeException, EmailException, OrmException,
- MissingObjectException, IncorrectObjectTypeException, IOException,
- InvalidChangeOperationException, PatchSetInfoNotAvailableException {
- Change.Id changeId = patchSetId.getParentKey();
- PatchSet originalPS = db.get().patchSets().get(patchSetId);
- if (originalPS == null) {
- throw new NoSuchChangeException(changeId);
- }
+ public Change.Id editCommitMessage(ChangeControl ctl, PatchSet ps,
+ String message, PersonIdent myIdent) throws NoSuchChangeException,
+ OrmException, MissingObjectException, IncorrectObjectTypeException,
+ IOException, InvalidChangeOperationException {
+ Change change = ctl.getChange();
+ Change.Id changeId = change.getId();
- if (message == null || message.length() == 0) {
+ if (Strings.isNullOrEmpty(message)) {
throw new InvalidChangeOperationException(
"The commit message cannot be empty");
}
- Repository git;
- try {
- git = gitManager.openRepository(ctl.getChange().getProject());
+ Project.NameKey project = ctl.getChange().getProject();
+ try (Repository git = gitManager.openRepository(project);
+ RevWalk revWalk = new RevWalk(git)) {
+ RevCommit commit =
+ revWalk.parseCommit(ObjectId.fromString(ps.getRevision()
+ .get()));
+ if (commit.getFullMessage().equals(message)) {
+ throw new InvalidChangeOperationException(
+ "New commit message cannot be same as existing commit message");
+ }
+
+ Date now = myIdent.getWhen();
+ PersonIdent authorIdent =
+ user().newCommitterIdent(now, myIdent.getTimeZone());
+
+ CommitBuilder commitBuilder = new CommitBuilder();
+ commitBuilder.setTreeId(commit.getTree());
+ commitBuilder.setParentIds(commit.getParents());
+ commitBuilder.setAuthor(commit.getAuthorIdent());
+ commitBuilder.setCommitter(authorIdent);
+ commitBuilder.setMessage(message);
+
+ RevCommit newCommit;
+ try (ObjectInserter oi = git.newObjectInserter()) {
+ ObjectId id = oi.insert(commitBuilder);
+ oi.flush();
+ newCommit = revWalk.parseCommit(id);
+ }
+
+ PatchSet.Id id = nextPatchSetId(git, change.currentPatchSetId());
+ PatchSet newPatchSet = new PatchSet(id);
+ newPatchSet.setCreatedOn(new Timestamp(now.getTime()));
+ newPatchSet.setUploader(user().getAccountId());
+ newPatchSet.setRevision(new RevId(newCommit.name()));
+
+ String msg = "Patch Set " + newPatchSet.getPatchSetId()
+ + ": Commit message was updated";
+
+ change = patchSetInserterFactory
+ .create(git, revWalk, ctl, newCommit)
+ .setPatchSet(newPatchSet)
+ .setMessage(msg)
+ .setValidatePolicy(RECEIVE_COMMITS)
+ .setDraft(ps.isDraft())
+ .insert();
+
+ return change.getId();
} catch (RepositoryNotFoundException e) {
throw new NoSuchChangeException(changeId, e);
}
- try {
- RevWalk revWalk = new RevWalk(git);
- try {
- RevCommit commit =
- revWalk.parseCommit(ObjectId.fromString(originalPS.getRevision()
- .get()));
- if (commit.getFullMessage().equals(message)) {
- throw new InvalidChangeOperationException(
- "New commit message cannot be same as existing commit message");
- }
-
- Date now = myIdent.getWhen();
- Change change = db.get().changes().get(changeId);
- PersonIdent authorIdent =
- user().newCommitterIdent(now, myIdent.getTimeZone());
-
- CommitBuilder commitBuilder = new CommitBuilder();
- commitBuilder.setTreeId(commit.getTree());
- commitBuilder.setParentIds(commit.getParents());
- commitBuilder.setAuthor(commit.getAuthorIdent());
- commitBuilder.setCommitter(authorIdent);
- commitBuilder.setMessage(message);
-
- RevCommit newCommit;
- ObjectInserter oi = git.newObjectInserter();
- try {
- ObjectId id = oi.insert(commitBuilder);
- oi.flush();
- newCommit = revWalk.parseCommit(id);
- } finally {
- oi.close();
- }
+ }
- PatchSet.Id id = nextPatchSetId(git, change.currentPatchSetId());
- PatchSet newPatchSet = new PatchSet(id);
- newPatchSet.setCreatedOn(new Timestamp(now.getTime()));
- newPatchSet.setUploader(user().getAccountId());
- newPatchSet.setRevision(new RevId(newCommit.name()));
-
- String msg = "Patch Set " + newPatchSet.getPatchSetId()
- + ": Commit message was updated";
-
- change = patchSetInserterFactory
- .create(git, revWalk, ctl, newCommit)
- .setPatchSet(newPatchSet)
- .setMessage(msg)
- .setCopyLabels(true)
- .setValidatePolicy(RECEIVE_COMMITS)
- .setDraft(originalPS.isDraft())
- .insert();
-
- return change.getId();
- } finally {
- revWalk.close();
- }
- } finally {
- git.close();
+ public String getMessage(Change change)
+ throws NoSuchChangeException, OrmException,
+ MissingObjectException, IncorrectObjectTypeException, IOException {
+ Change.Id changeId = change.getId();
+ PatchSet ps = db.get().patchSets().get(change.currentPatchSetId());
+ if (ps == null) {
+ throw new NoSuchChangeException(changeId);
}
- }
- public void deleteDraftChange(PatchSet.Id patchSetId)
- throws NoSuchChangeException, OrmException, IOException {
- deleteDraftChange(patchSetId.getParentKey());
+ try (Repository git = gitManager.openRepository(change.getProject());
+ RevWalk revWalk = new RevWalk(git)) {
+ RevCommit commit = revWalk.parseCommit(
+ ObjectId.fromString(ps.getRevision().get()));
+ return commit.getFullMessage();
+ } catch (RepositoryNotFoundException e) {
+ throw new NoSuchChangeException(changeId, e);
+ }
}
- public void deleteDraftChange(Change.Id changeId)
+ public void deleteDraftChange(Change change)
throws NoSuchChangeException, OrmException, IOException {
- ReviewDb db = this.db.get();
- Change change = db.changes().get(changeId);
- if (change == null || change.getStatus() != Change.Status.DRAFT) {
+ Change.Id changeId = change.getId();
+ if (change.getStatus() != Change.Status.DRAFT) {
throw new NoSuchChangeException(changeId);
}
+ ReviewDb db = this.db.get();
for (PatchSet ps : db.patchSets().byChange(changeId)) {
// These should all be draft patch sets.
deleteOnlyDraftPatchSet(ps, change);
@@ -474,7 +430,7 @@ public class ChangeUtil {
db.changeMessages().delete(db.changeMessages().byChange(changeId));
db.starredChanges().delete(db.starredChanges().byChange(changeId));
db.changes().delete(Collections.singleton(change));
- indexer.delete(db, change);
+ indexer.delete(change.getId());
}
public void deleteOnlyDraftPatchSet(PatchSet patch, Change change)
@@ -508,34 +464,54 @@ public class ChangeUtil {
ReviewDb db = this.db.get();
db.accountPatchReviews().delete(db.accountPatchReviews().byPatchSet(patchSetId));
db.changeMessages().delete(db.changeMessages().byPatchSet(patchSetId));
- db.patchComments().delete(db.patchComments().byPatchSet(patchSetId));
// No need to delete from notedb; draft patch sets will be filtered out.
+ db.patchComments().delete(db.patchComments().byPatchSet(patchSetId));
db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(patchSetId));
db.patchSetAncestors().delete(db.patchSetAncestors().byPatchSet(patchSetId));
db.patchSets().delete(Collections.singleton(patch));
}
+ /**
+ * Find changes matching the given identifier.
+ *
+ * @param id change identifier, either a numeric ID, a Change-Id, or
+ * project~branch~id triplet.
+ * @return all matching changes, even if they are not visible to the current
+ * user.
+ */
+ public List<Change> findChanges(String id)
+ throws OrmException, ResourceNotFoundException {
+ // Try legacy id
+ if (id.matches("^[1-9][0-9]*$")) {
+ Change c = db.get().changes().get(Change.Id.parse(id));
+ if (c != null) {
+ return ImmutableList.of(c);
+ }
+ return Collections.emptyList();
+ }
+
+ // Try isolated changeId
+ if (!id.contains("~")) {
+ return asChanges(queryProvider.get().byKeyPrefix(id));
+ }
+
+ // Try change triplet
+ Optional<ChangeTriplet> triplet = ChangeTriplet.parse(id);
+ if (triplet.isPresent()) {
+ return asChanges(queryProvider.get().byBranchKey(
+ triplet.get().branch(),
+ triplet.get().id()));
+ }
+
+ throw new ResourceNotFoundException(id);
+ }
+
private IdentifiedUser user() {
return (IdentifiedUser) userProvider.get();
}
- private static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
+ public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
return new PatchSet.Id(id.getParentKey(), id.get() + 1);
}
-
- private static final char[] hexchar =
- {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
- 'a', 'b', 'c', 'd', 'e', 'f'};
-
- private static void formatHexInt(final StringBuilder dst, final int p, int w) {
- int o = p + 7;
- while (o >= p && w != 0) {
- dst.setCharAt(o--, hexchar[w & 0xf]);
- w >>>= 4;
- }
- while (o >= p) {
- dst.setCharAt(o--, '0');
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
index d10366eb34..956a0d1d3c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
@@ -32,6 +32,7 @@ import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.gerrit.util.cli.OptionHandlers;
+
import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.args4j.spi.OptionHandler;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java b/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java
new file mode 100644
index 0000000000..be07bde99e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 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.common.GitPerson;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.sql.Timestamp;
+
+/**
+ * Converters to classes in {@code com.google.gerrit.extensions.common}.
+ * <p>
+ * The server frequently needs to convert internal types to types exposed in the
+ * extension API, but the converters themselves are not part of this API. This
+ * class contains such converters as static utility methods.
+ */
+public class CommonConverters {
+ public static GitPerson toGitPerson(PersonIdent ident) {
+ GitPerson result = new GitPerson();
+ result.name = ident.getName();
+ result.email = ident.getEmailAddress();
+ result.date = new Timestamp(ident.getWhen().getTime());
+ result.tz = ident.getTimeZoneOffset();
+ return result;
+ }
+
+ private CommonConverters() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 6d798cc4ab..be5e000c55 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -30,9 +30,11 @@ import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.DisableReverseDnsLookup;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -66,25 +68,31 @@ public class IdentifiedUser extends CurrentUser {
public static class GenericFactory {
private final CapabilityControl.Factory capabilityControlFactory;
private final AuthConfig authConfig;
+ private final Realm realm;
private final String anonymousCowardName;
private final Provider<String> canonicalUrl;
private final AccountCache accountCache;
private final GroupBackend groupBackend;
+ private final Boolean disableReverseDnsLookup;
@Inject
public GenericFactory(
@Nullable CapabilityControl.Factory capabilityControlFactory,
AuthConfig authConfig,
+ Realm realm,
@AnonymousCowardName String anonymousCowardName,
@CanonicalWebUrl Provider<String> canonicalUrl,
+ @DisableReverseDnsLookup Boolean disableReverseDnsLookup,
AccountCache accountCache,
GroupBackend groupBackend) {
this.capabilityControlFactory = capabilityControlFactory;
this.authConfig = authConfig;
+ this.realm = realm;
this.anonymousCowardName = anonymousCowardName;
this.canonicalUrl = canonicalUrl;
this.accountCache = accountCache;
this.groupBackend = groupBackend;
+ this.disableReverseDnsLookup = disableReverseDnsLookup;
}
public IdentifiedUser create(final Account.Id id) {
@@ -92,22 +100,22 @@ public class IdentifiedUser extends CurrentUser {
}
public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
- return new IdentifiedUser(capabilityControlFactory,
- authConfig, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, null, db, id, null);
+ return new IdentifiedUser(capabilityControlFactory, authConfig, realm,
+ anonymousCowardName, canonicalUrl, accountCache, groupBackend,
+ disableReverseDnsLookup, null, db, id, null);
}
public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
- return new IdentifiedUser(capabilityControlFactory,
- authConfig, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, Providers.of(remotePeer), null, id, null);
+ return new IdentifiedUser(capabilityControlFactory, authConfig, realm,
+ anonymousCowardName, canonicalUrl, accountCache, groupBackend,
+ disableReverseDnsLookup, Providers.of(remotePeer), null, id, null);
}
public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
@Nullable CurrentUser caller) {
- return new IdentifiedUser(capabilityControlFactory,
- authConfig, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, Providers.of(remotePeer), null, id, caller);
+ return new IdentifiedUser(capabilityControlFactory, authConfig, realm,
+ anonymousCowardName, canonicalUrl, accountCache, groupBackend,
+ disableReverseDnsLookup, Providers.of(remotePeer), null, id, caller);
}
}
@@ -121,10 +129,12 @@ public class IdentifiedUser extends CurrentUser {
public static class RequestFactory {
private final CapabilityControl.Factory capabilityControlFactory;
private final AuthConfig authConfig;
+ private final Realm realm;
private final String anonymousCowardName;
private final Provider<String> canonicalUrl;
private final AccountCache accountCache;
private final GroupBackend groupBackend;
+ private final Boolean disableReverseDnsLookup;
private final Provider<SocketAddress> remotePeerProvider;
private final Provider<ReviewDb> dbProvider;
@@ -133,34 +143,38 @@ public class IdentifiedUser extends CurrentUser {
RequestFactory(
CapabilityControl.Factory capabilityControlFactory,
final AuthConfig authConfig,
+ Realm realm,
final @AnonymousCowardName String anonymousCowardName,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final AccountCache accountCache,
final GroupBackend groupBackend,
+ final @DisableReverseDnsLookup Boolean disableReverseDnsLookup,
final @RemotePeer Provider<SocketAddress> remotePeerProvider,
final Provider<ReviewDb> dbProvider) {
this.capabilityControlFactory = capabilityControlFactory;
this.authConfig = authConfig;
+ this.realm = realm;
this.anonymousCowardName = anonymousCowardName;
this.canonicalUrl = canonicalUrl;
this.accountCache = accountCache;
this.groupBackend = groupBackend;
+ this.disableReverseDnsLookup = disableReverseDnsLookup;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
}
public IdentifiedUser create(Account.Id id) {
- return new IdentifiedUser(capabilityControlFactory,
- authConfig, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, remotePeerProvider, dbProvider, id, null);
+ return new IdentifiedUser(capabilityControlFactory, authConfig, realm,
+ anonymousCowardName, canonicalUrl, accountCache, groupBackend,
+ disableReverseDnsLookup, remotePeerProvider, dbProvider, id, null);
}
public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
- return new IdentifiedUser(capabilityControlFactory,
- authConfig, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, remotePeerProvider, dbProvider, id, caller);
+ return new IdentifiedUser(capabilityControlFactory, authConfig, realm,
+ anonymousCowardName, canonicalUrl, accountCache, groupBackend,
+ disableReverseDnsLookup, remotePeerProvider, dbProvider, id, caller);
}
}
@@ -175,8 +189,11 @@ public class IdentifiedUser extends CurrentUser {
private final Provider<String> canonicalUrl;
private final AccountCache accountCache;
private final AuthConfig authConfig;
+ private final Realm realm;
private final GroupBackend groupBackend;
private final String anonymousCowardName;
+ private final Boolean disableReverseDnsLookup;
+ private final Set<String> validEmails = Sets.newHashSetWithExpectedSize(4);
@Nullable
private final Provider<SocketAddress> remotePeerProvider;
@@ -187,7 +204,8 @@ public class IdentifiedUser extends CurrentUser {
private final Account.Id accountId;
private AccountState state;
- private Set<String> emailAddresses;
+ private boolean loadedAllEmails;
+ private Set<String> invalidEmails;
private GroupMembership effectiveGroups;
private Set<Change.Id> starredChanges;
private ResultSet<StarredChange> starredQuery;
@@ -197,10 +215,12 @@ public class IdentifiedUser extends CurrentUser {
private IdentifiedUser(
CapabilityControl.Factory capabilityControlFactory,
final AuthConfig authConfig,
+ Realm realm,
final String anonymousCowardName,
final Provider<String> canonicalUrl,
final AccountCache accountCache,
final GroupBackend groupBackend,
+ final Boolean disableReverseDnsLookup,
@Nullable final Provider<SocketAddress> remotePeerProvider,
@Nullable final Provider<ReviewDb> dbProvider,
final Account.Id id,
@@ -210,7 +230,9 @@ public class IdentifiedUser extends CurrentUser {
this.accountCache = accountCache;
this.groupBackend = groupBackend;
this.authConfig = authConfig;
+ this.realm = realm;
this.anonymousCowardName = anonymousCowardName;
+ this.disableReverseDnsLookup = disableReverseDnsLookup;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
this.accountId = id;
@@ -259,11 +281,27 @@ public class IdentifiedUser extends CurrentUser {
return diffPref;
}
+ public boolean hasEmailAddress(String email) {
+ if (validEmails.contains(email)) {
+ return true;
+ } else if (invalidEmails != null && invalidEmails.contains(email)) {
+ return false;
+ } else if (realm.hasEmailAddress(this, email)) {
+ validEmails.add(email);
+ return true;
+ } else if (invalidEmails == null) {
+ invalidEmails = Sets.newHashSetWithExpectedSize(4);
+ }
+ invalidEmails.add(email);
+ return false;
+ }
+
public Set<String> getEmailAddresses() {
- if (emailAddresses == null) {
- emailAddresses = state().getEmailAddresses();
+ if (!loadedAllEmails) {
+ validEmails.addAll(realm.getEmailAddresses(this));
+ loadedAllEmails = true;
}
- return emailAddresses;
+ return validEmails;
}
public String getName() {
@@ -383,7 +421,7 @@ public class IdentifiedUser extends CurrentUser {
final InetSocketAddress sa = (InetSocketAddress) remotePeer;
final InetAddress in = sa.getAddress();
- host = in != null ? in.getCanonicalHostName() : sa.getHostName();
+ host = in != null ? getHost(in) : sa.getHostName();
}
}
if (host == null || host.isEmpty()) {
@@ -444,4 +482,11 @@ public class IdentifiedUser extends CurrentUser {
public boolean isIdentifiedUser() {
return true;
}
+
+ private String getHost(final InetAddress in) {
+ if (Boolean.FALSE.equals(disableReverseDnsLookup)) {
+ return in.getCanonicalHostName();
+ }
+ return in.getHostAddress();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
index 6f5618bb7b..ef28ed81c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server;
+import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.CapabilityControl;
@@ -39,8 +40,9 @@ public class InternalUser extends CurrentUser {
InternalUser create();
}
+ @VisibleForTesting
@Inject
- protected InternalUser(CapabilityControl.Factory capabilityControlFactory) {
+ public InternalUser(CapabilityControl.Factory capabilityControlFactory) {
super(capabilityControlFactory);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
index 49185465c2..d5242c2bd8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
@@ -12,25 +12,47 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-
package com.google.gerrit.server;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
+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.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.DraftCommentNotes;
import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Utility functions to manipulate PatchLineComments.
@@ -40,44 +62,242 @@ import java.util.List;
*/
@Singleton
public class PatchLineCommentsUtil {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final DraftCommentNotes.Factory draftFactory;
private final NotesMigration migration;
@VisibleForTesting
@Inject
- public PatchLineCommentsUtil(NotesMigration migration) {
+ public PatchLineCommentsUtil(GitRepositoryManager repoManager,
+ AllUsersNameProvider allUsersProvider,
+ DraftCommentNotes.Factory draftFactory,
+ NotesMigration migration) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsersProvider.get();
+ this.draftFactory = draftFactory;
this.migration = migration;
}
+ public Optional<PatchLineComment> get(ReviewDb db, ChangeNotes notes,
+ PatchLineComment.Key key) throws OrmException {
+ if (!migration.readChanges()) {
+ return Optional.fromNullable(db.patchComments().get(key));
+ }
+ for (PatchLineComment c : publishedByChange(db, notes)) {
+ if (key.equals(c.getKey())) {
+ return Optional.of(c);
+ }
+ }
+ for (PatchLineComment c : draftByChange(db, notes)) {
+ if (key.equals(c.getKey())) {
+ return Optional.of(c);
+ }
+ }
+ return Optional.absent();
+ }
+
+ public List<PatchLineComment> publishedByChange(ReviewDb db,
+ ChangeNotes notes) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(byCommentStatus(
+ db.patchComments().byChange(notes.getChangeId()), Status.PUBLISHED));
+ }
+
+ notes.load();
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(notes.getBaseComments().values());
+ comments.addAll(notes.getPatchSetComments().values());
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByChange(ReviewDb db,
+ ChangeNotes notes) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(byCommentStatus(
+ db.patchComments().byChange(notes.getChangeId()), Status.DRAFT));
+ }
+
+ List<PatchLineComment> comments = Lists.newArrayList();
+ Iterable<String> filtered = getDraftRefs(notes.getChangeId());
+ for (String refName : filtered) {
+ Account.Id account = Account.Id.fromRefPart(refName);
+ if (account != null) {
+ comments.addAll(draftByChangeAuthor(db, notes, account));
+ }
+ }
+ return sort(comments);
+ }
+
+ private static List<PatchLineComment> byCommentStatus(
+ ResultSet<PatchLineComment> comments,
+ final PatchLineComment.Status status) {
+ return Lists.newArrayList(
+ Iterables.filter(comments, new Predicate<PatchLineComment>() {
+ @Override
+ public boolean apply(PatchLineComment input) {
+ return (input.getStatus() == status);
+ }
+ })
+ );
+ }
+
+ public List<PatchLineComment> byPatchSet(ReviewDb db,
+ ChangeNotes notes, PatchSet.Id psId) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(db.patchComments().byPatchSet(psId).toList());
+ }
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(publishedByPatchSet(db, notes, psId));
+
+ Iterable<String> filtered = getDraftRefs(notes.getChangeId());
+ for (String refName : filtered) {
+ Account.Id account = Account.Id.fromRefPart(refName);
+ if (account != null) {
+ comments.addAll(draftByPatchSetAuthor(db, psId, account, notes));
+ }
+ }
+ return sort(comments);
+ }
+
public List<PatchLineComment> publishedByChangeFile(ReviewDb db,
ChangeNotes notes, Change.Id changeId, String file) throws OrmException {
- if (!migration.readPublishedComments()) {
- return db.patchComments().publishedByChangeFile(changeId, file).toList();
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments().publishedByChangeFile(changeId, file).toList());
}
notes.load();
- List<PatchLineComment> commentsOnFile = new ArrayList<PatchLineComment>();
+ List<PatchLineComment> comments = Lists.newArrayList();
- // We must iterate through all comments to find the ones on this file.
- addCommentsInFile(commentsOnFile, notes.getBaseComments().values(), file);
- addCommentsInFile(commentsOnFile, notes.getPatchSetComments().values(),
+ addCommentsOnFile(comments, notes.getBaseComments().values(), file);
+ addCommentsOnFile(comments, notes.getPatchSetComments().values(),
file);
-
- Collections.sort(commentsOnFile, ChangeNotes.PatchLineCommentComparator);
- return commentsOnFile;
+ return sort(comments);
}
public List<PatchLineComment> publishedByPatchSet(ReviewDb db,
ChangeNotes notes, PatchSet.Id psId) throws OrmException {
- if (!migration.readPublishedComments()) {
- return db.patchComments().publishedByPatchSet(psId).toList();
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments().publishedByPatchSet(psId).toList());
}
notes.load();
- List<PatchLineComment> commentsOnPs = new ArrayList<PatchLineComment>();
- commentsOnPs.addAll(notes.getPatchSetComments().get(psId));
- commentsOnPs.addAll(notes.getBaseComments().get(psId));
- return commentsOnPs;
+ List<PatchLineComment> comments = new ArrayList<>();
+ comments.addAll(notes.getPatchSetComments().get(psId));
+ comments.addAll(notes.getBaseComments().get(psId));
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByPatchSetAuthor(ReviewDb db,
+ PatchSet.Id psId, Account.Id author, ChangeNotes notes)
+ throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments().draftByPatchSetAuthor(psId, author).toList());
+ }
+
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(notes.getDraftBaseComments(author).row(psId).values());
+ comments.addAll(notes.getDraftPsComments(author).row(psId).values());
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByChangeFileAuthor(ReviewDb db,
+ ChangeNotes notes, String file, Account.Id author)
+ throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(
+ db.patchComments()
+ .draftByChangeFileAuthor(notes.getChangeId(), file, author)
+ .toList());
+ }
+ List<PatchLineComment> comments = Lists.newArrayList();
+ addCommentsOnFile(comments, notes.getDraftBaseComments(author).values(),
+ file);
+ addCommentsOnFile(comments, notes.getDraftPsComments(author).values(),
+ file);
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByChangeAuthor(ReviewDb db,
+ ChangeNotes notes, Account.Id author)
+ throws OrmException {
+ if (!migration.readChanges()) {
+ final Change.Id matchId = notes.getChangeId();
+ return FluentIterable
+ .from(db.patchComments().draftByAuthor(author))
+ .filter(new Predicate<PatchLineComment>() {
+ @Override
+ public boolean apply(PatchLineComment in) {
+ Change.Id changeId =
+ in.getKey().getParentKey().getParentKey().getParentKey();
+ return changeId.equals(matchId);
+ }
+ }).toSortedList(ChangeNotes.PLC_ORDER);
+ }
+ List<PatchLineComment> comments = Lists.newArrayList();
+ comments.addAll(notes.getDraftBaseComments(author).values());
+ comments.addAll(notes.getDraftPsComments(author).values());
+ return sort(comments);
+ }
+
+ public List<PatchLineComment> draftByAuthor(ReviewDb db,
+ Account.Id author) throws OrmException {
+ if (!migration.readChanges()) {
+ return sort(db.patchComments().draftByAuthor(author).toList());
+ }
+
+ Set<String> refNames =
+ getRefNamesAllUsers(RefNames.REFS_DRAFT_COMMENTS);
+
+ List<PatchLineComment> comments = Lists.newArrayList();
+ for (String refName : refNames) {
+ Account.Id id = Account.Id.fromRefPart(refName);
+ if (!author.equals(id)) {
+ continue;
+ }
+ Change.Id changeId = Change.Id.parse(refName);
+ DraftCommentNotes draftNotes =
+ draftFactory.create(changeId, author).load();
+ comments.addAll(draftNotes.getDraftBaseComments().values());
+ comments.addAll(draftNotes.getDraftPsComments().values());
+ }
+ return sort(comments);
+ }
+
+ public void insertComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.insertComment(c);
+ }
+ db.patchComments().insert(comments);
+ }
+
+ public void upsertComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.upsertComment(c);
+ }
+ db.patchComments().upsert(comments);
+ }
+
+ public void updateComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.updateComment(c);
+ }
+ db.patchComments().update(comments);
}
- private static Collection<PatchLineComment> addCommentsInFile(
+ public void deleteComments(ReviewDb db, ChangeUpdate update,
+ Iterable<PatchLineComment> comments) throws OrmException {
+ for (PatchLineComment c : comments) {
+ update.deleteComment(c);
+ }
+ db.patchComments().delete(comments);
+ }
+
+ private static Collection<PatchLineComment> addCommentsOnFile(
Collection<PatchLineComment> commentsOnFile,
Collection<PatchLineComment> allComments,
String file) {
@@ -90,11 +310,53 @@ public class PatchLineCommentsUtil {
return commentsOnFile;
}
- public void addPublishedComments(ReviewDb db, ChangeUpdate update,
- Iterable<PatchLineComment> comments) throws OrmException {
- for (PatchLineComment c : comments) {
- update.putComment(c);
+ public static void setCommentRevId(PatchLineComment c,
+ PatchListCache cache, Change change, PatchSet ps) throws OrmException {
+ if (c.getRevId() != null) {
+ return;
}
- db.patchComments().upsert(comments);
+ PatchList patchList;
+ try {
+ patchList = cache.get(change, ps);
+ } catch (PatchListNotAvailableException e) {
+ throw new OrmException(e);
+ }
+ c.setRevId((c.getSide() == (short) 0)
+ ? new RevId(ObjectId.toString(patchList.getOldId()))
+ : new RevId(ObjectId.toString(patchList.getNewId())));
+ }
+
+ private Set<String> getRefNamesAllUsers(String prefix) throws OrmException {
+ Repository repo;
+ try {
+ repo = repoManager.openRepository(allUsers);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ try {
+ RefDatabase refDb = repo.getRefDatabase();
+ return refDb.getRefs(prefix).keySet();
+ } catch (IOException e) {
+ throw new OrmException(e);
+ } finally {
+ repo.close();
+ }
+ }
+
+ private Iterable<String> getDraftRefs(final Change.Id changeId)
+ throws OrmException {
+ Set<String> refNames = getRefNamesAllUsers(RefNames.REFS_DRAFT_COMMENTS);
+ final String suffix = "-" + changeId.get();
+ return Iterables.filter(refNames, new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ return input.endsWith(suffix);
+ }
+ });
+ }
+
+ private static List<PatchLineComment> sort(List<PatchLineComment> comments) {
+ Collections.sort(comments, ChangeNotes.PLC_ORDER);
+ return comments;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java b/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java
index b8c08881db..6e12346b37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java
@@ -45,6 +45,7 @@ public class RequestCleanup implements Runnable {
}
}
+ @Override
public void run() {
synchronized (cleanup) {
ran = true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
index fe07100828..1403e60d96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
@@ -14,40 +14,164 @@
package com.google.gerrit.server;
-import com.google.common.collect.Lists;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.BranchWebLink;
+import com.google.gerrit.extensions.webui.DiffWebLink;
+import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
+import com.google.gerrit.extensions.webui.WebLink;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
-import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+@Singleton
public class WebLinks {
+ private static final Logger log = LoggerFactory.getLogger(WebLinks.class);
+ private static final Predicate<WebLinkInfo> INVALID_WEBLINK =
+ new Predicate<WebLinkInfo>() {
+
+ @Override
+ public boolean apply(WebLinkInfo link) {
+ if (link == null){
+ return false;
+ } else if (Strings.isNullOrEmpty(link.name)
+ || Strings.isNullOrEmpty(link.url)) {
+ log.warn(String.format("%s is missing name and/or url",
+ link.getClass().getName()));
+ return false;
+ }
+ return true;
+ }
+ };
private final DynamicSet<PatchSetWebLink> patchSetLinks;
+ private final DynamicSet<FileWebLink> fileLinks;
+ private final DynamicSet<DiffWebLink> diffLinks;
private final DynamicSet<ProjectWebLink> projectLinks;
+ private final DynamicSet<BranchWebLink> branchLinks;
+ @Inject
public WebLinks(DynamicSet<PatchSetWebLink> patchSetLinks,
- DynamicSet<ProjectWebLink> projectLinks) {
+ DynamicSet<FileWebLink> fileLinks,
+ DynamicSet<DiffWebLink> diffLinks,
+ DynamicSet<ProjectWebLink> projectLinks,
+ DynamicSet<BranchWebLink> branchLinks) {
this.patchSetLinks = patchSetLinks;
+ this.fileLinks = fileLinks;
+ this.diffLinks = diffLinks;
this.projectLinks = projectLinks;
+ this.branchLinks = branchLinks;
+ }
+
+ /**
+ *
+ * @param project Project name.
+ * @param commit SHA1 of commit.
+ * @return Links for patch sets.
+ */
+ public FluentIterable<WebLinkInfo> getPatchSetLinks(final Project.NameKey project,
+ final String commit) {
+ return filterLinks(patchSetLinks, new Function<WebLink, WebLinkInfo>() {
+
+ @Override
+ public WebLinkInfo apply(WebLink webLink) {
+ return ((PatchSetWebLink)webLink).getPatchSetWebLink(project.get(), commit);
+ }
+ });
+ }
+
+ /**
+ *
+ * @param project Project name.
+ * @param revision SHA1 of revision.
+ * @param file File name.
+ * @return Links for files.
+ */
+ public FluentIterable<WebLinkInfo> getFileLinks(final String project, final String revision,
+ final String file) {
+ return filterLinks(fileLinks, new Function<WebLink, WebLinkInfo>() {
+
+ @Override
+ public WebLinkInfo apply(WebLink webLink) {
+ return ((FileWebLink)webLink).getFileWebLink(project, revision, file);
+ }
+ });
+ }
+
+ /**
+ *
+ * @param project Project name.
+ * @param patchSetIdA Patch set ID of side A, <code>null</code> if no base
+ * patch set was selected.
+ * @param revisionA SHA1 of revision of side A.
+ * @param fileA File name of side A.
+ * @param patchSetIdB Patch set ID of side B.
+ * @param revisionB SHA1 of revision of side B.
+ * @param fileB File name of side B.
+ * @return Links for file diffs.
+ */
+ public FluentIterable<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) {
+ return FluentIterable
+ .from(diffLinks)
+ .transform(new Function<WebLink, DiffWebLinkInfo>() {
+ @Override
+ public DiffWebLinkInfo apply(WebLink webLink) {
+ return ((DiffWebLink) webLink).getDiffLink(project, changeId,
+ patchSetIdA, revisionA, fileA,
+ patchSetIdB, revisionB, fileB);
+ }
+ })
+ .filter(INVALID_WEBLINK);
+ }
+
+ /**
+ *
+ * @param project Project name.
+ * @return Links for projects.
+ */
+ public FluentIterable<WebLinkInfo> getProjectLinks(final String project) {
+ return filterLinks(projectLinks, new Function<WebLink, WebLinkInfo>() {
+
+ @Override
+ public WebLinkInfo apply(WebLink webLink) {
+ return ((ProjectWebLink)webLink).getProjectWeblink(project);
+ }
+ });
}
- public Iterable<WebLinkInfo> getPatchSetLinks(String project, String commit) {
- List<WebLinkInfo> links = Lists.newArrayList();
- for (PatchSetWebLink webLink : patchSetLinks) {
- links.add(new WebLinkInfo(webLink.getLinkName(),
- webLink.getPatchSetUrl(project, commit)));
- }
- return links;
+ /**
+ *
+ * @param project Project name
+ * @param branch Branch name
+ * @return Links for branches.
+ */
+ public FluentIterable<WebLinkInfo> getBranchLinks(final String project, final String branch) {
+ return filterLinks(branchLinks, new Function<WebLink, WebLinkInfo>() {
+
+ @Override
+ public WebLinkInfo apply(WebLink webLink) {
+ return ((BranchWebLink)webLink).getBranchWebLink(project, branch);
+ }
+ });
}
- public Iterable<WebLinkInfo> getProjectLinks(String project) {
- List<WebLinkInfo> links = Lists.newArrayList();
- for (ProjectWebLink webLink : projectLinks) {
- links.add(new WebLinkInfo(webLink.getLinkName(),
- webLink.getProjectUrl(project)));
- }
- return links;
+ private FluentIterable<WebLinkInfo> filterLinks(DynamicSet<? extends WebLink> links,
+ Function<WebLink, WebLinkInfo> transformer) {
+ return FluentIterable
+ .from(links)
+ .transform(transformer)
+ .filter(INVALID_WEBLINK);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
deleted file mode 100644
index e3ffa62e8a..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2014 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.registration.DynamicSet;
-import com.google.gerrit.extensions.webui.PatchSetWebLink;
-import com.google.gerrit.extensions.webui.ProjectWebLink;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class WebLinksProvider implements Provider<WebLinks> {
-
- private final DynamicSet<PatchSetWebLink> patchSetLinks;
- private final DynamicSet<ProjectWebLink> projectLinks;
-
- @Inject
- public WebLinksProvider(DynamicSet<PatchSetWebLink> patchSetLinks,
- DynamicSet<ProjectWebLink> projectLinks) {
- this.patchSetLinks = patchSetLinks;
- this.projectLinks = projectLinks;
- }
-
- @Override
- public WebLinks get() {
- WebLinks webLinks = new WebLinks(patchSetLinks, projectLinks);
- return webLinks;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
index 5d651aedb6..580897fecc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -37,7 +37,6 @@ import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.group.GroupJson;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
@@ -78,7 +77,7 @@ public class ListAccess implements RestReadView<TopLevelResource> {
ProjectCache projectCache, ProjectJson projectJson,
MetaDataUpdate.Server metaDataUpdateFactory,
GroupControl.Factory groupControlFactory, GroupBackend groupBackend,
- GroupJson groupJson, AllProjectsName allProjectsName) {
+ AllProjectsName allProjectsName) {
this.self = self;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java
new file mode 100644
index 0000000000..b50b003c5f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2015 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 com.google.gerrit.reviewdb.client.AccountGroup;
+
+public abstract class AbstractGroupBackend implements GroupBackend {
+ @Override
+ public boolean isVisibleToAll(AccountGroup.UUID uuid) {
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java
new file mode 100644
index 0000000000..7031672fd2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 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 com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.server.IdentifiedUser;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+/** Basic implementation of {@link Realm}. */
+public abstract class AbstractRealm implements Realm {
+ @Override
+ public boolean hasEmailAddress(IdentifiedUser user, String email) {
+ for (AccountExternalId ext : user.state().getExternalIds()) {
+ if (Objects.equals(ext.getEmailAddress(), email)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Set<String> getEmailAddresses(IdentifiedUser user) {
+ Collection<AccountExternalId> ids = user.state().getExternalIds();
+ Set<String> emails = Sets.newHashSetWithExpectedSize(ids.size());
+ for (AccountExternalId ext : ids) {
+ if (!Strings.isNullOrEmpty(ext.getEmailAddress())) {
+ emails.add(ext.getEmailAddress());
+ }
+ }
+ return emails;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
index 4827ed5d52..45d3d1f7c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
@@ -65,6 +65,7 @@ public class AccountByEmailCacheImpl implements AccountByEmailCache {
this.cache = cache;
}
+ @Override
public Set<Account.Id> get(final String email) {
try {
return cache.get(email);
@@ -74,6 +75,7 @@ public class AccountByEmailCacheImpl implements AccountByEmailCache {
}
}
+ @Override
public void evict(final String email) {
if (email != null) {
cache.invalidate(email);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
index a521840bfd..cc62b2b8c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -18,13 +18,13 @@ import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -79,6 +79,7 @@ public class AccountCacheImpl implements AccountCache {
this.byName = byUsername;
}
+ @Override
public AccountState get(Account.Id accountId) {
try {
return byId.get(accountId);
@@ -88,6 +89,7 @@ public class AccountCacheImpl implements AccountCache {
}
}
+ @Override
public AccountState getIfPresent(Account.Id accountId) {
return byId.getIfPresent(accountId);
}
@@ -103,12 +105,14 @@ public class AccountCacheImpl implements AccountCache {
}
}
+ @Override
public void evict(Account.Id accountId) {
if (accountId != null) {
byId.invalidate(accountId);
}
}
+ @Override
public void evictByUsername(String username) {
if (username != null) {
byName.invalidate(username);
@@ -117,6 +121,7 @@ public class AccountCacheImpl implements AccountCache {
private static AccountState missing(Account.Id accountId) {
Account account = new Account(accountId, TimeUtil.nowTs());
+ account.setActive(false);
Collection<AccountExternalId> ids = Collections.emptySet();
Set<AccountGroup.UUID> anon = ImmutableSet.of();
return new AccountState(account, anon, ids);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
index 4dc9d793ee..445ac6e83c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.common.AccountInfo;
+
import java.util.Set;
/**
@@ -34,7 +36,10 @@ public abstract class AccountDirectory {
AVATARS,
/** Unique user identity to login to Gerrit, may be deprecated. */
- USERNAME
+ USERNAME,
+
+ /** Numeric account ID, may be deprecated. */
+ ID;
}
public abstract void fillAccountInfo(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
deleted file mode 100644
index 97abbf65f3..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
+++ /dev/null
@@ -1,130 +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.account;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.account.AccountDirectory.DirectoryException;
-import com.google.gerrit.server.account.AccountDirectory.FillOptions;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class AccountInfo {
- public static class Loader {
- private static final Set<FillOptions> DETAILED_OPTIONS =
- Collections.unmodifiableSet(EnumSet.of(
- FillOptions.NAME,
- FillOptions.EMAIL,
- FillOptions.USERNAME,
- FillOptions.AVATARS));
-
- public interface Factory {
- Loader create(boolean detailed);
- }
-
- private final InternalAccountDirectory directory;
- private final boolean detailed;
- private final Map<Account.Id, AccountInfo> created;
- private final List<AccountInfo> provided;
-
- @Inject
- Loader(InternalAccountDirectory directory, @Assisted boolean detailed) {
- this.directory = directory;
- this.detailed = detailed;
- created = Maps.newHashMap();
- provided = Lists.newArrayList();
- }
-
- public AccountInfo get(Account.Id id) {
- if (id == null) {
- return null;
- }
- AccountInfo info = created.get(id);
- if (info == null) {
- info = new AccountInfo(id);
- if (detailed) {
- info._accountId = id.get();
- }
- created.put(id, info);
- }
- return info;
- }
-
- public void put(AccountInfo info) {
- if (detailed) {
- info._accountId = info._id.get();
- }
- provided.add(info);
- }
-
- public void fill() throws OrmException {
- try {
- directory.fillAccountInfo(
- Iterables.concat(created.values(), provided),
- detailed ? DETAILED_OPTIONS : EnumSet.of(FillOptions.NAME));
- } catch (DirectoryException e) {
- Throwables.propagateIfPossible(e.getCause(), OrmException.class);
- throw new OrmException(e);
- }
- }
-
- public void fill(Collection<? extends AccountInfo> infos)
- throws OrmException {
- for (AccountInfo info : infos) {
- put(info);
- }
- fill();
- }
- }
-
- public transient Account.Id _id;
-
- public AccountInfo(Account.Id id) {
- _id = id;
- }
-
- public Integer _accountId;
- public String name;
- public String email;
- public String username;
- public List<AvatarInfo> avatars;
-
- public static class AvatarInfo {
- /**
- * Size in pixels the UI prefers an avatar image to be.
- *
- * 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 String url;
- public Integer height;
- public Integer width;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java
new file mode 100644
index 0000000000..3e9b575a0a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2014 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.checkArgument;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountDirectory.DirectoryException;
+import com.google.gerrit.server.account.AccountDirectory.FillOptions;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class AccountLoader {
+ private static final Set<FillOptions> DETAILED_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.of(
+ FillOptions.ID,
+ FillOptions.NAME,
+ FillOptions.EMAIL,
+ FillOptions.USERNAME,
+ FillOptions.AVATARS));
+
+ public interface Factory {
+ AccountLoader create(boolean detailed);
+ }
+
+ private final InternalAccountDirectory directory;
+ private final Set<FillOptions> options;
+ private final Map<Account.Id, AccountInfo> created;
+ private final List<AccountInfo> provided;
+
+ @Inject
+ AccountLoader(InternalAccountDirectory directory, @Assisted boolean detailed) {
+ this.directory = directory;
+ options = detailed ? DETAILED_OPTIONS : InternalAccountDirectory.ID_ONLY;
+ created = Maps.newHashMap();
+ provided = Lists.newArrayList();
+ }
+
+ public AccountInfo get(Account.Id id) {
+ if (id == null) {
+ return null;
+ }
+ AccountInfo info = created.get(id);
+ if (info == null) {
+ info = new AccountInfo(id.get());
+ created.put(id, info);
+ }
+ return info;
+ }
+
+ public void put(AccountInfo info) {
+ checkArgument(info._accountId != null, "_accountId field required");
+ provided.add(info);
+ }
+
+ public void fill() throws OrmException {
+ try {
+ directory.fillAccountInfo(
+ Iterables.concat(created.values(), provided), options);
+ } catch (DirectoryException e) {
+ Throwables.propagateIfPossible(e.getCause(), OrmException.class);
+ throw new OrmException(e);
+ }
+ }
+
+ public void fill(Collection<? extends AccountInfo> infos)
+ throws OrmException {
+ for (AccountInfo info : infos) {
+ put(info);
+ }
+ fill();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 77ebe0f352..62615c6a2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
@@ -23,12 +25,11 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -36,7 +37,9 @@ import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/** Tracks authentication related details for user accounts. */
@@ -53,14 +56,17 @@ public class AccountManager {
private final ChangeUserName.Factory changeUserNameFactory;
private final ProjectCache projectCache;
private final AtomicBoolean awaitsFirstAccountCheck;
+ private final AuditService auditService;
@Inject
- AccountManager(final SchemaFactory<ReviewDb> schema,
- final AccountCache byIdCache, final AccountByEmailCache byEmailCache,
- final Realm accountMapper,
- final IdentifiedUser.GenericFactory userFactory,
- final ChangeUserName.Factory changeUserNameFactory,
- final ProjectCache projectCache) throws OrmException {
+ AccountManager(SchemaFactory<ReviewDb> schema,
+ AccountCache byIdCache,
+ AccountByEmailCache byEmailCache,
+ Realm accountMapper,
+ IdentifiedUser.GenericFactory userFactory,
+ ChangeUserName.Factory changeUserNameFactory,
+ ProjectCache projectCache,
+ AuditService auditService) {
this.schema = schema;
this.byIdCache = byIdCache;
this.byEmailCache = byEmailCache;
@@ -69,16 +75,17 @@ public class AccountManager {
this.changeUserNameFactory = changeUserNameFactory;
this.projectCache = projectCache;
this.awaitsFirstAccountCheck = new AtomicBoolean(true);
+ this.auditService = auditService;
}
/**
* @return user identified by this external identity string, or null.
*/
- public Account.Id lookup(final String externalId) throws AccountException {
+ public Account.Id lookup(String externalId) throws AccountException {
try {
- final ReviewDb db = schema.open();
+ ReviewDb db = schema.open();
try {
- final AccountExternalId ext =
+ AccountExternalId ext =
db.accountExternalIds().get(new AccountExternalId.Key(externalId));
return ext != null ? ext.getAccountId() : null;
} finally {
@@ -100,10 +107,10 @@ public class AccountManager {
public AuthResult authenticate(AuthRequest who) throws AccountException {
who = realm.authenticate(who);
try {
- final ReviewDb db = schema.open();
+ ReviewDb db = schema.open();
try {
- final AccountExternalId.Key key = id(who);
- final AccountExternalId id = db.accountExternalIds().get(key);
+ AccountExternalId.Key key = id(who);
+ AccountExternalId id = db.accountExternalIds().get(key);
if (id == null) {
// New account, automatically create and return.
//
@@ -129,16 +136,16 @@ public class AccountManager {
}
}
- private void update(final ReviewDb db, final AuthRequest who,
- final AccountExternalId extId) throws OrmException {
- final IdentifiedUser user = userFactory.create(extId.getAccountId());
+ private void update(ReviewDb db, AuthRequest who, AccountExternalId extId)
+ throws OrmException {
+ IdentifiedUser user = userFactory.create(extId.getAccountId());
Account toUpdate = null;
// If the email address was modified by the authentication provider,
// update our records to match the changed email.
//
- final String newEmail = who.getEmailAddress();
- final String oldEmail = extId.getEmailAddress();
+ String newEmail = who.getEmailAddress();
+ String oldEmail = extId.getEmailAddress();
if (newEmail != null && !newEmail.equals(oldEmail)) {
if (oldEmail != null
&& oldEmail.equals(user.getAccount().getPreferredEmail())) {
@@ -185,21 +192,21 @@ public class AccountManager {
return toUpdate;
}
- private static boolean eq(final String a, final String b) {
+ private static boolean eq(String a, String b) {
return (a == null && b == null) || (a != null && a.equals(b));
}
- private AuthResult create(final ReviewDb db, final AuthRequest who)
+ private AuthResult create(ReviewDb db, AuthRequest who)
throws OrmException, AccountException {
- final Account.Id newId = new Account.Id(db.nextAccountId());
- final Account account = new Account(newId, TimeUtil.nowTs());
- final AccountExternalId extId = createId(newId, who);
+ Account.Id newId = new Account.Id(db.nextAccountId());
+ Account account = new Account(newId, TimeUtil.nowTs());
+ AccountExternalId extId = createId(newId, who);
extId.setEmailAddress(who.getEmailAddress());
account.setFullName(who.getDisplayName());
account.setPreferredEmail(extId.getEmailAddress());
- final boolean isFirstAccount = awaitsFirstAccountCheck.getAndSet(false)
+ boolean isFirstAccount = awaitsFirstAccountCheck.getAndSet(false)
&& db.accounts().anyAccounts().toList().isEmpty();
try {
@@ -222,13 +229,12 @@ public class AccountManager {
.getAccessSection(AccessSection.GLOBAL_CAPABILITIES)
.getPermission(GlobalCapability.ADMINISTRATE_SERVER);
- final AccountGroup.UUID uuid = admin.getRules().get(0).getGroup().getUUID();
- final AccountGroup g = db.accountGroups().byUUID(uuid).iterator().next();
- final AccountGroup.Id adminId = g.getId();
- final AccountGroupMember m =
+ AccountGroup.UUID uuid = admin.getRules().get(0).getGroup().getUUID();
+ AccountGroup g = db.accountGroups().byUUID(uuid).iterator().next();
+ AccountGroup.Id adminId = g.getId();
+ AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(newId, adminId));
- db.accountGroupMembersAudit().insert(Collections.singleton(
- new AccountGroupMemberAudit(m, newId, TimeUtil.nowTs())));
+ auditService.dispatchAddAccountsToGroup(newId, Collections.singleton(m));
db.accountGroupMembers().insert(Collections.singleton(m));
}
@@ -239,17 +245,17 @@ public class AccountManager {
try {
changeUserNameFactory.create(db, user, who.getUserName()).call();
} catch (NameAlreadyUsedException e) {
- final String message =
+ String message =
"Cannot assign user name \"" + who.getUserName() + "\" to account "
+ newId + "; name already in use.";
handleSettingUserNameFailure(db, account, extId, message, e, false);
} catch (InvalidUserNameException e) {
- final String message =
+ String message =
"Cannot assign user name \"" + who.getUserName() + "\" to account "
+ newId + "; name does not conform.";
handleSettingUserNameFailure(db, account, extId, message, e, false);
} catch (OrmException e) {
- final String message = "Cannot assign user name";
+ String message = "Cannot assign user name";
handleSettingUserNameFailure(db, account, extId, message, e, true);
}
}
@@ -278,9 +284,9 @@ public class AccountManager {
* user to manually set the user name
* @throws OrmException thrown if cleaning the database failed
*/
- private void handleSettingUserNameFailure(final ReviewDb db,
- final Account account, final AccountExternalId extId,
- final String errorMessage, final Exception e, final boolean logException)
+ private void handleSettingUserNameFailure(ReviewDb db, Account account,
+ AccountExternalId extId, String errorMessage, Exception e,
+ boolean logException)
throws AccountUserNameException, OrmException {
if (logException) {
log.error(errorMessage, e);
@@ -302,9 +308,8 @@ public class AccountManager {
}
}
- private static AccountExternalId createId(final Account.Id newId,
- final AuthRequest who) {
- final String ext = who.getExternalId();
+ private static AccountExternalId createId(Account.Id newId, AuthRequest who) {
+ String ext = who.getExternalId();
return new AccountExternalId(newId, new AccountExternalId.Key(ext));
}
@@ -317,13 +322,13 @@ public class AccountManager {
* @throws AccountException the identity belongs to a different account, or it
* cannot be linked at this time.
*/
- public AuthResult link(final Account.Id to, AuthRequest who)
+ public AuthResult link(Account.Id to, AuthRequest who)
throws AccountException, OrmException {
- final ReviewDb db = schema.open();
+ ReviewDb db = schema.open();
try {
who = realm.link(db, to, who);
- final AccountExternalId.Key key = id(who);
+ AccountExternalId.Key key = id(who);
AccountExternalId extId = db.accountExternalIds().get(key);
if (extId != null) {
if (!extId.getAccountId().equals(to)) {
@@ -337,7 +342,7 @@ public class AccountManager {
db.accountExternalIds().insert(Collections.singleton(extId));
if (who.getEmailAddress() != null) {
- final Account a = db.accounts().get(to);
+ Account a = db.accounts().get(to);
if (a.getPreferredEmail() == null) {
a.setPreferredEmail(who.getEmailAddress());
db.accounts().update(Collections.singleton(a));
@@ -358,6 +363,50 @@ public class AccountManager {
}
/**
+ * Update the link to another unique authentication identity to an existing account.
+ *
+ * Existing external identities with the same scheme will be removed and replaced
+ * with the new one.
+ *
+ * @param to account to link the identity onto.
+ * @param who the additional identity.
+ * @return the result of linking the identity to the user.
+ * @throws OrmException
+ * @throws AccountException the identity belongs to a different account, or it
+ * cannot be linked at this time.
+ */
+ public AuthResult updateLink(Account.Id to, AuthRequest who) throws OrmException,
+ AccountException {
+ ReviewDb db = schema.open();
+ try {
+ AccountExternalId.Key key = id(who);
+ List<AccountExternalId.Key> filteredKeysByScheme =
+ filterKeysByScheme(key.getScheme(), db.accountExternalIds()
+ .byAccount(to));
+ if (!filteredKeysByScheme.isEmpty()
+ && (filteredKeysByScheme.size() > 1 || !filteredKeysByScheme
+ .contains(key))) {
+ db.accountExternalIds().deleteKeys(filteredKeysByScheme);
+ }
+ byIdCache.evict(to);
+ return link(to, who);
+ } finally {
+ db.close();
+ }
+ }
+
+ private List<AccountExternalId.Key> filterKeysByScheme(
+ String keyScheme, ResultSet<AccountExternalId> externalIds) {
+ List<AccountExternalId.Key> filteredExternalIds = new ArrayList<>();
+ for (AccountExternalId accountExternalId : externalIds) {
+ if (accountExternalId.isScheme(keyScheme)) {
+ filteredExternalIds.add(accountExternalId.getKey());
+ }
+ }
+ return filteredExternalIds;
+ }
+
+ /**
* Unlink an authentication identity from an existing account.
*
* @param from account to unlink the identity from.
@@ -366,13 +415,13 @@ public class AccountManager {
* @throws AccountException the identity belongs to a different account, or it
* cannot be unlinked at this time.
*/
- public AuthResult unlink(final Account.Id from, AuthRequest who)
+ public AuthResult unlink(Account.Id from, AuthRequest who)
throws AccountException, OrmException {
- final ReviewDb db = schema.open();
+ ReviewDb db = schema.open();
try {
who = realm.unlink(db, from, who);
- final AccountExternalId.Key key = id(who);
+ AccountExternalId.Key key = id(who);
AccountExternalId extId = db.accountExternalIds().get(key);
if (extId != null) {
if (!extId.getAccountId().equals(from)) {
@@ -381,7 +430,7 @@ public class AccountManager {
db.accountExternalIds().delete(Collections.singleton(extId));
if (who.getEmailAddress() != null) {
- final Account a = db.accounts().get(from);
+ Account a = db.accounts().get(from);
if (a.getPreferredEmail() != null
&& a.getPreferredEmail().equals(who.getEmailAddress())) {
a.setPreferredEmail(null);
@@ -403,7 +452,7 @@ public class AccountManager {
}
- private static AccountExternalId.Key id(final AuthRequest who) {
+ private static AccountExternalId.Key id(AuthRequest who) {
return new AccountExternalId.Key(who.getExternalId());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
index 106c033ec0..75e5ae5185 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
@@ -48,11 +48,11 @@ public class AccountResource implements RestResource {
return user;
}
- static class Capability implements RestResource {
+ public static class Capability implements RestResource {
private final IdentifiedUser user;
private final String capability;
- Capability(IdentifiedUser user, String capability) {
+ public Capability(IdentifiedUser user, String capability) {
this.user = user;
this.capability = capability;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
index 66607e26b0..815b51940b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -21,7 +21,6 @@ import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import java.util.Collection;
-import java.util.HashSet;
import java.util.Set;
public class AccountState {
@@ -64,25 +63,6 @@ public class AccountState {
return null;
}
- /**
- * All email addresses registered to this account.
- * <p>
- * Gerrit is "reasonably certain" that the returned email addresses actually
- * belong to the user of the account. Some emails may have been obtained from
- * the authentication provider, which in the case of OpenID may be trusting
- * the provider to have validated the address. Other emails may have been
- * validated by Gerrit directly.
- */
- public Set<String> getEmailAddresses() {
- final Set<String> emails = new HashSet<>();
- for (final AccountExternalId e : externalIds) {
- if (e.getEmailAddress() != null && !e.getEmailAddress().isEmpty()) {
- emails.add(e.getEmailAddress());
- }
- }
- return emails;
- }
-
/** The external identities that identify the account holder. */
public Collection<AccountExternalId> getExternalIds() {
return externalIds;
@@ -93,7 +73,7 @@ public class AccountState {
return internalGroups;
}
- private static String getUserName(Collection<AccountExternalId> ids) {
+ public static String getUserName(Collection<AccountExternalId> ids) {
for (AccountExternalId id : ids) {
if (id.isScheme(SCHEME_USERNAME)) {
return id.getSchemeRest();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index 5ef745b27f..efe73228a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -40,6 +40,7 @@ public class AccountsCollection implements
private final AccountResolver resolver;
private final AccountControl.Factory accountControlFactory;
private final IdentifiedUser.GenericFactory userFactory;
+ private final Provider<SuggestAccounts> list;
private final DynamicMap<RestView<AccountResource>> views;
private final CreateAccount.Factory createAccountFactory;
@@ -48,12 +49,14 @@ public class AccountsCollection implements
AccountResolver resolver,
AccountControl.Factory accountControlFactory,
IdentifiedUser.GenericFactory userFactory,
+ Provider<SuggestAccounts> list,
DynamicMap<RestView<AccountResource>> views,
CreateAccount.Factory createAccountFactory) {
this.self = self;
this.resolver = resolver;
this.accountControlFactory = accountControlFactory;
this.userFactory = userFactory;
+ this.list = list;
this.views = views;
this.createAccountFactory = createAccountFactory;
}
@@ -128,7 +131,7 @@ public class AccountsCollection implements
@Override
public RestView<TopLevelResource> list() throws ResourceNotFoundException {
- throw new ResourceNotFoundException();
+ return list.get();
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
index f60c794400..3017f73cc1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
@@ -34,6 +34,7 @@ public class CapabilityCollection {
private final Map<String, List<PermissionRule>> permissions;
public final List<PermissionRule> administrateServer;
+ public final List<PermissionRule> batchChangesLimit;
public final List<PermissionRule> emailReviewers;
public final List<PermissionRule> priority;
public final List<PermissionRule> queryLimit;
@@ -74,6 +75,7 @@ public class CapabilityCollection {
permissions = Collections.unmodifiableMap(res);
administrateServer = getPermission(GlobalCapability.ADMINISTRATE_SERVER);
+ batchChangesLimit = getPermission(GlobalCapability.BATCH_CHANGES_LIMIT);
emailReviewers = getPermission(GlobalCapability.EMAIL_REVIEWERS);
priority = getPermission(GlobalCapability.PRIORITY);
queryLimit = getPermission(GlobalCapability.QUERY_LIMIT);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index f0f22b5dd4..2721057483 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -110,6 +110,12 @@ public class CapabilityControl {
|| canAdministrateServer();
}
+ /** @return true if the user can modify an account for another user. */
+ public boolean canModifyAccount() {
+ return canPerform(GlobalCapability.MODIFY_ACCOUNT)
+ || canAdministrateServer();
+ }
+
/** @return true if the user can view all accounts. */
public boolean canViewAllAccounts() {
return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS)
@@ -163,12 +169,6 @@ public class CapabilityControl {
|| canAdministrateServer();
}
- /** @return true if the user can generate HTTP passwords for users other than self. */
- public boolean canGenerateHttpPassword() {
- return canPerform(GlobalCapability.GENERATE_HTTP_PASSWORD)
- || canAdministrateServer();
- }
-
/** @return true if the user can impersonate another user. */
public boolean canRunAs() {
return canPerform(GlobalCapability.RUN_AS);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
index 6b680325fa..1cb27f1067 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
@@ -18,7 +18,6 @@ import com.google.gerrit.extensions.annotations.CapabilityScope;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.Provider;
import org.slf4j.Logger;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
index 6dd51e1a0e..1005569eb1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
@@ -87,6 +87,7 @@ public class ChangeUserName implements Callable<VoidResult> {
this.newUsername = newUsername;
}
+ @Override
public VoidResult call() throws OrmException, NameAlreadyUsedException,
InvalidUserNameException {
final Collection<AccountExternalId> old = old();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 2d42f0d3dc..38fda4cceb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -15,10 +15,13 @@
package com.google.gerrit.server.account;
import com.google.common.collect.Sets;
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -30,14 +33,12 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.CreateAccount.Input;
import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.ssh.SshKeyCache;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -72,15 +73,16 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
private final SshKeyCache sshKeyCache;
private final AccountCache accountCache;
private final AccountByEmailCache byEmailCache;
- private final AccountInfo.Loader.Factory infoLoader;
+ private final AccountLoader.Factory infoLoader;
private final String username;
+ private final AuditService auditService;
@Inject
CreateAccount(ReviewDb db, Provider<IdentifiedUser> currentUser,
GroupsCollection groupsCollection, SshKeyCache sshKeyCache,
AccountCache accountCache, AccountByEmailCache byEmailCache,
- AccountInfo.Loader.Factory infoLoader,
- @Assisted String username) {
+ AccountLoader.Factory infoLoader,
+ @Assisted String username, AuditService auditService) {
this.db = db;
this.currentUser = currentUser;
this.groupsCollection = groupsCollection;
@@ -89,6 +91,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
this.byEmailCache = byEmailCache;
this.infoLoader = infoLoader;
this.username = username;
+ this.auditService = auditService;
}
@Override
@@ -169,9 +172,8 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
for (AccountGroup.Id groupId : groups) {
AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
- db.accountGroupMembersAudit().insert(Collections.singleton(
- new AccountGroupMemberAudit(
- m, currentUser.get().getAccountId(), TimeUtil.nowTs())));
+ auditService.dispatchAddAccountsToGroup(currentUser.get().getAccountId(),
+ Collections.singleton(m));
db.accountGroupMembers().insert(Collections.singleton(m));
}
@@ -179,7 +181,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
accountCache.evictByUsername(username);
byEmailCache.evict(input.email);
- AccountInfo.Loader loader = infoLoader.create(true);
+ AccountLoader loader = infoLoader.create(true);
AccountInfo info = loader.get(id);
loader.fill();
return Response.created(info);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
index 4be806723c..441213d1e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -23,8 +23,8 @@ 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.AuthType;
import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.CreateEmail.Input;
@@ -85,7 +85,7 @@ public class CreateEmail implements RestModifyView<AccountResource, Input> {
ResourceNotFoundException, OrmException, EmailException,
MethodNotAllowedException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to add email address");
}
@@ -98,8 +98,8 @@ public class CreateEmail implements RestModifyView<AccountResource, Input> {
}
if (input.noConfirmation
- && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("must be administrator to use no_confirmation");
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("not allowed to use no_confirmation");
}
return apply(rsrc.getUser(), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 938d940ac6..362a39f075 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -25,7 +25,7 @@ import com.google.inject.Singleton;
import java.util.Set;
@Singleton
-public class DefaultRealm implements Realm {
+public class DefaultRealm extends AbstractRealm {
private final EmailExpander emailExpander;
private final AccountByEmailCache byEmail;
private final AuthConfig authConfig;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
index 52ab6512aa..abdaf2394e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
@@ -29,7 +29,7 @@ import com.google.inject.Singleton;
import java.util.Collections;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class DeleteActive implements RestModifyView<AccountResource, Input> {
public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index 60485869bb..f1e02bd57b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -55,7 +55,7 @@ public class DeleteEmail implements RestModifyView<AccountResource.Email, Input>
throws AuthException, ResourceNotFoundException,
ResourceConflictException, MethodNotAllowedException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to delete email address");
}
return apply(rsrc.getUser(), rsrc.getEmail());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
index 7df1848fa1..90668580fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
@@ -14,9 +14,11 @@
package com.google.gerrit.server.account;
+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.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.DeleteSshKey.Input;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.server.OrmException;
@@ -32,18 +34,26 @@ public class DeleteSshKey implements
public static class Input {
}
+ private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private final SshKeyCache sshKeyCache;
@Inject
- DeleteSshKey(Provider<ReviewDb> dbProvider, SshKeyCache sshKeyCache) {
+ DeleteSshKey(Provider<ReviewDb> dbProvider,
+ Provider<CurrentUser> self,
+ SshKeyCache sshKeyCache) {
+ this.self = self;
this.dbProvider = dbProvider;
this.sshKeyCache = sshKeyCache;
}
@Override
public Response<?> apply(AccountResource.SshKey rsrc, Input input)
- throws OrmException {
+ throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()
+ && !self.get().getCapabilities().canAdministrateServer()) {
+ throw new AuthException("not allowed to delete SSH keys");
+ }
dbProvider.get().accountSshKeys()
.deleteKeys(Collections.singleton(rsrc.getSshKey().getKey()));
sshKeyCache.evict(rsrc.getUser().getUserName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
index c664377f2d..f60492e7e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
@@ -65,6 +65,7 @@ public interface EmailExpander {
return !user.contains(" ");
}
+ @Override
public String expand(final String user) {
return lhs + user + rhs;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
index a1785628e6..733cf5bd5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
@@ -66,7 +66,7 @@ public class Emails implements
throw new ResourceNotFoundException();
}
return new AccountResource.Email(rsrc.getUser(), email);
- } else if (rsrc.getUser().getEmailAddresses().contains(id.get())) {
+ } else if (rsrc.getUser().hasEmailAddress(id.get())) {
return new AccountResource.Email(rsrc.getUser(), id.get());
} else {
throw new ResourceNotFoundException();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java
index 7214a5c204..f8d6bd1ec5 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java
@@ -12,16 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.testutil;
+package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.FieldName;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.account.AuthRequest;
-import com.google.gerrit.server.account.Realm;
-/** Fake implementation of {@link Realm} for testing. */
-public class FakeRealm implements Realm {
+/** Fake implementation of {@link Realm} that does not communicate. */
+public class FakeRealm extends AbstractRealm {
@Override
public boolean allowsEdit(FieldName field) {
return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAccount.java
index 200595f92d..05f830001c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAccount.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -21,16 +22,16 @@ import com.google.inject.Singleton;
@Singleton
public class GetAccount implements RestReadView<AccountResource> {
- private final AccountInfo.Loader.Factory infoFactory;
+ private final AccountLoader.Factory infoFactory;
@Inject
- GetAccount(AccountInfo.Loader.Factory infoFactory) {
+ GetAccount(AccountLoader.Factory infoFactory) {
this.infoFactory = infoFactory;
}
@Override
public AccountInfo apply(AccountResource rsrc) throws OrmException {
- AccountInfo.Loader loader = infoFactory.create(true);
+ AccountLoader loader = infoFactory.create(true);
AccountInfo info = loader.get(rsrc.getUser().getAccountId());
loader.fill();
return info;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 47047ed2d4..43b76e2bee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -21,6 +21,7 @@ import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
import static com.google.gerrit.common.data.GlobalCapability.EMAIL_REVIEWERS;
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
+import static com.google.gerrit.common.data.GlobalCapability.MODIFY_ACCOUNT;
import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
import static com.google.gerrit.common.data.GlobalCapability.RUN_GC;
import static com.google.gerrit.common.data.GlobalCapability.STREAM_EVENTS;
@@ -114,6 +115,7 @@ class GetCapabilities implements RestReadView<AccountResource> {
have.put(EMAIL_REVIEWERS, cc.canEmailReviewers());
have.put(FLUSH_CACHES, cc.canFlushCaches());
have.put(KILL_TASK, cc.canKillTask());
+ have.put(MODIFY_ACCOUNT, cc.canModifyAccount());
have.put(RUN_GC, cc.canRunGC());
have.put(STREAM_EVENTS, cc.canStreamEvents());
have.put(VIEW_ALL_ACCOUNTS, cc.canViewAllAccounts());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
index 5959fac306..19aeefcc93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.client.Theme;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -70,6 +70,7 @@ public class GetDiffPreferences implements RestReadView<AccountResource> {
info.skipDeleted = p.isSkipDeleted() ? true : null;
info.skipUncommented = p.isSkipUncommented() ? true : null;
info.hideTopMenu = p.isHideTopMenu() ? true : null;
+ info.autoHideDiffTableHeader = p.isAutoHideDiffTableHeader() ? true : null;
info.hideLineNumbers = p.isHideLineNumbers() ? true : null;
info.syntaxHighlighting = p.isSyntaxHighlighting() ? true : null;
info.tabSize = p.getTabSize();
@@ -93,6 +94,7 @@ public class GetDiffPreferences implements RestReadView<AccountResource> {
public Boolean skipUncommented;
public Boolean syntaxHighlighting;
public Boolean hideTopMenu;
+ public Boolean autoHideDiffTableHeader;
public Boolean hideLineNumbers;
public Boolean renderEntireFile;
public Boolean hideEmptyPane;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
index 64991e6112..bf9c9ecfe3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
@@ -40,7 +40,7 @@ public class GetEmails implements RestReadView<AccountResource> {
public List<EmailInfo> apply(AccountResource rsrc) throws AuthException,
OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to list email addresses");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index ccc6e489c7..e45e7ccb06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -21,13 +21,11 @@ import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -108,14 +106,12 @@ public class GetPreferences implements RestReadView<AccountResource> {
Boolean copySelfOnEmail;
DateFormat dateFormat;
TimeFormat timeFormat;
- Boolean reversePatchSetOrder;
Boolean relativeDateInChangeTable;
Boolean sizeBarInChangeTable;
Boolean legacycidInChangeTable;
+ Boolean muteCommonPathPrefixes;
ReviewCategoryStrategy reviewCategoryStrategy;
- CommentVisibilityStrategy commentVisibilityStrategy;
DiffView diffView;
- ChangeScreen changeScreen;
List<TopMenu.MenuItem> my;
public PreferenceInfo(AccountGeneralPreferences p,
@@ -129,14 +125,12 @@ public class GetPreferences implements RestReadView<AccountResource> {
copySelfOnEmail = p.isCopySelfOnEmails() ? true : null;
dateFormat = p.getDateFormat();
timeFormat = p.getTimeFormat();
- reversePatchSetOrder = p.isReversePatchSetOrder() ? true : null;
relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
sizeBarInChangeTable = p.isSizeBarInChangeTable() ? true : null;
legacycidInChangeTable = p.isLegacycidInChangeTable() ? true : null;
+ muteCommonPathPrefixes = p.isMuteCommonPathPrefixes() ? true : null;
reviewCategoryStrategy = p.getReviewCategoryStrategy();
- commentVisibilityStrategy = p.getCommentVisibilityStrategy();
diffView = p.getDiffView();
- changeScreen = p.getChangeScreen();
}
my = my(v, allUsers);
}
@@ -155,7 +149,7 @@ public class GetPreferences implements RestReadView<AccountResource> {
}
if (my.isEmpty()) {
my.add(new TopMenu.MenuItem("Changes", "#/dashboard/self", null));
- my.add(new TopMenu.MenuItem("Drafts", "#/q/is:draft", null));
+ my.add(new TopMenu.MenuItem("Drafts", "#/q/owner:self+is:draft", null));
my.add(new TopMenu.MenuItem("Draft Comments", "#/q/has:draft", null));
my.add(new TopMenu.MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
my.add(new TopMenu.MenuItem("Starred Changes", "#/q/is:starred", null));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
index 9266c3aad7..68464707fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -45,7 +45,7 @@ public class GetSshKeys implements RestReadView<AccountResource> {
public List<SshKeyInfo> apply(AccountResource rsrc) throws AuthException,
OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to get SSH keys");
}
return apply(rsrc.getUser());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java
index 43b94f3383..c65f6d6abd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java
@@ -50,4 +50,10 @@ public interface GroupBackend {
/** @return the group membership checker for the backend. */
GroupMembership membershipsOf(IdentifiedUser user);
+
+ /**
+ * @return {@code true} if the group with the given UUID is visible to all
+ * registered users.
+ */
+ boolean isVisibleToAll(AccountGroup.UUID uuid);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
index ede2431f54..084cfe8b5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
@@ -45,7 +45,7 @@ public class GroupControl {
if (group == null) {
throw new NoSuchGroupException(groupId);
}
- return new GroupControl(who, group);
+ return new GroupControl(who, group, groupBackend);
}
}
@@ -85,7 +85,7 @@ public class GroupControl {
}
public GroupControl controlFor(GroupDescription.Basic group) {
- return new GroupControl(user.get(), group);
+ return new GroupControl(user.get(), group, groupBackend);
}
public GroupControl validateFor(final AccountGroup.Id groupId)
@@ -110,10 +110,12 @@ public class GroupControl {
private final CurrentUser user;
private final GroupDescription.Basic group;
private Boolean isOwner;
+ private final GroupBackend groupBackend;
- GroupControl(CurrentUser who, GroupDescription.Basic gd) {
+ GroupControl(CurrentUser who, GroupDescription.Basic gd, GroupBackend gb) {
user = who;
group = gd;
+ groupBackend = gb;
}
public GroupDescription.Basic getGroup() {
@@ -126,16 +128,15 @@ public class GroupControl {
/** Can this user see this group exists? */
public boolean isVisible() {
- AccountGroup accountGroup = GroupDescriptions.toAccountGroup(group);
/* Check for canAdministrateServer may seem redundant, but allows
* for visibility of all groups that are not an internal group to
* server administrators.
*/
- return (accountGroup != null && accountGroup.isVisibleToAll())
- || user instanceof InternalUser
+ return user instanceof InternalUser
+ || groupBackend.isVisibleToAll(group.getGroupUUID())
|| user.getEffectiveGroups().contains(group.getGroupUUID())
- || isOwner()
- || user.getCapabilities().canAdministrateServer();
+ || user.getCapabilities().canAdministrateServer()
+ || isOwner();
}
public boolean isOwner() {
@@ -150,11 +151,11 @@ public class GroupControl {
return isOwner;
}
- public boolean canAddMember(Account.Id id) {
+ public boolean canAddMember() {
return isOwner();
}
- public boolean canRemoveMember(Account.Id id) {
+ public boolean canRemoveMember() {
return isOwner();
}
@@ -166,15 +167,15 @@ public class GroupControl {
return canSeeMembers();
}
- public boolean canAddGroup(AccountGroup.UUID uuid) {
+ public boolean canAddGroup() {
return isOwner();
}
- public boolean canRemoveGroup(AccountGroup.UUID uuid) {
+ public boolean canRemoveGroup() {
return isOwner();
}
- public boolean canSeeGroup(AccountGroup.UUID uuid) {
+ public boolean canSeeGroup() {
return canSeeMembers();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
index 889addf2ad..9c806de592 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
@@ -92,6 +92,7 @@ public class GroupDetailFactory implements Callable<GroupDetail> {
}
Collections.sort(members, new Comparator<AccountGroupMember>() {
+ @Override
public int compare(final AccountGroupMember o1,
final AccountGroupMember o2) {
final Account a = aic.get(o1.getAccountId());
@@ -120,13 +121,14 @@ public class GroupDetailFactory implements Callable<GroupDetail> {
List<AccountGroupById> groups = new ArrayList<>();
for (final AccountGroupById m : db.accountGroupById().byGroup(groupId)) {
- if (control.canSeeGroup(m.getIncludeUUID())) {
+ if (control.canSeeGroup()) {
gic.want(m.getIncludeUUID());
groups.add(m);
}
}
Collections.sort(groups, new Comparator<AccountGroupById>() {
+ @Override
public int compare(final AccountGroupById o1,
final AccountGroupById o2) {
GroupDescription.Basic a = gic.get(o1.getIncludeUUID());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalAccountDirectory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalAccountDirectory.java
index 1e3e4b11db..4f8eacd0d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalAccountDirectory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalAccountDirectory.java
@@ -19,11 +19,12 @@ import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
+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.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountInfo.AvatarInfo;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gwtorm.server.OrmException;
import com.google.inject.AbstractModule;
@@ -31,10 +32,15 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.util.Collections;
+import java.util.EnumSet;
import java.util.Set;
@Singleton
public class InternalAccountDirectory extends AccountDirectory {
+ static final Set<FillOptions> ID_ONLY =
+ Collections.unmodifiableSet(EnumSet.of(FillOptions.ID));
+
public static class Module extends AbstractModule {
@Override
protected void configure() {
@@ -63,18 +69,26 @@ public class InternalAccountDirectory extends AccountDirectory {
Iterable<? extends AccountInfo> in,
Set<FillOptions> options)
throws DirectoryException {
+ if (options.equals(ID_ONLY)) {
+ return;
+ }
Multimap<Account.Id, AccountInfo> missing = ArrayListMultimap.create();
for (AccountInfo info : in) {
- AccountState state = accountCache.getIfPresent(info._id);
+ Account.Id id = new Account.Id(info._accountId);
+ AccountState state = accountCache.getIfPresent(id);
if (state != null) {
fill(info, state.getAccount(), options);
} else {
- missing.put(info._id, info);
+ missing.put(id, info);
}
}
if (!missing.isEmpty()) {
try {
for (Account account : db.get().accounts().get(missing.keySet())) {
+ if (options.contains(FillOptions.USERNAME)) {
+ account.setUserName(AccountState.getUserName(
+ db.get().accountExternalIds().byAccount(account.getId()).toList()));
+ }
for (AccountInfo info : missing.get(account.getId())) {
fill(info, account, options);
}
@@ -88,6 +102,12 @@ public class InternalAccountDirectory extends AccountDirectory {
private void fill(AccountInfo info,
Account account,
Set<FillOptions> options) {
+ if (options.contains(FillOptions.ID)) {
+ info._accountId = account.getId().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());
if (info.name == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
index a70f9429a2..861d3e92f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -90,4 +90,10 @@ public class InternalGroupBackend implements GroupBackend {
public GroupMembership membershipsOf(IdentifiedUser user) {
return groupMembershipFactory.create(user);
}
+
+ @Override
+ public boolean isVisibleToAll(AccountGroup.UUID uuid) {
+ GroupDescription.Internal g = get(uuid);
+ return g != null && g.getAccountGroup().isVisibleToAll();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 11f2e9183f..14c224f173 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -62,7 +62,7 @@ public class Module extends RestApiModule {
child(ACCOUNT_KIND, "capabilities").to(Capabilities.class);
get(ACCOUNT_KIND, "groups").to(GetGroups.class);
get(ACCOUNT_KIND, "preferences").to(GetPreferences.class);
- post(ACCOUNT_KIND, "preferences").to(SetPreferences.class);
+ put(ACCOUNT_KIND, "preferences").to(SetPreferences.class);
get(ACCOUNT_KIND, "preferences.diff").to(GetDiffPreferences.class);
put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
index 86c840b0a9..fd1f33e62d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
@@ -14,19 +14,17 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -52,12 +50,13 @@ public class PerformCreateGroup {
private final PersonIdent serverIdent;
private final GroupCache groupCache;
private final CreateGroupArgs createGroupArgs;
+ private final AuditService auditService;
@Inject
PerformCreateGroup(ReviewDb db, AccountCache accountCache,
GroupIncludeCache groupIncludeCache, IdentifiedUser currentUser,
@GerritPersonIdent PersonIdent serverIdent, GroupCache groupCache,
- @Assisted CreateGroupArgs createGroupArgs) {
+ @Assisted CreateGroupArgs createGroupArgs, AuditService auditService) {
this.db = db;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
@@ -65,6 +64,7 @@ public class PerformCreateGroup {
this.serverIdent = serverIdent;
this.groupCache = groupCache;
this.createGroupArgs = createGroupArgs;
+ this.auditService = auditService;
}
/**
@@ -127,18 +127,13 @@ public class PerformCreateGroup {
private void addMembers(final AccountGroup.Id groupId,
final Collection<? extends Account.Id> members) throws OrmException {
List<AccountGroupMember> memberships = new ArrayList<>();
- List<AccountGroupMemberAudit> membershipsAudit = new ArrayList<>();
for (Account.Id accountId : members) {
final AccountGroupMember membership =
new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId));
memberships.add(membership);
-
- final AccountGroupMemberAudit audit = new AccountGroupMemberAudit(
- membership, currentUser.getAccountId(), TimeUtil.nowTs());
- membershipsAudit.add(audit);
}
db.accountGroupMembers().insert(memberships);
- db.accountGroupMembersAudit().insert(membershipsAudit);
+ auditService.dispatchAddAccountsToGroup(currentUser.getAccountId(), memberships);
for (Account.Id accountId : members) {
accountCache.evict(accountId);
@@ -148,18 +143,13 @@ public class PerformCreateGroup {
private void addGroups(final AccountGroup.Id groupId,
final Collection<? extends AccountGroup.UUID> groups) throws OrmException {
List<AccountGroupById> includeList = new ArrayList<>();
- List<AccountGroupByIdAud> includesAudit = new ArrayList<>();
for (AccountGroup.UUID includeUUID : groups) {
final AccountGroupById groupInclude =
new AccountGroupById(new AccountGroupById.Key(groupId, includeUUID));
includeList.add(groupInclude);
-
- final AccountGroupByIdAud audit = new AccountGroupByIdAud(
- groupInclude, currentUser.getAccountId(), TimeUtil.nowTs());
- includesAudit.add(audit);
}
db.accountGroupById().insert(includeList);
- db.accountGroupByIdAud().insert(includesAudit);
+ auditService.dispatchAddGroupsToGroup(currentUser.getAccountId(), includeList);
for (AccountGroup.UUID uuid : groups) {
groupIncludeCache.evictParentGroupsOf(uuid);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
index 69d16d8b23..c7a63e59a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
@@ -29,7 +29,7 @@ import com.google.inject.Singleton;
import java.util.Collections;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class PutActive implements RestModifyView<AccountResource, Input> {
public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index 63c54201db..8cba72e7fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -79,7 +79,7 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
String newPassword;
if (input.generate) {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canGenerateHttpPassword()) {
+ && !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("not allowed to generate HTTP password");
}
newPassword = generate();
@@ -93,7 +93,7 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
} else {
if (!self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("not allowed to set HTTP password directly, "
- + "need to be Gerrit administrator");
+ + "requires the Generate HTTP Password permission");
}
newPassword = input.httpPassword;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
index 554bae7bc4..601ee76196 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
@@ -64,7 +64,7 @@ public class PutName implements RestModifyView<AccountResource, Input> {
throws AuthException, MethodNotAllowedException,
ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to change name");
}
return apply(rsrc.getUser(), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
index 7ac987d57a..c49e3be0da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
@@ -52,7 +52,7 @@ public class PutPreferred implements
public Response<String> apply(AccountResource.Email rsrc, Input input)
throws AuthException, ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to set preferred email address");
}
return apply(rsrc.getUser(), rsrc.getEmail());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
index e44d46e195..8dd8de7dcc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -16,6 +16,9 @@ package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+
+import java.util.Set;
public interface Realm {
/** Can the end-user modify this field of their own account? */
@@ -31,6 +34,12 @@ public interface Realm {
public void onCreateAccount(AuthRequest who, Account account);
+ /** @return true if the user has the given email address. */
+ public boolean hasEmailAddress(IdentifiedUser who, String email);
+
+ /** @return all known email addresses for the identified user. */
+ public Set<String> getEmailAddresses(IdentifiedUser who);
+
/**
* Locate an account whose local username is the given account name.
* <p>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index 9b971e4976..4e08756e79 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.client.Theme;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -48,6 +48,7 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, Input
Boolean skipUncommented;
Boolean syntaxHighlighting;
Boolean hideTopMenu;
+ Boolean autoHideDiffTableHeader;
Boolean hideLineNumbers;
Boolean renderEntireFile;
Integer tabSize;
@@ -68,8 +69,8 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, Input
public DiffPreferencesInfo apply(AccountResource rsrc, Input input)
throws AuthException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("restricted to administrator");
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("restricted to members of Modify Accounts");
}
if (input == null) {
input = new Input();
@@ -127,6 +128,9 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, Input
if (input.hideTopMenu != null) {
p.setHideTopMenu(input.hideTopMenu);
}
+ if (input.autoHideDiffTableHeader != null) {
+ p.setAutoHideDiffTableHeader(input.autoHideDiffTableHeader);
+ }
if (input.hideLineNumbers != null) {
p.setHideLineNumbers(input.hideLineNumbers);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index c3cc636464..d75c5a2f63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -26,13 +26,11 @@ import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ReviewCategoryStrategy;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
@@ -62,14 +60,12 @@ public class SetPreferences implements RestModifyView<AccountResource, Input> {
public Boolean copySelfOnEmail;
public DateFormat dateFormat;
public TimeFormat timeFormat;
- public Boolean reversePatchSetOrder;
public Boolean relativeDateInChangeTable;
public Boolean sizeBarInChangeTable;
public Boolean legacycidInChangeTable;
- public CommentVisibilityStrategy commentVisibilityStrategy;
+ public Boolean muteCommonPathPrefixes;
public ReviewCategoryStrategy reviewCategoryStrategy;
public DiffView diffView;
- public ChangeScreen changeScreen;
public List<TopMenu.MenuItem> my;
}
@@ -95,8 +91,8 @@ public class SetPreferences implements RestModifyView<AccountResource, Input> {
throws AuthException, ResourceNotFoundException, OrmException,
IOException, ConfigInvalidException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("restricted to administrator");
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("restricted to members of Modify Accounts");
}
if (i == null) {
i = new Input();
@@ -146,9 +142,6 @@ public class SetPreferences implements RestModifyView<AccountResource, Input> {
if (i.timeFormat != null) {
p.setTimeFormat(i.timeFormat);
}
- if (i.reversePatchSetOrder != null) {
- p.setReversePatchSetOrder(i.reversePatchSetOrder);
- }
if (i.relativeDateInChangeTable != null) {
p.setRelativeDateInChangeTable(i.relativeDateInChangeTable);
}
@@ -158,18 +151,15 @@ public class SetPreferences implements RestModifyView<AccountResource, Input> {
if (i.legacycidInChangeTable != null) {
p.setLegacycidInChangeTable(i.legacycidInChangeTable);
}
+ if (i.muteCommonPathPrefixes != null) {
+ p.setMuteCommonPathPrefixes(i.muteCommonPathPrefixes);
+ }
if (i.reviewCategoryStrategy != null) {
p.setReviewCategoryStrategy(i.reviewCategoryStrategy);
}
- if (i.commentVisibilityStrategy != null) {
- p.setCommentVisibilityStrategy(i.commentVisibilityStrategy);
- }
if (i.diffView != null) {
p.setDiffView(i.diffView);
}
- if (i.changeScreen != null) {
- p.setChangeScreen(i.changeScreen);
- }
db.get().accounts().update(Collections.singleton(a));
db.get().commit();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
index b94158fee0..b35c03e4c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
@@ -55,7 +55,7 @@ public class SshKeys implements
public AccountResource.SshKey parse(AccountResource rsrc, IdString id)
throws ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
+ && !self.get().getCapabilities().canModifyAccount()) {
throw new ResourceNotFoundException();
}
return parse(rsrc.getUser(), id);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
new file mode 100644
index 0000000000..07936d9f60
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
@@ -0,0 +1,156 @@
+// Copyright (C) 2014 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 com.google.common.base.Strings;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+import org.kohsuke.args4j.Option;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+class SuggestAccounts implements RestReadView<TopLevelResource> {
+ private static final int MAX_RESULTS = 100;
+ private static final String MAX_SUFFIX = "\u9fa5";
+
+ private final AccountControl accountControl;
+ private final AccountLoader accountLoader;
+ private final ReviewDb db;
+ private final boolean suggest;
+ private final int suggestFrom;
+
+ private int limit = 10;
+
+ @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of users to return")
+ void setLimit(int n) {
+ if (n < 0) {
+ limit = 10;
+ } else if (n == 0) {
+ limit = MAX_RESULTS;
+ } else {
+ limit = Math.min(n, MAX_RESULTS);
+ }
+ }
+
+ @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "match users")
+ private String query;
+
+ @Inject
+ SuggestAccounts(AccountControl.Factory accountControlFactory,
+ AccountLoader.Factory accountLoaderFactory,
+ ReviewDb db,
+ @GerritServerConfig Config cfg) {
+ accountControl = accountControlFactory.get();
+ accountLoader = accountLoaderFactory.create(true);
+ this.db = db;
+ this.suggestFrom = cfg.getInt("suggest", null, "from", 0);
+
+ if ("off".equalsIgnoreCase(cfg.getString("suggest", null, "accounts"))) {
+ suggest = false;
+ } else {
+ boolean suggest;
+ try {
+ AccountVisibility av =
+ cfg.getEnum("suggest", null, "accounts", AccountVisibility.ALL);
+ suggest = (av != AccountVisibility.NONE);
+ } catch (IllegalArgumentException err) {
+ suggest = cfg.getBoolean("suggest", null, "accounts", true);
+ }
+ this.suggest = suggest;
+ }
+ }
+
+ @Override
+ public List<AccountInfo> apply(TopLevelResource rsrc)
+ throws OrmException, BadRequestException {
+ if (Strings.isNullOrEmpty(query)) {
+ throw new BadRequestException("missing query field");
+ }
+
+ if (!suggest || query.length() < suggestFrom) {
+ return Collections.emptyList();
+ }
+
+ String a = query;
+ String b = a + MAX_SUFFIX;
+
+ Map<Account.Id, AccountInfo> matches = new LinkedHashMap<>();
+ Map<Account.Id, String> queryEmail = new HashMap<>();
+
+ for (Account p : db.accounts().suggestByFullName(a, b, limit)) {
+ addSuggestion(matches, p.getId());
+ }
+ if (matches.size() < limit) {
+ for (Account p : db.accounts()
+ .suggestByPreferredEmail(a, b, limit - matches.size())) {
+ addSuggestion(matches, p.getId());
+ }
+ }
+ if (matches.size() < limit) {
+ for (AccountExternalId e : db.accountExternalIds()
+ .suggestByEmailAddress(a, b, limit - matches.size())) {
+ if (addSuggestion(matches, e.getAccountId())) {
+ queryEmail.put(e.getAccountId(), e.getEmailAddress());
+ }
+ }
+ }
+
+ accountLoader.fill();
+ for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
+ AccountInfo info = matches.get(p.getKey());
+ if (info != null) {
+ info.email = p.getValue();
+ }
+ }
+
+ List<AccountInfo> m = new ArrayList<>(matches.values());
+ Collections.sort(m, new Comparator<AccountInfo>() {
+ @Override
+ public int compare(AccountInfo a, AccountInfo b) {
+ return ComparisonChain.start()
+ .compare(a.name, b.name, Ordering.natural().nullsLast())
+ .compare(a.email, b.email, Ordering.natural().nullsLast())
+ .result();
+ }
+ });
+ return m;
+ }
+
+ private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account.Id id) {
+ if (!map.containsKey(id) && accountControl.canSee(id)) {
+ map.put(id, accountLoader.get(id));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index 1748395dda..9b32ca9c28 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -185,4 +185,14 @@ public class UniversalGroupBackend implements GroupBackend {
return groups;
}
}
+
+ @Override
+ public boolean isVisibleToAll(AccountGroup.UUID uuid) {
+ for (GroupBackend g : backends) {
+ if (g.handles(uuid)) {
+ return g.isVisibleToAll(uuid);
+ }
+ }
+ return false;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index b1fd979b70..44413b748d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -15,10 +15,11 @@
package com.google.gerrit.server.api.accounts;
import com.google.gerrit.extensions.api.accounts.AccountApi;
+import com.google.gerrit.extensions.common.AccountInfo;
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.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.StarredChanges;
import com.google.gerrit.server.change.ChangeResource;
@@ -34,12 +35,12 @@ public class AccountApiImpl extends AccountApi.NotImplemented implements Account
private final AccountResource account;
private final ChangesCollection changes;
- private final AccountInfo.Loader.Factory accountLoaderFactory;
+ private final AccountLoader.Factory accountLoaderFactory;
private final StarredChanges.Create starredChangesCreate;
private final StarredChanges.Delete starredChangesDelete;
@Inject
- AccountApiImpl(AccountInfo.Loader.Factory ailf,
+ AccountApiImpl(AccountLoader.Factory ailf,
ChangesCollection changes,
StarredChanges.Create starredChangesCreate,
StarredChanges.Delete starredChangesDelete,
@@ -54,11 +55,11 @@ public class AccountApiImpl extends AccountApi.NotImplemented implements Account
@Override
public com.google.gerrit.extensions.common.AccountInfo get()
throws RestApiException {
- AccountInfo.Loader accountLoader = accountLoaderFactory.create(true);
+ AccountLoader accountLoader = accountLoaderFactory.create(true);
try {
AccountInfo ai = accountLoader.get(account.getUser().getAccountId());
accountLoader.fill();
- return AccountInfoMapper.fromAcountInfo(ai);
+ return ai;
} catch (OrmException e) {
throw new RestApiException("Cannot parse change", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 5f90d34259..161461d763 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -19,20 +19,33 @@ import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.api.changes.FixInput;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.RestoreInput;
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
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.server.change.Abandon;
+import com.google.gerrit.server.change.ChangeEdits;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.Check;
+import com.google.gerrit.server.change.GetHashtags;
+import com.google.gerrit.server.change.GetTopic;
+import com.google.gerrit.server.change.PostHashtags;
import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.change.PutTopic;
import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.Revert;
import com.google.gerrit.server.change.Revisions;
+import com.google.gerrit.server.change.SuggestReviewers;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -40,6 +53,8 @@ import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi {
interface Factory {
@@ -49,31 +64,52 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi {
private final Changes changeApi;
private final Revisions revisions;
private final RevisionApiImpl.Factory revisionApi;
+ private final Provider<SuggestReviewers> suggestReviewers;
private final ChangeResource change;
private final Abandon abandon;
private final Revert revert;
private final Restore restore;
- private final Provider<PostReviewers> postReviewers;
+ private final GetTopic getTopic;
+ private final PutTopic putTopic;
+ private final PostReviewers postReviewers;
private final Provider<ChangeJson> changeJson;
+ private final PostHashtags postHashtags;
+ private final GetHashtags getHashtags;
+ private final Check check;
+ private final ChangeEdits.Detail editDetail;
@Inject
ChangeApiImpl(Changes changeApi,
Revisions revisions,
RevisionApiImpl.Factory revisionApi,
+ Provider<SuggestReviewers> suggestReviewers,
Abandon abandon,
Revert revert,
Restore restore,
- Provider<PostReviewers> postReviewers,
+ GetTopic getTopic,
+ PutTopic putTopic,
+ PostReviewers postReviewers,
Provider<ChangeJson> changeJson,
+ PostHashtags postHashtags,
+ GetHashtags getHashtags,
+ Check check,
+ ChangeEdits.Detail editDetail,
@Assisted ChangeResource change) {
this.changeApi = changeApi;
this.revert = revert;
this.revisions = revisions;
this.revisionApi = revisionApi;
+ this.suggestReviewers = suggestReviewers;
this.abandon = abandon;
this.restore = restore;
+ this.getTopic = getTopic;
+ this.putTopic = putTopic;
this.postReviewers = postReviewers;
this.changeJson = changeJson;
+ this.postHashtags = postHashtags;
+ this.getHashtags = getHashtags;
+ this.check = check;
+ this.editDetail = editDetail;
this.change = change;
}
@@ -97,7 +133,7 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi {
try {
return revisionApi.create(
revisions.parse(change, IdString.fromDecoded(id)));
- } catch (OrmException e) {
+ } catch (OrmException | IOException e) {
throw new RestApiException("Cannot parse revision", e);
}
}
@@ -145,6 +181,22 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi {
}
@Override
+ public String topic() throws RestApiException {
+ return getTopic.apply(change);
+ }
+
+ @Override
+ public void topic(String topic) throws RestApiException {
+ PutTopic.Input in = new PutTopic.Input();
+ in.topic = topic;
+ try {
+ putTopic.apply(change, in);
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot set topic", e);
+ }
+ }
+
+ @Override
public void addReviewer(String reviewer) throws RestApiException {
AddReviewerInput in = new AddReviewerInput();
in.reviewer = reviewer;
@@ -154,18 +206,45 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi {
@Override
public void addReviewer(AddReviewerInput in) throws RestApiException {
try {
- postReviewers.get().apply(change, in);
+ postReviewers.apply(change, in);
} catch (OrmException | EmailException | IOException e) {
throw new RestApiException("Cannot add change reviewer", e);
}
}
@Override
+ public SuggestedReviewersRequest suggestReviewers() throws RestApiException {
+ return new SuggestedReviewersRequest() {
+ @Override
+ public List<SuggestedReviewerInfo> get() throws RestApiException {
+ return ChangeApiImpl.this.suggestReviewers(this);
+ }
+ };
+ }
+
+ @Override
+ public SuggestedReviewersRequest suggestReviewers(String query)
+ throws RestApiException {
+ return suggestReviewers().withQuery(query);
+ }
+
+ private List<SuggestedReviewerInfo> suggestReviewers(SuggestedReviewersRequest r)
+ throws RestApiException {
+ try {
+ SuggestReviewers mySuggestReviewers = suggestReviewers.get();
+ mySuggestReviewers.setQuery(r.getQuery());
+ mySuggestReviewers.setLimit(r.getLimit());
+ return mySuggestReviewers.apply(change);
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot retrieve suggested reviewers", e);
+ }
+ }
+
+ @Override
public ChangeInfo get(EnumSet<ListChangesOption> s)
throws RestApiException {
try {
- return ChangeInfoMapper.INSTANCE.apply(
- changeJson.get().addOptions(s).format(change));
+ return changeJson.get().addOptions(s).format(change);
} catch (OrmException e) {
throw new RestApiException("Cannot retrieve change", e);
}
@@ -173,11 +252,57 @@ class ChangeApiImpl extends ChangeApi.NotImplemented implements ChangeApi {
@Override
public ChangeInfo get() throws RestApiException {
- return get(EnumSet.allOf(ListChangesOption.class));
+ return get(EnumSet.complementOf(EnumSet.of(ListChangesOption.CHECK)));
+ }
+
+ @Override
+ public EditInfo getEdit() throws RestApiException {
+ try {
+ Response<EditInfo> edit = editDetail.apply(change);
+ return edit.isNone() ? null : edit.value();
+ } catch (IOException | OrmException | InvalidChangeOperationException e) {
+ throw new RestApiException("Cannot retrieve change edit", e);
+ }
}
@Override
public ChangeInfo info() throws RestApiException {
return get(EnumSet.noneOf(ListChangesOption.class));
}
+
+ @Override
+ public void setHashtags(HashtagsInput input) throws RestApiException {
+ try {
+ postHashtags.apply(change, input);
+ } catch (IOException | OrmException e) {
+ throw new RestApiException("Cannot post hashtags", e);
+ }
+ }
+
+ @Override
+ public Set<String> getHashtags() throws RestApiException {
+ try {
+ return getHashtags.apply(change).value();
+ } catch (IOException | OrmException e) {
+ throw new RestApiException("Cannot get hashtags", e);
+ }
+ }
+
+ @Override
+ public ChangeInfo check() throws RestApiException {
+ try {
+ return check.apply(change).value();
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot check change", e);
+ }
+ }
+
+ @Override
+ public ChangeInfo check(FixInput fix) throws RestApiException {
+ try {
+ return check.apply(change, fix).value();
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot check change", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java
deleted file mode 100644
index 8e6c20bb79..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (C) 2014 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.api.changes;
-
-import com.google.common.base.Function;
-import com.google.common.collect.EnumBiMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.gerrit.extensions.common.ApprovalInfo;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.extensions.common.ChangeStatus;
-import com.google.gerrit.extensions.common.LabelInfo;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.server.api.accounts.AccountInfoMapper;
-import com.google.gerrit.server.change.ChangeJson;
-
-import java.util.List;
-import java.util.Map;
-
-public class ChangeInfoMapper
- implements Function<ChangeJson.ChangeInfo, ChangeInfo> {
- public static final ChangeInfoMapper INSTANCE = new ChangeInfoMapper();
-
- private final static EnumBiMap<Change.Status, ChangeStatus> STATUS_MAP =
- EnumBiMap.create(Change.Status.class, ChangeStatus.class);
- static {
- STATUS_MAP.put(Status.DRAFT, ChangeStatus.DRAFT);
- STATUS_MAP.put(Status.NEW, ChangeStatus.NEW);
- STATUS_MAP.put(Status.SUBMITTED, ChangeStatus.SUBMITTED);
- STATUS_MAP.put(Status.MERGED, ChangeStatus.MERGED);
- STATUS_MAP.put(Status.ABANDONED, ChangeStatus.ABANDONED);
- }
-
- public static Status changeStatus2Status(ChangeStatus status) {
- if (status != null) {
- return STATUS_MAP.inverse().get(status);
- }
- return Change.Status.NEW;
- }
-
- private ChangeInfoMapper() {
- }
-
- @Override
- public ChangeInfo apply(ChangeJson.ChangeInfo i) {
- ChangeInfo o = new ChangeInfo();
- mapCommon(i, o);
- mapLabels(i, o);
- mapMessages(i, o);
- o.revisions = i.revisions;
- o.actions = i.actions;
- return o;
- }
-
- private void mapCommon(ChangeJson.ChangeInfo i, ChangeInfo o) {
- o.id = i.id;
- o.project = i.project;
- o.branch = i.branch;
- o.topic = i.topic;
- o.changeId = i.changeId;
- o.subject = i.subject;
- o.status = STATUS_MAP.get(i.status);
- o.created = i.created;
- o.updated = i.updated;
- o.starred = i.starred;
- o.reviewed = i.reviewed;
- o.mergeable = i.mergeable;
- o.insertions = i.insertions;
- o.deletions = i.deletions;
- o.owner = AccountInfoMapper.fromAcountInfo(i.owner);
- o.currentRevision = i.currentRevision;
- o._number = i._number;
- }
-
- private void mapMessages(ChangeJson.ChangeInfo i, ChangeInfo o) {
- if (i.messages == null) {
- return;
- }
- List<ChangeMessageInfo> r =
- Lists.newArrayListWithCapacity(i.messages.size());
- for (ChangeJson.ChangeMessageInfo m : i.messages) {
- ChangeMessageInfo cmi = new ChangeMessageInfo();
- cmi.id = m.id;
- cmi.author = AccountInfoMapper.fromAcountInfo(m.author);
- cmi.date = m.date;
- cmi.message = m.message;
- cmi._revisionNumber = m._revisionNumber;
- r.add(cmi);
- }
- o.messages = r;
- }
-
- private void mapLabels(ChangeJson.ChangeInfo i, ChangeInfo o) {
- if (i.labels == null) {
- return;
- }
- Map<String, LabelInfo> r = Maps.newLinkedHashMap();
- for (Map.Entry<String, ChangeJson.LabelInfo> e : i.labels.entrySet()) {
- ChangeJson.LabelInfo li = e.getValue();
- LabelInfo lo = new LabelInfo();
- lo.approved = AccountInfoMapper.fromAcountInfo(li.approved);
- lo.rejected = AccountInfoMapper.fromAcountInfo(li.rejected);
- lo.recommended = AccountInfoMapper.fromAcountInfo(li.recommended);
- lo.disliked = AccountInfoMapper.fromAcountInfo(li.disliked);
- lo.value = li.value;
- lo.defaultValue = li.defaultValue;
- lo.optional = li.optional;
- lo.blocking = li.blocking;
- lo.values = li.values;
- if (li.all != null) {
- lo.all = Lists.newArrayListWithExpectedSize(li.all.size());
- for (ChangeJson.ApprovalInfo ai : li.all) {
- lo.all.add(fromApprovalInfo(ai));
- }
- }
- r.put(e.getKey(), lo);
- }
- o.labels = r;
- }
-
- private static ApprovalInfo fromApprovalInfo(ChangeJson.ApprovalInfo ai) {
- ApprovalInfo ao = new ApprovalInfo();
- ao.value = ai.value;
- ao.date = ai.date;
- AccountInfoMapper.fromAccount(ai, ao);
- return ao;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index db72c9cffa..91809ec881 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -19,18 +19,16 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ListChangesOption;
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.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.CreateChange;
import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -89,7 +87,7 @@ class ChangesImpl implements Changes {
@Override
public ChangeApi create(ChangeInfo in) throws RestApiException {
try {
- ChangeJson.ChangeInfo out = createChange.apply(
+ ChangeInfo out = createChange.apply(
TopLevelResource.INSTANCE, in).value();
return api.create(changes.parse(TopLevelResource.INSTANCE,
IdString.fromUrl(out.changeId)));
@@ -133,12 +131,11 @@ class ChangesImpl implements Changes {
// Check type safety of result; the extension API should be safer than the
// REST API in this case, since it's intended to be used in Java.
Object first = checkNotNull(result.iterator().next());
- checkState(first instanceof ChangeJson.ChangeInfo);
+ checkState(first instanceof ChangeInfo);
@SuppressWarnings("unchecked")
- List<ChangeJson.ChangeInfo> infos = (List<ChangeJson.ChangeInfo>) result;
+ List<ChangeInfo> infos = (List<ChangeInfo>) result;
- return ImmutableList.copyOf(
- Lists.transform(infos, ChangeInfoMapper.INSTANCE));
+ return ImmutableList.copyOf(infos);
} catch (BadRequestException | AuthException | OrmException e) {
throw new RestApiException("Cannot query changes", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
new file mode 100644
index 0000000000..0352aff56a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.api.changes.CommentApi;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.CommentResource;
+import com.google.gerrit.server.change.GetComment;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+class CommentApiImpl implements CommentApi {
+ interface Factory {
+ CommentApiImpl create(CommentResource c);
+ }
+
+ private final GetComment getComment;
+ private final CommentResource comment;
+
+ @Inject
+ CommentApiImpl(GetComment getComment,
+ @Assisted CommentResource comment) {
+ this.getComment = getComment;
+ this.comment = comment;
+ }
+
+ @Override
+ public CommentInfo get() throws RestApiException {
+ try {
+ return getComment.apply(comment);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve comment", e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
new file mode 100644
index 0000000000..647f577246
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.api.changes.DraftApi;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.DeleteDraftComment;
+import com.google.gerrit.server.change.DraftCommentResource;
+import com.google.gerrit.server.change.GetDraftComment;
+import com.google.gerrit.server.change.PutDraftComment;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+
+class DraftApiImpl implements DraftApi {
+ interface Factory {
+ DraftApiImpl create(DraftCommentResource d);
+ }
+
+ private final DeleteDraftComment deleteDraft;
+ private final GetDraftComment getDraft;
+ private final PutDraftComment putDraft;
+ private final DraftCommentResource draft;
+
+ @Inject
+ DraftApiImpl(DeleteDraftComment deleteDraft,
+ GetDraftComment getDraft,
+ PutDraftComment putDraft,
+ @Assisted DraftCommentResource draft) {
+ this.deleteDraft = deleteDraft;
+ this.getDraft = getDraft;
+ this.putDraft = putDraft;
+ this.draft = draft;
+ }
+
+ @Override
+ public CommentInfo get() throws RestApiException {
+ try {
+ return getDraft.apply(draft);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve draft", e);
+ }
+ }
+
+ @Override
+ public CommentInfo update(DraftInput in) throws RestApiException {
+ try {
+ return putDraft.apply(draft, in).value();
+ } catch (IOException | OrmException e) {
+ throw new RestApiException("Cannot update draft", e);
+ }
+ }
+
+ @Override
+ public void delete() throws RestApiException {
+ try {
+ deleteDraft.apply(draft, null);
+ } catch (IOException | OrmException e) {
+ throw new RestApiException("Cannot delete draft", e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/FileApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/FileApiImpl.java
new file mode 100644
index 0000000000..42c1e237cb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/FileApiImpl.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2014 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.api.changes;
+
+import com.google.gerrit.extensions.api.changes.FileApi;
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.FileResource;
+import com.google.gerrit.server.change.GetContent;
+import com.google.gerrit.server.change.GetDiff;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+
+class FileApiImpl extends FileApi.NotImplemented implements FileApi {
+ interface Factory {
+ FileApiImpl create(FileResource r);
+ }
+
+ private final GetContent getContent;
+ private final Provider<GetDiff> getDiff;
+ private final FileResource file;
+
+ @Inject
+ FileApiImpl(GetContent getContent,
+ Provider<GetDiff> getDiff,
+ @Assisted FileResource file) {
+ this.getContent = getContent;
+ this.getDiff = getDiff;
+ this.file = file;
+ }
+
+ @Override
+ public BinaryResult content() throws RestApiException {
+ try {
+ return getContent.apply(file);
+ } catch (NoSuchChangeException | IOException | OrmException e) {
+ throw new RestApiException("Cannot retrieve file content", e);
+ }
+ }
+
+ @Override
+ public DiffInfo diff() throws RestApiException {
+ try {
+ return getDiff.get().apply(file).value();
+ } catch (IOException | InvalidChangeOperationException | OrmException e) {
+ throw new RestApiException("Cannot retrieve diff", e);
+ }
+ }
+
+ @Override
+ public DiffInfo diff(String base) throws RestApiException {
+ try {
+ return getDiff.get().setBase(base).apply(file).value();
+ } catch (IOException | InvalidChangeOperationException | OrmException e) {
+ throw new RestApiException("Cannot retrieve diff", e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
index dbf5f2740a..a37f8be56e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
@@ -23,6 +23,9 @@ public class Module extends FactoryModule {
bind(Changes.class).to(ChangesImpl.class);
factory(ChangeApiImpl.Factory.class);
+ factory(CommentApiImpl.Factory.class);
+ factory(DraftApiImpl.Factory.class);
factory(RevisionApiImpl.Factory.class);
+ factory(FileApiImpl.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index c709b3488b..c36faa2915 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -19,18 +19,32 @@ import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.Changes;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.changes.CommentApi;
+import com.google.gerrit.extensions.api.changes.DraftApi;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.FileApi;
+import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.common.MergeableInfo;
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.server.change.CherryPick;
+import com.google.gerrit.server.change.Comments;
+import com.google.gerrit.server.change.CreateDraftComment;
import com.google.gerrit.server.change.DeleteDraftPatchSet;
+import com.google.gerrit.server.change.DraftComments;
import com.google.gerrit.server.change.FileResource;
import com.google.gerrit.server.change.Files;
+import com.google.gerrit.server.change.ListComments;
+import com.google.gerrit.server.change.ListDraftComments;
+import com.google.gerrit.server.change.Mergeable;
import com.google.gerrit.server.change.PostReview;
-import com.google.gerrit.server.change.Publish;
+import com.google.gerrit.server.change.PublishDraftPatchSet;
import com.google.gerrit.server.change.Rebase;
import com.google.gerrit.server.change.Reviewed;
import com.google.gerrit.server.change.RevisionResource;
@@ -42,6 +56,8 @@ import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi {
@@ -55,13 +71,22 @@ class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi
private final Rebase rebase;
private final RebaseChange rebaseChange;
private final Submit submit;
- private final Publish publish;
+ private final PublishDraftPatchSet publish;
private final Reviewed.PutReviewed putReviewed;
private final Reviewed.DeleteReviewed deleteReviewed;
private final RevisionResource revision;
private final Provider<Files> files;
private final Provider<Files.ListFiles> listFiles;
private final Provider<PostReview> review;
+ private final Provider<Mergeable> mergeable;
+ private final FileApiImpl.Factory fileApi;
+ private final ListComments listComments;
+ private final ListDraftComments listDrafts;
+ private final CreateDraftComment createDraft;
+ private final DraftComments drafts;
+ private final DraftApiImpl.Factory draftFactory;
+ private final Comments comments;
+ private final CommentApiImpl.Factory commentFactory;
@Inject
RevisionApiImpl(Changes changes,
@@ -70,12 +95,21 @@ class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi
Rebase rebase,
RebaseChange rebaseChange,
Submit submit,
- Publish publish,
+ PublishDraftPatchSet publish,
Reviewed.PutReviewed putReviewed,
Reviewed.DeleteReviewed deleteReviewed,
Provider<Files> files,
Provider<Files.ListFiles> listFiles,
Provider<PostReview> review,
+ Provider<Mergeable> mergeable,
+ FileApiImpl.Factory fileApi,
+ ListComments listComments,
+ ListDraftComments listDrafts,
+ CreateDraftComment createDraft,
+ DraftComments drafts,
+ DraftApiImpl.Factory draftFactory,
+ Comments comments,
+ CommentApiImpl.Factory commentFactory,
@Assisted RevisionResource r) {
this.changes = changes;
this.cherryPick = cherryPick;
@@ -89,6 +123,15 @@ class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi
this.putReviewed = putReviewed;
this.deleteReviewed = deleteReviewed;
this.listFiles = listFiles;
+ this.mergeable = mergeable;
+ this.fileApi = fileApi;
+ this.listComments = listComments;
+ this.listDrafts = listDrafts;
+ this.createDraft = createDraft;
+ this.drafts = drafts;
+ this.draftFactory = draftFactory;
+ this.comments = comments;
+ this.commentFactory = commentFactory;
this.revision = r;
}
@@ -120,7 +163,7 @@ class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi
@Override
public void publish() throws RestApiException {
try {
- publish.apply(revision, new Publish.Input());
+ publish.apply(revision, new PublishDraftPatchSet.Input());
} catch (OrmException | IOException e) {
throw new RestApiException("Cannot publish draft patch set", e);
}
@@ -137,8 +180,14 @@ class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi
@Override
public ChangeApi rebase() throws RestApiException {
+ RebaseInput in = new RebaseInput();
+ return rebase(in);
+ }
+
+ @Override
+ public ChangeApi rebase(RebaseInput in) throws RestApiException {
try {
- return changes.id(rebase.apply(revision, null)._number);
+ return changes.id(rebase.apply(revision, in)._number);
} catch (OrmException | EmailException e) {
throw new RestApiException("Cannot rebase ps", e);
}
@@ -182,8 +231,102 @@ class RevisionApiImpl extends RevisionApi.NotImplemented implements RevisionApi
return ImmutableSet.copyOf((Iterable<String>) listFiles
.get().setReviewed(true)
.apply(revision).value());
- } catch (OrmException e) {
+ } catch (OrmException | IOException e) {
throw new RestApiException("Cannot list reviewed files", e);
}
}
+
+ @Override
+ public MergeableInfo mergeable() throws RestApiException {
+ try {
+ return mergeable.get().apply(revision);
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot check mergeability", e);
+ }
+ }
+
+ @Override
+ public MergeableInfo mergeableOtherBranches() throws RestApiException {
+ try {
+ Mergeable m = mergeable.get();
+ m.setOtherBranches(true);
+ return m.apply(revision);
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot check mergeability", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<String, FileInfo> files() throws RestApiException {
+ try {
+ return (Map<String, FileInfo>)listFiles.get().apply(revision).value();
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot retrieve files", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<String, FileInfo> files(String base) throws RestApiException {
+ try {
+ return (Map<String, FileInfo>) listFiles.get().setBase(base)
+ .apply(revision).value();
+ } catch (OrmException | IOException e) {
+ throw new RestApiException("Cannot retrieve files", e);
+ }
+ }
+
+ @Override
+ public FileApi file(String path) {
+ return fileApi.create(files.get().parse(revision,
+ IdString.fromDecoded(path)));
+ }
+
+ @Override
+ public Map<String, List<CommentInfo>> comments() throws RestApiException {
+ try {
+ return listComments.apply(revision);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve comments", e);
+ }
+ }
+
+ @Override
+ public Map<String, List<CommentInfo>> drafts() throws RestApiException {
+ try {
+ return listDrafts.apply(revision);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve drafts", e);
+ }
+ }
+
+ @Override
+ public DraftApi draft(String id) throws RestApiException {
+ try {
+ return draftFactory.create(drafts.parse(revision,
+ IdString.fromDecoded(id)));
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve draft", e);
+ }
+ }
+
+ @Override
+ public DraftApi createDraft(DraftInput in) throws RestApiException {
+ try {
+ return draft(createDraft.apply(revision, in).value().id);
+ } catch (IOException | OrmException e) {
+ throw new RestApiException("Cannot create draft", e);
+ }
+ }
+
+ @Override
+ public CommentApi comment(String id) throws RestApiException {
+ try {
+ return commentFactory.create(comments.parse(revision,
+ IdString.fromDecoded(id)));
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot retrieve comment", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
index 9c3d052372..1cbab8ac48 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
@@ -17,9 +17,11 @@ package com.google.gerrit.server.args4j;
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.server.ReviewDb;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.kohsuke.args4j.CmdLineException;
@@ -30,17 +32,16 @@ import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
public class ChangeIdHandler extends OptionHandler<Change.Id> {
-
- @Inject
- private ReviewDb db;
+ private final Provider<InternalChangeQuery> queryProvider;
@Inject
public ChangeIdHandler(
- final ReviewDb db,
+ // TODO(dborowitz): Not sure whether this is injectable here.
+ Provider<InternalChangeQuery> queryProvider,
@Assisted final CmdLineParser parser, @Assisted final OptionDef option,
@Assisted final Setter<Change.Id> setter) {
super(parser, option, setter);
- this.db = db;
+ this.queryProvider = queryProvider;
}
@Override
@@ -58,8 +59,8 @@ public class ChangeIdHandler extends OptionHandler<Change.Id> {
final Project.NameKey project = new Project.NameKey(tokens[0]);
final Branch.NameKey branch =
new Branch.NameKey(project, "refs/heads/" + tokens[1]);
- for (final Change change : db.changes().byBranchKey(branch, key)) {
- setter.addValue(change.getId());
+ for (final ChangeData cd : queryProvider.get().byBranchKey(branch, key)) {
+ setter.addValue(cd.getId());
return 1;
}
} catch (IllegalArgumentException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/TimestampHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/TimestampHandler.java
index 8dd42703e1..21ef31af10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/TimestampHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/TimestampHandler.java
@@ -31,7 +31,7 @@ import java.text.SimpleDateFormat;
import java.util.TimeZone;
public class TimestampHandler extends OptionHandler<Timestamp> {
- public final static String TIMESTAMP_FORMAT = "yyyyMMdd_HHmm";
+ public static final String TIMESTAMP_FORMAT = "yyyyMMdd_HHmm";
@Inject
public TimestampHandler(@Assisted CmdLineParser parser,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
index 09ab56bf13..e194eb743c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
@@ -14,9 +14,10 @@
package com.google.gerrit.server.auth;
-import com.google.common.base.Objects;
import com.google.gerrit.common.Nullable;
+import java.util.Objects;
+
/**
* Defines an abstract request for user authentication to Gerrit.
*/
@@ -50,7 +51,7 @@ public abstract class AuthRequest {
}
public void checkPassword(String pwd) throws AuthException {
- if (!Objects.equal(getPassword(), pwd)) {
+ if (!Objects.equals(getPassword(), pwd)) {
throw new InvalidCredentialsException();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java
index 65f1f58de5..f2c8222d22 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.auth;
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.auto.value.AutoValue;
import com.google.gerrit.common.Nullable;
/**
@@ -26,40 +27,19 @@ public class AuthUser {
/**
* Globally unique identifier for the user.
*/
- public static final class UUID {
- private final String uuid;
-
+ @AutoValue
+ public abstract static class UUID {
/**
* A new unique identifier.
*
* @param uuid the unique identifier.
+ * @return identifier instance.
*/
- public UUID(String uuid) {
- this.uuid = checkNotNull(uuid);
- }
-
- /** @return the globally unique identifier. */
- public String get() {
- return uuid;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof UUID) {
- return get().equals(((UUID) obj).get());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return get().hashCode();
+ public static UUID create(String uuid) {
+ return new AutoValue_AuthUser_UUID(uuid);
}
- @Override
- public String toString() {
- return String.format("AuthUser.UUID[%s]", get());
- }
+ public abstract String uuid();
}
private final UUID uuid;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
index 0b4baf2432..6ecea5e221 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
@@ -64,6 +64,6 @@ public class InternalAuthBackend implements AuthBackend {
}
req.checkPassword(who.getPassword(username));
- return new AuthUser(new AuthUser.UUID(username), username);
+ return new AuthUser(AuthUser.UUID.create(username), username);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
index 689c94cc00..87c8af25a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
@@ -18,12 +18,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* Universal implementation of the AuthBackend that works with the injected
* set of AuthBackends.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 618aa7d7f1..4c29c9b225 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -70,6 +70,7 @@ import javax.security.auth.login.LoginException;
private final String readTimeoutMillis;
private final String connectTimeoutMillis;
private final boolean useConnectionPooling;
+ private final boolean groupsVisibleToAll;
@Inject
Helper(@GerritServerConfig final Config config,
@@ -81,6 +82,7 @@ import javax.security.auth.login.LoginException;
this.password = LdapRealm.optional(config, "password", "");
this.referral = LdapRealm.optional(config, "referral", "ignore");
this.sslVerify = config.getBoolean("ldap", "sslverify", true);
+ this.groupsVisibleToAll = config.getBoolean("ldap", "groupsVisibleToAll", false);
this.authentication =
LdapRealm.optional(config, "authentication", "simple");
String readTimeout = LdapRealm.optional(config, "readTimeout");
@@ -211,7 +213,7 @@ import javax.security.auth.login.LoginException;
Set<AccountGroup.UUID> queryForGroups(final DirContext ctx,
final String username, LdapQuery.Result account)
- throws NamingException, AccountException {
+ throws NamingException {
final LdapSchema schema = getSchema(ctx);
final Set<String> groupDNs = new HashSet<>();
@@ -309,6 +311,10 @@ import javax.security.auth.login.LoginException;
}
}
+ public boolean groupsVisibleToAll() {
+ return this.groupsVisibleToAll;
+ }
+
class LdapSchema {
final LdapType type;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
index 1d90f0c3b9..8dc71776ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
@@ -91,7 +91,7 @@ public class LdapAuthBackend implements AuthBackend {
//
helper.authenticate(m.getDN(), req.getPassword()).close();
}
- return new AuthUser(new AuthUser.UUID(username), username);
+ return new AuthUser(AuthUser.UUID.create(username), username);
} finally {
try {
ctx.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index 6cdce8ddb5..158aae43c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -235,4 +235,9 @@ public class LdapGroupBackend implements GroupBackend {
}
return out;
}
+
+ @Override
+ public boolean isVisibleToAll(AccountGroup.UUID uuid) {
+ return handles(uuid) && helper.groupsVisibleToAll();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 7b79add4a3..90601080df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -26,13 +26,12 @@ import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AbstractRealm;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.EmailExpander;
-import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
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.server.SchemaFactory;
import com.google.inject.Inject;
@@ -59,7 +58,7 @@ import javax.naming.directory.DirContext;
import javax.security.auth.login.LoginException;
@Singleton
-public class LdapRealm implements Realm {
+public class LdapRealm extends AbstractRealm {
static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
static final String LDAP = "com.sun.jndi.ldap.LdapCtxFactory";
static final String USERNAME = "username";
@@ -102,7 +101,7 @@ public class LdapRealm implements Realm {
}
static SearchScope scope(final Config c, final String setting) {
- return ConfigUtil.getEnum(c, "ldap", null, setting, SearchScope.SUBTREE);
+ return c.getEnum("ldap", null, setting, SearchScope.SUBTREE);
}
static String optional(final Config config, final String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
index b99648b323..69d523b870 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
@@ -52,6 +52,7 @@ public class ForwardingRemovalListener<K, V> implements RemovalListener<K, V> {
}
}
+ @Override
@SuppressWarnings("unchecked")
public void onRemoval(RemovalNotification<K, V> notification) {
for (CacheRemovalListener<K, V> l : listeners) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index bb9a40b115..d0424d9ab6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -18,6 +18,7 @@ import com.google.common.base.Strings;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.extensions.api.changes.AbandonInput;
+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.RestModifyView;
@@ -28,7 +29,6 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.ReplyToChangeSender;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
new file mode 100644
index 0000000000..df6b76e347
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -0,0 +1,103 @@
+// Copyright (C) 2015 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.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.extensions.webui.UiActions;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.util.Providers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Singleton
+public class ActionJson {
+ private final Revisions revisions;
+ private final DynamicMap<RestView<ChangeResource>> changeViews;
+ private final RebaseChange rebaseChange;
+
+ @Inject
+ ActionJson(
+ Revisions revisions,
+ DynamicMap<RestView<ChangeResource>> changeViews,
+ RebaseChange rebaseChange) {
+ this.revisions = revisions;
+ this.changeViews = changeViews;
+ this.rebaseChange = rebaseChange;
+ }
+
+ public Map<String, ActionInfo> format(RevisionResource rsrc) {
+ return toActionMap(rsrc);
+ }
+
+ public ChangeInfo addChangeActions(ChangeInfo to, ChangeControl ctl) {
+ to.actions = toActionMap(ctl);
+ return to;
+ }
+
+ public RevisionInfo addRevisionActions(RevisionInfo to,
+ RevisionResource rsrc) {
+ to.actions = toActionMap(rsrc);
+ return to;
+ }
+
+ private Map<String, ActionInfo> toActionMap(ChangeControl ctl) {
+ Map<String, ActionInfo> out = new LinkedHashMap<>();
+ if (!ctl.getCurrentUser().isIdentifiedUser()) {
+ return out;
+ }
+
+ Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser());
+ for (UiAction.Description d : UiActions.from(
+ changeViews,
+ new ChangeResource(ctl, rebaseChange),
+ userProvider)) {
+ out.put(d.getId(), new ActionInfo(d));
+ }
+ // TODO(sbeller): why do we need to treat followup specially here?
+ if (ctl.getChange().getStatus().isOpen()) {
+ UiAction.Description descr = new UiAction.Description();
+ PrivateInternals_UiActionDescription.setId(descr, "followup");
+ PrivateInternals_UiActionDescription.setMethod(descr, "POST");
+ descr.setTitle("Create follow-up change");
+ out.put(descr.getId(), new ActionInfo(descr));
+ }
+ return out;
+ }
+
+ private Map<String, ActionInfo> toActionMap(RevisionResource rsrc) {
+ Map<String, ActionInfo> out = new LinkedHashMap<>();
+ if (rsrc.getControl().getCurrentUser().isIdentifiedUser()) {
+ Provider<CurrentUser> userProvider = Providers.of(
+ rsrc.getControl().getCurrentUser());
+ for (UiAction.Description d : UiActions.from(
+ revisions, rsrc, userProvider)) {
+ out.put(d.getId(), new ActionInfo(d));
+ }
+ }
+ return out;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
new file mode 100644
index 0000000000..c57f5a09d8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEditResource.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2014 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.extensions.restapi.RestResource;
+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.server.IdentifiedUser;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Represents change edit resource, that is actualy two kinds of resources:
+ * <ul>
+ * <li>the change edit itself</li>
+ * <li>a path within the edit</li>
+ * </ul>
+ * distinguished by whether path is null or not.
+ */
+public class ChangeEditResource implements RestResource {
+ public static final TypeLiteral<RestView<ChangeEditResource>> CHANGE_EDIT_KIND =
+ new TypeLiteral<RestView<ChangeEditResource>>() {};
+
+ private final ChangeResource change;
+ private final ChangeEdit edit;
+ private final String path;
+
+ public ChangeEditResource(ChangeResource change, ChangeEdit edit,
+ String path) {
+ this.change = change;
+ this.edit = edit;
+ this.path = path;
+ }
+
+ // TODO(davido): Make this cacheable.
+ // Should just depend on the SHA-1 of the edit itself.
+ public boolean isCacheable() {
+ return false;
+ }
+
+ public ChangeResource getChangeResource() {
+ return change;
+ }
+
+ public ChangeControl getControl() {
+ return getChangeResource().getControl();
+ }
+
+ public Change getChange() {
+ return edit.getChange();
+ }
+
+ public ChangeEdit getChangeEdit() {
+ return edit;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ Account.Id getAccountId() {
+ return getUser().getAccountId();
+ }
+
+ IdentifiedUser getUser() {
+ return edit.getUser();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
new file mode 100644
index 0000000000..9bd625d096
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -0,0 +1,557 @@
+// Copyright (C) 2014 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.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AcceptsDelete;
+import com.google.gerrit.extensions.restapi.AcceptsPost;
+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.ChildCollection;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RawInput;
+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.RestReadView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.WebLinks;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditJson;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.edit.UnchangedCommitMessageException;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.util.List;
+
+@Singleton
+public class ChangeEdits implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsCreate<ChangeResource>,
+ AcceptsPost<ChangeResource>,
+ AcceptsDelete<ChangeResource> {
+ private final DynamicMap<RestView<ChangeEditResource>> views;
+ private final Create.Factory createFactory;
+ private final DeleteFile.Factory deleteFileFactory;
+ private final Provider<Detail> detail;
+ private final ChangeEditUtil editUtil;
+ private final Post post;
+
+ @Inject
+ ChangeEdits(DynamicMap<RestView<ChangeEditResource>> views,
+ Create.Factory createFactory,
+ Provider<Detail> detail,
+ ChangeEditUtil editUtil,
+ Post post,
+ DeleteFile.Factory deleteFileFactory) {
+ this.views = views;
+ this.createFactory = createFactory;
+ this.detail = detail;
+ this.editUtil = editUtil;
+ this.post = post;
+ this.deleteFileFactory = deleteFileFactory;
+ }
+
+ @Override
+ public DynamicMap<RestView<ChangeEditResource>> views() {
+ return views;
+ }
+
+ @Override
+ public RestView<ChangeResource> list() {
+ return detail.get();
+ }
+
+ @Override
+ public ChangeEditResource parse(ChangeResource rsrc, IdString id)
+ throws ResourceNotFoundException, AuthException, IOException,
+ InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new ChangeEditResource(rsrc, edit.get(), id.get());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Create create(ChangeResource parent, IdString id)
+ throws RestApiException {
+ return createFactory.create(parent.getChange(), id.get());
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Post post(ChangeResource parent) throws RestApiException {
+ return post;
+ }
+
+ /**
+ * Create handler that is activated when collection element is accessed
+ * but doesn't exist, e. g. PUT request with a path was called but
+ * change edit wasn't created yet. Change edit is created and PUT
+ * handler is called.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public DeleteFile delete(ChangeResource parent, IdString id)
+ throws RestApiException {
+ // It's safe to assume that id can never be null, because
+ // otherwise we would end up in dedicated endpoint for
+ // deleting of change edits and not a file in change edit
+ return deleteFileFactory.create(id.get());
+ }
+
+ static class Create implements
+ RestModifyView<ChangeResource, Put.Input> {
+
+ interface Factory {
+ Create create(Change change, String path);
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+ private final Put putEdit;
+ private final Change change;
+ private final String path;
+
+ @Inject
+ Create(Provider<ReviewDb> db,
+ ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier,
+ Put putEdit,
+ @Assisted Change change,
+ @Assisted @Nullable String path) {
+ this.db = db;
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ this.putEdit = putEdit;
+ this.change = change;
+ this.path = path;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource resource, Put.Input input)
+ throws AuthException, IOException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(change);
+ if (edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "edit already exists for the change %s",
+ resource.getChange().getChangeId()));
+ }
+ edit = createEdit();
+ if (!Strings.isNullOrEmpty(path)) {
+ putEdit.apply(new ChangeEditResource(resource, edit.get(), path),
+ input);
+ }
+ return Response.none();
+ }
+
+ private Optional<ChangeEdit> createEdit() throws AuthException,
+ IOException, ResourceConflictException, OrmException {
+ editModifier.createEdit(change,
+ db.get().patchSets().get(change.currentPatchSetId()));
+ return editUtil.byChange(change);
+ }
+ }
+
+ static class DeleteFile implements
+ RestModifyView<ChangeResource, DeleteFile.Input> {
+ public static class Input {
+ }
+
+ interface Factory {
+ DeleteFile create(String path);
+ }
+
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+ private final Provider<ReviewDb> db;
+ private final String path;
+
+ @Inject
+ DeleteFile(ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier,
+ Provider<ReviewDb> db,
+ @Assisted String path) {
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ this.db = db;
+ this.path = path;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, DeleteFile.Input in)
+ throws IOException, AuthException, ResourceConflictException,
+ OrmException, InvalidChangeOperationException, BadRequestException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (edit.isPresent()) {
+ // Edit is wiped out
+ editUtil.delete(edit.get());
+ } else {
+ // Edit is created on top of current patch set by deleting path.
+ // Even if the latest patch set changed since the user triggered
+ // the operation, deleting the whole file is probably still what
+ // they intended.
+ editModifier.createEdit(rsrc.getChange(), db.get().patchSets().get(
+ rsrc.getChange().currentPatchSetId()));
+ edit = editUtil.byChange(rsrc.getChange());
+ editModifier.deleteFile(edit.get(), path);
+ }
+ return Response.none();
+ }
+ }
+
+ // TODO(davido): Turn the boolean options to ChangeEditOption enum,
+ // like it's already the case for ListChangesOption/ListGroupsOption
+ public static class Detail implements RestReadView<ChangeResource> {
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditJson editJson;
+ private final FileInfoJson fileInfoJson;
+ private final Revisions revisions;
+
+ @Option(name = "--base", metaVar = "revision-id")
+ String base;
+
+ @Option(name = "--list")
+ boolean list;
+
+ @Option(name = "--download-commands")
+ boolean downloadCommands;
+
+ @Inject
+ Detail(ChangeEditUtil editUtil,
+ ChangeEditJson editJson,
+ FileInfoJson fileInfoJson,
+ Revisions revisions) {
+ this.editJson = editJson;
+ this.editUtil = editUtil;
+ this.fileInfoJson = fileInfoJson;
+ this.revisions = revisions;
+ }
+
+ @Override
+ public Response<EditInfo> apply(ChangeResource rsrc) throws AuthException,
+ IOException, InvalidChangeOperationException,
+ ResourceNotFoundException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ return Response.none();
+ }
+
+ EditInfo editInfo = editJson.toEditInfo(edit.get(), downloadCommands);
+ if (list) {
+ PatchSet basePatchSet = null;
+ if (base != null) {
+ RevisionResource baseResource = revisions.parse(
+ rsrc, IdString.fromDecoded(base));
+ basePatchSet = baseResource.getPatchSet();
+ }
+ try {
+ editInfo.files =
+ fileInfoJson.toFileInfoMap(
+ rsrc.getChange(),
+ edit.get().getRevision(),
+ basePatchSet);
+ } catch (PatchListNotAvailableException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+ }
+ return Response.ok(editInfo);
+ }
+ }
+
+ /**
+ * Post to edit collection resource. Two different operations are
+ * supported:
+ * <ul>
+ * <li>Create non existing change edit</li>
+ * <li>Restore path in existing change edit</li>
+ * </ul>
+ * The combination of two operations in one request is supported.
+ */
+ @Singleton
+ public static class Post implements
+ RestModifyView<ChangeResource, Post.Input> {
+ public static class Input {
+ public String restorePath;
+ public String oldPath;
+ public String newPath;
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditUtil editUtil;
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ Post(Provider<ReviewDb> db,
+ ChangeEditUtil editUtil,
+ ChangeEditModifier editModifier) {
+ this.db = db;
+ this.editUtil = editUtil;
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource resource, Post.Input input)
+ throws AuthException, InvalidChangeOperationException, IOException,
+ ResourceConflictException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(resource.getChange());
+ if (!edit.isPresent()) {
+ edit = createEdit(resource.getChange());
+ }
+
+ if (input != null) {
+ if (!Strings.isNullOrEmpty(input.restorePath)) {
+ editModifier.restoreFile(edit.get(), input.restorePath);
+ } else if (!Strings.isNullOrEmpty(input.oldPath)
+ && !Strings.isNullOrEmpty(input.newPath)) {
+ editModifier.renameFile(edit.get(), input.oldPath, input.newPath);
+ }
+ }
+ return Response.none();
+ }
+
+ private Optional<ChangeEdit> createEdit(Change change)
+ throws AuthException, IOException, ResourceConflictException,
+ OrmException {
+ editModifier.createEdit(change,
+ db.get().patchSets().get(change.currentPatchSetId()));
+ return editUtil.byChange(change);
+ }
+ }
+
+ /**
+ * Put handler that is activated when PUT request is called on
+ * collection element.
+ */
+ @Singleton
+ public static class Put implements
+ RestModifyView<ChangeEditResource, Put.Input> {
+ public static class Input {
+ @DefaultInput
+ public RawInput content;
+ }
+
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ Put(ChangeEditModifier editModifier) {
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc, Input input)
+ throws AuthException, ResourceConflictException, IOException {
+ String path = rsrc.getPath();
+ if (Strings.isNullOrEmpty(path) || path.charAt(0) == '/') {
+ throw new ResourceConflictException("Invalid path: " + path);
+ }
+
+ try {
+ editModifier.modifyFile(
+ rsrc.getChangeEdit(),
+ rsrc.getPath(),
+ input.content);
+ } catch(InvalidChangeOperationException | IOException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ return Response.none();
+ }
+ }
+
+ /**
+ * Handler to delete a file.
+ * <p>
+ * This deletes the file from the repository completely. This is not the same
+ * as reverting or restoring a file to its previous contents.
+ */
+ @Singleton
+ static class DeleteContent implements
+ RestModifyView<ChangeEditResource, DeleteContent.Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditModifier editModifier;
+
+ @Inject
+ DeleteContent(ChangeEditModifier editModifier) {
+ this.editModifier = editModifier;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc, DeleteContent.Input input)
+ throws AuthException, ResourceConflictException {
+ try {
+ editModifier.deleteFile(rsrc.getChangeEdit(), rsrc.getPath());
+ } catch(InvalidChangeOperationException | IOException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ return Response.none();
+ }
+ }
+
+ @Singleton
+ static class Get implements RestReadView<ChangeEditResource> {
+ private final FileContentUtil fileContentUtil;
+
+ @Inject
+ Get(FileContentUtil fileContentUtil) {
+ this.fileContentUtil = fileContentUtil;
+ }
+
+ @Override
+ public Response<?> apply(ChangeEditResource rsrc)
+ throws ResourceNotFoundException, IOException {
+ try {
+ return Response.ok(fileContentUtil.getContent(
+ rsrc.getControl().getProjectControl().getProjectState(),
+ ObjectId.fromString(rsrc.getChangeEdit().getRevision().get()),
+ rsrc.getPath()));
+ } catch (ResourceNotFoundException rnfe) {
+ return Response.none();
+ }
+ }
+ }
+
+ @Singleton
+ static class GetMeta implements RestReadView<ChangeEditResource> {
+ private final WebLinks webLinks;
+
+ @Inject
+ GetMeta(WebLinks webLinks) {
+ this.webLinks = webLinks;
+ }
+
+ @Override
+ public FileInfo apply(ChangeEditResource rsrc) {
+ FileInfo r = new FileInfo();
+ ChangeEdit edit = rsrc.getChangeEdit();
+ Change change = edit.getChange();
+ FluentIterable<DiffWebLinkInfo> links =
+ webLinks.getDiffLinks(change.getProject().get(),
+ change.getChangeId(),
+ edit.getBasePatchSet().getPatchSetId(),
+ edit.getBasePatchSet().getRefName(),
+ rsrc.getPath(),
+ 0,
+ edit.getRefName(),
+ rsrc.getPath());
+ r.webLinks = links.isEmpty() ? null : links.toList();
+ return r;
+ }
+
+ static class FileInfo {
+ List<DiffWebLinkInfo> webLinks;
+ }
+ }
+
+ @Singleton
+ public static class EditMessage implements
+ RestModifyView<ChangeResource, EditMessage.Input> {
+ public static class Input {
+ @DefaultInput
+ public String message;
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeEditModifier editModifier;
+ private final ChangeEditUtil editUtil;
+
+ @Inject
+ EditMessage(Provider<ReviewDb> db,
+ ChangeEditModifier editModifier,
+ ChangeEditUtil editUtil) {
+ this.db = db;
+ this.editModifier = editModifier;
+ this.editUtil = editUtil;
+ }
+
+ @Override
+ public Object apply(ChangeResource rsrc, Input input) throws AuthException,
+ IOException, InvalidChangeOperationException, BadRequestException,
+ ResourceConflictException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ editModifier.createEdit(rsrc.getChange(),
+ db.get().patchSets().get(rsrc.getChange().currentPatchSetId()));
+ edit = editUtil.byChange(rsrc.getChange());
+ }
+
+ if (input == null || Strings.isNullOrEmpty(input.message)) {
+ throw new BadRequestException("commit message must be provided");
+ }
+
+ try {
+ editModifier.modifyMessage(edit.get(), input.message);
+ } catch (UnchangedCommitMessageException ucm) {
+ throw new ResourceConflictException(ucm.getMessage());
+ }
+
+ return Response.none();
+ }
+ }
+
+ @Singleton
+ public static class GetMessage implements RestReadView<ChangeResource> {
+ private final ChangeEditUtil editUtil;
+
+ @Inject
+ GetMessage(ChangeEditUtil editUtil) {
+ this.editUtil = editUtil;
+ }
+
+ @Override
+ public BinaryResult apply(ChangeResource rsrc) throws AuthException,
+ IOException, ResourceNotFoundException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (edit.isPresent()) {
+ String msg = edit.get().getEditCommit().getFullMessage();
+ return BinaryResult.create(msg)
+ .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
+ .base64();
+ }
+ throw new ResourceNotFoundException();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index d0c2b98107..ff833f4e63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -19,6 +19,8 @@ import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
+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.ChangeMessage;
@@ -29,12 +31,17 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.util.RequestScopePropagator;
+import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -52,7 +59,7 @@ import java.util.Set;
public class ChangeInserter {
public static interface Factory {
- ChangeInserter create(RefControl ctl, Change c, RevCommit rc);
+ ChangeInserter create(ProjectControl ctl, Change c, RevCommit rc);
}
private static final Logger log =
@@ -64,10 +71,13 @@ public class ChangeInserter {
private final ChangeHooks hooks;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
- private final MergeabilityChecker mergeabilityChecker;
+ private final ChangeIndexer indexer;
private final CreateChangeSender.Factory createChangeSenderFactory;
+ private final HashtagsUtil hashtagsUtil;
+ private final AccountCache accountCache;
+ private final WorkQueue workQueue;
- private final RefControl refControl;
+ private final ProjectControl projectControl;
private final Change change;
private final PatchSet patchSet;
private final RevCommit commit;
@@ -77,6 +87,8 @@ public class ChangeInserter {
private Set<Account.Id> reviewers;
private Set<Account.Id> extraCC;
private Map<String, Short> approvals;
+ private Set<String> hashtags;
+ private RequestScopePropagator requestScopePropagator;
private boolean runHooks;
private boolean sendMail;
@@ -88,9 +100,12 @@ public class ChangeInserter {
ChangeHooks hooks,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
- MergeabilityChecker mergeabilityChecker,
+ ChangeIndexer indexer,
CreateChangeSender.Factory createChangeSenderFactory,
- @Assisted RefControl refControl,
+ HashtagsUtil hashtagsUtil,
+ AccountCache accountCache,
+ WorkQueue workQueue,
+ @Assisted ProjectControl projectControl,
@Assisted Change change,
@Assisted RevCommit commit) {
this.dbProvider = dbProvider;
@@ -99,14 +114,18 @@ public class ChangeInserter {
this.hooks = hooks;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
- this.mergeabilityChecker = mergeabilityChecker;
+ this.indexer = indexer;
this.createChangeSenderFactory = createChangeSenderFactory;
- this.refControl = refControl;
+ this.hashtagsUtil = hashtagsUtil;
+ this.accountCache = accountCache;
+ this.workQueue = workQueue;
+ this.projectControl = projectControl;
this.change = change;
this.commit = commit;
this.reviewers = Collections.emptySet();
this.extraCC = Collections.emptySet();
this.approvals = Collections.emptyMap();
+ this.hashtags = Collections.emptySet();
this.runHooks = true;
this.sendMail = true;
@@ -117,7 +136,6 @@ public class ChangeInserter {
patchSet.setRevision(new RevId(commit.name()));
patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
change.setCurrentPatchSet(patchSetInfo);
- ChangeUtil.computeSortKey(change);
}
public Change getChange() {
@@ -145,6 +163,11 @@ public class ChangeInserter {
return this;
}
+ public ChangeInserter setHashtags(Set<String> hashtags) {
+ this.hashtags = hashtags;
+ return this;
+ }
+
public ChangeInserter setRunHooks(boolean runHooks) {
this.runHooks = runHooks;
return this;
@@ -155,6 +178,11 @@ public class ChangeInserter {
return this;
}
+ public ChangeInserter setRequestScopePropagator(RequestScopePropagator r) {
+ this.requestScopePropagator = r;
+ return this;
+ }
+
public PatchSet getPatchSet() {
return patchSet;
}
@@ -170,7 +198,7 @@ public class ChangeInserter {
public Change insert() throws OrmException, IOException {
ReviewDb db = dbProvider.get();
- ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+ ChangeControl ctl = projectControl.controlFor(change);
ChangeUpdate update = updateFactory.create(
ctl,
change.getCreatedOn());
@@ -179,11 +207,11 @@ public class ChangeInserter {
ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
db.patchSets().insert(Collections.singleton(patchSet));
db.changes().insert(Collections.singleton(change));
- LabelTypes labelTypes = refControl.getProjectControl().getLabelTypes();
+ LabelTypes labelTypes = projectControl.getLabelTypes();
approvalsUtil.addReviewers(db, update, labelTypes, change,
patchSet, patchSetInfo, reviewers, Collections.<Account.Id> emptySet());
approvalsUtil.addApprovals(db, update, labelTypes, patchSet, patchSetInfo,
- change, ctl, approvals);
+ ctl, approvals);
if (messageIsForChange()) {
cmUtil.addChangeMessage(db, update, changeMessage);
}
@@ -191,27 +219,51 @@ public class ChangeInserter {
} finally {
db.rollback();
}
+
update.commit();
- CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
- .addChange(change)
- .reindex()
- .runAsync();
- if(!messageIsForChange()) {
+ if (hashtags != null && hashtags.size() > 0) {
+ try {
+ HashtagsInput input = new HashtagsInput();
+ input.add = hashtags;
+ hashtagsUtil.setHashtags(ctl, input, false, false);
+ } catch (ValidationException | AuthException e) {
+ log.error("Cannot add hashtags to change " + change.getId(), e);
+ }
+ }
+
+ CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
+
+ if (!messageIsForChange()) {
commitMessageNotForChange();
}
if (sendMail) {
- try {
- CreateChangeSender cm =
- createChangeSenderFactory.create(change);
- cm.setFrom(change.getOwner());
- cm.setPatchSet(patchSet, patchSetInfo);
- cm.addReviewers(reviewers);
- cm.addExtraCC(extraCC);
- cm.send();
- } catch (Exception err) {
- log.error("Cannot send email for new change " + change.getId(), err);
+ Runnable sender = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ CreateChangeSender cm =
+ createChangeSenderFactory.create(change);
+ cm.setFrom(change.getOwner());
+ cm.setPatchSet(patchSet, patchSetInfo);
+ cm.addReviewers(reviewers);
+ cm.addExtraCC(extraCC);
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot send email for new change " + change.getId(), e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "send-email newchange";
+ }
+ };
+ if (requestScopePropagator != null) {
+ workQueue.getDefaultQueue().submit(requestScopePropagator.wrap(sender));
+ } else {
+ sender.run();
}
}
f.checkedGet();
@@ -221,6 +273,11 @@ public class ChangeInserter {
if (runHooks) {
hooks.doPatchsetCreatedHook(change, patchSet, db);
+ if (hashtags != null && hashtags.size() > 0) {
+ hooks.doHashtagsChangedHook(change,
+ accountCache.get(change.getOwner()).getAccount(),
+ hashtags, null, hashtags, db);
+ }
}
return change;
@@ -234,8 +291,7 @@ public class ChangeInserter {
db.changes().get(changeMessage.getPatchSetId().getParentKey());
ChangeUtil.bumpRowVersionNotLastUpdatedOn(
changeMessage.getKey().getParentKey(), db);
- ChangeControl otherControl =
- refControl.getProjectControl().controlFor(otherChange);
+ ChangeControl otherControl = projectControl.controlFor(otherChange);
ChangeUpdate updateForOtherChange =
updateFactory.create(otherControl, change.getLastUpdatedOn());
cmUtil.addChangeMessage(db, updateForOtherChange, changeMessage);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 586c294909..4bcc82e2d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -14,28 +14,34 @@
package com.google.gerrit.server.change;
-import static com.google.gerrit.extensions.common.ListChangesOption.ALL_COMMITS;
-import static com.google.gerrit.extensions.common.ListChangesOption.ALL_FILES;
-import static com.google.gerrit.extensions.common.ListChangesOption.ALL_REVISIONS;
-import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_ACTIONS;
-import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_COMMIT;
-import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_FILES;
-import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
-import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_ACCOUNTS;
-import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_LABELS;
-import static com.google.gerrit.extensions.common.ListChangesOption.DOWNLOAD_COMMANDS;
-import static com.google.gerrit.extensions.common.ListChangesOption.DRAFT_COMMENTS;
-import static com.google.gerrit.extensions.common.ListChangesOption.LABELS;
-import static com.google.gerrit.extensions.common.ListChangesOption.MESSAGES;
-import static com.google.gerrit.extensions.common.ListChangesOption.REVIEWED;
-import static com.google.gerrit.extensions.common.ListChangesOption.WEB_LINKS;
-
+import static com.google.gerrit.extensions.client.ListChangesOption.ALL_COMMITS;
+import static com.google.gerrit.extensions.client.ListChangesOption.ALL_FILES;
+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;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_ACTIONS;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_FILES;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_ACCOUNTS;
+import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
+import static com.google.gerrit.extensions.client.ListChangesOption.DOWNLOAD_COMMANDS;
+import static com.google.gerrit.extensions.client.ListChangesOption.DRAFT_COMMENTS;
+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.WEB_LINKS;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Function;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
@@ -44,25 +50,30 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
+import com.google.gerrit.common.Nullable;
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.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.api.changes.FixInput;
+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.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.FetchInfo;
import com.google.gerrit.extensions.common.GitPerson;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.Url;
-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;
@@ -71,24 +82,27 @@ 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.reviewdb.client.PatchSetInfo.ParentInfo;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.WebLinks;
-import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.extensions.webui.UiActions;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
+import com.google.gerrit.server.query.change.QueryResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -108,6 +122,7 @@ import java.util.TreeMap;
public class ChangeJson {
private static final Logger log = LoggerFactory.getLogger(ChangeJson.class);
+
private static final List<ChangeMessage> NO_MESSAGES =
ImmutableList.of();
@@ -119,16 +134,19 @@ public class ChangeJson {
private final ChangeData.Factory changeDataFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final FileInfoJson fileInfoJson;
- private final AccountInfo.Loader.Factory accountLoaderFactory;
+ private final AccountLoader.Factory accountLoaderFactory;
private final DynamicMap<DownloadScheme> downloadSchemes;
private final DynamicMap<DownloadCommand> downloadCommands;
- private final DynamicMap<RestView<ChangeResource>> changeViews;
- private final Revisions revisions;
- private final Provider<WebLinks> webLinks;
+ private final WebLinks webLinks;
private final EnumSet<ListChangesOption> options;
private final ChangeMessagesUtil cmUtil;
+ private final PatchLineCommentsUtil plcUtil;
+ private final Provider<ConsistencyChecker> checkerProvider;
+ private final ActionJson actionJson;
+ private final RebaseChange rebaseChange;
- private AccountInfo.Loader accountLoader;
+ private AccountLoader accountLoader;
+ private FixInput fix;
@Inject
ChangeJson(
@@ -137,17 +155,18 @@ public class ChangeJson {
Provider<CurrentUser> user,
AnonymousUser au,
IdentifiedUser.GenericFactory uf,
- ProjectControl.GenericFactory pcf,
ChangeData.Factory cdf,
PatchSetInfoFactory psi,
FileInfoJson fileInfoJson,
- AccountInfo.Loader.Factory ailf,
+ AccountLoader.Factory ailf,
DynamicMap<DownloadScheme> downloadSchemes,
DynamicMap<DownloadCommand> downloadCommands,
- DynamicMap<RestView<ChangeResource>> changeViews,
- Revisions revisions,
- Provider<WebLinks> webLinks,
- ChangeMessagesUtil cmUtil) {
+ WebLinks webLinks,
+ ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
+ Provider<ConsistencyChecker> checkerProvider,
+ ActionJson actionJson,
+ RebaseChange rebaseChange) {
this.db = db;
this.labelNormalizer = ln;
this.userProvider = user;
@@ -159,10 +178,12 @@ public class ChangeJson {
this.accountLoaderFactory = ailf;
this.downloadSchemes = downloadSchemes;
this.downloadCommands = downloadCommands;
- this.changeViews = changeViews;
- this.revisions = revisions;
this.webLinks = webLinks;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
+ this.checkerProvider = checkerProvider;
+ this.actionJson = actionJson;
+ this.rebaseChange = rebaseChange;
options = EnumSet.noneOf(ListChangesOption.class);
}
@@ -176,6 +197,11 @@ public class ChangeJson {
return this;
}
+ public ChangeJson fix(FixInput fix) {
+ this.fix = fix;
+ return this;
+ }
+
public ChangeInfo format(ChangeResource rsrc) throws OrmException {
return format(changeDataFactory.create(db.get(), rsrc.getControl()));
}
@@ -185,7 +211,16 @@ public class ChangeJson {
}
public ChangeInfo format(Change.Id id) throws OrmException {
- return format(changeDataFactory.create(db.get(), id));
+ Change c;
+ try {
+ c = db.get().changes().get(id);
+ } catch (OrmException e) {
+ if (!has(CHECK)) {
+ throw e;
+ }
+ return checkOnly(changeDataFactory.create(db.get(), id));
+ }
+ return format(changeDataFactory.create(db.get(), c));
}
public ChangeInfo format(ChangeData cd) throws OrmException {
@@ -194,14 +229,21 @@ public class ChangeJson {
private ChangeInfo format(ChangeData cd, Optional<PatchSet.Id> limitToPsId)
throws OrmException {
- accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
- Set<Change.Id> reviewed = Sets.newHashSet();
- if (has(REVIEWED)) {
- reviewed = loadReviewed(Collections.singleton(cd));
+ try {
+ accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
+ Set<Change.Id> reviewed = Sets.newHashSet();
+ if (has(REVIEWED)) {
+ reviewed = loadReviewed(Collections.singleton(cd));
+ }
+ ChangeInfo res = toChangeInfo(cd, reviewed, limitToPsId);
+ accountLoader.fill();
+ return res;
+ } catch (OrmException | RuntimeException e) {
+ if (!has(CHECK)) {
+ throw e;
+ }
+ return checkOnly(cd);
}
- ChangeInfo res = toChangeInfo(cd, reviewed, limitToPsId);
- accountLoader.fill();
- return res;
}
public ChangeInfo format(RevisionResource rsrc) throws OrmException {
@@ -209,14 +251,20 @@ public class ChangeJson {
return format(cd, Optional.of(rsrc.getPatchSet().getId()));
}
- public List<List<ChangeInfo>> formatList2(List<List<ChangeData>> in)
+ public List<List<ChangeInfo>> formatQueryResults(List<QueryResult> in)
throws OrmException {
accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
- Iterable<ChangeData> all = Iterables.concat(in);
+ Iterable<ChangeData> all = FluentIterable.from(in)
+ .transformAndConcat(new Function<QueryResult, List<ChangeData>>() {
+ @Override
+ public List<ChangeData> apply(QueryResult in) {
+ return in.changes();
+ }
+ });
ChangeData.ensureChangeLoaded(all);
if (has(ALL_REVISIONS)) {
ChangeData.ensureAllPatchSetsLoaded(all);
- } else {
+ } else if (has(CURRENT_REVISION) || has(MESSAGES)) {
ChangeData.ensureCurrentPatchSetLoaded(all);
}
Set<Change.Id> reviewed = Sets.newHashSet();
@@ -227,8 +275,12 @@ public class ChangeJson {
List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
- for (List<ChangeData> changes : in) {
- res.add(toChangeInfo(out, changes, reviewed));
+ for (QueryResult r : in) {
+ List<ChangeInfo> infos = toChangeInfo(out, r.changes(), reviewed);
+ if (r.moreChanges()) {
+ infos.get(infos.size() - 1)._moreChanges = true;
+ }
+ res.add(infos);
}
accountLoader.fill();
return res;
@@ -239,17 +291,21 @@ public class ChangeJson {
}
private List<ChangeInfo> toChangeInfo(Map<Change.Id, ChangeInfo> out,
- List<ChangeData> changes, Set<Change.Id> reviewed) throws OrmException {
+ List<ChangeData> changes, Set<Change.Id> reviewed) {
List<ChangeInfo> info = Lists.newArrayListWithCapacity(changes.size());
for (ChangeData cd : changes) {
ChangeInfo i = out.get(cd.getId());
if (i == null) {
try {
i = toChangeInfo(cd, reviewed, Optional.<PatchSet.Id> absent());
- } catch (OrmException e) {
- log.warn(
- "Omitting corrupt change " + cd.getId() + " from results", e);
- continue;
+ } catch (OrmException | RuntimeException e) {
+ if (has(CHECK)) {
+ i = checkOnly(cd);
+ } else {
+ log.warn(
+ "Omitting corrupt change " + cd.getId() + " from results", e);
+ continue;
+ }
}
out.put(cd.getId(), i);
}
@@ -258,34 +314,76 @@ public class ChangeJson {
return info;
}
+ private ChangeInfo checkOnly(ChangeData cd) {
+ ConsistencyChecker.Result result = checkerProvider.get().check(cd, fix);
+ ChangeInfo info;
+ Change c = result.change();
+ if (c != null) {
+ info = new ChangeInfo();
+ info.project = c.getProject().get();
+ info.branch = c.getDest().getShortName();
+ info.topic = c.getTopic();
+ info.changeId = c.getKey().get();
+ info.subject = c.getSubject();
+ info.status = c.getStatus().asChangeStatus();
+ info.owner = new AccountInfo(c.getOwner().get());
+ info.created = c.getCreatedOn();
+ info.updated = c.getLastUpdatedOn();
+ info._number = c.getId().get();
+ info.problems = result.problems();
+ finish(info);
+ } else {
+ info = new ChangeInfo();
+ info._number = result.id().get();
+ info.problems = result.problems();
+ }
+ return info;
+ }
+
private ChangeInfo toChangeInfo(ChangeData cd, Set<Change.Id> reviewed,
Optional<PatchSet.Id> limitToPsId) throws OrmException {
- ChangeControl ctl = cd.changeControl().forUser(userProvider.get());
ChangeInfo out = new ChangeInfo();
+
+ if (has(CHECK)) {
+ out.problems = checkerProvider.get().check(cd.change(), fix).problems();
+ // If any problems were fixed, the ChangeData needs to be reloaded.
+ for (ProblemInfo p : out.problems) {
+ if (p.status == ProblemInfo.Status.FIXED) {
+ cd = changeDataFactory.create(cd.db(), cd.getId());
+ break;
+ }
+ }
+ }
+
Change in = cd.change();
+ ChangeControl ctl = cd.changeControl().forUser(userProvider.get());
out.project = in.getProject().get();
out.branch = in.getDest().getShortName();
out.topic = in.getTopic();
+ out.hashtags = ctl.getNotes().load().getHashtags();
out.changeId = in.getKey().get();
- out.mergeable = isMergeable(in);
+ // TODO(dborowitz): This gets the submit type, so we could include that in
+ // the response and avoid making a request to /submit_type from the UI.
+ out.mergeable = in.getStatus() == Change.Status.MERGED
+ ? null : cd.isMergeable();
ChangedLines changedLines = cd.changedLines();
if (changedLines != null) {
out.insertions = changedLines.insertions;
out.deletions = changedLines.deletions;
}
out.subject = in.getSubject();
- out.status = in.getStatus();
+ out.status = in.getStatus().asChangeStatus();
out.owner = accountLoader.get(in.getOwner());
out.created = in.getCreatedOn();
out.updated = in.getLastUpdatedOn();
out._number = in.getId().get();
- out._sortkey = in.getSortKey();
out.starred = userProvider.get().getStarredChanges().contains(in.getId())
? true
: null;
out.reviewed = in.getStatus().isOpen()
&& has(REVIEWED)
&& reviewed.contains(cd.getId()) ? true : null;
+
out.labels = labelsFor(ctl, cd, has(LABELS), has(DETAILED_LABELS));
if (out.labels != null && has(DETAILED_LABELS)) {
@@ -295,19 +393,26 @@ public class ChangeJson {
|| limitToPsId.get().equals(in.currentPatchSetId())) {
out.permittedLabels = permittedLabels(ctl, cd);
}
- out.removableReviewers = removableReviewers(ctl, cd, out.labels.values());
+ out.removableReviewers = removableReviewers(ctl, out.labels.values());
}
- Map<PatchSet.Id, PatchSet> src = loadPatchSets(cd, limitToPsId);
- if (has(MESSAGES)) {
+ boolean needMessages = has(MESSAGES);
+ boolean needRevisions = has(ALL_REVISIONS)
+ || has(CURRENT_REVISION)
+ || limitToPsId.isPresent();
+ Map<PatchSet.Id, PatchSet> src;
+ if (needMessages || needRevisions) {
+ src = loadPatchSets(cd, limitToPsId);
+ } else {
+ src = null;
+ }
+ if (needMessages) {
out.messages = messages(ctl, cd, src);
}
- out.finish();
+ finish(out);
- if (has(ALL_REVISIONS)
- || has(CURRENT_REVISION)
- || limitToPsId.isPresent()) {
- out.revisions = revisions(ctl, cd, limitToPsId, out.project, src);
+ if (needRevisions) {
+ out.revisions = revisions(ctl, cd, src);
if (out.revisions != null) {
for (Map.Entry<String, RevisionInfo> entry : out.revisions.entrySet()) {
if (entry.getValue().isCurrent) {
@@ -318,44 +423,26 @@ public class ChangeJson {
}
}
- if (has(CURRENT_ACTIONS) && userProvider.get().isIdentifiedUser()) {
- out.actions = Maps.newTreeMap();
- for (UiAction.Description d : UiActions.from(
- changeViews,
- new ChangeResource(ctl),
- userProvider)) {
- out.actions.put(d.getId(), new ActionInfo(d));
- }
+ if (has(CURRENT_ACTIONS) || has(CHANGE_ACTIONS)) {
+ actionJson.addChangeActions(out, ctl);
}
- return out;
- }
- private Boolean isMergeable(Change c) {
- if (c.getStatus() == Change.Status.MERGED
- || c.getLastSha1MergeTested() == null) {
- return null;
- }
- return c.isMergeable();
+ return out;
}
- private List<SubmitRecord> submitRecords(ChangeControl ctl, ChangeData cd)
- throws OrmException {
+ private List<SubmitRecord> submitRecords(ChangeData cd) throws OrmException {
if (cd.getSubmitRecords() != null) {
return cd.getSubmitRecords();
}
- if (ctl == null) {
- return ImmutableList.of();
- }
- PatchSet ps = cd.currentPatchSet();
- if (ps == null) {
- return ImmutableList.of();
- }
- cd.setSubmitRecords(ctl.canSubmit(db.get(), ps, cd, true, false, true));
+ cd.setSubmitRecords(new SubmitRuleEvaluator(cd)
+ .setFastEvalLabels(true)
+ .setAllowDraft(true)
+ .evaluate());
return cd.getSubmitRecords();
}
- private Map<String, LabelInfo> labelsFor(ChangeControl ctl, ChangeData cd, boolean standard,
- boolean detailed) throws OrmException {
+ private Map<String, LabelInfo> labelsFor(ChangeControl ctl,
+ ChangeData cd, boolean standard, boolean detailed) throws OrmException {
if (!standard && !detailed) {
return null;
}
@@ -365,21 +452,21 @@ public class ChangeJson {
}
LabelTypes labelTypes = ctl.getLabelTypes();
- if (cd.change().getStatus().isOpen()) {
- return labelsForOpenChange(ctl, cd, labelTypes, standard, detailed);
- } else {
- return labelsForClosedChange(cd, labelTypes, standard, detailed);
- }
+ Map<String, LabelWithStatus> withStatus = cd.change().getStatus().isOpen()
+ ? labelsForOpenChange(ctl, cd, labelTypes, standard, detailed)
+ : labelsForClosedChange(cd, labelTypes, standard, detailed);
+ return ImmutableMap.copyOf(
+ Maps.transformValues(withStatus, LabelWithStatus.TO_LABEL_INFO));
}
- private Map<String, LabelInfo> labelsForOpenChange(ChangeControl ctl,
+ private Map<String, LabelWithStatus> labelsForOpenChange(ChangeControl ctl,
ChangeData cd, LabelTypes labelTypes, boolean standard, boolean detailed)
throws OrmException {
- Map<String, LabelInfo> labels = initLabels(ctl, cd, labelTypes, standard);
+ Map<String, LabelWithStatus> labels = initLabels(cd, labelTypes, standard);
if (detailed) {
setAllApprovals(ctl, cd, labels);
}
- for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
+ for (Map.Entry<String, LabelWithStatus> e : labels.entrySet()) {
LabelType type = labelTypes.byLabel(e.getKey());
if (type == null) {
continue;
@@ -400,19 +487,18 @@ public class ChangeJson {
return labels;
}
- private Map<String, LabelInfo> initLabels(ChangeControl ctl, ChangeData cd,
+ private Map<String, LabelWithStatus> initLabels(ChangeData cd,
LabelTypes labelTypes, boolean standard) throws OrmException {
// Don't use Maps.newTreeMap(Comparator) due to OpenJDK bug 100167.
- Map<String, LabelInfo> labels = new TreeMap<>(labelTypes.nameComparator());
- for (SubmitRecord rec : submitRecords(ctl, cd)) {
+ Map<String, LabelWithStatus> labels = new TreeMap<>(labelTypes.nameComparator());
+ for (SubmitRecord rec : submitRecords(cd)) {
if (rec.labels == null) {
continue;
}
for (SubmitRecord.Label r : rec.labels) {
- LabelInfo p = labels.get(r.label);
- if (p == null || p._status.compareTo(r.status) < 0) {
+ LabelWithStatus p = labels.get(r.label);
+ if (p == null || p.status().compareTo(r.status) < 0) {
LabelInfo n = new LabelInfo();
- n._status = r.status;
if (standard) {
switch (r.status) {
case OK:
@@ -427,8 +513,8 @@ public class ChangeJson {
}
}
- n.optional = n._status == SubmitRecord.Label.Status.MAY ? true : null;
- labels.put(r.label, n);
+ n.optional = r.status == SubmitRecord.Label.Status.MAY ? true : null;
+ labels.put(r.label, LabelWithStatus.create(n, r.status));
}
}
}
@@ -436,9 +522,8 @@ public class ChangeJson {
}
private void setLabelScores(LabelType type,
- LabelInfo label, short score, Account.Id accountId)
- throws OrmException {
- if (label.approved != null || label.rejected != null) {
+ LabelWithStatus l, short score, Account.Id accountId) {
+ if (l.label().approved != null || l.label().rejected != null) {
return;
}
@@ -449,21 +534,21 @@ public class ChangeJson {
if (score != 0) {
if (score == type.getMin().getValue()) {
- label.rejected = accountLoader.get(accountId);
+ l.label().rejected = accountLoader.get(accountId);
} else if (score == type.getMax().getValue()) {
- label.approved = accountLoader.get(accountId);
+ l.label().approved = accountLoader.get(accountId);
} else if (score < 0) {
- label.disliked = accountLoader.get(accountId);
- label.value = score;
- } else if (score > 0 && label.disliked == null) {
- label.recommended = accountLoader.get(accountId);
- label.value = score;
+ l.label().disliked = accountLoader.get(accountId);
+ l.label().value = score;
+ } else if (score > 0 && l.label().disliked == null) {
+ l.label().recommended = accountLoader.get(accountId);
+ l.label().value = score;
}
}
}
private void setAllApprovals(ChangeControl baseCtrl, ChangeData cd,
- Map<String, LabelInfo> labels) throws OrmException {
+ Map<String, LabelWithStatus> labels) throws OrmException {
// Include a user in the output for this label if either:
// - They are an explicit reviewer.
// - They ever voted on this change.
@@ -482,7 +567,7 @@ public class ChangeJson {
for (Account.Id accountId : allUsers) {
IdentifiedUser user = userFactory.create(accountId);
ChangeControl ctl = baseCtrl.forUser(user);
- for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
+ for (Map.Entry<String, LabelWithStatus> e : labels.entrySet()) {
LabelType lt = ctl.getLabelTypes().byLabel(e.getKey());
if (lt == null) {
// Ignore submit record for undefined label; likely the submit rule
@@ -501,17 +586,23 @@ public class ChangeJson {
// user can vote on this label.
value = labelNormalizer.canVote(ctl, lt, accountId) ? 0 : null;
}
- e.getValue().addApproval(approvalInfo(accountId, value, date));
+ addApproval(e.getValue().label(), approvalInfo(accountId, value, date));
}
}
}
- private Map<String, LabelInfo> labelsForClosedChange(ChangeData cd,
+ private Map<String, LabelWithStatus> labelsForClosedChange(ChangeData cd,
LabelTypes labelTypes, boolean standard, boolean detailed)
throws OrmException {
Set<Account.Id> allUsers = Sets.newHashSet();
- for (PatchSetApproval psa : cd.approvals().values()) {
- allUsers.add(psa.getAccountId());
+ if (detailed) {
+ // Users expect to see all reviewers on closed changes, even if they
+ // didn't vote on the latest patch set. If we don't need detailed labels,
+ // 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());
+ }
}
// We can only approximately reconstruct what the submit rule evaluator
@@ -519,6 +610,7 @@ public class ChangeJson {
Set<String> labelNames = Sets.newHashSet();
Multimap<Account.Id, PatchSetApproval> current = HashMultimap.create();
for (PatchSetApproval a : cd.currentApprovals()) {
+ allUsers.add(a.getAccountId());
LabelType type = labelTypes.byLabel(a.getLabelId());
if (type != null) {
labelNames.add(type.getName());
@@ -529,14 +621,15 @@ public class ChangeJson {
}
// Don't use Maps.newTreeMap(Comparator) due to OpenJDK bug 100167.
- Map<String, LabelInfo> labels = new TreeMap<>(labelTypes.nameComparator());
+ Map<String, LabelWithStatus> labels =
+ new TreeMap<>(labelTypes.nameComparator());
for (String name : labelNames) {
LabelType type = labelTypes.byLabel(name);
- LabelInfo li = new LabelInfo();
+ LabelWithStatus l = LabelWithStatus.create(new LabelInfo(), null);
if (detailed) {
- setLabelValues(type, li);
+ setLabelValues(type, l);
}
- labels.put(type.getName(), li);
+ labels.put(type.getName(), l);
}
for (Account.Id accountId : allUsers) {
@@ -544,10 +637,10 @@ public class ChangeJson {
Maps.newHashMapWithExpectedSize(labels.size());
if (detailed) {
- for (Map.Entry<String, LabelInfo> entry : labels.entrySet()) {
+ for (Map.Entry<String, LabelWithStatus> entry : labels.entrySet()) {
ApprovalInfo ai = approvalInfo(accountId, 0, null);
byLabel.put(entry.getKey(), ai);
- entry.getValue().addApproval(ai);
+ addApproval(entry.getValue().label(), ai);
}
}
for (PatchSetApproval psa : current.get(accountId)) {
@@ -562,20 +655,18 @@ public class ChangeJson {
info.value = Integer.valueOf(val);
info.date = psa.getGranted();
}
-
- LabelInfo li = labels.get(type.getName());
if (!standard) {
continue;
}
- setLabelScores(type, li, val, accountId);
+ setLabelScores(type, labels.get(type.getName()), val, accountId);
}
}
return labels;
}
private ApprovalInfo approvalInfo(Account.Id id, Integer value, Timestamp date) {
- ApprovalInfo ai = new ApprovalInfo(id);
+ ApprovalInfo ai = new ApprovalInfo(id.get());
ai.value = value;
ai.date = date;
accountLoader.put(ai);
@@ -586,14 +677,14 @@ public class ChangeJson {
return values.isEmpty() || (values.size() == 1 && values.contains(" 0"));
}
- private void setLabelValues(LabelType type, LabelInfo label) {
- label.defaultValue = type.getDefaultValue();
- label.values = Maps.newLinkedHashMap();
+ private void setLabelValues(LabelType type, LabelWithStatus l) {
+ l.label().defaultValue = type.getDefaultValue();
+ l.label().values = Maps.newLinkedHashMap();
for (LabelValue v : type.getValues()) {
- label.values.put(v.formatValue(), v.getText());
+ l.label().values.put(v.formatValue(), v.getText());
}
- if (isOnlyZero(label.values.keySet())) {
- label.values = null;
+ if (isOnlyZero(l.label().values.keySet())) {
+ l.label().values = null;
}
}
@@ -605,7 +696,7 @@ public class ChangeJson {
LabelTypes labelTypes = ctl.getLabelTypes();
SetMultimap<String, String> permitted = LinkedHashMultimap.create();
- for (SubmitRecord rec : submitRecords(ctl, cd)) {
+ for (SubmitRecord rec : submitRecords(cd)) {
if (rec.labels == null) {
continue;
}
@@ -670,8 +761,8 @@ public class ChangeJson {
return result;
}
- private Collection<AccountInfo> removableReviewers(ChangeControl ctl, ChangeData cd,
- Collection<LabelInfo> labels) throws OrmException {
+ private Collection<AccountInfo> removableReviewers(ChangeControl ctl,
+ Collection<LabelInfo> labels) {
Set<Account.Id> fixed = Sets.newHashSetWithExpectedSize(labels.size());
Set<Account.Id> removable = Sets.newHashSetWithExpectedSize(labels.size());
for (LabelInfo label : labels) {
@@ -679,10 +770,11 @@ public class ChangeJson {
continue;
}
for (ApprovalInfo ai : label.all) {
- if (ctl.canRemoveReviewer(ai._id, Objects.firstNonNull(ai.value, 0))) {
- removable.add(ai._id);
+ Account.Id id = new Account.Id(ai._accountId);
+ if (ctl.canRemoveReviewer(id, MoreObjects.firstNonNull(ai.value, 0))) {
+ removable.add(id);
} else {
- fixed.add(ai._id);
+ fixed.add(id);
}
}
}
@@ -739,14 +831,13 @@ public class ChangeJson {
}
private Map<String, RevisionInfo> revisions(ChangeControl ctl, ChangeData cd,
- Optional<PatchSet.Id> limitToPsId, String project,
Map<PatchSet.Id, PatchSet> map) throws OrmException {
Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
for (PatchSet in : map.values()) {
if ((has(ALL_REVISIONS)
|| in.getId().equals(cd.change().currentPatchSetId()))
&& ctl.isPatchVisible(in, db.get())) {
- res.put(in.getRevision().get(), toRevisionInfo(ctl, cd, in, project));
+ res.put(in.getRevision().get(), toRevisionInfo(ctl, cd, in));
}
}
return res;
@@ -781,18 +872,21 @@ public class ChangeJson {
}
private RevisionInfo toRevisionInfo(ChangeControl ctl, ChangeData cd,
- PatchSet in, String project) throws OrmException {
+ PatchSet in) throws OrmException {
RevisionInfo out = new RevisionInfo();
out.isCurrent = in.getId().equals(cd.change().currentPatchSetId());
out._number = in.getId().get();
+ out.ref = in.getRefName();
+ out.created = in.getCreatedOn();
+ out.uploader = accountLoader.get(in.getUploader());
out.draft = in.isDraft() ? true : null;
- out.fetch = makeFetchMap(ctl, cd, in);
+ out.fetch = makeFetchMap(ctl, in);
if (has(ALL_COMMITS) || (out.isCurrent && has(CURRENT_COMMIT))) {
try {
- out.commit = toCommit(in);
+ out.commit = toCommit(in, cd.change().getProject(), has(WEB_LINKS));
} catch (PatchSetInfoNotAvailableException e) {
- log.warn("Cannot load PatchSetInfo " + in.getId(), e);
+ throw new OrmException(e);
}
}
@@ -801,44 +895,31 @@ public class ChangeJson {
out.files = fileInfoJson.toFileInfoMap(cd.change(), in);
out.files.remove(Patch.COMMIT_MSG);
} catch (PatchListNotAvailableException e) {
- log.warn("Cannot load PatchList " + in.getId(), e);
+ throw new OrmException(e);
}
}
if ((out.isCurrent || (out.draft != null && out.draft))
&& has(CURRENT_ACTIONS)
&& userProvider.get().isIdentifiedUser()) {
- out.actions = Maps.newTreeMap();
- for (UiAction.Description d : UiActions.from(
- revisions,
- new RevisionResource(new ChangeResource(ctl), in),
- userProvider)) {
- out.actions.put(d.getId(), new ActionInfo(d));
- }
+
+ actionJson.addRevisionActions(out,
+ new RevisionResource(new ChangeResource(ctl, rebaseChange), in));
}
if (has(DRAFT_COMMENTS)
&& userProvider.get().isIdentifiedUser()) {
IdentifiedUser user = (IdentifiedUser)userProvider.get();
out.hasDraftComments =
- db.get().patchComments()
- .draftByPatchSetAuthor(in.getId(), user.getAccountId())
- .iterator().hasNext()
+ plcUtil.draftByPatchSetAuthor(db.get(), in.getId(),
+ user.getAccountId(), ctl.getNotes()).iterator().hasNext()
? true
: null;
}
-
- if (has(WEB_LINKS)) {
- out.webLinks = Lists.newArrayList();
- for (WebLinkInfo link : webLinks.get().getPatchSetLinks(
- project, in.getRevision().get())) {
- out.webLinks.add(link);
- }
- }
return out;
}
- CommitInfo toCommit(PatchSet in)
+ CommitInfo toCommit(PatchSet in, Project.NameKey project, boolean addLinks)
throws PatchSetInfoNotAvailableException {
PatchSetInfo info = patchSetInfoFactory.get(db.get(), in.getId());
CommitInfo commit = new CommitInfo();
@@ -847,16 +928,28 @@ public class ChangeJson {
commit.committer = toGitPerson(info.getCommitter());
commit.subject = info.getSubject();
commit.message = info.getMessage();
+
+ if (addLinks) {
+ FluentIterable<WebLinkInfo> links =
+ webLinks.getPatchSetLinks(project, in.getRevision().get());
+ commit.webLinks = links.isEmpty() ? null : links.toList();
+ }
+
for (ParentInfo parent : info.getParents()) {
CommitInfo i = new CommitInfo();
i.commit = parent.id.get();
i.subject = parent.shortMessage;
+ if (addLinks) {
+ FluentIterable<WebLinkInfo> parentLinks =
+ webLinks.getPatchSetLinks(project, parent.id.get());
+ i.webLinks = parentLinks.isEmpty() ? null : parentLinks.toList();
+ }
commit.parents.add(i);
}
return commit;
}
- private Map<String, FetchInfo> makeFetchMap(ChangeControl ctl, ChangeData cd, PatchSet in)
+ private Map<String, FetchInfo> makeFetchMap(ChangeControl ctl, PatchSet in)
throws OrmException {
Map<String, FetchInfo> r = Maps.newLinkedHashMap();
@@ -880,21 +973,29 @@ public class ChangeJson {
r.put(schemeName, fetchInfo);
if (has(DOWNLOAD_COMMANDS)) {
- for (DynamicMap.Entry<DownloadCommand> e2 : downloadCommands) {
- String commandName = e2.getExportName();
- DownloadCommand command = e2.getProvider().get();
- String c = command.getCommand(scheme, projectName, refName);
- if (c != null) {
- addCommand(fetchInfo, commandName, c);
- }
- }
+ populateFetchMap(scheme, downloadCommands, projectName, refName,
+ fetchInfo);
}
}
return r;
}
- private void addCommand(FetchInfo fetchInfo, String commandName, String c) {
+ public static void populateFetchMap(DownloadScheme scheme,
+ DynamicMap<DownloadCommand> commands, String projectName,
+ String refName, FetchInfo fetchInfo) {
+ for (DynamicMap.Entry<DownloadCommand> e2 : commands) {
+ String commandName = e2.getExportName();
+ DownloadCommand command = e2.getProvider().get();
+ String c = command.getCommand(scheme, projectName, refName);
+ if (c != null) {
+ addCommand(fetchInfo, commandName, c);
+ }
+ }
+ }
+
+ private static void addCommand(FetchInfo fetchInfo, String commandName,
+ String c) {
if (fetchInfo.commands == null) {
fetchInfo.commands = Maps.newTreeMap();
}
@@ -910,83 +1011,36 @@ public class ChangeJson {
return p;
}
- public static class ChangeInfo {
- public String id;
- public String project;
- public String branch;
- public String topic;
- public String changeId;
- public String subject;
- public Change.Status status;
- public Timestamp created;
- public Timestamp updated;
- public Boolean starred;
- public Boolean reviewed;
- public Boolean mergeable;
- public Integer insertions;
- public Integer deletions;
-
- public String _sortkey;
- public int _number;
-
- public AccountInfo owner;
-
- public Map<String, ActionInfo> actions;
- public Map<String, LabelInfo> labels;
- public Map<String, Collection<String>> permittedLabels;
- public Collection<AccountInfo> removableReviewers;
- public Collection<ChangeMessageInfo> messages;
-
- public String currentRevision;
- public Map<String, RevisionInfo> revisions;
- public Boolean _moreChanges;
-
- void finish() {
- id = Joiner.on('~').join(
- Url.encode(project),
- Url.encode(branch),
- Url.encode(changeId));
- }
+ static void finish(ChangeInfo info) {
+ info.id = Joiner.on('~').join(
+ Url.encode(info.project),
+ Url.encode(info.branch),
+ Url.encode(info.changeId));
}
- public static class LabelInfo {
- transient SubmitRecord.Label.Status _status;
-
- public AccountInfo approved;
- public AccountInfo rejected;
- public AccountInfo recommended;
- public AccountInfo disliked;
- public List<ApprovalInfo> all;
-
- public Map<String, String> values;
-
- public Short value;
- public Short defaultValue;
- public Boolean optional;
- public Boolean blocking;
-
- void addApproval(ApprovalInfo ai) {
- if (all == null) {
- all = Lists.newArrayList();
- }
- all.add(ai);
+ private static void addApproval(LabelInfo label, ApprovalInfo approval) {
+ if (label.all == null) {
+ label.all = Lists.newArrayList();
}
+ label.all.add(approval);
}
- public static class ApprovalInfo extends AccountInfo {
- public Integer value;
- public Timestamp date;
+ @AutoValue
+ abstract static class LabelWithStatus {
+ private static final Function<LabelWithStatus, LabelInfo> TO_LABEL_INFO =
+ new Function<LabelWithStatus, LabelInfo>() {
+ @Override
+ public LabelInfo apply(LabelWithStatus in) {
+ return in.label();
+ }
+ };
- ApprovalInfo(Account.Id id) {
- super(id);
+ private static LabelWithStatus create(LabelInfo label,
+ SubmitRecord.Label.Status status) {
+ return new AutoValue_ChangeJson_LabelWithStatus(label, status);
}
- }
- public static class ChangeMessageInfo {
- public String id;
- public AccountInfo author;
- public Timestamp date;
- public String message;
- public Integer _revisionNumber;
+ abstract LabelInfo label();
+ @Nullable abstract SubmitRecord.Label.Status status();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java
index c6e088f8ea..d22d6ff344 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java
@@ -22,6 +22,9 @@ public enum ChangeKind {
/** Conflict-free merge between the new parent and the prior patch set. */
TRIVIAL_REBASE,
- /** Same tree and same parents. */
- NO_CODE_CHANGE;
+ /** Same tree and same parent tree. */
+ NO_CODE_CHANGE,
+
+ /** Same tree, parent tree, same commit message. */
+ NO_CHANGE;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 82d783ce14..a2b78aaebc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -19,7 +19,6 @@ import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
@@ -39,6 +38,7 @@ import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
+import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -53,6 +53,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
+import java.util.Objects;
import java.util.concurrent.ExecutionException;
public class ChangeKindCacheImpl implements ChangeKindCache {
@@ -156,16 +157,16 @@ public class ChangeKindCacheImpl implements ChangeKindCache {
public boolean equals(Object o) {
if (o instanceof Key) {
Key k = (Key) o;
- return Objects.equal(prior, k.prior)
- && Objects.equal(next, k.next)
- && Objects.equal(strategyName, k.strategyName);
+ return Objects.equals(prior, k.prior)
+ && Objects.equals(next, k.next)
+ && Objects.equals(strategyName, k.strategyName);
}
return false;
}
@Override
public int hashCode() {
- return Objects.hashCode(prior, next, strategyName);
+ return Objects.hash(prior, next, strategyName);
}
private void writeObject(ObjectOutputStream out) throws IOException {
@@ -185,60 +186,73 @@ public class ChangeKindCacheImpl implements ChangeKindCache {
private static class Loader extends CacheLoader<Key, ChangeKind> {
@Override
public ChangeKind load(Key key) throws IOException {
- if (Objects.equal(key.prior, key.next)) {
+ if (Objects.equals(key.prior, key.next)) {
return ChangeKind.NO_CODE_CHANGE;
}
- RevWalk walk = new RevWalk(key.repo);
- try {
+ try (RevWalk walk = new RevWalk(key.repo)) {
RevCommit prior = walk.parseCommit(key.prior);
walk.parseBody(prior);
RevCommit next = walk.parseCommit(key.next);
walk.parseBody(next);
if (!next.getFullMessage().equals(prior.getFullMessage())) {
- if (next.getTree() == prior.getTree() && isSameParents(prior, next)) {
+ if (isSameDeltaAndTree(prior, next)) {
return ChangeKind.NO_CODE_CHANGE;
} else {
return ChangeKind.REWORK;
}
}
+ if (isSameDeltaAndTree(prior, next)) {
+ return ChangeKind.NO_CHANGE;
+ }
+
if (prior.getParentCount() != 1 || next.getParentCount() != 1) {
// Trivial rebases done by machine only work well on 1 parent.
return ChangeKind.REWORK;
}
- if (next.getTree() == prior.getTree() &&
- isSameParents(prior, next)) {
- return ChangeKind.TRIVIAL_REBASE;
- }
-
// A trivial rebase can be detected by looking for the next commit
// having the same tree as would exist when the prior commit is
// cherry-picked onto the next commit's new first parent.
ThreeWayMerger merger = MergeUtil.newThreeWayMerger(
key.repo, MergeUtil.createDryRunInserter(key.repo), key.strategyName);
merger.setBase(prior.getParent(0));
- if (merger.merge(next.getParent(0), prior)
- && merger.getResultTreeId().equals(next.getTree())) {
- return ChangeKind.TRIVIAL_REBASE;
- } else {
- return ChangeKind.REWORK;
+ try {
+ if (merger.merge(next.getParent(0), prior)
+ && merger.getResultTreeId().equals(next.getTree())) {
+ return ChangeKind.TRIVIAL_REBASE;
+ }
+ } catch (LargeObjectException e) {
+ // Some object is too large for the merge attempt to succeed. Assume
+ // it was a rework.
}
+ return ChangeKind.REWORK;
} finally {
key.repo = null;
- walk.close();
}
}
- private static boolean isSameParents(RevCommit prior, RevCommit next) {
+ private static boolean isSameDeltaAndTree(RevCommit prior, RevCommit next) {
+ if (next.getTree() != prior.getTree()) {
+ return false;
+ }
+
if (prior.getParentCount() != next.getParentCount()) {
return false;
} else if (prior.getParentCount() == 0) {
return true;
}
- return prior.getParent(0).equals(next.getParent(0));
+
+ // Make sure that the prior/next delta is the same - not just the tree.
+ // This is done by making sure that the parent trees are equal.
+ for (int i = 0; i < prior.getParentCount(); i++) {
+ if (next.getParent(i).getTree() != prior.getParent(i).getTree()) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index fb8397f4e5..ff4a9f71ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.change;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gerrit.extensions.restapi.RestResource;
@@ -23,9 +23,11 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.ObjectId;
@@ -35,13 +37,16 @@ public class ChangeResource implements RestResource, HasETag {
new TypeLiteral<RestView<ChangeResource>>() {};
private final ChangeControl control;
+ private final RebaseChange rebaseChange;
- ChangeResource(ChangeControl control) {
+ public ChangeResource(ChangeControl control, RebaseChange rebaseChange) {
this.control = control;
+ this.rebaseChange = rebaseChange;
}
protected ChangeResource(ChangeResource copy) {
this.control = copy.control;
+ this.rebaseChange = copy.rebaseChange;
}
public ChangeControl getControl() {
@@ -65,14 +70,28 @@ public class ChangeResource implements RestResource, HasETag {
.putBoolean(user.getStarredChanges().contains(getChange().getId()))
.putInt(user.isIdentifiedUser()
? ((IdentifiedUser) user).getAccountId().get()
- : 0);
+ : 0)
+ .putBoolean(rebaseChange != null && rebaseChange.canRebase(this));
byte[] buf = new byte[20];
+ ObjectId noteId;
+ try {
+ noteId = getNotes().loadRevision();
+ } catch (OrmException e) {
+ noteId = null; // This ETag will be invalidated if it loads next time.
+ }
+ hashObjectId(h, noteId, buf);
+ // TODO(dborowitz): Include more notedb and other related refs, e.g. drafts
+ // and edits.
+
for (ProjectState p : control.getProjectControl().getProjectState().tree()) {
- ObjectId id = p.getConfig().getRevision();
- Objects.firstNonNull(id, ObjectId.zeroId()).copyRawTo(buf, 0);
- h.putBytes(buf);
+ hashObjectId(h, p.getConfig().getRevision(), buf);
}
return h.hash().toString();
}
+
+ private void hashObjectId(Hasher h, ObjectId id, byte[] buf) {
+ MoreObjects.firstNonNull(id, ObjectId.zeroId()).copyRawTo(buf, 0);
+ h.putBytes(buf);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeTriplet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeTriplet.java
index 79c442c1eb..45bb1d4b87 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeTriplet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeTriplet.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.change;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Optional;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -21,17 +23,30 @@ import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.lib.Constants;
-public class ChangeTriplet {
+@AutoValue
+public abstract class ChangeTriplet {
+ public static String format(Change change) {
+ return format(change.getDest(), change.getKey());
+ }
- private final Change.Key changeKey;
- private final Project.NameKey projectNameKey;
- private final Branch.NameKey branchNameKey;
+ private static String format(Branch.NameKey branch, Change.Key change) {
+ return branch.getParentKey().get()
+ + "~" + branch.getShortName()
+ + "~" + change.get();
+ }
- public ChangeTriplet(final String triplet) throws ParseException {
+ /**
+ * Parse a triplet out of a string.
+ *
+ * @param triplet string of the form "project~branch~id".
+ * @return the triplet if the input string has the proper format, or absent if
+ * not.
+ */
+ public static Optional<ChangeTriplet> parse(String triplet) {
int t2 = triplet.lastIndexOf('~');
int t1 = triplet.lastIndexOf('~', t2 - 1);
if (t1 < 0 || t2 < 0) {
- throw new ParseException();
+ return Optional.absent();
}
String project = Url.decode(triplet.substring(0, t1));
@@ -42,30 +57,21 @@ public class ChangeTriplet {
branch = Constants.R_HEADS + branch;
}
- changeKey = new Change.Key(changeId);
- projectNameKey = new Project.NameKey(project);
- branchNameKey = new Branch.NameKey(projectNameKey, branch);
+ ChangeTriplet result = new AutoValue_ChangeTriplet(
+ new Branch.NameKey(new Project.NameKey(project), branch),
+ new Change.Key(changeId));
+ return Optional.of(result);
}
- public Change.Key getChangeKey() {
- return changeKey;
+ public final Project.NameKey project() {
+ return branch().getParentKey();
}
- public Branch.NameKey getBranchNameKey() {
- return branchNameKey;
- }
+ public abstract Branch.NameKey branch();
+ public abstract Change.Key id();
- public static String format(final Change change) {
- return change.getProject().get() + "~"
- + change.getDest().getShortName() + "~"
- + change.getKey().get();
- }
-
- public static class ParseException extends Exception {
- private static final long serialVersionUID = 1L;
-
- ParseException() {
- super();
- }
+ @Override
+ public String toString() {
+ return format(branch(), id());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index d3e97ca8ef..42f16a3a39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.change;
-import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
@@ -25,8 +24,9 @@ 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.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -37,37 +37,39 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.util.Collections;
import java.util.List;
@Singleton
public class ChangesCollection implements
RestCollection<TopLevelResource, ChangeResource>,
AcceptsPost<TopLevelResource> {
- private final Provider<ReviewDb> db;
private final Provider<CurrentUser> user;
private final ChangeControl.GenericFactory changeControlFactory;
private final Provider<QueryChanges> queryFactory;
private final DynamicMap<RestView<ChangeResource>> views;
+ private final ChangeUtil changeUtil;
private final CreateChange createChange;
private final ChangeIndexer changeIndexer;
+ private final RebaseChange rebaseChange;
@Inject
ChangesCollection(
- Provider<ReviewDb> dbProvider,
Provider<CurrentUser> user,
ChangeControl.GenericFactory changeControlFactory,
Provider<QueryChanges> queryFactory,
DynamicMap<RestView<ChangeResource>> views,
+ ChangeUtil changeUtil,
CreateChange createChange,
- ChangeIndexer changeIndexer) {
- this.db = dbProvider;
+ ChangeIndexer changeIndexer,
+ RebaseChange rebaseChange) {
this.user = user;
this.changeControlFactory = changeControlFactory;
this.queryFactory = queryFactory;
this.views = views;
+ this.changeUtil = changeUtil;
this.createChange = createChange;
this.changeIndexer = changeIndexer;
+ this.rebaseChange = rebaseChange;
}
@Override
@@ -83,12 +85,12 @@ public class ChangesCollection implements
@Override
public ChangeResource parse(TopLevelResource root, IdString id)
throws ResourceNotFoundException, OrmException {
- List<Change> changes = findChanges(id.encoded());
+ List<Change> changes = changeUtil.findChanges(id.encoded());
if (changes.isEmpty()) {
Integer changeId = Ints.tryParse(id.get());
if (changeId != null) {
try {
- changeIndexer.delete(changeId);
+ changeIndexer.delete(new Change.Id(changeId));
} catch (IOException e) {
throw new ResourceNotFoundException(id.get(), e);
}
@@ -104,7 +106,7 @@ public class ChangesCollection implements
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(id);
}
- return new ChangeResource(control);
+ return new ChangeResource(control, rebaseChange);
}
public ChangeResource parse(Change.Id id)
@@ -113,41 +115,8 @@ public class ChangesCollection implements
IdString.fromUrl(Integer.toString(id.get())));
}
- public ChangeResource parse(ChangeControl control) throws OrmException {
- return new ChangeResource(control);
- }
-
- private List<Change> findChanges(String id)
- throws OrmException, ResourceNotFoundException {
- // Try legacy id
- if (id.matches("^[1-9][0-9]*$")) {
- Change c = db.get().changes().get(Change.Id.parse(id));
- if (c != null) {
- return ImmutableList.of(c);
- }
- return Collections.emptyList();
- }
-
- // Try isolated changeId
- if (!id.contains("~")) {
- Change.Key key = new Change.Key(id);
- if (key.get().length() == 41) {
- return db.get().changes().byKey(key).toList();
- } else {
- return db.get().changes().byKeyRange(key, key.max()).toList();
- }
- }
-
- // Try change triplet
- ChangeTriplet triplet;
- try {
- triplet = new ChangeTriplet(id);
- } catch (ChangeTriplet.ParseException e) {
- throw new ResourceNotFoundException(id);
- }
- return db.get().changes().byBranchKey(
- triplet.getBranchNameKey(),
- triplet.getChangeKey()).toList();
+ public ChangeResource parse(ChangeControl control) {
+ return new ChangeResource(control, rebaseChange);
}
@SuppressWarnings("unchecked")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
new file mode 100644
index 0000000000..5c9fe911fb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 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.extensions.api.changes.FixInput;
+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.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+public class Check implements RestReadView<ChangeResource>,
+ RestModifyView<ChangeResource, FixInput> {
+ private final ChangeJson json;
+
+ @Inject
+ Check(ChangeJson json) {
+ this.json = json;
+ json.addOption(ListChangesOption.CHECK);
+ }
+
+ @Override
+ public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
+ return Response.withMustRevalidate(json.format(rsrc));
+ }
+
+ @Override
+ public Response<ChangeInfo> apply(ChangeResource rsrc, FixInput input)
+ throws AuthException, OrmException {
+ ChangeControl ctl = rsrc.getControl();
+ if (!ctl.isOwner()
+ && !ctl.getProjectControl().isOwner()
+ && !ctl.getCurrentUser().getCapabilities().canAdministrateServer()) {
+ throw new AuthException("Not owner");
+ }
+ return Response.withMustRevalidate(json.fix(input).format(rsrc));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index d034a10eee..efcd6d9766 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.change;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
+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.ResourceConflictException;
@@ -23,9 +24,7 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -83,15 +82,15 @@ public class CherryPick implements RestModifyView<RevisionResource, CherryPickIn
+ input.destination);
}
- final PatchSet.Id patchSetId = revision.getPatchSet().getId();
try {
- Change.Id cherryPickedChangeId = cherryPickChange.cherryPick(
- patchSetId, input.message,
- input.destination, refControl);
+ Change.Id cherryPickedChangeId =
+ cherryPickChange.cherryPick(revision.getChange(),
+ revision.getPatchSet(), input.message, input.destination,
+ refControl);
return json.format(cherryPickedChangeId);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
- } catch (MergeException e) {
+ } catch (MergeException e) {
throw new ResourceConflictException(e.getMessage());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index fd23f339f7..b386894ece 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -14,30 +14,36 @@
package com.google.gerrit.server.change;
-import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.common.TimeUtil;
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.reviewdb.server.ReviewDb;
+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.IdentifiedUser;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeConflictException;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.ssh.NoSshInfo;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -52,7 +58,6 @@ 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.FooterKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
@@ -65,27 +70,32 @@ import java.util.TimeZone;
@Singleton
public class CherryPickChange {
- private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
-
private final Provider<ReviewDb> db;
+ private final Provider<InternalChangeQuery> queryProvider;
private final GitRepositoryManager gitManager;
private final TimeZone serverTimeZone;
private final Provider<CurrentUser> currentUser;
private final CommitValidators.Factory commitValidatorsFactory;
private final ChangeInserter.Factory changeInserterFactory;
private final PatchSetInserter.Factory patchSetInserterFactory;
- final MergeUtil.Factory mergeUtilFactory;
+ private final MergeUtil.Factory mergeUtilFactory;
+ private final ChangeMessagesUtil changeMessagesUtil;
+ private final ChangeUpdate.Factory updateFactory;
@Inject
- CherryPickChange(final Provider<ReviewDb> db,
- @GerritPersonIdent final PersonIdent myIdent,
- final GitRepositoryManager gitManager,
- final Provider<CurrentUser> currentUser,
- final CommitValidators.Factory commitValidatorsFactory,
- final ChangeInserter.Factory changeInserterFactory,
- final PatchSetInserter.Factory patchSetInserterFactory,
- final MergeUtil.Factory mergeUtilFactory) {
+ CherryPickChange(Provider<ReviewDb> db,
+ Provider<InternalChangeQuery> queryProvider,
+ @GerritPersonIdent PersonIdent myIdent,
+ GitRepositoryManager gitManager,
+ Provider<CurrentUser> currentUser,
+ CommitValidators.Factory commitValidatorsFactory,
+ ChangeInserter.Factory changeInserterFactory,
+ PatchSetInserter.Factory patchSetInserterFactory,
+ MergeUtil.Factory mergeUtilFactory,
+ ChangeMessagesUtil changeMessagesUtil,
+ ChangeUpdate.Factory updateFactory) {
this.db = db;
+ this.queryProvider = queryProvider;
this.gitManager = gitManager;
this.serverTimeZone = myIdent.getTimeZone();
this.currentUser = currentUser;
@@ -93,115 +103,105 @@ public class CherryPickChange {
this.changeInserterFactory = changeInserterFactory;
this.patchSetInserterFactory = patchSetInserterFactory;
this.mergeUtilFactory = mergeUtilFactory;
+ this.changeMessagesUtil = changeMessagesUtil;
+ this.updateFactory = updateFactory;
}
- public Change.Id cherryPick(final PatchSet.Id patchSetId,
+ public Change.Id cherryPick(Change change, PatchSet patch,
final String message, final String destinationBranch,
final RefControl refControl) throws NoSuchChangeException,
- EmailException, OrmException, MissingObjectException,
+ OrmException, MissingObjectException,
IncorrectObjectTypeException, IOException,
InvalidChangeOperationException, MergeException {
- final Change.Id changeId = patchSetId.getParentKey();
- final PatchSet patch = db.get().patchSets().get(patchSetId);
- if (patch == null) {
- throw new NoSuchChangeException(changeId);
- }
if (destinationBranch == null || destinationBranch.length() == 0) {
throw new InvalidChangeOperationException(
"Cherry Pick: Destination branch cannot be null or empty");
}
- Project.NameKey project = db.get().changes().get(changeId).getProject();
+ Project.NameKey project = change.getProject();
IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get();
- final Repository git;
- try {
- git = gitManager.openRepository(project);
- } catch (RepositoryNotFoundException e) {
- throw new NoSuchChangeException(changeId, e);
- }
+ try (Repository git = gitManager.openRepository(project);
+ RevWalk revWalk = new RevWalk(git)) {
+ Ref destRef = git.getRef(destinationBranch);
+ if (destRef == null) {
+ throw new InvalidChangeOperationException("Branch "
+ + destinationBranch + " does not exist.");
+ }
- try {
- RevWalk revWalk = new RevWalk(git);
- try {
- Ref destRef = git.getRef(destinationBranch);
- if (destRef == null) {
- throw new InvalidChangeOperationException("Branch "
- + destinationBranch + " does not exist.");
- }
-
- final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
-
- RevCommit commitToCherryPick =
- revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
-
- PersonIdent committerIdent =
- identifiedUser.newCommitterIdent(TimeUtil.nowTs(),
- serverTimeZone);
-
- final ObjectId computedChangeId =
- ChangeIdUtil
- .computeChangeId(commitToCherryPick.getTree(), mergeTip,
- commitToCherryPick.getAuthorIdent(), committerIdent, message);
- String commitMessage =
- ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
-
- RevCommit cherryPickCommit;
- ObjectInserter oi = git.newObjectInserter();
- try {
- ProjectState projectState = refControl.getProjectControl().getProjectState();
- cherryPickCommit =
- mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
- commitToCherryPick, committerIdent, commitMessage, revWalk);
- } finally {
- oi.close();
- }
-
- if (cherryPickCommit == null) {
- throw new MergeException("Cherry pick failed");
- }
-
- Change.Key changeKey;
- final List<String> idList = cherryPickCommit.getFooterLines(CHANGE_ID);
- if (!idList.isEmpty()) {
- final String idStr = idList.get(idList.size() - 1).trim();
- changeKey = new Change.Key(idStr);
- } else {
- changeKey = new Change.Key("I" + computedChangeId.name());
- }
-
- List<Change> destChanges =
- db.get().changes()
- .byBranchKey(
- new Branch.NameKey(db.get().changes().get(changeId).getProject(),
- destRef.getName()), changeKey).toList();
-
- if (destChanges.size() > 1) {
- throw new InvalidChangeOperationException("Several changes with key "
- + changeKey + " reside on the same branch. "
- + "Cannot create a new patch set.");
- } else if (destChanges.size() == 1) {
- // The change key exists on the destination branch. The cherry pick
- // will be added as a new patch set.
- return insertPatchSet(git, revWalk, destChanges.get(0), patchSetId,
- cherryPickCommit, refControl, identifiedUser);
- } else {
- // Change key not found on destination branch. We can create a new
- // change.
- return createNewChange(git, revWalk, changeKey, project, patchSetId, destRef,
- cherryPickCommit, refControl, identifiedUser);
- }
- } finally {
- revWalk.close();
+ final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
+
+ RevCommit commitToCherryPick =
+ revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+
+ PersonIdent committerIdent =
+ identifiedUser.newCommitterIdent(TimeUtil.nowTs(),
+ serverTimeZone);
+
+ final ObjectId computedChangeId =
+ ChangeIdUtil
+ .computeChangeId(commitToCherryPick.getTree(), mergeTip,
+ commitToCherryPick.getAuthorIdent(), committerIdent, message);
+ String commitMessage =
+ ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
+
+ RevCommit cherryPickCommit;
+ try (ObjectInserter oi = git.newObjectInserter()) {
+ ProjectState projectState = refControl.getProjectControl().getProjectState();
+ cherryPickCommit =
+ mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
+ commitToCherryPick, committerIdent, commitMessage, revWalk);
+ } catch (MergeIdenticalTreeException | MergeConflictException e) {
+ throw new MergeException("Cherry pick failed: " + e.getMessage());
+ }
+
+ Change.Key changeKey;
+ 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);
+ } else {
+ changeKey = new Change.Key("I" + computedChangeId.name());
+ }
+
+ Branch.NameKey newDest =
+ new Branch.NameKey(change.getProject(), destRef.getName());
+ List<ChangeData> destChanges = queryProvider.get()
+ .setLimit(2)
+ .byBranchKey(newDest, changeKey);
+ if (destChanges.size() > 1) {
+ throw new InvalidChangeOperationException("Several changes with key "
+ + changeKey + " reside on the same branch. "
+ + "Cannot create a new patch set.");
+ } else if (destChanges.size() == 1) {
+ // The change key exists on the destination branch. The cherry pick
+ // will be added as a new patch set.
+ return insertPatchSet(git, revWalk, destChanges.get(0).change(),
+ cherryPickCommit, refControl, identifiedUser);
+ } else {
+ // Change key not found on destination branch. We can create a new
+ // change.
+ Change newChange = createNewChange(git, revWalk, changeKey, project,
+ destRef, cherryPickCommit, refControl,
+ identifiedUser, change.getTopic());
+
+ addMessageToSourceChange(change, patch.getId(), destinationBranch,
+ cherryPickCommit, identifiedUser, refControl);
+
+ addMessageToDestinationChange(newChange, change.getDest().getShortName(),
+ identifiedUser, refControl);
+
+ return newChange.getId();
}
- } finally {
- git.close();
+ } catch (RepositoryNotFoundException e) {
+ throw new NoSuchChangeException(change.getId(), e);
}
}
private Change.Id insertPatchSet(Repository git, RevWalk revWalk, Change change,
- PatchSet.Id patchSetId, RevCommit cherryPickCommit,
- RefControl refControl, IdentifiedUser identifiedUser)
+ RevCommit cherryPickCommit, RefControl refControl,
+ IdentifiedUser identifiedUser)
throws InvalidChangeOperationException, IOException, OrmException,
NoSuchChangeException {
final ChangeControl changeControl =
@@ -214,22 +214,24 @@ public class CherryPickChange {
.setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
.setDraft(current.isDraft())
.setUploader(identifiedUser.getAccountId())
- .setCopyLabels(true)
+ .setSendMail(false)
.insert();
return change.getId();
}
- private Change.Id createNewChange(Repository git, RevWalk revWalk,
- Change.Key changeKey, Project.NameKey project, PatchSet.Id patchSetId,
+ private Change createNewChange(Repository git, RevWalk revWalk,
+ Change.Key changeKey, Project.NameKey project,
Ref destRef, RevCommit cherryPickCommit, RefControl refControl,
- IdentifiedUser identifiedUser)
+ IdentifiedUser identifiedUser, String topic)
throws OrmException, InvalidChangeOperationException, IOException {
Change change =
new Change(changeKey, new Change.Id(db.get().nextChangeId()),
identifiedUser.getAccountId(), new Branch.NameKey(project,
destRef.getName()), TimeUtil.nowTs());
+ change.setTopic(topic);
ChangeInserter ins =
- changeInserterFactory.create(refControl, change, cherryPickCommit);
+ changeInserterFactory.create(refControl.getProjectControl(), change,
+ cherryPickCommit);
PatchSet newPatchSet = ins.getPatchSet();
CommitValidators commitValidators =
@@ -256,31 +258,51 @@ public class CherryPickChange {
change.getDest().getParentKey().get(), ru.getResult()));
}
- ins.setMessage(buildChangeMessage(patchSetId, change, cherryPickCommit,
- identifiedUser))
- .insert();
+ ins.insert();
- return change.getId();
+ return change;
}
- private ChangeMessage buildChangeMessage(PatchSet.Id patchSetId, Change dest,
- RevCommit cherryPickCommit, IdentifiedUser identifiedUser)
- throws OrmException {
- ChangeMessage cmsg = new ChangeMessage(
+ private void addMessageToSourceChange(Change change, PatchSet.Id patchSetId,
+ String destinationBranch, RevCommit cherryPickCommit,
+ IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+ ChangeMessage changeMessage = new ChangeMessage(
new ChangeMessage.Key(
patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
- String destBranchName = dest.getDest().get();
- StringBuilder msgBuf = new StringBuilder("Patch Set ")
+ StringBuilder sb = new StringBuilder("Patch Set ")
.append(patchSetId.get())
.append(": Cherry Picked")
.append("\n\n")
.append("This patchset was cherry picked to branch ")
- .append(destBranchName.substring(
- destBranchName.indexOf("refs/heads/") + "refs/heads/".length()))
+ .append(destinationBranch)
.append(" as commit ")
.append(cherryPickCommit.getId().getName());
- cmsg.setMessage(msgBuf.toString());
- return cmsg;
+ changeMessage.setMessage(sb.toString());
+
+ ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+ ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+ changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
+ }
+
+ private void addMessageToDestinationChange(Change change, String sourceBranch,
+ IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+ PatchSet.Id patchSetId =
+ db.get().patchSets().get(change.currentPatchSetId()).getId();
+ ChangeMessage changeMessage = new ChangeMessage(
+ new ChangeMessage.Key(
+ patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
+ identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
+
+ StringBuilder sb = new StringBuilder("Patch Set ")
+ .append(patchSetId.get())
+ .append(": Cherry Picked from branch ")
+ .append(sourceBranch)
+ .append(".");
+ changeMessage.setMessage(sb.toString());
+
+ ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+ ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+ changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
deleted file mode 100644
index adc1644a77..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
+++ /dev/null
@@ -1,54 +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.change;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.common.changes.Side;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.CommentRange;
-import com.google.gerrit.server.account.AccountInfo;
-
-import java.sql.Timestamp;
-
-public class CommentInfo {
- String id;
- String path;
- Side side;
- Integer line;
- String inReplyTo;
- String message;
- Timestamp updated;
- AccountInfo author;
- CommentRange range;
-
- CommentInfo(PatchLineComment c, AccountInfo.Loader accountLoader) {
- id = Url.encode(c.getKey().get());
- path = c.getKey().getParentKey().getFileName();
- if (c.getSide() == 0) {
- side = Side.PARENT;
- }
- if (c.getLine() > 0) {
- line = c.getLine();
- }
- inReplyTo = Url.encode(c.getParentUuid());
- message = Strings.emptyToNull(c.getMessage());
- updated = c.getWrittenOn();
- range = c.getRange();
- if (accountLoader != null) {
- author = accountLoader.get(c.getAuthor());
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
new file mode 100644
index 0000000000..4a52fc5748
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
@@ -0,0 +1,137 @@
+// Copyright (C) 2014 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 static com.google.common.base.MoreObjects.firstNonNull;
+
+import com.google.common.base.Strings;
+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.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.CommentRange;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+@Singleton
+class CommentJson {
+
+ private final AccountLoader.Factory accountLoaderFactory;
+
+ @Inject
+ CommentJson(AccountLoader.Factory accountLoaderFactory) {
+ this.accountLoaderFactory = accountLoaderFactory;
+ }
+
+ CommentInfo format(PatchLineComment c) throws OrmException {
+ return format(c, true);
+ }
+
+ CommentInfo format(PatchLineComment c, boolean fill) throws OrmException {
+ AccountLoader loader = null;
+ if (fill) {
+ loader = accountLoaderFactory.create(true);
+ }
+ CommentInfo commentInfo = toCommentInfo(c, loader);
+ if (fill) {
+ loader.fill();
+ }
+ return commentInfo;
+ }
+
+ Map<String, List<CommentInfo>> format(Iterable<PatchLineComment> l,
+ boolean fill) throws OrmException {
+ Map<String, List<CommentInfo>> out = new TreeMap<>();
+ AccountLoader accountLoader = fill
+ ? accountLoaderFactory.create(true)
+ : null;
+
+ for (PatchLineComment c : l) {
+ CommentInfo o = toCommentInfo(c, accountLoader);
+ List<CommentInfo> list = out.get(o.path);
+ if (list == null) {
+ list = new ArrayList<>();
+ out.put(o.path, list);
+ }
+ o.path = null;
+ list.add(o);
+ }
+
+ for (List<CommentInfo> list : out.values()) {
+ Collections.sort(list, new Comparator<CommentInfo>() {
+ @Override
+ public int compare(CommentInfo a, CommentInfo b) {
+ int c = firstNonNull(a.side, Side.REVISION).ordinal()
+ - firstNonNull(b.side, Side.REVISION).ordinal();
+ if (c == 0) {
+ c = firstNonNull(a.line, 0) - firstNonNull(b.line, 0);
+ }
+ if (c == 0) {
+ c = a.id.compareTo(b.id);
+ }
+ return c;
+ }
+ });
+ }
+
+ if (accountLoader != null) {
+ accountLoader.fill();
+ }
+
+ return out;
+ }
+
+ private CommentInfo toCommentInfo(PatchLineComment c, AccountLoader loader) {
+ CommentInfo r = new CommentInfo();
+ r.id = Url.encode(c.getKey().get());
+ r.path = c.getKey().getParentKey().getFileName();
+ if (c.getSide() == 0) {
+ r.side = Side.PARENT;
+ }
+ if (c.getLine() > 0) {
+ r.line = c.getLine();
+ }
+ r.inReplyTo = Url.encode(c.getParentUuid());
+ r.message = Strings.emptyToNull(c.getMessage());
+ r.updated = c.getWrittenOn();
+ r.range = toRange(c.getRange());
+ if (loader != null) {
+ r.author = loader.get(c.getAuthor());
+ }
+ return r;
+ }
+
+ private Range toRange(CommentRange commentRange) {
+ Range range = null;
+ if (commentRange != null) {
+ range = new Range();
+ range.startLine = commentRange.getStartLine();
+ range.startCharacter = commentRange.getStartCharacter();
+ range.endLine = commentRange.getEndLine();
+ range.endCharacter = commentRange.getEndCharacter();
+ }
+ return range;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java
index ec47d019a3..c535e9e6df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentResource.java
@@ -28,7 +28,7 @@ public class CommentResource implements RestResource {
private final RevisionResource rev;
private final PatchLineComment comment;
- CommentResource(RevisionResource rev, PatchLineComment c) {
+ public CommentResource(RevisionResource rev, PatchLineComment c) {
this.rev = rev;
this.comment = c;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Comments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Comments.java
index c987ce8892..eff408e9c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Comments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Comments.java
@@ -29,7 +29,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
-class Comments implements ChildCollection<RevisionResource, CommentResource> {
+public class Comments implements ChildCollection<RevisionResource, CommentResource> {
private final DynamicMap<RestView<CommentResource>> views;
private final ListComments list;
private final Provider<ReviewDb> dbProvider;
@@ -51,7 +51,7 @@ class Comments implements ChildCollection<RevisionResource, CommentResource> {
}
@Override
- public RestView<RevisionResource> list() {
+ public ListComments list() {
return list;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
new file mode 100644
index 0000000000..ee28ed204c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -0,0 +1,490 @@
+// Copyright (C) 2014 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.auto.value.AutoValue;
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
+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.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+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.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Checks changes for various kinds of inconsistency and corruption.
+ * <p>
+ * A single instance may be reused for checking multiple changes, but not
+ * concurrently.
+ */
+public class ConsistencyChecker {
+ private static final Logger log =
+ LoggerFactory.getLogger(ConsistencyChecker.class);
+
+ @AutoValue
+ public abstract static class Result {
+ private static Result create(Change.Id id, List<ProblemInfo> problems) {
+ return new AutoValue_ConsistencyChecker_Result(id, null, problems);
+ }
+
+ private static Result create(Change c, List<ProblemInfo> problems) {
+ return new AutoValue_ConsistencyChecker_Result(c.getId(), c, problems);
+ }
+
+ public abstract Change.Id id();
+
+ @Nullable
+ public abstract Change change();
+
+ public abstract List<ProblemInfo> problems();
+ }
+
+ private final Provider<ReviewDb> db;
+ private final GitRepositoryManager repoManager;
+ private final Provider<CurrentUser> user;
+ private final Provider<PersonIdent> serverIdent;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+
+ private FixInput fix;
+ private Change change;
+ private Repository repo;
+ private RevWalk rw;
+
+ private PatchSet currPs;
+ private RevCommit currPsCommit;
+
+ private List<ProblemInfo> problems;
+
+ @Inject
+ ConsistencyChecker(Provider<ReviewDb> db,
+ GitRepositoryManager repoManager,
+ Provider<CurrentUser> user,
+ @GerritPersonIdent Provider<PersonIdent> serverIdent,
+ PatchSetInfoFactory patchSetInfoFactory) {
+ this.db = db;
+ this.repoManager = repoManager;
+ this.user = user;
+ this.serverIdent = serverIdent;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ reset();
+ }
+
+ private void reset() {
+ change = null;
+ repo = null;
+ rw = null;
+ problems = new ArrayList<>();
+ }
+
+ public Result check(ChangeData cd) {
+ return check(cd, null);
+ }
+
+ public Result check(ChangeData cd, @Nullable FixInput f) {
+ reset();
+ try {
+ return check(cd.change(), f);
+ } catch (OrmException e) {
+ error("Error looking up change", e);
+ return Result.create(cd.getId(), problems);
+ }
+ }
+
+ public Result check(Change c) {
+ return check(c, null);
+ }
+
+ public Result check(Change c, @Nullable FixInput f) {
+ reset();
+ fix = f;
+ change = c;
+ try {
+ checkImpl();
+ return Result.create(c, problems);
+ } finally {
+ if (rw != null) {
+ rw.close();
+ }
+ if (repo != null) {
+ repo.close();
+ }
+ }
+ }
+
+ private void checkImpl() {
+ checkOwner();
+ checkCurrentPatchSetEntity();
+
+ // All checks that require the repo.
+ if (!openRepo()) {
+ return;
+ }
+ if (!checkPatchSets()) {
+ return;
+ }
+ checkMerged();
+ }
+
+ private void checkOwner() {
+ try {
+ if (db.get().accounts().get(change.getOwner()) == null) {
+ problem("Missing change owner: " + change.getOwner());
+ }
+ } catch (OrmException e) {
+ error("Failed to look up owner", e);
+ }
+ }
+
+ private void checkCurrentPatchSetEntity() {
+ try {
+ PatchSet.Id psId = change.currentPatchSetId();
+ currPs = db.get().patchSets().get(psId);
+ if (currPs == null) {
+ problem(String.format("Current patch set %d not found", psId.get()));
+ }
+ } catch (OrmException e) {
+ error("Failed to look up current patch set", e);
+ }
+ }
+
+ private boolean openRepo() {
+ Project.NameKey project = change.getDest().getParentKey();
+ try {
+ repo = repoManager.openRepository(project);
+ rw = new RevWalk(repo);
+ return true;
+ } catch (RepositoryNotFoundException e) {
+ return error("Destination repository not found: " + project, e);
+ } catch (IOException e) {
+ return error("Failed to open repository: " + project, e);
+ }
+ }
+
+ private static final Function<PatchSet, Integer> TO_PS_ID =
+ new Function<PatchSet, Integer>() {
+ @Override
+ public Integer apply(PatchSet in) {
+ return in.getId().get();
+ }
+ };
+
+ private static final Ordering<PatchSet> PS_ID_ORDER = Ordering.natural()
+ .onResultOf(TO_PS_ID);
+
+ private boolean checkPatchSets() {
+ List<PatchSet> all;
+ try {
+ all = Lists.newArrayList(db.get().patchSets().byChange(change.getId()));
+ } catch (OrmException e) {
+ return error("Failed to look up patch sets", e);
+ }
+ // Iterate in descending order so deletePatchSet can assume the latest patch
+ // set exists.
+ Collections.sort(all, PS_ID_ORDER.reverse());
+ Multimap<ObjectId, PatchSet> bySha = MultimapBuilder.hashKeys(all.size())
+ .treeSetValues(PS_ID_ORDER)
+ .build();
+ for (PatchSet ps : all) {
+ // Check revision format.
+ ObjectId objId;
+ String rev = ps.getRevision().get();
+ int psNum = ps.getId().get();
+ String refName = ps.getId().toRefName();
+ try {
+ objId = ObjectId.fromString(rev);
+ } catch (IllegalArgumentException e) {
+ error(String.format("Invalid revision on patch set %d: %s", psNum, rev),
+ e);
+ continue;
+ }
+ bySha.put(objId, ps);
+
+ // Check ref existence.
+ ProblemInfo refProblem = null;
+ try {
+ Ref ref = repo.getRef(refName);
+ if (ref == null) {
+ refProblem = problem("Ref missing: " + refName);
+ } else if (!objId.equals(ref.getObjectId())) {
+ String actual = ref.getObjectId() != null
+ ? ref.getObjectId().name()
+ : "null";
+ refProblem = problem(String.format(
+ "Expected %s to point to %s, found %s",
+ ref.getName(), objId.name(), actual));
+ }
+ } catch (IOException e) {
+ error("Error reading ref: " + refName, e);
+ refProblem = lastProblem();
+ }
+
+ // Check object existence.
+ RevCommit psCommit = parseCommit(
+ objId, String.format("patch set %d", psNum));
+ if (psCommit == null) {
+ if (fix != null && fix.deletePatchSetIfCommitMissing) {
+ deletePatchSet(lastProblem(), ps.getId());
+ }
+ continue;
+ } else if (refProblem != null && fix != null) {
+ fixPatchSetRef(refProblem, ps);
+ }
+ if (ps.getId().equals(change.currentPatchSetId())) {
+ currPsCommit = psCommit;
+ }
+ }
+
+ // Check for duplicates.
+ for (Map.Entry<ObjectId, Collection<PatchSet>> e
+ : bySha.asMap().entrySet()) {
+ if (e.getValue().size() > 1) {
+ problem(String.format("Multiple patch sets pointing to %s: %s",
+ e.getKey().name(),
+ Collections2.transform(e.getValue(), TO_PS_ID)));
+ }
+ }
+
+ return currPs != null && currPsCommit != null;
+ }
+
+ private void checkMerged() {
+ String refName = change.getDest().get();
+ Ref dest;
+ try {
+ dest = repo.getRef(refName);
+ } catch (IOException e) {
+ problem("Failed to look up destination ref: " + refName);
+ return;
+ }
+ if (dest == null) {
+ problem("Destination ref not found (may be new branch): "
+ + change.getDest().get());
+ return;
+ }
+ RevCommit tip = parseCommit(dest.getObjectId(),
+ "destination ref " + refName);
+ if (tip == null) {
+ return;
+ }
+ boolean merged;
+ try {
+ merged = rw.isMergedInto(currPsCommit, tip);
+ } catch (IOException e) {
+ problem("Error checking whether patch set " + currPs.getId().get()
+ + " is merged");
+ return;
+ }
+ if (merged && change.getStatus() != Change.Status.MERGED) {
+ ProblemInfo p = problem(String.format(
+ "Patch set %d (%s) is merged into destination ref %s (%s), but change"
+ + " status is %s", currPs.getId().get(), currPsCommit.name(),
+ refName, tip.name(), change.getStatus()));
+ if (fix != null) {
+ fixMerged(p);
+ }
+ } else if (!merged && change.getStatus() == Change.Status.MERGED) {
+ problem(String.format("Patch set %d (%s) is not merged into"
+ + " destination ref %s (%s), but change status is %s",
+ currPs.getId().get(), currPsCommit.name(), refName, tip.name(),
+ change.getStatus()));
+ }
+ }
+
+ private void fixMerged(ProblemInfo p) {
+ try {
+ change = db.get().changes().atomicUpdate(change.getId(),
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change c) {
+ c.setStatus(Change.Status.MERGED);
+ return c;
+ }
+ });
+ p.status = Status.FIXED;
+ p.outcome = "Marked change as merged";
+ } catch (OrmException e) {
+ log.warn("Error marking " + change.getId() + "as merged", e);
+ p.status = Status.FIX_FAILED;
+ p.outcome = "Error updating status to merged";
+ }
+ }
+
+ private void fixPatchSetRef(ProblemInfo p, PatchSet ps) {
+ try {
+ RefUpdate ru = repo.updateRef(ps.getId().toRefName());
+ ru.setForceUpdate(true);
+ ru.setNewObjectId(ObjectId.fromString(ps.getRevision().get()));
+ ru.setRefLogIdent(newRefLogIdent());
+ ru.setRefLogMessage("Repair patch set ref", true);
+ RefUpdate.Result result = ru.update();
+ switch (result) {
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ case NO_CHANGE:
+ p.status = Status.FIXED;
+ p.outcome = "Repaired patch set ref";
+ return;
+ default:
+ p.status = Status.FIX_FAILED;
+ p.outcome = "Failed to update patch set ref: " + result;
+ return;
+ }
+ } catch (IOException e) {
+ String msg = "Error fixing patch set ref";
+ log.warn(msg + ' ' + ps.getId().toRefName(), e);
+ p.status = Status.FIX_FAILED;
+ p.outcome = msg;
+ }
+ }
+
+ private void deletePatchSet(ProblemInfo p, PatchSet.Id psId) {
+ ReviewDb db = this.db.get();
+ Change.Id cid = psId.getParentKey();
+ try {
+ db.changes().beginTransaction(cid);
+ try {
+ Change c = db.changes().get(cid);
+ if (c == null) {
+ throw new OrmException("Change missing: " + cid);
+ }
+
+ if (psId.equals(c.currentPatchSetId())) {
+ List<PatchSet> all = Lists.newArrayList(db.patchSets().byChange(cid));
+ if (all.size() == 1 && all.get(0).getId().equals(psId)) {
+ p.status = Status.FIX_FAILED;
+ p.outcome = "Cannot delete patch set; no patch sets would remain";
+ return;
+ }
+ // If there were multiple missing patch sets, assumes deletePatchSet
+ // has been called in decreasing order, so the max remaining PatchSet
+ // is the effective current patch set.
+ Collections.sort(all, PS_ID_ORDER.reverse());
+ PatchSet.Id latest = null;
+ for (PatchSet ps : all) {
+ latest = ps.getId();
+ if (!ps.getId().equals(psId)) {
+ break;
+ }
+ }
+ c.setCurrentPatchSet(patchSetInfoFactory.get(db, latest));
+ db.changes().update(Collections.singleton(c));
+ }
+
+ // Delete dangling primary key references. Don't delete ChangeMessages,
+ // which don't use patch sets as a primary key, and may provide useful
+ // historical information.
+ db.accountPatchReviews().delete(
+ db.accountPatchReviews().byPatchSet(psId));
+ db.patchSetAncestors().delete(
+ db.patchSetAncestors().byPatchSet(psId));
+ db.patchSetApprovals().delete(
+ db.patchSetApprovals().byPatchSet(psId));
+ db.patchComments().delete(
+ db.patchComments().byPatchSet(psId));
+ db.patchSets().deleteKeys(Collections.singleton(psId));
+ db.commit();
+
+ p.status = Status.FIXED;
+ p.outcome = "Deleted patch set";
+ } finally {
+ db.rollback();
+ }
+ } catch (PatchSetInfoNotAvailableException | OrmException e) {
+ String msg = "Error deleting patch set";
+ log.warn(msg + ' ' + psId, e);
+ p.status = Status.FIX_FAILED;
+ p.outcome = msg;
+ }
+ }
+
+ private PersonIdent newRefLogIdent() {
+ CurrentUser u = user.get();
+ if (u.isIdentifiedUser()) {
+ return ((IdentifiedUser) u).newRefLogIdent();
+ } else {
+ return serverIdent.get();
+ }
+ }
+
+ private RevCommit parseCommit(ObjectId objId, String desc) {
+ try {
+ return rw.parseCommit(objId);
+ } catch (MissingObjectException e) {
+ problem(String.format("Object missing: %s: %s", desc, objId.name()));
+ } catch (IncorrectObjectTypeException e) {
+ problem(String.format("Not a commit: %s: %s", desc, objId.name()));
+ } catch (IOException e) {
+ problem(String.format("Failed to look up: %s: %s", desc, objId.name()));
+ }
+ return null;
+ }
+
+ private ProblemInfo problem(String msg) {
+ ProblemInfo p = new ProblemInfo();
+ p.message = msg;
+ problems.add(p);
+ return p;
+ }
+
+ private ProblemInfo lastProblem() {
+ return problems.get(problems.size() - 1);
+ }
+
+ private boolean error(String msg, Throwable t) {
+ problem(msg);
+ // TODO(dborowitz): Expose stack trace to administrators.
+ log.warn("Error in consistency check of change " + change.getId(), t);
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index 04c12c3401..4dffd67619 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -15,11 +15,16 @@
package com.google.gerrit.server.change;
import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ChangeStatus;
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.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -29,13 +34,13 @@ 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.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.api.changes.ChangeInfoMapper;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -43,13 +48,13 @@ import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.ssh.NoSshInfo;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
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;
@@ -80,6 +85,8 @@ public class CreateChange implements
private final CommitValidators.Factory commitValidatorsFactory;
private final ChangeInserter.Factory changeInserterFactory;
private final ChangeJson json;
+ private final ChangeUtil changeUtil;
+ private final boolean allowDrafts;
@Inject
CreateChange(Provider<ReviewDb> db,
@@ -89,7 +96,9 @@ public class CreateChange implements
ProjectsCollection projectsCollection,
CommitValidators.Factory commitValidatorsFactory,
ChangeInserter.Factory changeInserterFactory,
- ChangeJson json) {
+ ChangeJson json,
+ ChangeUtil changeUtil,
+ @GerritServerConfig Config config) {
this.db = db;
this.gitManager = gitManager;
this.serverTimeZone = myIdent.getTimeZone();
@@ -98,14 +107,16 @@ public class CreateChange implements
this.commitValidatorsFactory = commitValidatorsFactory;
this.changeInserterFactory = changeInserterFactory;
this.json = json;
+ this.changeUtil = changeUtil;
+ this.allowDrafts = config.getBoolean("change", "allowDrafts", true);
}
@Override
- public Response<ChangeJson.ChangeInfo> apply(TopLevelResource parent,
+ public Response<ChangeInfo> apply(TopLevelResource parent,
ChangeInfo input) throws AuthException, OrmException,
BadRequestException, UnprocessableEntityException, IOException,
- InvalidChangeOperationException {
-
+ InvalidChangeOperationException, ResourceNotFoundException,
+ MethodNotAllowedException {
if (Strings.isNullOrEmpty(input.project)) {
throw new BadRequestException("project must be non-empty");
}
@@ -123,6 +134,10 @@ public class CreateChange implements
&& input.status != ChangeStatus.DRAFT) {
throw new BadRequestException("unsupported change status");
}
+
+ if (!allowDrafts && input.status == ChangeStatus.DRAFT) {
+ throw new MethodNotAllowedException("draft workflow is disabled");
+ }
}
String refName = input.branch;
@@ -143,51 +158,63 @@ public class CreateChange implements
}
Project.NameKey project = rsrc.getNameKey();
- Repository git = gitManager.openRepository(project);
-
- try {
- RevWalk rw = new RevWalk(git);
- try {
+ try (Repository git = gitManager.openRepository(project);
+ RevWalk rw = new RevWalk(git)) {
+ ObjectId parentCommit;
+ if (input.baseChange != null) {
+ List<Change> changes = changeUtil.findChanges(input.baseChange);
+ if (changes.size() != 1) {
+ throw new InvalidChangeOperationException(
+ "Base change not found: " + input.baseChange);
+ }
+ Change change = Iterables.getOnlyElement(changes);
+ if (!rsrc.getControl().controlFor(change).isVisible(db.get())) {
+ throw new InvalidChangeOperationException(
+ "Base change not found: " + input.baseChange);
+ }
+ PatchSet ps = db.get().patchSets().get(
+ new PatchSet.Id(change.getId(),
+ change.currentPatchSetId().get()));
+ parentCommit = ObjectId.fromString(ps.getRevision().get());
+ } else {
Ref destRef = git.getRef(refName);
if (destRef == null) {
throw new UnprocessableEntityException(String.format(
"Branch %s does not exist.", refName));
}
+ parentCommit = destRef.getObjectId();
+ }
+ RevCommit mergeTip = rw.parseCommit(parentCommit);
- Timestamp now = TimeUtil.nowTs();
- IdentifiedUser me = (IdentifiedUser) userProvider.get();
- PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
+ Timestamp now = TimeUtil.nowTs();
+ IdentifiedUser me = (IdentifiedUser) userProvider.get();
+ PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
- RevCommit mergeTip = rw.parseCommit(destRef.getObjectId());
- ObjectId id = ChangeIdUtil.computeChangeId(mergeTip.getTree(),
- mergeTip, author, author, input.subject);
- String commitMessage = ChangeIdUtil.insertId(input.subject, id);
+ ObjectId id = ChangeIdUtil.computeChangeId(mergeTip.getTree(),
+ mergeTip, author, author, input.subject);
+ String commitMessage = ChangeIdUtil.insertId(input.subject, id);
- RevCommit c = newCommit(git, rw, author, mergeTip, commitMessage);
+ RevCommit c = newCommit(git, rw, author, mergeTip, commitMessage);
- Change change = new Change(
- getChangeId(id, c),
- new Change.Id(db.get().nextChangeId()),
- me.getAccountId(),
- new Branch.NameKey(project, destRef.getName()),
- now);
+ Change change = new Change(
+ getChangeId(id, c),
+ new Change.Id(db.get().nextChangeId()),
+ me.getAccountId(),
+ new Branch.NameKey(project, refName),
+ now);
- ChangeInserter ins =
- changeInserterFactory.create(refControl, change, c);
+ ChangeInserter ins =
+ changeInserterFactory.create(refControl.getProjectControl(),
+ change, c);
- validateCommit(git, refControl, c, me, ins);
- updateRef(git, rw, c, change, ins.getPatchSet());
+ validateCommit(git, refControl, c, me, ins);
+ updateRef(git, rw, c, change, ins.getPatchSet());
- change.setTopic(input.topic);
- change.setStatus(ChangeInfoMapper.changeStatus2Status(input.status));
- ins.insert();
+ change.setTopic(input.topic);
+ ins.setDraft(input.status != null && input.status == ChangeStatus.DRAFT);
+ ins.insert();
- return Response.created(json.format(change.getId()));
- } finally {
- rw.close();
- }
- } finally {
- git.close();
+ return Response.created(json.format(change.getId()));
}
}
@@ -229,7 +256,7 @@ public class CreateChange implements
private static Change.Key getChangeId(ObjectId id, RevCommit emptyCommit) {
List<String> idList = emptyCommit.getFooterLines(
- MergeUtil.CHANGE_ID);
+ FooterConstants.CHANGE_ID);
Change.Key changeKey = !idList.isEmpty()
? new Change.Key(idList.get(idList.size() - 1).trim())
: new Change.Key("I" + id.name());
@@ -240,8 +267,7 @@ public class CreateChange implements
PersonIdent authorIdent, RevCommit mergeTip, String commitMessage)
throws IOException {
RevCommit emptyCommit;
- ObjectInserter oi = git.newObjectInserter();
- try {
+ try (ObjectInserter oi = git.newObjectInserter()) {
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(mergeTip.getTree().getId());
commit.setParentId(mergeTip);
@@ -249,8 +275,6 @@ public class CreateChange implements
commit.setCommitter(authorIdent);
commit.setMessage(commitMessage);
emptyCommit = rw.parseCommit(insert(oi, commit));
- } finally {
- oi.close();
}
return emptyCommit;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
index 1d2fa40669..36b9692510 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
@@ -14,8 +14,13 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+
import com.google.common.base.Strings;
-import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -24,50 +29,70 @@ import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.change.PutDraft.Input;
-import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
import com.google.gwtorm.server.OrmException;
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.Collections;
@Singleton
-class CreateDraft implements RestModifyView<RevisionResource, Input> {
+public class CreateDraftComment implements RestModifyView<RevisionResource, DraftInput> {
private final Provider<ReviewDb> db;
+ private final ChangeUpdate.Factory updateFactory;
+ private final CommentJson commentJson;
+ private final PatchLineCommentsUtil plcUtil;
+ private final PatchListCache patchListCache;
@Inject
- CreateDraft(Provider<ReviewDb> db) {
+ CreateDraftComment(Provider<ReviewDb> db,
+ ChangeUpdate.Factory updateFactory,
+ CommentJson commentJson,
+ PatchLineCommentsUtil plcUtil,
+ PatchListCache patchListCache) {
this.db = db;
+ this.updateFactory = updateFactory;
+ this.commentJson = commentJson;
+ this.plcUtil = plcUtil;
+ this.patchListCache = patchListCache;
}
@Override
- public Response<CommentInfo> apply(RevisionResource rsrc, Input in)
- throws BadRequestException, OrmException {
+ public Response<CommentInfo> apply(RevisionResource rsrc, DraftInput in)
+ throws BadRequestException, OrmException, IOException {
if (Strings.isNullOrEmpty(in.path)) {
throw new BadRequestException("path must be non-empty");
} else if (in.message == null || in.message.trim().isEmpty()) {
throw new BadRequestException("message must be non-empty");
} else if (in.line != null && in.line <= 0) {
throw new BadRequestException("line must be > 0");
- } else if (in.line != null && in.range != null && in.line != in.range.getEndLine()) {
+ } else if (in.line != null && in.range != null && in.line != in.range.endLine) {
throw new BadRequestException("range endLine must be on the same line as the comment");
}
int line = in.line != null
? in.line
- : in.range != null ? in.range.getEndLine() : 0;
+ : in.range != null ? in.range.endLine : 0;
+
+ Timestamp now = TimeUtil.nowTs();
+ ChangeUpdate update = updateFactory.create(rsrc.getControl(), now);
PatchLineComment c = new PatchLineComment(
new PatchLineComment.Key(
new Patch.Key(rsrc.getPatchSet().getId(), in.path),
ChangeUtil.messageUUID(db.get())),
- line, rsrc.getAccountId(), Url.decode(in.inReplyTo), TimeUtil.nowTs());
+ line, rsrc.getAccountId(), Url.decode(in.inReplyTo), now);
c.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
c.setMessage(in.message.trim());
c.setRange(in.range);
- db.get().patchComments().insert(Collections.singleton(c));
- return Response.created(new CommentInfo(c, null));
+ setCommentRevId(c, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ plcUtil.insertComments(db.get(), update, Collections.singleton(c));
+ update.commit();
+ return Response.created(commentJson.format(c, false));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
new file mode 100644
index 0000000000..9bb1f02d91
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 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.common.base.Optional;
+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.server.change.DeleteChangeEdit.Input;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class DeleteChangeEdit implements RestModifyView<ChangeResource, Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditUtil editUtil;
+
+ @Inject
+ DeleteChangeEdit(ChangeEditUtil editUtil) {
+ this.editUtil = editUtil;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, Input input)
+ throws AuthException, ResourceNotFoundException, IOException,
+ InvalidChangeOperationException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (edit.isPresent()) {
+ editUtil.delete(edit.get());
+ } else {
+ throw new ResourceNotFoundException();
+ }
+
+ return Response.none();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
deleted file mode 100644
index 46ae8341d3..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2012 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.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.DeleteDraft.Input;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-import java.util.Collections;
-
-@Singleton
-class DeleteDraft implements RestModifyView<DraftResource, Input> {
- static class Input {
- }
-
- private final Provider<ReviewDb> db;
-
- @Inject
- DeleteDraft(Provider<ReviewDb> db) {
- this.db = db;
- }
-
- @Override
- public Response<CommentInfo> apply(DraftResource rsrc, Input input)
- throws OrmException {
- db.get().patchComments().delete(Collections.singleton(rsrc.getComment()));
- return Response.none();
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
index 28885db58e..b276aaea7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.change;
import com.google.gerrit.extensions.restapi.AuthException;
+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.extensions.restapi.Response;
@@ -25,7 +26,6 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.DeleteDraftChange.Input;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -48,7 +48,6 @@ public class DeleteDraftChange implements
@Inject
public DeleteDraftChange(Provider<ReviewDb> dbProvider,
- PatchSetInfoFactory patchSetInfoFactory,
ChangeUtil changeUtil,
@GerritServerConfig Config cfg) {
this.dbProvider = dbProvider;
@@ -59,7 +58,8 @@ public class DeleteDraftChange implements
@Override
public Response<?> apply(ChangeResource rsrc, Input input)
throws ResourceConflictException, AuthException,
- ResourceNotFoundException, OrmException, IOException {
+ ResourceNotFoundException, MethodNotAllowedException,
+ OrmException, IOException {
if (rsrc.getChange().getStatus() != Status.DRAFT) {
throw new ResourceConflictException("Change is not a draft");
}
@@ -69,11 +69,11 @@ public class DeleteDraftChange implements
}
if (!allowDrafts) {
- throw new ResourceConflictException("Draft workflow is disabled.");
+ throw new MethodNotAllowedException("draft workflow is disabled");
}
try {
- changeUtil.deleteDraftChange(rsrc.getChange().getId());
+ changeUtil.deleteDraftChange(rsrc.getChange());
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
new file mode 100644
index 0000000000..c4270a9139
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2012 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 static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.change.DeleteDraftComment.Input;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.Collections;
+
+@Singleton
+public class DeleteDraftComment implements RestModifyView<DraftCommentResource, Input> {
+ static class Input {
+ }
+
+ private final Provider<ReviewDb> db;
+ private final PatchLineCommentsUtil plcUtil;
+ private final ChangeUpdate.Factory updateFactory;
+ private final PatchListCache patchListCache;
+
+ @Inject
+ DeleteDraftComment(Provider<ReviewDb> db,
+ PatchLineCommentsUtil plcUtil,
+ ChangeUpdate.Factory updateFactory,
+ PatchListCache patchListCache) {
+ this.db = db;
+ this.plcUtil = plcUtil;
+ this.updateFactory = updateFactory;
+ this.patchListCache = patchListCache;
+ }
+
+ @Override
+ public Response<CommentInfo> apply(DraftCommentResource rsrc, Input input)
+ throws OrmException, IOException {
+ ChangeUpdate update = updateFactory.create(rsrc.getControl());
+
+ PatchLineComment c = rsrc.getComment();
+ setCommentRevId(c, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ plcUtil.deleteComments(db.get(), update, Collections.singleton(c));
+ update.commit();
+ return Response.none();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
index 46d0ed9240..a266337551 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.change;
import com.google.gerrit.extensions.restapi.AuthException;
+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.extensions.restapi.Response;
@@ -27,6 +28,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -49,33 +51,37 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
protected final Provider<ReviewDb> dbProvider;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ChangeUtil changeUtil;
+ private final ChangeIndexer indexer;
private final boolean allowDrafts;
@Inject
public DeleteDraftPatchSet(Provider<ReviewDb> dbProvider,
PatchSetInfoFactory patchSetInfoFactory,
ChangeUtil changeUtil,
+ ChangeIndexer indexer,
@GerritServerConfig Config cfg) {
this.dbProvider = dbProvider;
this.patchSetInfoFactory = patchSetInfoFactory;
this.changeUtil = changeUtil;
+ this.indexer = indexer;
this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
}
@Override
public Response<?> apply(RevisionResource rsrc, Input input)
throws AuthException, ResourceNotFoundException,
- ResourceConflictException, OrmException, IOException {
+ ResourceConflictException, MethodNotAllowedException,
+ OrmException, IOException {
PatchSet patchSet = rsrc.getPatchSet();
PatchSet.Id patchSetId = patchSet.getId();
Change change = rsrc.getChange();
if (!patchSet.isDraft()) {
- throw new ResourceConflictException("Patch set is not a draft.");
+ throw new ResourceConflictException("Patch set is not a draft");
}
if (!allowDrafts) {
- throw new ResourceConflictException("Draft workflow is disabled.");
+ throw new MethodNotAllowedException("draft workflow is disabled");
}
if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
@@ -121,7 +127,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
.patchSets()
.byChange(change.getId())
.toList().size() == 0) {
- deleteDraftChange(patchSetId);
+ deleteDraftChange(change);
} else {
if (change.currentPatchSetId().equals(patchSetId)) {
updateChange(dbProvider.get(), change,
@@ -133,38 +139,40 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
}
}
- private void deleteDraftChange(PatchSet.Id patchSetId)
+ private void deleteDraftChange(Change change)
throws OrmException, IOException, ResourceNotFoundException {
try {
- changeUtil.deleteDraftChange(patchSetId);
+ changeUtil.deleteDraftChange(change);
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
}
}
private PatchSetInfo previousPatchSetInfo(PatchSet.Id patchSetId)
- throws ResourceNotFoundException {
+ throws OrmException {
try {
return patchSetInfoFactory.get(dbProvider.get(),
new PatchSet.Id(patchSetId.getParentKey(),
patchSetId.get() - 1));
} catch (PatchSetInfoNotAvailableException e) {
- throw new ResourceNotFoundException(e.getMessage());
+ throw new OrmException(e);
}
}
- private static void updateChange(final ReviewDb db,
- final Change change, final PatchSetInfo psInfo)
- throws OrmException {
- db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
- @Override
- public Change update(Change c) {
- if (psInfo != null) {
- c.setCurrentPatchSet(psInfo);
- }
- ChangeUtil.updated(c);
- return c;
- }
- });
+ private void updateChange(final ReviewDb db,
+ Change change, final PatchSetInfo psInfo)
+ throws OrmException, IOException {
+ change = db.changes().atomicUpdate(change.getId(),
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change c) {
+ if (psInfo != null) {
+ c.setCurrentPatchSet(psInfo);
+ }
+ ChangeUtil.updated(c);
+ return c;
+ }
+ });
+ indexer.index(db, change);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index e64d3f46ea..d9512eb5ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.change;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
@@ -34,7 +35,6 @@ import com.google.gerrit.server.change.DeleteReviewer.Input;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
index bcd8902008..006b1ecf0a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
@@ -24,14 +24,14 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.TypeLiteral;
-public class DraftResource implements RestResource {
- public static final TypeLiteral<RestView<DraftResource>> DRAFT_KIND =
- new TypeLiteral<RestView<DraftResource>>() {};
+public class DraftCommentResource implements RestResource {
+ public static final TypeLiteral<RestView<DraftCommentResource>> DRAFT_COMMENT_KIND =
+ new TypeLiteral<RestView<DraftCommentResource>>() {};
private final RevisionResource rev;
private final PatchLineComment comment;
- DraftResource(RevisionResource rev, PatchLineComment c) {
+ public DraftCommentResource(RevisionResource rev, PatchLineComment c) {
this.rev = rev;
this.comment = c;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftComments.java
index 322faea778..7edd679d94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Drafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftComments.java
@@ -23,51 +23,53 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
-class Drafts implements ChildCollection<RevisionResource, DraftResource> {
- private final DynamicMap<RestView<DraftResource>> views;
+public class DraftComments implements ChildCollection<RevisionResource, DraftCommentResource> {
+ private final DynamicMap<RestView<DraftCommentResource>> views;
private final Provider<CurrentUser> user;
- private final ListDrafts list;
+ private final ListDraftComments list;
private final Provider<ReviewDb> dbProvider;
+ private final PatchLineCommentsUtil plcUtil;
@Inject
- Drafts(DynamicMap<RestView<DraftResource>> views,
+ DraftComments(DynamicMap<RestView<DraftCommentResource>> views,
Provider<CurrentUser> user,
- ListDrafts list,
- Provider<ReviewDb> dbProvider) {
+ ListDraftComments list,
+ Provider<ReviewDb> dbProvider,
+ PatchLineCommentsUtil plcUtil) {
this.views = views;
this.user = user;
this.list = list;
this.dbProvider = dbProvider;
+ this.plcUtil = plcUtil;
}
@Override
- public DynamicMap<RestView<DraftResource>> views() {
+ public DynamicMap<RestView<DraftCommentResource>> views() {
return views;
}
@Override
- public RestView<RevisionResource> list() throws AuthException {
+ public ListDraftComments list() throws AuthException {
checkIdentifiedUser();
return list;
}
@Override
- public DraftResource parse(RevisionResource rev, IdString id)
+ public DraftCommentResource parse(RevisionResource rev, IdString id)
throws ResourceNotFoundException, OrmException, AuthException {
checkIdentifiedUser();
String uuid = id.get();
- for (PatchLineComment c : dbProvider.get().patchComments()
- .draftByPatchSetAuthor(
- rev.getPatchSet().getId(),
- rev.getAccountId())) {
+ for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(dbProvider.get(),
+ rev.getPatchSet().getId(), rev.getAccountId(), rev.getNotes())) {
if (uuid.equals(c.getKey().get())) {
- return new DraftResource(rev, c);
+ return new DraftCommentResource(rev, c);
}
}
throw new ResourceNotFoundException(id);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
deleted file mode 100644
index af8162746f..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
+++ /dev/null
@@ -1,102 +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.change;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.EditMessage.Input;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.util.TimeUtil;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.PersonIdent;
-
-import java.io.IOException;
-
-@Singleton
-class EditMessage implements RestModifyView<RevisionResource, Input>,
- UiAction<RevisionResource> {
- private final ChangeUtil changeUtil;
- private final PersonIdent myIdent;
- private final ChangeJson json;
-
- static class Input {
- @DefaultInput
- String message;
- }
-
- @Inject
- EditMessage(ChangeUtil changeUtil,
- @GerritPersonIdent PersonIdent myIdent,
- ChangeJson json) {
- this.changeUtil = changeUtil;
- this.myIdent = myIdent;
- this.json = json;
- }
-
- @Override
- public ChangeInfo apply(RevisionResource rsrc, Input input)
- throws BadRequestException, ResourceConflictException,
- ResourceNotFoundException, EmailException, OrmException, IOException {
- if (Strings.isNullOrEmpty(input.message)) {
- throw new BadRequestException("message must be non-empty");
- } else if (!rsrc.getPatchSet().getId()
- .equals(rsrc.getChange().currentPatchSetId())) {
- throw new ResourceConflictException(String.format(
- "revision %s is not current revision",
- rsrc.getPatchSet().getRevision().get()));
- }
- try {
- return json.format(changeUtil.editCommitMessage(
- rsrc.getControl(),
- rsrc.getPatchSet().getId(),
- input.message,
- new PersonIdent(myIdent, TimeUtil.nowTs())));
- } catch (InvalidChangeOperationException e) {
- throw new BadRequestException(e.getMessage());
- } catch (NoSuchChangeException e) {
- throw new ResourceNotFoundException();
- } catch (MissingObjectException | IncorrectObjectTypeException
- | PatchSetInfoNotAvailableException e) {
- throw new ResourceConflictException(e.getMessage());
- }
- }
-
- @Override
- public UiAction.Description getDescription(RevisionResource resource) {
- PatchSet.Id current = resource.getChange().currentPatchSetId();
- return new UiAction.Description()
- .setLabel("Edit commit message")
- .setVisible(resource.getChange().getStatus().isOpen()
- && resource.getPatchSet().getId().equals(current)
- && resource.getControl().canAddPatchSet());
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index bc38039f4e..6330e3425d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -32,6 +32,7 @@ import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
+import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.assistedinject.Assisted;
@@ -152,7 +153,7 @@ public class EmailReviewComments implements Runnable, RequestContext {
@Override
public CurrentUser getCurrentUser() {
- return null;
+ throw new OutOfScopeException("No user on email thread");
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
new file mode 100644
index 0000000000..a8e1793fee
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -0,0 +1,144 @@
+// 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.change;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.gerrit.common.data.PatchScript.FileMode;
+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;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+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 java.io.IOException;
+import java.io.OutputStream;
+
+@Singleton
+public class FileContentUtil {
+ public static final String TEXT_X_GERRIT_COMMIT_MESSAGE = "text/x-gerrit-commit-message";
+ private static final String X_GIT_SYMLINK = "x-git/symlink";
+ private static final String X_GIT_GITLINK = "x-git/gitlink";
+ private static final int MAX_SIZE = 5 << 20;
+
+ private final GitRepositoryManager repoManager;
+ private final FileTypeRegistry registry;
+
+ @Inject
+ FileContentUtil(GitRepositoryManager repoManager,
+ FileTypeRegistry ftr) {
+ this.repoManager = repoManager;
+ this.registry = ftr;
+ }
+
+ public BinaryResult getContent(ProjectState project, ObjectId revstr,
+ String path) throws ResourceNotFoundException, IOException {
+ try (Repository repo = openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ RevCommit commit = rw.parseCommit(revstr);
+ ObjectReader reader = rw.getObjectReader();
+ TreeWalk tw = TreeWalk.forPath(reader, path, commit.getTree());
+ if (tw == null) {
+ throw new ResourceNotFoundException();
+ }
+
+ org.eclipse.jgit.lib.FileMode mode = tw.getFileMode(0);
+ ObjectId id = tw.getObjectId(0);
+ if (mode == org.eclipse.jgit.lib.FileMode.GITLINK) {
+ return BinaryResult.create(id.name())
+ .setContentType(X_GIT_GITLINK)
+ .base64();
+ }
+
+ final ObjectLoader obj = repo.open(id, OBJ_BLOB);
+ byte[] raw;
+ try {
+ raw = obj.getCachedBytes(MAX_SIZE);
+ } catch (LargeObjectException e) {
+ raw = null;
+ }
+
+ BinaryResult result;
+ if (raw != null) {
+ result = BinaryResult.create(raw);
+ } else {
+ result = asBinaryResult(obj);
+ }
+
+ String type;
+ if (mode == org.eclipse.jgit.lib.FileMode.SYMLINK) {
+ type = X_GIT_SYMLINK;
+ } else {
+ type = registry.getMimeType(path, raw).toString();
+ type = resolveContentType(project, path, FileMode.FILE, type);
+ }
+ return result.setContentType(type).base64();
+ }
+ }
+
+ private static BinaryResult asBinaryResult(final ObjectLoader obj) {
+ BinaryResult result = new BinaryResult() {
+ @Override
+ public void writeTo(OutputStream os) throws IOException {
+ obj.copyTo(os);
+ }
+ };
+ result.setContentLength(obj.getSize());
+ return result;
+ }
+
+ public static String resolveContentType(ProjectState project, String path,
+ FileMode fileMode, String mimeType) {
+ switch (fileMode) {
+ case FILE:
+ if (Patch.COMMIT_MSG.equals(path)) {
+ return TEXT_X_GERRIT_COMMIT_MESSAGE;
+ }
+ if (project != null) {
+ for (ProjectState p : project.tree()) {
+ String t = p.getConfig().getMimeTypes().getMimeType(path);
+ if (t != null) {
+ return t;
+ }
+ }
+ }
+ return mimeType;
+ case GITLINK:
+ return X_GIT_GITLINK;
+ case SYMLINK:
+ return X_GIT_SYMLINK;
+ default:
+ throw new IllegalStateException("file mode: " + fileMode);
+ }
+ }
+
+ private Repository openRepository(ProjectState project)
+ throws RepositoryNotFoundException, IOException {
+ return repoManager.openRepository(project.getProject().getNameKey());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
index 6bb72367c3..6ae87a3faf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
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.RevId;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -44,15 +45,15 @@ public class FileInfoJson {
Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
- return toFileInfoMap(change, patchSet, null);
+ return toFileInfoMap(change, patchSet.getRevision(), null);
}
- Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet, @Nullable PatchSet base)
+ Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, @Nullable PatchSet base)
throws PatchListNotAvailableException {
ObjectId a = (base == null)
? null
: ObjectId.fromString(base.getRevision().get());
- ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
+ ObjectId b = ObjectId.fromString(revision.get());
PatchList list = patchListCache.get(
new PatchListKey(change.getProject(), a, b, Whitespace.IGNORE_NONE));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java
index 521e8c869a..1662237026 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java
@@ -27,7 +27,7 @@ public class FileResource implements RestResource {
private final RevisionResource rev;
private final Patch.Key key;
- FileResource(RevisionResource rev, String name) {
+ public FileResource(RevisionResource rev, String name) {
this.rev = rev;
this.key = new Patch.Key(rev.getPatchSet().getId(), name);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 0689aecda4..e19b6a91b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountPatchReview;
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.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -43,8 +44,11 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+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;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -53,6 +57,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -82,8 +87,7 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
}
@Override
- public FileResource parse(RevisionResource rev, IdString id)
- throws ResourceNotFoundException, OrmException, AuthException {
+ public FileResource parse(RevisionResource rev, IdString id) {
return new FileResource(rev, id.get());
}
@@ -96,6 +100,9 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
@Option(name = "--reviewed")
boolean reviewed;
+ @Option(name = "-q")
+ String query;
+
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
private final FileInfoJson fileInfoJson;
@@ -125,11 +132,13 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
@Override
public Response<?> apply(RevisionResource resource) throws AuthException,
- BadRequestException, ResourceNotFoundException, OrmException {
- if (base != null && reviewed) {
- throw new BadRequestException("cannot combine base and reviewed");
- } else if (reviewed) {
+ BadRequestException, ResourceNotFoundException, OrmException,
+ RepositoryNotFoundException, IOException {
+ checkOptions();
+ if (reviewed) {
return Response.ok(reviewed(resource));
+ } else if (query != null) {
+ return Response.ok(query(resource));
}
PatchSet basePatchSet = null;
@@ -141,7 +150,7 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
try {
Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
- resource.getPatchSet(),
+ resource.getPatchSet().getRevision(),
basePatchSet));
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
@@ -152,6 +161,45 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
}
}
+ private void checkOptions() throws BadRequestException {
+ int supplied = 0;
+ if (base != null) {
+ supplied++;
+ }
+ if (reviewed) {
+ supplied++;
+ }
+ if (query != null) {
+ supplied++;
+ }
+ if (supplied > 1) {
+ throw new BadRequestException("cannot combine base, reviewed, query");
+ }
+ }
+
+ private List<String> query(RevisionResource resource)
+ throws RepositoryNotFoundException, IOException {
+ Project.NameKey project = resource.getChange().getProject();
+ try (Repository git = gitManager.openRepository(project);
+ ObjectReader or = git.newObjectReader();
+ RevWalk rw = new RevWalk(or);
+ TreeWalk tw = new TreeWalk(or)) {
+ RevCommit c = rw.parseCommit(
+ ObjectId.fromString(resource.getPatchSet().getRevision().get()));
+
+ tw.addTree(c.getTree());
+ tw.setRecursive(true);
+ List<String> paths = new ArrayList<>();
+ while (tw.next() && paths.size() < 20) {
+ String s = tw.getPathString();
+ if (s.contains(query)) {
+ paths.add(s);
+ }
+ }
+ return paths;
+ }
+ }
+
private List<String> reviewed(RevisionResource resource)
throws AuthException, OrmException {
CurrentUser user = self.get();
@@ -208,77 +256,75 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
private List<String> copy(Set<String> paths, PatchSet.Id old,
RevisionResource resource, Account.Id userId) throws IOException,
PatchListNotAvailableException, OrmException {
- Repository git =
- gitManager.openRepository(resource.getChange().getProject());
- try {
- ObjectReader reader = git.newObjectReader();
- try {
- PatchList oldList = patchListCache.get(
- resource.getChange(),
- db.get().patchSets().get(old));
+ Project.NameKey project = resource.getChange().getProject();
+ try (Repository git = gitManager.openRepository(project);
+ ObjectReader reader = git.newObjectReader();
+ RevWalk rw = new RevWalk(reader);
+ TreeWalk tw = new TreeWalk(reader)) {
+ PatchList oldList = patchListCache.get(
+ resource.getChange(),
+ db.get().patchSets().get(old));
- PatchList curList = patchListCache.get(
- resource.getChange(),
- resource.getPatchSet());
+ PatchList curList = patchListCache.get(
+ resource.getChange(),
+ resource.getPatchSet());
- int sz = paths.size();
- List<AccountPatchReview> inserts = Lists.newArrayListWithCapacity(sz);
- List<String> pathList = Lists.newArrayListWithCapacity(sz);
+ int sz = paths.size();
+ List<AccountPatchReview> inserts = Lists.newArrayListWithCapacity(sz);
+ List<String> pathList = Lists.newArrayListWithCapacity(sz);
- RevWalk rw = new RevWalk(reader);
- TreeWalk tw = new TreeWalk(reader);
- tw.setFilter(PathFilterGroup.createFromStrings(paths));
- tw.setRecursive(true);
- int o = tw.addTree(rw.parseCommit(oldList.getNewId()).getTree());
- int c = tw.addTree(rw.parseCommit(curList.getNewId()).getTree());
-
- int op = -1;
- if (oldList.getOldId() != null) {
- op = tw.addTree(rw.parseTree(oldList.getOldId()));
- }
+ tw.setFilter(PathFilterGroup.createFromStrings(paths));
+ tw.setRecursive(true);
+ int o = tw.addTree(rw.parseCommit(oldList.getNewId()).getTree());
+ int c = tw.addTree(rw.parseCommit(curList.getNewId()).getTree());
- int cp = -1;
- if (curList.getOldId() != null) {
- cp = tw.addTree(rw.parseTree(curList.getOldId()));
- }
+ int op = -1;
+ if (oldList.getOldId() != null) {
+ op = tw.addTree(rw.parseTree(oldList.getOldId()));
+ }
- while (tw.next()) {
- String path = tw.getPathString();
- if (tw.getRawMode(o) != 0 && tw.getRawMode(c) != 0
- && tw.idEqual(o, c)
- && paths.contains(path)) {
- // File exists in previously reviewed oldList and in curList.
- // File content is identical.
- inserts.add(new AccountPatchReview(
- new Patch.Key(
- resource.getPatchSet().getId(),
- path),
- userId));
- pathList.add(path);
- } else if (op >= 0 && cp >= 0
- && tw.getRawMode(o) == 0 && tw.getRawMode(c) == 0
- && tw.getRawMode(op) != 0 && tw.getRawMode(cp) != 0
- && tw.idEqual(op, cp)
- && paths.contains(path)) {
- // File was deleted in previously reviewed oldList and curList.
- // File exists in ancestor of oldList and curList.
- // File content is identical in ancestors.
- inserts.add(new AccountPatchReview(
- new Patch.Key(
- resource.getPatchSet().getId(),
- path),
- userId));
- pathList.add(path);
- }
+ int cp = -1;
+ if (curList.getOldId() != null) {
+ cp = tw.addTree(rw.parseTree(curList.getOldId()));
+ }
+
+ while (tw.next()) {
+ String path = tw.getPathString();
+ if (tw.getRawMode(o) != 0 && tw.getRawMode(c) != 0
+ && tw.idEqual(o, c)
+ && paths.contains(path)) {
+ // File exists in previously reviewed oldList and in curList.
+ // File content is identical.
+ inserts.add(new AccountPatchReview(
+ new Patch.Key(
+ resource.getPatchSet().getId(),
+ path),
+ userId));
+ pathList.add(path);
+ } else if (op >= 0 && cp >= 0
+ && tw.getRawMode(o) == 0 && tw.getRawMode(c) == 0
+ && tw.getRawMode(op) != 0 && tw.getRawMode(cp) != 0
+ && tw.idEqual(op, cp)
+ && paths.contains(path)) {
+ // File was deleted in previously reviewed oldList and curList.
+ // File exists in ancestor of oldList and curList.
+ // File content is identical in ancestors.
+ inserts.add(new AccountPatchReview(
+ new Patch.Key(
+ resource.getPatchSet().getId(),
+ path),
+ userId));
+ pathList.add(path);
}
- db.get().accountPatchReviews().insert(inserts);
- return pathList;
- } finally {
- reader.close();
}
- } finally {
- git.close();
+ db.get().accountPatchReviews().insert(inserts);
+ return pathList;
}
}
+
+ public ListFiles setBase(String base) {
+ this.base = base;
+ return this;
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
index c50a6efca5..8b64c1b74e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
@@ -14,18 +14,15 @@
package com.google.gerrit.server.change;
-import com.google.gerrit.extensions.common.ListChangesOption;
-import com.google.gerrit.extensions.restapi.CacheControl;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import org.kohsuke.args4j.Option;
-import java.util.concurrent.TimeUnit;
-
public class GetChange implements RestReadView<ChangeResource> {
private final ChangeJson json;
@@ -46,15 +43,10 @@ public class GetChange implements RestReadView<ChangeResource> {
@Override
public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
- return cache(json.format(rsrc));
+ return Response.withMustRevalidate(json.format(rsrc));
}
Response<ChangeInfo> apply(RevisionResource rsrc) throws OrmException {
- return cache(json.format(rsrc));
- }
-
- private Response<ChangeInfo> cache(ChangeInfo res) {
- return Response.ok(res)
- .caching(CacheControl.PRIVATE(0, TimeUnit.SECONDS).setMustRevalidate());
+ return Response.withMustRevalidate(json.format(rsrc));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
index 27de91caad..ea84f5052f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
@@ -14,27 +14,24 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.AccountInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-class GetComment implements RestReadView<CommentResource> {
+public class GetComment implements RestReadView<CommentResource> {
- private final AccountInfo.Loader.Factory accountLoaderFactory;
+ private final CommentJson commentJson;
@Inject
- GetComment(AccountInfo.Loader.Factory accountLoaderFactory) {
- this.accountLoaderFactory = accountLoaderFactory;
+ GetComment(CommentJson commentJson) {
+ this.commentJson = commentJson;
}
@Override
public CommentInfo apply(CommentResource rsrc) throws OrmException {
- AccountInfo.Loader accountLoader = accountLoaderFactory.create(true);
- CommentInfo ci = new CommentInfo(rsrc.getComment(), accountLoader);
- accountLoader.fill();
- return ci;
+ return commentJson.format(rsrc.getComment());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
index 2cd948e7fc..296a262ef1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -16,20 +16,22 @@ package com.google.gerrit.server.change;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.restapi.CacheControl;
-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.patch.PatchSetInfoNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Singleton;
+
+import org.kohsuke.args4j.Option;
import java.util.concurrent.TimeUnit;
-@Singleton
public class GetCommit implements RestReadView<RevisionResource> {
private final ChangeJson json;
+ @Option(name = "--links", usage = "Add weblinks")
+ private boolean addLinks;
+
@Inject
GetCommit(ChangeJson json) {
this.json = json;
@@ -37,16 +39,17 @@ public class GetCommit implements RestReadView<RevisionResource> {
@Override
public Response<CommitInfo> apply(RevisionResource resource)
- throws ResourceNotFoundException, OrmException {
+ throws OrmException {
try {
Response<CommitInfo> r =
- Response.ok(json.toCommit(resource.getPatchSet()));
+ Response.ok(json.toCommit(resource.getPatchSet(), resource
+ .getChange().getProject(), addLinks));
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
}
return r;
} catch (PatchSetInfoNotAvailableException e) {
- throw new ResourceNotFoundException(e.getMessage());
+ throw new OrmException(e);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index 62458cd8e3..810a3a6358 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -17,69 +17,43 @@ package com.google.gerrit.server.change;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.reviewdb.client.Patch;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.ObjectLoader;
-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.lib.ObjectId;
import java.io.IOException;
-import java.io.OutputStream;
@Singleton
public class GetContent implements RestReadView<FileResource> {
- private final GitRepositoryManager repoManager;
+ private final FileContentUtil fileContentUtil;
+ private final ChangeUtil changeUtil;
@Inject
- GetContent(GitRepositoryManager repoManager) {
- this.repoManager = repoManager;
+ GetContent(FileContentUtil fileContentUtil,
+ ChangeUtil changeUtil) {
+ this.fileContentUtil = fileContentUtil;
+ this.changeUtil = changeUtil;
}
@Override
public BinaryResult apply(FileResource rsrc)
- throws ResourceNotFoundException, IOException {
- return apply(rsrc.getRevision().getControl().getProject().getNameKey(),
- rsrc.getRevision().getPatchSet().getRevision().get(),
- rsrc.getPatchKey().get());
- }
-
- public BinaryResult apply(Project.NameKey project, String revstr, String path)
- throws ResourceNotFoundException, IOException {
- Repository repo = repoManager.openRepository(project);
- try {
- RevWalk rw = new RevWalk(repo);
- try {
- RevCommit commit =
- rw.parseCommit(repo.resolve(revstr));
- TreeWalk tw =
- TreeWalk.forPath(rw.getObjectReader(), path,
- commit.getTree().getId());
- if (tw == null) {
- throw new ResourceNotFoundException();
- }
- try {
- final ObjectLoader object = repo.open(tw.getObjectId(0));
- @SuppressWarnings("resource")
- BinaryResult result = new BinaryResult() {
- @Override
- public void writeTo(OutputStream os) throws IOException {
- object.copyTo(os);
- }
- };
- return result.setContentLength(object.getSize()).base64();
- } finally {
- tw.close();
- }
- } finally {
- rw.close();
- }
- } finally {
- repo.close();
+ throws ResourceNotFoundException, IOException, NoSuchChangeException,
+ OrmException {
+ String path = rsrc.getPatchKey().get();
+ if (Patch.COMMIT_MSG.equals(path)) {
+ String msg = changeUtil.getMessage(rsrc.getRevision().getChange());
+ return BinaryResult.create(msg)
+ .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
+ .base64();
}
+ return fileContentUtil.getContent(
+ rsrc.getRevision().getControl().getProjectControl().getProjectState(),
+ ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get()),
+ path);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
index 514c12a1c8..509bbd4e77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.change;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
index 0f6818205a..8e3a5d142d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -16,12 +16,22 @@ package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkState;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.FluentIterable;
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.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
-import com.google.gerrit.common.data.PatchScript.FileMode;
+import com.google.gerrit.extensions.common.ChangeType;
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
+import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
+import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.CacheControl;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -32,10 +42,12 @@ import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
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.reviewdb.client.Project;
+import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.patch.PatchScriptFactory;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
@@ -53,13 +65,26 @@ import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
+import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class GetDiff implements RestReadView<FileResource> {
+ private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
+ Maps.immutableEnumMap(
+ new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
+ .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
+ .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
+ .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
+ .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
+ .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
+ .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
+ .build());
+
private final ProjectCache projectCache;
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
private final Revisions revisions;
+ private final WebLinks webLinks;
@Option(name = "--base", metaVar = "REVISION")
String base;
@@ -73,23 +98,29 @@ public class GetDiff implements RestReadView<FileResource> {
@Option(name = "--intraline")
boolean intraline;
+ @Option(name = "--weblinks-only")
+ boolean webLinksOnly;
+
@Inject
GetDiff(ProjectCache projectCache,
PatchScriptFactory.Factory patchScriptFactoryFactory,
- Revisions revisions) {
+ Revisions revisions,
+ WebLinks webLinks) {
this.projectCache = projectCache;
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
this.revisions = revisions;
+ this.webLinks = webLinks;
}
@Override
- public Response<Result> apply(FileResource resource)
- throws ResourceConflictException, ResourceNotFoundException, OrmException {
- PatchSet.Id basePatchSet = null;
+ public Response<DiffInfo> apply(FileResource resource)
+ throws ResourceConflictException, ResourceNotFoundException,
+ OrmException, AuthException, InvalidChangeOperationException, IOException {
+ PatchSet basePatchSet = null;
if (base != null) {
RevisionResource baseResource = revisions.parse(
resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
- basePatchSet = baseResource.getPatchSet().getId();
+ basePatchSet = baseResource.getPatchSet();
}
AccountDiffPreference prefs = new AccountDiffPreference(new Account.Id(0));
prefs.setIgnoreWhitespace(ignoreWhitespace.whitespace);
@@ -100,7 +131,7 @@ public class GetDiff implements RestReadView<FileResource> {
PatchScriptFactory psf = patchScriptFactoryFactory.create(
resource.getRevision().getControl(),
resource.getPatchKey().getFileName(),
- basePatchSet,
+ basePatchSet != null ? basePatchSet.getId() : null,
resource.getPatchKey().getParentKey(),
prefs);
psf.setLoadHistory(false);
@@ -114,9 +145,9 @@ public class GetDiff implements RestReadView<FileResource> {
content.addCommon(edit.getBeginA());
checkState(content.nextA == edit.getBeginA(),
- "nextA = %d; want %d", content.nextA, edit.getBeginA());
+ "nextA = %s; want %s", content.nextA, edit.getBeginA());
checkState(content.nextB == edit.getBeginB(),
- "nextB = %d; want %d", content.nextB, edit.getBeginB());
+ "nextB = %s; want %s", content.nextB, edit.getBeginB());
switch (edit.getType()) {
case DELETE:
case INSERT:
@@ -136,37 +167,75 @@ public class GetDiff implements RestReadView<FileResource> {
ProjectState state =
projectCache.get(resource.getRevision().getChange().getProject());
- Result result = new Result();
- if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
- result.metaA = new FileMeta();
- result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
- setContentType(result.metaA, state, ps.getFileModeA(), ps.getMimeTypeA());
- result.metaA.lines = ps.getA().size();
- }
+ DiffInfo result = new DiffInfo();
+ // TODO referring to the parent commit by refs/changes/12/60012/1^1
+ // will likely not work for inline edits
+ String revA = basePatchSet != null
+ ? basePatchSet.getRefName()
+ : resource.getRevision().getPatchSet().getRefName() + "^1";
+ String revB = resource.getRevision().getEdit().isPresent()
+ ? resource.getRevision().getEdit().get().getRefName()
+ : resource.getRevision().getPatchSet().getRefName();
+
+ FluentIterable<DiffWebLinkInfo> links =
+ webLinks.getDiffLinks(state.getProject().getName(),
+ resource.getPatchKey().getParentKey().getParentKey().get(),
+ basePatchSet != null ? basePatchSet.getId().get() : null,
+ revA,
+ MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
+ resource.getPatchKey().getParentKey().get(),
+ revB,
+ ps.getNewName());
+ result.webLinks = links.isEmpty() ? null : links.toList();
+
+ if (!webLinksOnly) {
+ if (ps.isBinary()) {
+ result.binary = true;
+ }
+ if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
+ result.metaA = new FileMeta();
+ result.metaA.name = MoreObjects.firstNonNull(ps.getOldName(),
+ ps.getNewName());
+ result.metaA.contentType = FileContentUtil.resolveContentType(
+ state, result.metaA.name, ps.getFileModeA(), ps.getMimeTypeA());
+ result.metaA.lines = ps.getA().size();
+ result.metaA.webLinks =
+ getFileWebLinks(state.getProject(), revA, result.metaA.name);
+ }
- if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
- result.metaB = new FileMeta();
- result.metaB.name = ps.getNewName();
- setContentType(result.metaB, state, ps.getFileModeB(), ps.getMimeTypeB());
- result.metaB.lines = ps.getB().size();
- }
+ if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
+ result.metaB = new FileMeta();
+ result.metaB.name = ps.getNewName();
+ result.metaB.contentType = FileContentUtil.resolveContentType(
+ state, result.metaB.name, ps.getFileModeB(), ps.getMimeTypeB());
+ result.metaB.lines = ps.getB().size();
+ result.metaB.webLinks =
+ getFileWebLinks(state.getProject(), revB, result.metaB.name);
+ }
+
+ if (intraline) {
+ if (ps.hasIntralineTimeout()) {
+ result.intralineStatus = IntraLineStatus.TIMEOUT;
+ } else if (ps.hasIntralineFailure()) {
+ result.intralineStatus = IntraLineStatus.FAILURE;
+ } else {
+ result.intralineStatus = IntraLineStatus.OK;
+ }
+ }
- if (intraline) {
- if (ps.hasIntralineTimeout()) {
- result.intralineStatus = IntraLineStatus.TIMEOUT;
- } else if (ps.hasIntralineFailure()) {
- result.intralineStatus = IntraLineStatus.FAILURE;
- } else {
- result.intralineStatus = IntraLineStatus.OK;
+ result.changeType = CHANGE_TYPE.get(ps.getChangeType());
+ if (result.changeType == null) {
+ throw new IllegalStateException(
+ "unknown change type: " + ps.getChangeType());
}
- }
- result.changeType = ps.getChangeType();
- if (ps.getPatchHeader().size() > 0) {
- result.diffHeader = ps.getPatchHeader();
+ if (ps.getPatchHeader().size() > 0) {
+ result.diffHeader = ps.getPatchHeader();
+ }
+ result.content = content.lines;
}
- result.content = content.lines;
- Response<Result> r = Response.ok(result);
+
+ Response<DiffInfo> r = Response.ok(result);
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
}
@@ -178,53 +247,16 @@ public class GetDiff implements RestReadView<FileResource> {
}
}
- static class Result {
- FileMeta metaA;
- FileMeta metaB;
- IntraLineStatus intralineStatus;
- ChangeType changeType;
- List<String> diffHeader;
- List<ContentEntry> content;
- }
-
- static class FileMeta {
- String name;
- String contentType;
- Integer lines;
- }
-
- private void setContentType(FileMeta meta, ProjectState project,
- FileMode fileMode, String mimeType) {
- switch (fileMode) {
- case FILE:
- if (Patch.COMMIT_MSG.equals(meta.name)) {
- mimeType = "text/x-gerrit-commit-message";
- } else if (project != null) {
- for (ProjectState p : project.tree()) {
- String t = p.getConfig().getMimeTypes().getMimeType(meta.name);
- if (t != null) {
- mimeType = t;
- break;
- }
- }
- }
- meta.contentType = mimeType;
- break;
- case GITLINK:
- meta.contentType = "x-git/gitlink";
- break;
- case SYMLINK:
- meta.contentType = "x-git/symlink";
- break;
- default:
- throw new IllegalStateException("file mode: " + fileMode);
- }
+ private List<WebLinkInfo> getFileWebLinks(Project project, String rev,
+ String file) {
+ FluentIterable<WebLinkInfo> links =
+ webLinks.getFileLinks(project.getName(), rev, file);
+ return links.isEmpty() ? null : links.toList();
}
- enum IntraLineStatus {
- OK,
- TIMEOUT,
- FAILURE
+ public GetDiff setBase(String base) {
+ this.base = base;
+ return this;
}
private static class Content {
@@ -310,11 +342,13 @@ public class GetDiff implements RestReadView<FileResource> {
int lastB = 0;
for (Edit edit : internalEdit) {
if (edit.getBeginA() != edit.getEndA()) {
- e.editA.add(ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
+ e.editA.add(ImmutableList.of(
+ edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
lastA = edit.getEndA();
}
if (edit.getBeginB() != edit.getEndB()) {
- e.editB.add(ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
+ e.editB.add(ImmutableList.of(
+ edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
lastB = edit.getEndB();
}
}
@@ -341,28 +375,6 @@ public class GetDiff implements RestReadView<FileResource> {
}
}
- static final class ContentEntry {
- // Common lines to both sides.
- List<String> ab;
- // Lines of a.
- List<String> a;
- // Lines of b.
- List<String> b;
-
- // A list of changed sections of the corresponding line list.
- // Each entry is a character <offset, length> pair. The offset is from the
- // beginning of the first line in the list. Also, the offset includes an
- // implied trailing newline character for each line.
- List<List<Integer>> editA;
- List<List<Integer>> editB;
-
- // a and b are actually common with this whitespace ignore setting.
- Boolean common;
-
- // Number of lines to skip on both sides.
- Integer skip;
- }
-
public static class ContextOptionHandler extends OptionHandler<Short> {
public ContextOptionHandler(
CmdLineParser parser, OptionDef option, Setter<Short> setter) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java
index 12c50ae603..a13ecdfb6a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraftComment.java
@@ -14,27 +14,24 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.AccountInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-class GetDraft implements RestReadView<DraftResource> {
+public class GetDraftComment implements RestReadView<DraftCommentResource> {
- private final AccountInfo.Loader.Factory accountLoaderFactory;
+ private final CommentJson commentJson;
@Inject
- GetDraft(AccountInfo.Loader.Factory accountLoaderFactory) {
- this.accountLoaderFactory = accountLoaderFactory;
+ GetDraftComment(CommentJson commentJson) {
+ this.commentJson = commentJson;
}
@Override
- public CommentInfo apply(DraftResource rsrc) throws OrmException {
- AccountInfo.Loader accountLoader = accountLoaderFactory.create(true);
- CommentInfo ci = new CommentInfo(rsrc.getComment(), accountLoader);
- accountLoader.fill();
- return ci;
+ public CommentInfo apply(DraftCommentResource rsrc) throws OrmException {
+ return commentJson.format(rsrc.getComment());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetHashtags.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetHashtags.java
new file mode 100644
index 0000000000..4846c0b709
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetHashtags.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2014 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.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.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+@Singleton
+public class GetHashtags implements RestReadView<ChangeResource> {
+ @Override
+ public Response<? extends Set<String>> apply(ChangeResource req)
+ throws AuthException, OrmException, IOException, BadRequestException {
+
+ ChangeControl control = req.getControl();
+ ChangeNotes notes = control.getNotes().load();
+ Set<String> hashtags = notes.getHashtags();
+ if (hashtags == null) {
+ hashtags = Collections.emptySet();
+ }
+ return Response.ok(hashtags);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
index 4e40d629b0..afd11569ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
@@ -94,10 +94,11 @@ public class GetPatch implements RestReadView<RevisionResource> {
private void format(OutputStream out) throws IOException {
out.write(formatEmailHeader(commit).getBytes(UTF_8));
- DiffFormatter fmt = new DiffFormatter(out);
- fmt.setRepository(repo);
- fmt.format(base.getTree(), commit.getTree());
- fmt.flush();
+ try (DiffFormatter fmt = new DiffFormatter(out)) {
+ fmt.setRepository(repo);
+ fmt.format(base.getTree(), commit.getTree());
+ fmt.flush();
+ }
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
index b58572cdb6..6cdae44bbc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
@@ -21,15 +21,17 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GitPerson;
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.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
@@ -39,7 +41,6 @@ import com.google.inject.Singleton;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -50,7 +51,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
@@ -64,36 +64,33 @@ public class GetRelated implements RestReadView<RevisionResource> {
private final GitRepositoryManager gitMgr;
private final Provider<ReviewDb> dbProvider;
+ private final Provider<InternalChangeQuery> queryProvider;
@Inject
- GetRelated(GitRepositoryManager gitMgr, Provider<ReviewDb> db) {
+ GetRelated(GitRepositoryManager gitMgr,
+ Provider<ReviewDb> db,
+ Provider<InternalChangeQuery> queryProvider) {
this.gitMgr = gitMgr;
this.dbProvider = db;
+ this.queryProvider = queryProvider;
}
@Override
public RelatedInfo apply(RevisionResource rsrc)
throws RepositoryNotFoundException, IOException, OrmException {
- Repository git = gitMgr.openRepository(rsrc.getChange().getProject());
- try {
+ try (Repository git = gitMgr.openRepository(rsrc.getChange().getProject());
+ RevWalk rw = new RevWalk(git)) {
Ref ref = git.getRef(rsrc.getChange().getDest().get());
- RevWalk rw = new RevWalk(git);
- try {
- RelatedInfo info = new RelatedInfo();
- info.changes = walk(rsrc, rw, ref);
- return info;
- } finally {
- rw.close();
- }
- } finally {
- git.close();
+ RelatedInfo info = new RelatedInfo();
+ info.changes = walk(rsrc, rw, ref);
+ return info;
}
}
private List<ChangeAndCommit> walk(RevisionResource rsrc, RevWalk rw, Ref ref)
throws OrmException, IOException {
- Map<Change.Id, Change> changes = allOpenChanges(rsrc);
- Map<PatchSet.Id, PatchSet> patchSets = allPatchSets(changes.keySet());
+ Map<Change.Id, ChangeData> changes = allOpenChanges(rsrc);
+ Map<PatchSet.Id, PatchSet> patchSets = allPatchSets(rsrc, changes.values());
Map<String, PatchSet> commits = Maps.newHashMap();
for (PatchSet p : patchSets.values()) {
@@ -119,7 +116,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
PatchSet p = commits.get(c.name());
Change g = null;
if (p != null) {
- g = changes.get(p.getId().getParentKey());
+ g = changes.get(p.getId().getParentKey()).change();
added.add(p.getId().getParentKey());
}
parents.add(new ChangeAndCommit(g, p, c));
@@ -129,42 +126,37 @@ public class GetRelated implements RestReadView<RevisionResource> {
if (list.size() == 1) {
ChangeAndCommit r = list.get(0);
- if (r._changeNumber != null && r._revisionNumber != null
- && r._changeNumber == rsrc.getChange().getChangeId()
- && r._revisionNumber == rsrc.getPatchSet().getPatchSetId()) {
+ if (r.commit != null && r.commit.commit.equals(rsrc.getPatchSet().getRevision().get())) {
return Collections.emptyList();
}
}
return list;
}
- private Map<Change.Id, Change> allOpenChanges(RevisionResource rsrc)
+ private Map<Change.Id, ChangeData> allOpenChanges(RevisionResource rsrc)
throws OrmException {
- ReviewDb db = dbProvider.get();
- return db.changes().toMap(
- db.changes().byBranchOpenAll(rsrc.getChange().getDest()));
+ return ChangeData.asMap(
+ queryProvider.get().byBranchOpen(rsrc.getChange().getDest()));
}
- private Map<PatchSet.Id, PatchSet> allPatchSets(Collection<Change.Id> ids)
- throws OrmException {
- int n = ids.size();
- ReviewDb db = dbProvider.get();
- List<ResultSet<PatchSet>> t = Lists.newArrayListWithCapacity(n);
- for (Change.Id id : ids) {
- t.add(db.patchSets().byChange(id));
- }
-
- Map<PatchSet.Id, PatchSet> r = Maps.newHashMapWithExpectedSize(n * 2);
- for (ResultSet<PatchSet> rs : t) {
- for (PatchSet p : rs) {
+ private Map<PatchSet.Id, PatchSet> allPatchSets(RevisionResource rsrc,
+ Collection<ChangeData> cds) throws OrmException {
+ Map<PatchSet.Id, PatchSet> r =
+ Maps.newHashMapWithExpectedSize(cds.size() * 2);
+ for (ChangeData cd : cds) {
+ for (PatchSet p : cd.patches()) {
r.put(p.getId(), p);
}
}
+
+ if (rsrc.getEdit().isPresent()) {
+ r.put(rsrc.getPatchSet().getId(), rsrc.getPatchSet());
+ }
return r;
}
private List<ChangeAndCommit> children(RevisionResource rsrc, RevWalk rw,
- Map<Change.Id, Change> changes, Map<PatchSet.Id, PatchSet> patchSets,
+ Map<Change.Id, ChangeData> changes, Map<PatchSet.Id, PatchSet> patchSets,
Set<Change.Id> added)
throws OrmException, IOException {
// children is a map of parent commit name to PatchSet built on it.
@@ -191,9 +183,9 @@ public class GetRelated implements RestReadView<RevisionResource> {
}
for (Map.Entry<Change.Id, PatchSet.Id> e : matches.entrySet()) {
- Change change = changes.get(e.getKey());
+ ChangeData cd = changes.get(e.getKey());
PatchSet ps = patchSets.get(e.getValue());
- if (change == null || ps == null || !seenChange.add(e.getKey())) {
+ if (cd == null || ps == null || !seenChange.add(e.getKey())) {
continue;
}
@@ -204,7 +196,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
q.addFirst(ps.getRevision().get());
if (added.add(ps.getId().getParentKey())) {
rw.parseBody(c);
- graph.add(new ChangeAndCommit(change, ps, c));
+ graph.add(new ChangeAndCommit(cd.change(), ps, c));
}
}
}
@@ -214,13 +206,15 @@ public class GetRelated implements RestReadView<RevisionResource> {
}
private boolean isVisible(ProjectControl projectCtl,
- Map<Change.Id, Change> changes,
+ Map<Change.Id, ChangeData> changes,
Map<PatchSet.Id, PatchSet> patchSets,
PatchSet.Id psId) throws OrmException {
- Change c = changes.get(psId.getParentKey());
+ ChangeData cd = changes.get(psId.getParentKey());
PatchSet ps = patchSets.get(psId);
- if (c != null && ps != null) {
- ChangeControl ctl = projectCtl.controlFor(c);
+ if (cd != null && ps != null) {
+ // Related changes are in the same project, so reuse the existing
+ // ProjectControl.
+ ChangeControl ctl = projectCtl.controlFor(cd.change());
return ctl.isVisible(dbProvider.get())
&& ctl.isPatchVisible(ps, dbProvider.get());
}
@@ -272,15 +266,6 @@ public class GetRelated implements RestReadView<RevisionResource> {
return r;
}
- private static GitPerson toGitPerson(PersonIdent id) {
- GitPerson p = new GitPerson();
- p.name = id.getName();
- p.email = id.getEmailAddress();
- p.date = new Timestamp(id.getWhen().getTime());
- p.tz = id.getTimeZoneOffset();
- return p;
- }
-
public static class RelatedInfo {
public List<ChangeAndCommit> changes;
}
@@ -309,7 +294,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
p.commit = c.getParent(i).name();
commit.parents.add(p);
}
- commit.author = toGitPerson(c.getAuthorIdent());
+ commit.author = CommonConverters.toGitPerson(c.getAuthorIdent());
commit.subject = c.getShortMessage();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
index 9f98590106..f379d8314b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.change;
-import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index 632e6ac053..d58c8d2a5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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.
@@ -14,23 +14,22 @@
package com.google.gerrit.server.change;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
-import com.google.gerrit.server.git.WorkQueue.Executor;
-import com.google.inject.BindingAnnotation;
+@Singleton
+public class GetRevisionActions implements RestReadView<RevisionResource> {
+ private final ActionJson delegate;
-import java.lang.annotation.Retention;
-
-/**
- * Marker on the global {@link Executor} used by
- * {@link MergeabilityChecker}.
- */
-@Retention(RUNTIME)
-@BindingAnnotation
-public @interface MergeabilityChecksExecutor {
- public enum Priority {
- BACKGROUND, INTERACTIVE;
+ @Inject
+ GetRevisionActions(ActionJson delegate) {
+ this.delegate = delegate;
}
- Priority value();
+ @Override
+ public Object apply(RevisionResource rsrc) {
+ return Response.withMustRevalidate(delegate.format(rsrc));
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
index 3a2f7e70ee..074658893b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
@@ -19,7 +19,7 @@ import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.inject.Singleton;
@Singleton
-class GetTopic implements RestReadView<ChangeResource> {
+public class GetTopic implements RestReadView<ChangeResource> {
@Override
public String apply(ChangeResource rsrc) {
return Strings.nullToEmpty(rsrc.getChange().getTopic());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
new file mode 100644
index 0000000000..cd3b3b132a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
@@ -0,0 +1,156 @@
+// Copyright (C) 2014 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 static com.google.common.base.CharMatcher.WHITESPACE;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.validators.HashtagValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Singleton
+public class HashtagsUtil {
+ private static final CharMatcher LEADER = WHITESPACE.or(CharMatcher.is('#'));
+ private static final String PATTERN = "(?:\\s|\\A)#[\\p{L}[0-9]-_]+";
+
+ private final ChangeUpdate.Factory updateFactory;
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeIndexer indexer;
+ private final ChangeHooks hooks;
+ private final DynamicSet<HashtagValidationListener> hashtagValidationListeners;
+
+ @Inject
+ HashtagsUtil(ChangeUpdate.Factory updateFactory,
+ Provider<ReviewDb> dbProvider,
+ ChangeIndexer indexer,
+ ChangeHooks hooks,
+ DynamicSet<HashtagValidationListener> hashtagValidationListeners) {
+ this.updateFactory = updateFactory;
+ this.dbProvider = dbProvider;
+ this.indexer = indexer;
+ this.hooks = hooks;
+ this.hashtagValidationListeners = hashtagValidationListeners;
+ }
+
+ public static String cleanupHashtag(String hashtag) {
+ hashtag = LEADER.trimLeadingFrom(hashtag);
+ hashtag = WHITESPACE.trimTrailingFrom(hashtag);
+ return hashtag;
+ }
+
+ public static Set<String> extractTags(String input) {
+ Set<String> result = new HashSet<>();
+ if (!Strings.isNullOrEmpty(input)) {
+ Matcher matcher = Pattern.compile(PATTERN).matcher(input);
+ while (matcher.find()) {
+ result.add(cleanupHashtag(matcher.group()));
+ }
+ }
+ return result;
+ }
+
+ private Set<String> extractTags(Set<String> input)
+ throws IllegalArgumentException {
+ if (input == null) {
+ return Collections.emptySet();
+ } else {
+ HashSet<String> result = new HashSet<>();
+ for (String hashtag : input) {
+ if (hashtag.contains(",")) {
+ throw new IllegalArgumentException("Hashtags may not contain commas");
+ }
+ hashtag = cleanupHashtag(hashtag);
+ if (!hashtag.isEmpty()) {
+ result.add(hashtag);
+ }
+ }
+ return result;
+ }
+ }
+
+ public TreeSet<String> setHashtags(ChangeControl control,
+ HashtagsInput input, boolean runHooks, boolean index)
+ throws IllegalArgumentException, IOException,
+ ValidationException, AuthException, OrmException {
+ if (input == null
+ || (input.add == null && input.remove == null)) {
+ throw new IllegalArgumentException("Hashtags are required");
+ }
+
+ if (!control.canEditHashtags()) {
+ throw new AuthException("Editing hashtags not permitted");
+ }
+ ChangeUpdate update = updateFactory.create(control);
+ ChangeNotes notes = control.getNotes().load();
+
+ Set<String> existingHashtags = notes.getHashtags();
+ Set<String> updatedHashtags = new HashSet<>();
+ Set<String> toAdd = new HashSet<>(extractTags(input.add));
+ Set<String> toRemove = new HashSet<>(extractTags(input.remove));
+
+ for (HashtagValidationListener validator : hashtagValidationListeners) {
+ validator.validateHashtags(update.getChange(), toAdd, toRemove);
+ }
+
+ if (existingHashtags != null && !existingHashtags.isEmpty()) {
+ updatedHashtags.addAll(existingHashtags);
+ toAdd.removeAll(existingHashtags);
+ toRemove.retainAll(existingHashtags);
+ }
+
+ if (toAdd.size() > 0 || toRemove.size() > 0) {
+ updatedHashtags.addAll(toAdd);
+ updatedHashtags.removeAll(toRemove);
+ update.setHashtags(updatedHashtags);
+ update.commit();
+
+ if (index) {
+ indexer.index(dbProvider.get(), update.getChange());
+ }
+
+ if (runHooks) {
+ IdentifiedUser currentUser = ((IdentifiedUser) control.getCurrentUser());
+ hooks.doHashtagsChangedHook(
+ update.getChange(), currentUser.getAccount(),
+ toAdd, toRemove, updatedHashtags,
+ dbProvider.get());
+ }
+ }
+ return new TreeSet<>(updatedHashtags);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
index 410bcab940..7e9bb14747 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
@@ -19,6 +19,7 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
@@ -55,26 +56,19 @@ class IncludedIn implements RestReadView<ChangeResource> {
ChangeControl ctl = rsrc.getControl();
PatchSet ps =
db.get().patchSets().get(ctl.getChange().currentPatchSetId());
- Repository r =
- repoManager.openRepository(ctl.getProject().getNameKey());
- try {
- RevWalk rw = new RevWalk(r);
+ Project.NameKey project = ctl.getProject().getNameKey();
+ try (Repository r = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(r)) {
+ rw.setRetainBody(false);
+ RevCommit rev;
try {
- rw.setRetainBody(false);
- RevCommit rev;
- try {
- rev = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
- } catch (IncorrectObjectTypeException err) {
- throw new BadRequestException(err.getMessage());
- } catch (MissingObjectException err) {
- throw new ResourceConflictException(err.getMessage());
- }
- return new IncludedInInfo(IncludedInResolver.resolve(r, rw, rev));
- } finally {
- rw.close();
+ rev = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ } catch (IncorrectObjectTypeException err) {
+ throw new BadRequestException(err.getMessage());
+ } catch (MissingObjectException err) {
+ throw new ResourceConflictException(err.getMessage());
}
- } finally {
- r.close();
+ return new IncludedInInfo(IncludedInResolver.resolve(r, rw, rev));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
index 201ee149a6..fcac76d7c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -49,12 +49,26 @@ public class IncludedInResolver {
public static IncludedInDetail resolve(final Repository repo,
final RevWalk rw, final RevCommit commit) throws IOException {
- return new IncludedInResolver(repo, rw, commit).resolve();
+ RevFlag flag = newFlag(rw);
+ try {
+ return new IncludedInResolver(repo, rw, commit, flag).resolve();
+ } finally {
+ rw.disposeFlag(flag);
+ }
}
public static boolean includedInOne(final Repository repo, final RevWalk rw,
final RevCommit commit, final Collection<Ref> refs) throws IOException {
- return new IncludedInResolver(repo, rw, commit).includedInOne(refs);
+ RevFlag flag = newFlag(rw);
+ try {
+ return new IncludedInResolver(repo, rw, commit, flag).includedInOne(refs);
+ } finally {
+ rw.disposeFlag(flag);
+ }
+ }
+
+ private static RevFlag newFlag(RevWalk rw) {
+ return rw.newFlag("CONTAINS_TARGET");
}
private final Repository repo;
@@ -65,12 +79,12 @@ public class IncludedInResolver {
private Multimap<RevCommit, String> commitToRef;
private List<RevCommit> tipsByCommitTime;
- private IncludedInResolver(final Repository repo, final RevWalk rw,
- final RevCommit target) {
+ private IncludedInResolver(Repository repo, RevWalk rw, RevCommit target,
+ RevFlag containsTarget) {
this.repo = repo;
this.rw = rw;
this.target = target;
- this.containsTarget = rw.newFlag("CONTAINS_TARGET");
+ this.containsTarget = containsTarget;
}
private IncludedInDetail resolve() throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
index 6cf3e0029b..280d078d14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.change;
-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.RestModifyView;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -27,7 +25,6 @@ import com.google.inject.Singleton;
import java.io.IOException;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@Singleton
public class Index implements RestModifyView<ChangeResource, Input> {
public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java
index f4d7b496e7..b50e2439d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListComments.java
@@ -17,7 +17,6 @@ package com.google.gerrit.server.change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.PatchLineCommentsUtil;
-import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -25,14 +24,12 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
-class ListComments extends ListDrafts {
- private final PatchLineCommentsUtil plcUtil;
-
+public class ListComments extends ListDraftComments {
@Inject
- ListComments(Provider<ReviewDb> db, AccountInfo.Loader.Factory alf,
+ ListComments(Provider<ReviewDb> db,
+ CommentJson commentJson,
PatchLineCommentsUtil plcUtil) {
- super(db, alf);
- this.plcUtil = plcUtil;
+ super(db, commentJson, plcUtil);
}
@Override
@@ -40,6 +37,7 @@ class ListComments extends ListDrafts {
return true;
}
+ @Override
protected Iterable<PatchLineComment> listComments(RevisionResource rsrc)
throws OrmException {
ChangeNotes notes = rsrc.getNotes();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDraftComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDraftComments.java
new file mode 100644
index 0000000000..3375cba93e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDraftComments.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2012 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.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.util.List;
+import java.util.Map;
+
+@Singleton
+public class ListDraftComments implements RestReadView<RevisionResource> {
+ protected final Provider<ReviewDb> db;
+ protected CommentJson commentJson;
+ protected final PatchLineCommentsUtil plcUtil;
+
+ @Inject
+ ListDraftComments(Provider<ReviewDb> db,
+ CommentJson commentJson,
+ PatchLineCommentsUtil plcUtil) {
+ this.db = db;
+ this.commentJson = commentJson;
+ this.plcUtil = plcUtil;
+ }
+
+ protected Iterable<PatchLineComment> listComments(RevisionResource rsrc)
+ throws OrmException {
+ return plcUtil.draftByPatchSetAuthor(db.get(), rsrc.getPatchSet().getId(),
+ rsrc.getAccountId(), rsrc.getNotes());
+ }
+
+ protected boolean includeAuthorInfo() {
+ return false;
+ }
+
+ @Override
+ public Map<String, List<CommentInfo>> apply(RevisionResource rsrc)
+ throws OrmException {
+ return commentJson.format(listComments(rsrc), includeAuthorInfo());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
deleted file mode 100644
index bd3aa0447f..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (C) 2012 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 static com.google.common.base.Objects.firstNonNull;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.gerrit.common.changes.Side;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.account.AccountInfo;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-@Singleton
-class ListDrafts implements RestReadView<RevisionResource> {
- protected final Provider<ReviewDb> db;
- private final AccountInfo.Loader.Factory accountLoaderFactory;
-
- @Inject
- ListDrafts(Provider<ReviewDb> db, AccountInfo.Loader.Factory alf) {
- this.db = db;
- this.accountLoaderFactory = alf;
- }
-
- protected Iterable<PatchLineComment> listComments(RevisionResource rsrc)
- throws OrmException {
- return db.get().patchComments()
- .draftByPatchSetAuthor(
- rsrc.getPatchSet().getId(),
- rsrc.getAccountId());
- }
-
- protected boolean includeAuthorInfo() {
- return false;
- }
-
- @Override
- public Map<String, List<CommentInfo>> apply(RevisionResource rsrc)
- throws OrmException {
- Map<String, List<CommentInfo>> out = Maps.newTreeMap();
- AccountInfo.Loader accountLoader =
- includeAuthorInfo() ? accountLoaderFactory.create(true) : null;
- for (PatchLineComment c : listComments(rsrc)) {
- CommentInfo o = new CommentInfo(c, accountLoader);
- List<CommentInfo> list = out.get(o.path);
- if (list == null) {
- list = Lists.newArrayList();
- out.put(o.path, list);
- }
- o.path = null;
- list.add(o);
- }
- for (List<CommentInfo> list : out.values()) {
- Collections.sort(list, new Comparator<CommentInfo>() {
- @Override
- public int compare(CommentInfo a, CommentInfo b) {
- int c = firstNonNull(a.side, Side.REVISION).ordinal()
- - firstNonNull(b.side, Side.REVISION).ordinal();
- if (c == 0) {
- c = firstNonNull(a.line, 0) - firstNonNull(b.line, 0);
- }
- if (c == 0) {
- c = a.id.compareTo(b.id);
- }
- return c;
- }
- });
- }
- if (accountLoader != null) {
- accountLoader.fill();
- }
- return out;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java
new file mode 100644
index 0000000000..547a5009a2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 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.extensions.client.SubmitType;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+
+/** Cache for mergeability of commits into destination branches. */
+public interface MergeabilityCache {
+ public static class NotImplemented implements MergeabilityCache {
+ @Override
+ public boolean get(ObjectId commit, Ref intoRef, SubmitType submitType,
+ String mergeStrategy, Branch.NameKey dest, Repository repo,
+ ReviewDb db) {
+ throw new UnsupportedOperationException("Mergeability checking disabled");
+ }
+
+ @Override
+ public Boolean getIfPresent(ObjectId commit, Ref intoRef,
+ SubmitType submitType, String mergeStrategy) {
+ throw new UnsupportedOperationException("Mergeability checking disabled");
+ }
+ }
+
+ public boolean get(ObjectId commit, Ref intoRef, SubmitType submitType,
+ String mergeStrategy, Branch.NameKey dest, Repository repo, ReviewDb db);
+
+ public Boolean getIfPresent(ObjectId commit, Ref intoRef,
+ SubmitType submitType, String mergeStrategy);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
new file mode 100644
index 0000000000..a840651f55
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -0,0 +1,312 @@
+// Copyright (C) 2014 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 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.ioutil.BasicSerialization.readString;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
+import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
+import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+@Singleton
+public class MergeabilityCacheImpl implements MergeabilityCache {
+ private static final Logger log =
+ LoggerFactory.getLogger(MergeabilityCacheImpl.class);
+
+ private static final String CACHE_NAME = "mergeability";
+
+ public static final BiMap<SubmitType, Character> SUBMIT_TYPES = ImmutableBiMap.of(
+ SubmitType.FAST_FORWARD_ONLY, 'F',
+ SubmitType.MERGE_IF_NECESSARY, 'M',
+ SubmitType.REBASE_IF_NECESSARY, 'R',
+ SubmitType.MERGE_ALWAYS, 'A',
+ SubmitType.CHERRY_PICK, 'C');
+
+ static {
+ checkState(SUBMIT_TYPES.size() == SubmitType.values().length,
+ "SubmitType <-> char BiMap needs updating");
+ }
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ persist(CACHE_NAME, EntryKey.class, Boolean.class)
+ .maximumWeight(1 << 20)
+ .weigher(MergeabilityWeigher.class)
+ .loader(Loader.class);
+ bind(MergeabilityCache.class).to(MergeabilityCacheImpl.class);
+ }
+ };
+ }
+
+ public static ObjectId toId(Ref ref) {
+ return ref != null && ref.getObjectId() != null
+ ? ref.getObjectId()
+ : ObjectId.zeroId();
+ }
+
+ public static class EntryKey implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private ObjectId commit;
+ private ObjectId into;
+ private SubmitType submitType;
+ private String mergeStrategy;
+
+ // Only used for loading, not stored.
+ private transient LoadHelper load;
+
+ public EntryKey(ObjectId commit, ObjectId into, SubmitType submitType,
+ String mergeStrategy) {
+ this.commit = checkNotNull(commit, "commit");
+ this.into = checkNotNull(into, "into");
+ this.submitType = checkNotNull(submitType, "submitType");
+ this.mergeStrategy = checkNotNull(mergeStrategy, "mergeStrategy");
+ }
+
+ private EntryKey(ObjectId commit, ObjectId into, SubmitType submitType,
+ String mergeStrategy, Branch.NameKey dest, Repository repo,
+ ReviewDb db) {
+ this(commit, into, submitType, mergeStrategy);
+ load = new LoadHelper(dest, repo, db);
+ }
+
+ public ObjectId getCommit() {
+ return commit;
+ }
+
+ public ObjectId getInto() {
+ return into;
+ }
+
+ public SubmitType getSubmitType() {
+ return submitType;
+ }
+
+ public String getMergeStrategy() {
+ return mergeStrategy;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof EntryKey) {
+ EntryKey k = (EntryKey) o;
+ return commit.equals(k.commit)
+ && into.equals(k.into)
+ && submitType == k.submitType
+ && mergeStrategy.equals(k.mergeStrategy);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(commit, into, submitType, mergeStrategy);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("commit", commit.name())
+ .add("into", into.name())
+ .addValue(submitType)
+ .addValue(mergeStrategy)
+ .toString();
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeNotNull(out, commit);
+ writeNotNull(out, into);
+ Character c = SUBMIT_TYPES.get(submitType);
+ if (c == null) {
+ throw new IOException("Invalid submit type: " + submitType);
+ }
+ out.writeChar(c);
+ writeString(out, mergeStrategy);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException {
+ commit = readNotNull(in);
+ into = readNotNull(in);
+ char t = in.readChar();
+ submitType = SUBMIT_TYPES.inverse().get(t);
+ if (submitType == null) {
+ throw new IOException("Invalid submit type code: " + t);
+ }
+ mergeStrategy = readString(in);
+ }
+ }
+
+ private static class LoadHelper {
+ private final Branch.NameKey dest;
+ private final Repository repo;
+ private final ReviewDb db;
+
+ private LoadHelper(Branch.NameKey dest, Repository repo, ReviewDb db) {
+ this.dest = checkNotNull(dest, "dest");
+ this.repo = checkNotNull(repo, "repo");
+ this.db = checkNotNull(db, "db");
+ }
+ }
+
+ @Singleton
+ public static class Loader extends CacheLoader<EntryKey, Boolean> {
+ private final SubmitStrategyFactory submitStrategyFactory;
+
+ @Inject
+ Loader(SubmitStrategyFactory submitStrategyFactory) {
+ this.submitStrategyFactory = submitStrategyFactory;
+ }
+
+ @Override
+ public Boolean load(EntryKey key)
+ throws NoSuchProjectException, MergeException, IOException {
+ checkArgument(key.load != null, "Key cannot be loaded: %s", key);
+ if (key.into.equals(ObjectId.zeroId())) {
+ return true; // Assume yes on new branch.
+ }
+ try {
+ RefDatabase refDatabase = key.load.repo.getRefDatabase();
+ Iterable<Ref> refs = Iterables.concat(
+ refDatabase.getRefs(Constants.R_HEADS).values(),
+ refDatabase.getRefs(Constants.R_TAGS).values());
+ try (RevWalk rw = CodeReviewCommit.newRevWalk(key.load.repo)) {
+ RevFlag canMerge = rw.newFlag("CAN_MERGE");
+ CodeReviewCommit rev = parse(rw, key.commit);
+ rev.add(canMerge);
+ CodeReviewCommit tip = parse(rw, key.into);
+ Set<RevCommit> accepted = alreadyAccepted(rw, refs);
+ accepted.add(tip);
+ accepted.addAll(Arrays.asList(rev.getParents()));
+ return submitStrategyFactory.create(
+ key.submitType,
+ key.load.db,
+ key.load.repo,
+ rw,
+ null /*inserter*/,
+ canMerge,
+ accepted,
+ key.load.dest).dryRun(tip, rev);
+ }
+ } finally {
+ key.load = null;
+ }
+ }
+
+ private static Set<RevCommit> alreadyAccepted(RevWalk rw, Iterable<Ref> refs)
+ throws MissingObjectException, IOException {
+ Set<RevCommit> accepted = Sets.newHashSet();
+ for (Ref r : refs) {
+ try {
+ accepted.add(rw.parseCommit(r.getObjectId()));
+ } catch (IncorrectObjectTypeException nonCommit) {
+ // Not a commit? Skip over it.
+ }
+ }
+ return accepted;
+ }
+
+ private static CodeReviewCommit parse(RevWalk rw, ObjectId id)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return (CodeReviewCommit) rw.parseCommit(id);
+ }
+ }
+
+ public static class MergeabilityWeigher
+ implements Weigher<EntryKey, Boolean> {
+ @Override
+ public int weigh(EntryKey k, Boolean v) {
+ return 16 + 2 * (16 + 20) + 3 * 8 // Size of EntryKey, 64-bit JVM.
+ + 8; // Size of Boolean.
+ }
+ }
+
+ private final LoadingCache<EntryKey, Boolean> cache;
+
+ @Inject
+ MergeabilityCacheImpl(@Named(CACHE_NAME) LoadingCache<EntryKey, Boolean> cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public boolean get(ObjectId commit, Ref intoRef, SubmitType submitType,
+ String mergeStrategy, Branch.NameKey dest, Repository repo, ReviewDb db) {
+ ObjectId into = intoRef != null ? intoRef.getObjectId() : ObjectId.zeroId();
+ EntryKey key =
+ new EntryKey(commit, into, submitType, mergeStrategy, dest, repo, db);
+ try {
+ return cache.get(key);
+ } catch (ExecutionException e) {
+ log.error(String.format("Error checking mergeability of %s into %s (%s)",
+ key.commit.name(), key.into.name(), key.submitType.name()),
+ e.getCause());
+ return false;
+ }
+ }
+
+ @Override
+ public Boolean getIfPresent(ObjectId commit, Ref intoRef,
+ SubmitType submitType, String mergeStrategy) {
+ return cache.getIfPresent(
+ new EntryKey(commit, toId(intoRef), submitType, mergeStrategy));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java
deleted file mode 100644
index 6598238ac8..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java
+++ /dev/null
@@ -1,46 +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.change;
-
-import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Change;
-
-import java.util.Collection;
-import java.util.Set;
-
-import javax.inject.Singleton;
-
-@Singleton
-class MergeabilityCheckQueue {
- private final Set<Change.Id> pending = Sets.newHashSet();
- private final Set<Change.Id> forcePending = Sets.newHashSet();
-
- synchronized Set<Change> addAll(Collection<Change> changes, boolean force) {
- Set<Change> r = Sets.newLinkedHashSetWithExpectedSize(changes.size());
- for (Change c : changes) {
- if (force ? forcePending.add(c.getId()) : pending.add(c.getId())) {
- r.add(c);
- }
- }
- return r;
- }
-
- synchronized void updatingMergeabilityFlag(Change change, boolean force) {
- if (force) {
- forcePending.remove(change.getId());
- }
- pending.remove(change.getId());
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
deleted file mode 100644
index 0a6db2be2b..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
+++ /dev/null
@@ -1,373 +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.change;
-
-import com.google.common.base.Function;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-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.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
-import com.google.gerrit.server.change.Mergeable.MergeableInfo;
-import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.git.WorkQueue.Executor;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.util.RequestContext;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-
-public class MergeabilityChecker implements GitReferenceUpdatedListener {
- private static final Logger log = LoggerFactory
- .getLogger(MergeabilityChecker.class);
-
- private static final Function<Exception, IOException> MAPPER =
- new Function<Exception, IOException>() {
- @Override
- public IOException apply(Exception in) {
- if (in instanceof IOException) {
- return (IOException) in;
- } else if (in instanceof ExecutionException
- && in.getCause() instanceof IOException) {
- return (IOException) in.getCause();
- } else {
- return new IOException(in);
- }
- }
- };
-
- public class Check {
- private List<Change> changes;
- private List<Branch.NameKey> branches;
- private List<Project.NameKey> projects;
- private boolean force;
- private boolean reindex;
- private boolean interactive;
-
- private Check() {
- changes = Lists.newArrayListWithExpectedSize(1);
- branches = Lists.newArrayListWithExpectedSize(1);
- projects = Lists.newArrayListWithExpectedSize(1);
- interactive = true;
- }
-
- public Check addChange(Change change) {
- changes.add(change);
- return this;
- }
-
- public Check addBranch(Branch.NameKey branch) {
- branches.add(branch);
- interactive = false;
- return this;
- }
-
- public Check addProject(Project.NameKey project) {
- projects.add(project);
- interactive = false;
- return this;
- }
-
- /** Force reindexing regardless of whether mergeable flag was modified. */
- public Check reindex() {
- reindex = true;
- return this;
- }
-
- /** Force mergeability check even if change is not stale. */
- private Check force() {
- force = true;
- return this;
- }
-
- private ListeningExecutorService getExecutor() {
- return interactive ? interactiveExecutor : backgroundExecutor;
- }
-
- public CheckedFuture<?, IOException> runAsync() {
- final ListeningExecutorService executor = getExecutor();
- ListenableFuture<List<Change>> getChanges;
- if (branches.isEmpty() && projects.isEmpty()) {
- getChanges = Futures.immediateFuture(changes);
- } else {
- getChanges = executor.submit(
- new Callable<List<Change>>() {
- @Override
- public List<Change> call() throws OrmException {
- return getChanges();
- }
- });
- }
-
- return Futures.makeChecked(Futures.transform(getChanges,
- new AsyncFunction<List<Change>, List<Object>>() {
- @Override
- public ListenableFuture<List<Object>> apply(List<Change> changes) {
- List<ListenableFuture<?>> result =
- Lists.newArrayListWithCapacity(changes.size());
- for (final Change c : changes) {
- ListenableFuture<Boolean> b =
- executor.submit(new Task(c, force));
- if (reindex) {
- result.add(Futures.transform(
- b, new AsyncFunction<Boolean, Object>() {
- @SuppressWarnings("unchecked")
- @Override
- public ListenableFuture<Object> apply(
- Boolean indexUpdated) throws Exception {
- if (!indexUpdated) {
- return (ListenableFuture<Object>)
- indexer.indexAsync(c.getId());
- }
- return Futures.immediateFuture(null);
- }
- }));
- } else {
- result.add(b);
- }
- }
- return Futures.allAsList(result);
- }
- }), MAPPER);
- }
-
- public void run() throws IOException {
- try {
- runAsync().checkedGet();
- } catch (Exception e) {
- Throwables.propagateIfPossible(e, IOException.class);
- throw MAPPER.apply(e);
- }
- }
-
- private List<Change> getChanges() throws OrmException {
- ReviewDb db = schemaFactory.open();
- try {
- List<Change> results = Lists.newArrayList();
- results.addAll(changes);
- for (Project.NameKey p : projects) {
- Iterables.addAll(results, db.changes().byProjectOpenAll(p));
- }
- for (Branch.NameKey b : branches) {
- Iterables.addAll(results, db.changes().byBranchOpenAll(b));
- }
- return results;
- } catch (OrmException e) {
- log.error("Failed to fetch changes for mergeability check", e);
- throw e;
- } finally {
- db.close();
- }
- }
- }
-
- private final ThreadLocalRequestContext tl;
- private final SchemaFactory<ReviewDb> schemaFactory;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
- private final ChangeControl.GenericFactory changeControlFactory;
- private final Provider<Mergeable> mergeable;
- private final ChangeIndexer indexer;
- private final ListeningExecutorService backgroundExecutor;
- private final ListeningExecutorService interactiveExecutor;
- private final MergeabilityCheckQueue mergeabilityCheckQueue;
- private final MetaDataUpdate.Server metaDataUpdateFactory;
-
- @Inject
- public MergeabilityChecker(ThreadLocalRequestContext tl,
- SchemaFactory<ReviewDb> schemaFactory,
- IdentifiedUser.GenericFactory identifiedUserFactory,
- ChangeControl.GenericFactory changeControlFactory,
- Provider<Mergeable> mergeable, ChangeIndexer indexer,
- @MergeabilityChecksExecutor(Priority.BACKGROUND)
- Executor backgroundExecutor,
- @MergeabilityChecksExecutor(Priority.INTERACTIVE)
- Executor interactiveExecutor,
- MergeabilityCheckQueue mergeabilityCheckQueue,
- MetaDataUpdate.Server metaDataUpdateFactory) {
- this.tl = tl;
- this.schemaFactory = schemaFactory;
- this.identifiedUserFactory = identifiedUserFactory;
- this.changeControlFactory = changeControlFactory;
- this.mergeable = mergeable;
- this.indexer = indexer;
- this.backgroundExecutor =
- MoreExecutors.listeningDecorator(backgroundExecutor);
- this.interactiveExecutor =
- MoreExecutors.listeningDecorator(interactiveExecutor);
- this.mergeabilityCheckQueue = mergeabilityCheckQueue;
- this.metaDataUpdateFactory = metaDataUpdateFactory;
- }
-
- public Check newCheck() {
- return new Check();
- }
-
- @Override
- public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
- String ref = event.getRefName();
- if (ref.startsWith(Constants.R_HEADS) || ref.equals(RefNames.REFS_CONFIG)) {
- Branch.NameKey branch = new Branch.NameKey(
- new Project.NameKey(event.getProjectName()), ref);
- newCheck().addBranch(branch).runAsync();
- }
- if (ref.equals(RefNames.REFS_CONFIG)) {
- Project.NameKey p = new Project.NameKey(event.getProjectName());
- try {
- ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
- ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
- if (recheckMerges(oldCfg, newCfg)) {
- newCheck().addProject(p).force().runAsync();
- }
- } catch (ConfigInvalidException | IOException e) {
- String msg = "Failed to update mergeability flags for project " + p.get()
- + " on update of " + RefNames.REFS_CONFIG;
- log.error(msg, e);
- throw new RuntimeException(msg, e);
- }
- }
- }
-
- private boolean recheckMerges(ProjectConfig oldCfg, ProjectConfig newCfg) {
- if (oldCfg == null || newCfg == null) {
- return true;
- }
- return !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())
- || oldCfg.getProject().getUseContentMerge() != newCfg.getProject().getUseContentMerge()
- || (oldCfg.getRulesId() == null
- ? newCfg.getRulesId() != null
- : !oldCfg.getRulesId().equals(newCfg.getRulesId()));
- }
-
- private ProjectConfig parseConfig(Project.NameKey p, String idStr)
- throws IOException, ConfigInvalidException, RepositoryNotFoundException {
- ObjectId id = ObjectId.fromString(idStr);
- if (ObjectId.zeroId().equals(id)) {
- return null;
- }
- return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
- }
-
- private class Task implements Callable<Boolean> {
- private final Change change;
- private final boolean force;
-
- private ReviewDb reviewDb;
-
- Task(Change change, boolean force) {
- this.change = change;
- this.force = force;
- }
-
- @Override
- public String toString() {
- return "mergeability-check-change-" + change.getId().get() + "-project-"
- + change.getDest().getParentKey();
- }
-
- @Override
- public Boolean call() throws Exception {
- mergeabilityCheckQueue.updatingMergeabilityFlag(change, force);
-
- RequestContext context = new RequestContext() {
- @Override
- public CurrentUser getCurrentUser() {
- return identifiedUserFactory.create(change.getOwner());
- }
-
- @Override
- public Provider<ReviewDb> getReviewDbProvider() {
- return new Provider<ReviewDb>() {
- @Override
- public ReviewDb get() {
- if (reviewDb == null) {
- try {
- reviewDb = schemaFactory.open();
- } catch (OrmException e) {
- throw new ProvisionException("Cannot open ReviewDb", e);
- }
- }
- return reviewDb;
- }
- };
- }
- };
- RequestContext old = tl.setContext(context);
- ReviewDb db = context.getReviewDbProvider().get();
- try {
- PatchSet ps = db.patchSets().get(change.currentPatchSetId());
- if (ps == null) {
- // Cannot compute mergeability if current patch set is missing.
- return false;
- }
-
- Mergeable m = mergeable.get();
- m.setForce(force);
-
- ChangeControl control =
- changeControlFactory.controlFor(change, context.getCurrentUser());
- MergeableInfo info = m.apply(
- new RevisionResource(new ChangeResource(control), ps));
- return change.isMergeable() != info.mergeable;
- } catch (ResourceConflictException e) {
- // change is closed
- return false;
- } catch (Exception e) {
- log.error(String.format(
- "cannot update mergeability flag of change %d in project %s after update of %s",
- change.getId().get(),
- change.getDest().getParentKey(), change.getDest().get()), e);
- throw e;
- } finally {
- tl.setContext(old);
- if (reviewDb != null) {
- reviewDb.close();
- reviewDb = null;
- }
- }
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
deleted file mode 100644
index e5bcabe4bc..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
+++ /dev/null
@@ -1,57 +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.change;
-
-import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.lib.Config;
-
-/** Module providing the {@link MergeabilityChecksExecutor}. */
-public class MergeabilityChecksExecutorModule extends AbstractModule {
- @Override
- protected void configure() {
- }
-
- @Provides
- @Singleton
- @MergeabilityChecksExecutor(Priority.BACKGROUND)
- public WorkQueue.Executor createMergeabilityChecksExecutor(
- @GerritServerConfig Config config,
- WorkQueue queues) {
- int poolSize = config.getInt("changeMerge", null, "threadPoolSize", 1);
- return queues.createQueue(poolSize, "MergeabilityChecks-Background");
- }
-
- @Provides
- @Singleton
- @MergeabilityChecksExecutor(Priority.INTERACTIVE)
- public WorkQueue.Executor createMergeabilityChecksExecutor(
- @GerritServerConfig Config config,
- WorkQueue queues,
- @MergeabilityChecksExecutor(Priority.BACKGROUND)
- WorkQueue.Executor backgroundExecutor) {
- int poolSize =
- config.getInt("changeMerge", null, "interactiveThreadPoolSize", 1);
- if (poolSize <= 0) {
- return backgroundExecutor;
- }
- return queues.createQueue(poolSize, "MergeabilityChecks-Interactive");
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index b9daaf47ee..2653f1bd8f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -14,93 +14,75 @@
package com.google.gerrit.server.change;
-import com.google.common.collect.Sets;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+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.RestReadView;
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.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.git.BranchOrderSection;
-import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeException;
-import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
+import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
public class Mergeable implements RestReadView<RevisionResource> {
private static final Logger log = LoggerFactory.getLogger(Mergeable.class);
- public static class MergeableInfo {
- public SubmitType submitType;
- public boolean mergeable;
- public List<String> mergeableInto;
- }
-
@Option(name = "--other-branches", aliases = {"-o"},
usage = "test mergeability for other branches too")
private boolean otherBranches;
- @Option(name = "--force", aliases = {"-f"},
- usage = "force recheck of mergeable field")
- public void setForce(boolean force) {
- this.force = force;
- }
-
- private final TestSubmitType.Get submitType;
private final GitRepositoryManager gitManager;
private final ProjectCache projectCache;
- private final SubmitStrategyFactory submitStrategyFactory;
+ private final MergeUtil.Factory mergeUtilFactory;
+ private final ChangeData.Factory changeDataFactory;
private final Provider<ReviewDb> db;
private final ChangeIndexer indexer;
-
- private boolean force;
+ private final MergeabilityCache cache;
@Inject
- Mergeable(TestSubmitType.Get submitType,
- GitRepositoryManager gitManager,
+ Mergeable(GitRepositoryManager gitManager,
ProjectCache projectCache,
- SubmitStrategyFactory submitStrategyFactory,
+ MergeUtil.Factory mergeUtilFactory,
+ ChangeData.Factory changeDataFactory,
Provider<ReviewDb> db,
- ChangeIndexer indexer) {
- this.submitType = submitType;
+ ChangeIndexer indexer,
+ MergeabilityCache cache) {
this.gitManager = gitManager;
this.projectCache = projectCache;
- this.submitStrategyFactory = submitStrategyFactory;
+ this.mergeUtilFactory = mergeUtilFactory;
+ this.changeDataFactory = changeDataFactory;
this.db = db;
this.indexer = indexer;
+ this.cache = cache;
+ }
+
+ public void setOtherBranches(boolean otherBranches) {
+ this.otherBranches = otherBranches;
}
@Override
@@ -117,30 +99,49 @@ public class Mergeable implements RestReadView<RevisionResource> {
return result;
}
- result.submitType = submitType.apply(resource);
- result.mergeable = change.isMergeable();
+ ChangeData cd = changeDataFactory.create(db.get(), resource.getControl());
+ SubmitTypeRecord rec = new SubmitRuleEvaluator(cd)
+ .setPatchSet(ps)
+ .getSubmitType();
+ if (rec.status != SubmitTypeRecord.Status.OK) {
+ throw new OrmException("Submit type rule failed: " + rec);
+ }
+ result.submitType = rec.type;
Repository git = gitManager.openRepository(change.getProject());
try {
- Map<String, Ref> refs = git.getRefDatabase().getRefs(RefDatabase.ALL);
- Ref ref = refs.get(change.getDest().get());
- if (force || isStale(change, ref)) {
- result.mergeable =
- refresh(change, ps, result.submitType, git, refs, ref);
+ ObjectId commit = toId(ps);
+ if (commit == null) {
+ result.mergeable = false;
+ return result;
+ }
+
+ Ref ref = git.getRef(change.getDest().get());
+ ProjectState projectState = projectCache.get(change.getProject());
+ String strategy = mergeUtilFactory.create(projectState)
+ .mergeStrategyName();
+ Boolean old =
+ cache.getIfPresent(commit, ref, result.submitType, strategy);
+
+ if (old == null) {
+ result.mergeable = refresh(change, commit, ref, result.submitType,
+ strategy, git, old);
+ } else {
+ result.mergeable = old;
}
if (otherBranches) {
result.mergeableInto = new ArrayList<>();
- BranchOrderSection branchOrder =
- projectCache.get(change.getProject()).getBranchOrderSection();
+ BranchOrderSection branchOrder = projectState.getBranchOrderSection();
if (branchOrder != null) {
int prefixLen = Constants.R_HEADS.length();
for (String n : branchOrder.getMoreStable(ref.getName())) {
- Ref other = refs.get(n);
+ Ref other = git.getRef(n);
if (other == null) {
continue;
}
- if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, other)) {
+ if (cache.get(commit, other, SubmitType.CHERRY_PICK, strategy,
+ change.getDest(), git, db.get())) {
result.mergeableInto.add(other.getName().substring(prefixLen));
}
}
@@ -152,121 +153,25 @@ public class Mergeable implements RestReadView<RevisionResource> {
return result;
}
- private static boolean isStale(Change change, Ref ref) {
- return change.getLastSha1MergeTested() == null
- || !toRevId(ref).equals(change.getLastSha1MergeTested());
- }
-
- private static RevId toRevId(Ref ref) {
- return new RevId(ref != null && ref.getObjectId() != null
- ? ref.getObjectId().name()
- : "");
- }
-
- private boolean refresh(Change change,
- final PatchSet ps,
- SubmitType type,
- Repository git,
- Map<String, Ref> refs,
- final Ref ref) throws IOException, OrmException {
-
- final boolean mergeable = isMergeable(change, ps, type, git, refs, ref);
-
- Change c = db.get().changes().atomicUpdate(
- change.getId(),
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change c) {
- if (c.getStatus().isOpen()
- && ps.getId().equals(c.currentPatchSetId())) {
- c.setMergeable(mergeable);
- c.setLastSha1MergeTested(toRevId(ref));
- return c;
- } else {
- return null;
- }
- }
- });
- if (c != null) {
- indexer.index(db.get(), c);
- }
- return mergeable;
- }
-
- private boolean isMergeable(Change change,
- final PatchSet ps,
- SubmitType type,
- Repository git,
- Map<String, Ref> refs,
- final Ref ref) throws IOException, OrmException {
- RevWalk rw = new RevWalk(git) {
- @Override
- protected CodeReviewCommit createCommit(AnyObjectId id) {
- return new CodeReviewCommit(id);
- }
- };
+ private static ObjectId toId(PatchSet ps) {
try {
- ObjectId id;
- try {
- id = ObjectId.fromString(ps.getRevision().get());
- } catch (IllegalArgumentException e) {
- log.error(String.format(
- "Invalid revision on patch set %d of %d",
- ps.getId().get(),
- change.getId().get()));
- return false;
- }
-
- RevFlag canMerge = rw.newFlag("CAN_MERGE");
- CodeReviewCommit rev = parse(rw, id);
- rev.add(canMerge);
-
- final boolean mergeable;
- if (ref == null || ref.getObjectId() == null) {
- mergeable = true; // Assume yes on new branch.
- } else {
- CodeReviewCommit tip = parse(rw, ref.getObjectId());
- Set<RevCommit> accepted = alreadyAccepted(rw, refs.values());
- accepted.add(tip);
- accepted.addAll(Arrays.asList(rev.getParents()));
- mergeable = submitStrategyFactory.create(
- type,
- db.get(),
- git,
- rw,
- null /*inserter*/,
- canMerge,
- accepted,
- change.getDest()).dryRun(tip, rev);
- }
- return mergeable;
- } catch (MergeException | IOException | NoSuchProjectException e) {
- log.error(String.format(
- "Cannot merge test change %d", change.getId().get()), e);
- return false;
- } finally {
- rw.close();
+ return ObjectId.fromString(ps.getRevision().get());
+ } catch (IllegalArgumentException e) {
+ log.error("Invalid revision on patch set " + ps);
+ return null;
}
}
- private static Set<RevCommit> alreadyAccepted(RevWalk rw, Collection<Ref> refs)
- throws MissingObjectException, IOException {
- Set<RevCommit> accepted = Sets.newHashSet();
- for (Ref r : refs) {
- if (r.getName().startsWith(Constants.R_HEADS)
- || r.getName().startsWith(Constants.R_TAGS)) {
- try {
- accepted.add(rw.parseCommit(r.getObjectId()));
- } catch (IncorrectObjectTypeException nonCommit) {
- // Not a commit? Skip over it.
- }
- }
+ private boolean refresh(final Change change, ObjectId commit,
+ final Ref ref, SubmitType type, String strategy, Repository git,
+ Boolean old) throws OrmException, IOException {
+ final boolean mergeable =
+ cache.get(commit, ref, type, strategy, change.getDest(), git, db.get());
+ if (!Objects.equals(mergeable, old)) {
+ // TODO(dborowitz): Include cache info in ETag somehow instead.
+ ChangeUtil.bumpRowVersionNotLastUpdatedOn(change.getId(), db.get());
+ indexer.index(db.get(), change);
}
- return accepted;
- }
-
- private static CodeReviewCommit parse(RevWalk rw, ObjectId id)
- throws MissingObjectException, IncorrectObjectTypeException, IOException {
- return (CodeReviewCommit) rw.parseCommit(id);
+ return mergeable;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 6880ca297e..d0e4c99c94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -14,16 +14,17 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.change.ChangeEditResource.CHANGE_EDIT_KIND;
import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
import static com.google.gerrit.server.change.CommentResource.COMMENT_KIND;
-import static com.google.gerrit.server.change.DraftResource.DRAFT_KIND;
+import static com.google.gerrit.server.change.DraftCommentResource.DRAFT_COMMENT_KIND;
import static com.google.gerrit.server.change.FileResource.FILE_KIND;
import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.change.Reviewed.DeleteReviewed;
import com.google.gerrit.server.change.Reviewed.PutReviewed;
import com.google.gerrit.server.config.FactoryModule;
@@ -34,26 +35,31 @@ public class Module extends RestApiModule {
bind(ChangesCollection.class);
bind(Revisions.class);
bind(Reviewers.class);
- bind(Drafts.class);
+ bind(DraftComments.class);
bind(Comments.class);
bind(Files.class);
DynamicMap.mapOf(binder(), CHANGE_KIND);
DynamicMap.mapOf(binder(), COMMENT_KIND);
- DynamicMap.mapOf(binder(), DRAFT_KIND);
+ DynamicMap.mapOf(binder(), DRAFT_COMMENT_KIND);
DynamicMap.mapOf(binder(), FILE_KIND);
DynamicMap.mapOf(binder(), REVIEWER_KIND);
DynamicMap.mapOf(binder(), REVISION_KIND);
+ DynamicMap.mapOf(binder(), CHANGE_EDIT_KIND);
get(CHANGE_KIND).to(GetChange.class);
get(CHANGE_KIND, "detail").to(GetDetail.class);
get(CHANGE_KIND, "topic").to(GetTopic.class);
get(CHANGE_KIND, "in").to(IncludedIn.class);
+ get(CHANGE_KIND, "hashtags").to(GetHashtags.class);
+ get(CHANGE_KIND, "check").to(Check.class);
+ post(CHANGE_KIND, "check").to(Check.class);
put(CHANGE_KIND, "topic").to(PutTopic.class);
delete(CHANGE_KIND, "topic").to(PutTopic.class);
delete(CHANGE_KIND).to(DeleteDraftChange.class);
post(CHANGE_KIND, "abandon").to(Abandon.class);
- post(CHANGE_KIND, "publish").to(Publish.CurrentRevision.class);
+ post(CHANGE_KIND, "hashtags").to(PostHashtags.class);
+ post(CHANGE_KIND, "publish").to(PublishDraftPatchSet.CurrentRevision.class);
post(CHANGE_KIND, "restore").to(Restore.class);
post(CHANGE_KIND, "revert").to(Revert.class);
post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
@@ -67,28 +73,28 @@ public class Module extends RestApiModule {
delete(REVIEWER_KIND).to(DeleteReviewer.class);
child(CHANGE_KIND, "revisions").to(Revisions.class);
+ get(REVISION_KIND, "actions").to(GetRevisionActions.class);
post(REVISION_KIND, "cherrypick").to(CherryPick.class);
get(REVISION_KIND, "commit").to(GetCommit.class);
delete(REVISION_KIND).to(DeleteDraftPatchSet.class);
get(REVISION_KIND, "mergeable").to(Mergeable.class);
- post(REVISION_KIND, "publish").to(Publish.class);
+ post(REVISION_KIND, "publish").to(PublishDraftPatchSet.class);
get(REVISION_KIND, "related").to(GetRelated.class);
get(REVISION_KIND, "review").to(GetReview.class);
post(REVISION_KIND, "review").to(PostReview.class);
post(REVISION_KIND, "submit").to(Submit.class);
post(REVISION_KIND, "rebase").to(Rebase.class);
- post(REVISION_KIND, "message").to(EditMessage.class);
get(REVISION_KIND, "patch").to(GetPatch.class);
get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class);
post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class);
post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class);
get(REVISION_KIND, "archive").to(GetArchive.class);
- child(REVISION_KIND, "drafts").to(Drafts.class);
- put(REVISION_KIND, "drafts").to(CreateDraft.class);
- get(DRAFT_KIND).to(GetDraft.class);
- put(DRAFT_KIND).to(PutDraft.class);
- delete(DRAFT_KIND).to(DeleteDraft.class);
+ child(REVISION_KIND, "drafts").to(DraftComments.class);
+ put(REVISION_KIND, "drafts").to(CreateDraftComment.class);
+ get(DRAFT_COMMENT_KIND).to(GetDraftComment.class);
+ put(DRAFT_COMMENT_KIND).to(PutDraftComment.class);
+ delete(DRAFT_COMMENT_KIND).to(DeleteDraftComment.class);
child(REVISION_KIND, "comments").to(Comments.class);
get(COMMENT_KIND).to(GetComment.class);
@@ -99,14 +105,27 @@ public class Module extends RestApiModule {
get(FILE_KIND, "content").to(GetContent.class);
get(FILE_KIND, "diff").to(GetDiff.class);
+ child(CHANGE_KIND, "edit").to(ChangeEdits.class);
+ delete(CHANGE_KIND, "edit").to(DeleteChangeEdit.class);
+ child(CHANGE_KIND, "edit:publish").to(PublishChangeEdit.class);
+ child(CHANGE_KIND, "edit:rebase").to(RebaseChangeEdit.class);
+ put(CHANGE_KIND, "edit:message").to(ChangeEdits.EditMessage.class);
+ get(CHANGE_KIND, "edit:message").to(ChangeEdits.GetMessage.class);
+ put(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Put.class);
+ delete(CHANGE_EDIT_KIND).to(ChangeEdits.DeleteContent.class);
+ get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
+ get(CHANGE_EDIT_KIND, "meta").to(ChangeEdits.GetMeta.class);
+
install(new FactoryModule() {
@Override
protected void configure() {
factory(ReviewerResource.Factory.class);
- factory(AccountInfo.Loader.Factory.class);
+ factory(AccountLoader.Factory.class);
factory(EmailReviewComments.Factory.class);
factory(ChangeInserter.Factory.class);
factory(PatchSetInserter.Factory.class);
+ factory(ChangeEdits.Create.Factory.class);
+ factory(ChangeEdits.DeleteFile.Factory.class);
}
});
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 635421a991..baddd4044b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -33,18 +34,20 @@ import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.ReviewerState;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ChangeModifiedException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gerrit.server.ssh.SshInfo;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -53,6 +56,7 @@ import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.ObjectId;
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;
import org.eclipse.jgit.transport.ReceiveCommand;
@@ -87,7 +91,7 @@ public class PatchSetInserter {
private final ChangeControl.GenericFactory ctlFactory;
private final GitReferenceUpdated gitRefUpdated;
private final CommitValidators.Factory commitValidatorsFactory;
- private final MergeabilityChecker mergeabilityChecker;
+ private final ChangeIndexer indexer;
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final ApprovalsUtil approvalsUtil;
private final ApprovalCopier approvalCopier;
@@ -101,7 +105,6 @@ public class PatchSetInserter {
private PatchSet patchSet;
private ChangeMessage changeMessage;
- private boolean copyLabels;
private SshInfo sshInfo;
private ValidatePolicy validatePolicy = ValidatePolicy.GERRIT;
private boolean draft;
@@ -120,7 +123,7 @@ public class PatchSetInserter {
PatchSetInfoFactory patchSetInfoFactory,
GitReferenceUpdated gitRefUpdated,
CommitValidators.Factory commitValidatorsFactory,
- MergeabilityChecker mergeabilityChecker,
+ ChangeIndexer indexer,
ReplacePatchSetSender.Factory replacePatchSetFactory,
@Assisted Repository git,
@Assisted RevWalk revWalk,
@@ -139,7 +142,7 @@ public class PatchSetInserter {
this.patchSetInfoFactory = patchSetInfoFactory;
this.gitRefUpdated = gitRefUpdated;
this.commitValidatorsFactory = commitValidatorsFactory;
- this.mergeabilityChecker = mergeabilityChecker;
+ this.indexer = indexer;
this.replacePatchSetFactory = replacePatchSetFactory;
this.git = git;
@@ -177,17 +180,11 @@ public class PatchSetInserter {
return this;
}
- public PatchSetInserter setMessage(ChangeMessage changeMessage)
- throws OrmException {
+ public PatchSetInserter setMessage(ChangeMessage changeMessage) {
this.changeMessage = changeMessage;
return this;
}
- public PatchSetInserter setCopyLabels(boolean copyLabels) {
- this.copyLabels = copyLabels;
- return this;
- }
-
public PatchSetInserter setSshInfo(SshInfo sshInfo) {
this.sshInfo = sshInfo;
return this;
@@ -267,7 +264,6 @@ public class PatchSetInserter {
if (change.getStatus() != Change.Status.DRAFT) {
change.setStatus(Change.Status.NEW);
}
- change.setLastSha1MergeTested(null);
change.setCurrentPatchSet(patchSetInfoFactory.get(commit,
patchSet.getId()));
ChangeUtil.updated(change);
@@ -283,9 +279,7 @@ public class PatchSetInserter {
cmUtil.addChangeMessage(db, update, changeMessage);
}
- if (copyLabels) {
- approvalCopier.copy(db, ctl, patchSet);
- }
+ approvalCopier.copy(db, ctl, patchSet);
db.commit();
if (messageIsForChange()) {
update.commit();
@@ -315,10 +309,7 @@ public class PatchSetInserter {
} finally {
db.rollback();
}
- mergeabilityChecker.newCheck()
- .addChange(updatedChange)
- .reindex()
- .run();
+ indexer.index(db, updatedChange);
if (runHooks) {
hooks.doPatchsetCreatedHook(updatedChange, patchSet, db);
}
@@ -356,7 +347,7 @@ public class PatchSetInserter {
}
}
- private void validate() throws InvalidChangeOperationException {
+ private void validate() throws InvalidChangeOperationException, IOException {
CommitValidators cv =
commitValidatorsFactory.create(ctl.getRefControl(), sshInfo, git);
@@ -372,7 +363,8 @@ public class PatchSetInserter {
try {
switch (validatePolicy) {
case RECEIVE_COMMITS:
- cv.validateForReceiveCommits(event);
+ NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(git, revWalk);
+ cv.validateForReceiveCommits(event, rejectCommits);
break;
case GERRIT:
cv.validateForGerritCommits(event);
@@ -389,12 +381,4 @@ public class PatchSetInserter {
return changeMessage != null && changeMessage.getKey().getParentKey()
.equals(patchSet.getId().getParentKey());
}
-
- public class ChangeModifiedException extends InvalidChangeOperationException {
- private static final long serialVersionUID = 1L;
-
- public ChangeModifiedException(String msg) {
- super(msg);
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
new file mode 100644
index 0000000000..6638f91f3b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 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.extensions.api.changes.HashtagsInput;
+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.validators.ValidationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.Set;
+
+@Singleton
+public class PostHashtags implements RestModifyView<ChangeResource, HashtagsInput> {
+ private HashtagsUtil hashtagsUtil;
+
+ @Inject
+ PostHashtags(HashtagsUtil hashtagsUtil) {
+ this.hashtagsUtil = hashtagsUtil;
+ }
+
+ @Override
+ public Response<? extends Set<String>> apply(ChangeResource req, HashtagsInput input)
+ throws AuthException, OrmException, IOException, BadRequestException,
+ ResourceConflictException {
+
+ try {
+ return Response.ok(hashtagsUtil.setHashtags(
+ req.getControl(), input, true, true));
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage());
+ } catch (ValidationException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 38070c0236..8e989b634b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -15,8 +15,9 @@
package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -24,6 +25,7 @@ import com.google.common.collect.Sets;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.Futures;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
@@ -32,9 +34,10 @@ 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.api.changes.ReviewInput.NotifyHandling;
-import com.google.gerrit.extensions.common.Comment.Side;
+import com.google.gerrit.extensions.client.Side;
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.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
@@ -44,7 +47,6 @@ import com.google.gerrit.reviewdb.client.CommentRange;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -54,18 +56,14 @@ import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.LabelVote;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -133,8 +131,19 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
@Override
public Output apply(RevisionResource revision, ReviewInput input)
- throws AuthException, BadRequestException, UnprocessableEntityException,
- OrmException, IOException {
+ throws AuthException, BadRequestException, ResourceConflictException,
+ UnprocessableEntityException, OrmException, IOException {
+ return apply(revision, input, TimeUtil.nowTs());
+ }
+
+ public Output apply(RevisionResource revision, ReviewInput input,
+ Timestamp ts) throws AuthException, BadRequestException,
+ ResourceConflictException, UnprocessableEntityException, OrmException,
+ IOException {
+ timestamp = ts;
+ if (revision.getEdit().isPresent()) {
+ throw new ResourceConflictException("cannot post review on edit");
+ }
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, input);
}
@@ -149,15 +158,15 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
input.notify = NotifyHandling.NONE;
}
- ChangeUpdate update = null;
db.get().changes().beginTransaction(revision.getChange().getId());
boolean dirty = false;
try {
change = db.get().changes().get(revision.getChange().getId());
- ChangeUtil.updated(change);
- timestamp = change.getLastUpdatedOn();
+ if (change.getLastUpdatedOn().before(timestamp)) {
+ change.setLastUpdatedOn(timestamp);
+ }
- update = updateFactory.create(revision.getControl(), timestamp);
+ ChangeUpdate update = updateFactory.create(revision.getControl(), timestamp);
update.setPatchSetId(revision.getPatchSet().getId());
dirty |= insertComments(revision, update, input.comments, input.drafts);
dirty |= updateLabels(revision, update, input.labels);
@@ -166,12 +175,10 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
db.get().changes().update(Collections.singleton(change));
db.get().commit();
}
+ update.commit();
} finally {
db.get().rollback();
}
- if (update != null) {
- update.commit();
- }
CheckedFuture<?, IOException> indexWrite;
if (dirty) {
@@ -295,7 +302,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
in.entrySet().iterator();
Set<String> filePaths =
Sets.newHashSet(changeDataFactory.create(
- db.get(), revision.getChange()).filePaths(
+ db.get(), revision.getControl()).filePaths(
revision.getPatchSet()));
while (mapItr.hasNext()) {
Map.Entry<String, List<CommentInput>> ent = mapItr.next();
@@ -319,7 +326,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
listItr.remove();
continue;
}
- if (c.line < 0) {
+ if (c.line != null && c.line < 0) {
throw new BadRequestException(String.format(
"negative line number %d not allowed on %s",
c.line, path));
@@ -337,7 +344,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
private boolean insertComments(RevisionResource rsrc,
ChangeUpdate update, Map<String, List<CommentInput>> in, DraftHandling draftsHandling)
- throws OrmException, IOException {
+ throws OrmException {
if (in == null) {
in = Collections.emptyMap();
}
@@ -350,15 +357,6 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
List<PatchLineComment> del = Lists.newArrayList();
List<PatchLineComment> ups = Lists.newArrayList();
- PatchList patchList = null;
- try {
- patchList = patchListCache.get(rsrc.getChange(), rsrc.getPatchSet());
- } catch (PatchListNotAvailableException e) {
- throw new OrmException("could not load PatchList for this patchset", e);
- }
- RevId patchSetCommit = new RevId(ObjectId.toString(patchList.getNewId()));
- RevId baseCommit = new RevId(ObjectId.toString(patchList.getOldId()));;
-
for (Map.Entry<String, List<CommentInput>> ent : in.entrySet()) {
String path = ent.getKey();
for (CommentInput c : ent.getValue()) {
@@ -369,7 +367,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
new PatchLineComment.Key(
new Patch.Key(rsrc.getPatchSet().getId(), path),
ChangeUtil.messageUUID(db.get())),
- c.line,
+ c.line != null ? c.line : 0,
rsrc.getAccountId(),
parent, timestamp);
} else if (parent != null) {
@@ -378,7 +376,8 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
e.setStatus(PatchLineComment.Status.PUBLISHED);
e.setWrittenOn(timestamp);
e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
- e.setRevId(c.side == Side.PARENT ? baseCommit : patchSetCommit);
+ setCommentRevId(e, patchListCache, rsrc.getChange(),
+ rsrc.getPatchSet());
e.setMessage(c.message);
if (c.range != null) {
e.setRange(new CommentRange(
@@ -392,7 +391,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
}
}
- switch (Objects.firstNonNull(draftsHandling, DraftHandling.DELETE)) {
+ switch (MoreObjects.firstNonNull(draftsHandling, DraftHandling.DELETE)) {
case KEEP:
default:
break;
@@ -403,13 +402,14 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
for (PatchLineComment e : drafts.values()) {
e.setStatus(PatchLineComment.Status.PUBLISHED);
e.setWrittenOn(timestamp);
- e.setRevId(e.getSide() == (short) 0 ? baseCommit : patchSetCommit);
+ setCommentRevId(e, patchListCache, rsrc.getChange(),
+ rsrc.getPatchSet());
ups.add(e);
}
break;
}
- db.get().patchComments().delete(del);
- plcUtil.addPublishedComments(db.get(), update, ups);
+ plcUtil.deleteComments(db.get(), update, del);
+ plcUtil.upsertComments(db.get(), update, ups);
comments.addAll(ups);
return !del.isEmpty() || !ups.isEmpty();
}
@@ -417,9 +417,8 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
private Map<String, PatchLineComment> scanDraftComments(
RevisionResource rsrc) throws OrmException {
Map<String, PatchLineComment> drafts = Maps.newHashMap();
- for (PatchLineComment c : db.get().patchComments().draftByPatchSetAuthor(
- rsrc.getPatchSet().getId(),
- rsrc.getAccountId())) {
+ for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(db.get(),
+ rsrc.getPatchSet().getId(), rsrc.getAccountId(), rsrc.getNotes())) {
drafts.put(c.getKey().get(), c);
}
return drafts;
@@ -535,7 +534,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
}
private void addLabelDelta(String name, short value) {
- labelDelta.add(new LabelVote(name, value).format());
+ labelDelta.add(LabelVote.create(name, value).format());
}
private boolean insertMessage(RevisionResource rsrc, String msg,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index 5b95ab2ab1..ecfb43c3f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -39,7 +39,7 @@ import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.change.ReviewerJson.PostResult;
@@ -81,7 +81,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
private final AddReviewerSender.Factory addReviewerSenderFactory;
private final GroupsCollection groupsCollection;
private final GroupMembers.Factory groupMembersFactory;
- private final AccountInfo.Loader.Factory accountLoaderFactory;
+ private final AccountLoader.Factory accountLoaderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeUpdate.Factory updateFactory;
private final Provider<CurrentUser> currentUser;
@@ -99,7 +99,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
AddReviewerSender.Factory addReviewerSenderFactory,
GroupsCollection groupsCollection,
GroupMembers.Factory groupMembersFactory,
- AccountInfo.Loader.Factory accountLoaderFactory,
+ AccountLoader.Factory accountLoaderFactory,
Provider<ReviewDb> db,
ChangeUpdate.Factory updateFactory,
Provider<CurrentUser> currentUser,
@@ -150,7 +150,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
}
private PostResult putAccount(ReviewerResource rsrc) throws OrmException,
- EmailException, IOException {
+ IOException {
Account member = rsrc.getUser().getAccount();
ChangeControl control = rsrc.getControl();
PostResult result = new PostResult();
@@ -162,7 +162,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
private PostResult putGroup(ChangeResource rsrc, AddReviewerInput input)
throws BadRequestException,
- UnprocessableEntityException, OrmException, EmailException, IOException {
+ UnprocessableEntityException, OrmException, IOException {
GroupDescription.Basic group = groupsCollection.parseInternal(input.reviewer);
PostResult result = new PostResult();
if (!isLegalReviewerGroup(group.getGroupUUID())) {
@@ -228,7 +228,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
private void addReviewers(ChangeResource rsrc, PostResult result,
Map<Account.Id, ChangeControl> reviewers)
- throws OrmException, EmailException, IOException {
+ throws OrmException, IOException {
ReviewDb db = dbProvider.get();
ChangeUpdate update = updateFactory.create(rsrc.getControl());
List<PatchSetApproval> added;
@@ -266,8 +266,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
}
}
- private void emailReviewers(Change change, List<PatchSetApproval> added)
- throws OrmException, EmailException {
+ private void emailReviewers(Change change, List<PatchSetApproval> added) {
if (added.isEmpty()) {
return;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
new file mode 100644
index 0000000000..952636f80f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2014 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.common.base.Optional;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsPost;
+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.NotImplementedException;
+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.RestView;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class PublishChangeEdit implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsPost<ChangeResource> {
+
+ private final Publish publish;
+
+ @Inject
+ PublishChangeEdit(Publish publish) {
+ this.publish = publish;
+ }
+
+ @Override
+ public DynamicMap<RestView<ChangeEditResource>> views() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public RestView<ChangeResource> list() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public ChangeEditResource parse(ChangeResource parent, IdString id) {
+ throw new NotImplementedException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Publish post(ChangeResource parent) throws RestApiException {
+ return publish;
+ }
+
+ @Singleton
+ public static class Publish implements RestModifyView<ChangeResource, Publish.Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditUtil editUtil;
+
+ @Inject
+ Publish(ChangeEditUtil editUtil) {
+ this.editUtil = editUtil;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, Publish.Input in)
+ throws AuthException, ResourceConflictException, NoSuchChangeException,
+ IOException, InvalidChangeOperationException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "no edit exists for change %s",
+ rsrc.getChange().getChangeId()));
+ }
+ editUtil.publish(edit.get());
+ return Response.none();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index 88da0949f6..9f77f0e5bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -26,24 +26,20 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.change.Publish.Input;
-import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.change.PublishDraftPatchSet.Input;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.PatchSetNotificationSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.Config;
-
import java.io.IOException;
@Singleton
-public class Publish implements RestModifyView<RevisionResource, Input>,
+public class PublishDraftPatchSet implements RestModifyView<RevisionResource, Input>,
UiAction<RevisionResource> {
public static class Input {
}
@@ -53,21 +49,18 @@ public class Publish implements RestModifyView<RevisionResource, Input>,
private final PatchSetNotificationSender sender;
private final ChangeHooks hooks;
private final ChangeIndexer indexer;
- private final boolean allowDrafts;
@Inject
- public Publish(Provider<ReviewDb> dbProvider,
+ public PublishDraftPatchSet(Provider<ReviewDb> dbProvider,
ChangeUpdate.Factory updateFactory,
PatchSetNotificationSender sender,
ChangeHooks hooks,
- ChangeIndexer indexer,
- @GerritServerConfig Config cfg) {
+ ChangeIndexer indexer) {
this.dbProvider = dbProvider;
this.updateFactory = updateFactory;
this.sender = sender;
this.hooks = hooks;
this.indexer = indexer;
- this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
}
@Override
@@ -82,30 +75,22 @@ public class Publish implements RestModifyView<RevisionResource, Input>,
throw new AuthException("Cannot publish this draft patch set");
}
- if (!allowDrafts) {
- throw new ResourceConflictException("Draft workflow is disabled.");
- }
-
PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
Change updatedChange = updateDraftChange(rsrc);
ChangeUpdate update = updateFactory.create(rsrc.getControl(),
updatedChange.getLastUpdatedOn());
- try {
- if (!updatedPatchSet.isDraft()
- || updatedChange.getStatus() == Change.Status.NEW) {
- CheckedFuture<?, IOException> indexFuture =
- indexer.indexAsync(updatedChange.getId());
- sender.send(rsrc.getNotes(), update,
- rsrc.getChange().getStatus() == Change.Status.DRAFT,
- rsrc.getUser(), updatedChange, updatedPatchSet,
- rsrc.getControl().getLabelTypes());
- indexFuture.checkedGet();
- hooks.doDraftPublishedHook(updatedChange, updatedPatchSet,
- dbProvider.get());
- }
- } catch (PatchSetInfoNotAvailableException e) {
- throw new ResourceNotFoundException(e.getMessage());
+ if (!updatedPatchSet.isDraft()
+ || updatedChange.getStatus() == Change.Status.NEW) {
+ CheckedFuture<?, IOException> indexFuture =
+ indexer.indexAsync(updatedChange.getId());
+ sender.send(rsrc.getNotes(), update,
+ rsrc.getChange().getStatus() == Change.Status.DRAFT,
+ rsrc.getUser(), updatedChange, updatedPatchSet,
+ rsrc.getControl().getLabelTypes());
+ indexFuture.checkedGet();
+ hooks.doDraftPublishedHook(updatedChange, updatedPatchSet,
+ dbProvider.get());
}
return Response.none();
@@ -154,11 +139,11 @@ public class Publish implements RestModifyView<RevisionResource, Input>,
public static class CurrentRevision implements
RestModifyView<ChangeResource, Input> {
private final Provider<ReviewDb> dbProvider;
- private final Publish publish;
+ private final PublishDraftPatchSet publish;
@Inject
CurrentRevision(Provider<ReviewDb> dbProvider,
- Publish publish) {
+ PublishDraftPatchSet publish) {
this.dbProvider = dbProvider;
this.publish = publish;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
index c1fb30430a..2a0bcb3f91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -14,61 +14,67 @@
package com.google.gerrit.server.change;
-import com.google.gerrit.common.changes.Side;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
-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.Url;
-import com.google.gerrit.reviewdb.client.CommentRange;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.PutDraft.Input;
-import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.sql.Timestamp;
+import java.io.IOException;
import java.util.Collections;
@Singleton
-class PutDraft implements RestModifyView<DraftResource, Input> {
- static class Input {
- String id;
- String path;
- Side side;
- Integer line;
- String inReplyTo;
- Timestamp updated; // Accepted but ignored.
- CommentRange range;
-
- @DefaultInput
- String message;
- }
+public class PutDraftComment implements RestModifyView<DraftCommentResource, DraftInput> {
private final Provider<ReviewDb> db;
- private final DeleteDraft delete;
+ private final DeleteDraftComment delete;
+ private final PatchLineCommentsUtil plcUtil;
+ private final ChangeUpdate.Factory updateFactory;
+ private final CommentJson commentJson;
+ private final PatchListCache patchListCache;
@Inject
- PutDraft(Provider<ReviewDb> db, DeleteDraft delete) {
+ PutDraftComment(Provider<ReviewDb> db,
+ DeleteDraftComment delete,
+ PatchLineCommentsUtil plcUtil,
+ ChangeUpdate.Factory updateFactory,
+ CommentJson commentJson,
+ PatchListCache patchListCache) {
this.db = db;
this.delete = delete;
+ this.plcUtil = plcUtil;
+ this.updateFactory = updateFactory;
+ this.commentJson = commentJson;
+ this.patchListCache = patchListCache;
}
@Override
- public Response<CommentInfo> apply(DraftResource rsrc, Input in) throws
- BadRequestException, OrmException {
+ public Response<CommentInfo> apply(DraftCommentResource rsrc, DraftInput in) throws
+ BadRequestException, OrmException, IOException {
PatchLineComment c = rsrc.getComment();
+ ChangeUpdate update = updateFactory.create(rsrc.getControl());
if (in == null || in.message == null || in.message.trim().isEmpty()) {
return delete.apply(rsrc, null);
} else if (in.id != null && !rsrc.getId().equals(in.id)) {
throw new BadRequestException("id must match URL");
} else if (in.line != null && in.line < 0) {
throw new BadRequestException("line must be >= 0");
- } else if (in.line != null && in.range != null && in.line != in.range.getEndLine()) {
+ } else if (in.line != null && in.range != null && in.line != in.range.endLine) {
throw new BadRequestException("range endLine must be on the same line as the comment");
}
@@ -76,7 +82,8 @@ class PutDraft implements RestModifyView<DraftResource, Input> {
&& !in.path.equals(c.getKey().getParentKey().getFileName())) {
// Updating the path alters the primary key, which isn't possible.
// Delete then recreate the comment instead of an update.
- db.get().patchComments().delete(Collections.singleton(c));
+
+ plcUtil.deleteComments(db.get(), update, Collections.singleton(c));
c = new PatchLineComment(
new PatchLineComment.Key(
new Patch.Key(rsrc.getPatchSet().getId(), in.path),
@@ -84,14 +91,22 @@ class PutDraft implements RestModifyView<DraftResource, Input> {
c.getLine(),
rsrc.getAuthorId(),
c.getParentUuid(), TimeUtil.nowTs());
- db.get().patchComments().insert(Collections.singleton(update(c, in)));
+ setCommentRevId(c, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ plcUtil.insertComments(db.get(), update,
+ Collections.singleton(update(c, in)));
} else {
- db.get().patchComments().update(Collections.singleton(update(c, in)));
+ if (c.getRevId() == null) {
+ setCommentRevId(c, patchListCache, rsrc.getChange(),
+ rsrc.getPatchSet());
+ }
+ plcUtil.updateComments(db.get(), update,
+ Collections.singleton(update(c, in)));
}
- return Response.ok(new CommentInfo(c, null));
+ update.commit();
+ return Response.ok(commentJson.format(c, false));
}
- private PatchLineComment update(PatchLineComment e, Input in) {
+ private PatchLineComment update(PatchLineComment e, DraftInput in) {
if (in.side != null) {
e.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
}
@@ -101,7 +116,7 @@ class PutDraft implements RestModifyView<DraftResource, Input> {
e.setMessage(in.message.trim());
if (in.range != null || in.line != null) {
e.setRange(in.range);
- e.setLine(in.range != null ? in.range.getEndLine() : in.line);
+ e.setLine(in.range != null ? in.range.endLine : in.line);
}
e.setWrittenOn(TimeUtil.nowTs());
return e;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 7f7c5fbef3..d171785719 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.change;
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.Response;
@@ -31,7 +32,6 @@ import com.google.gerrit.server.change.PutTopic.Input;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -41,7 +41,7 @@ import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
-class PutTopic implements RestModifyView<ChangeResource, Input>,
+public class PutTopic implements RestModifyView<ChangeResource, Input>,
UiAction<ChangeResource> {
private final Provider<ReviewDb> dbProvider;
private final ChangeIndexer indexer;
@@ -49,9 +49,9 @@ class PutTopic implements RestModifyView<ChangeResource, Input>,
private final ChangeUpdate.Factory updateFactory;
private final ChangeMessagesUtil cmUtil;
- static class Input {
+ public static class Input {
@DefaultInput
- String topic;
+ public String topic;
}
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index 295189bdaf..d2afd9a3ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -15,17 +15,20 @@
package com.google.gerrit.server.change;
import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.extensions.common.ListChangesOption;
+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.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
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.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetAncestor;
+import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.Rebase.Input;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -35,27 +38,36 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
+import java.util.ArrayList;
+
@Singleton
-public class Rebase implements RestModifyView<RevisionResource, Input>,
+public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
UiAction<RevisionResource> {
- public static class Input {
- }
+
+ private static final Logger log =
+ LoggerFactory.getLogger(Rebase.class);
private final Provider<RebaseChange> rebaseChange;
private final ChangeJson json;
+ private final Provider<ReviewDb> dbProvider;
@Inject
- public Rebase(Provider<RebaseChange> rebaseChange, ChangeJson json) {
+ public Rebase(Provider<RebaseChange> rebaseChange, ChangeJson json,
+ Provider<ReviewDb> dbProvider) {
this.rebaseChange = rebaseChange;
this.json = json
.addOption(ListChangesOption.CURRENT_REVISION)
.addOption(ListChangesOption.CURRENT_COMMIT);
+ this.dbProvider = dbProvider;
}
@Override
- public ChangeInfo apply(RevisionResource rsrc, Input input)
+ public ChangeInfo apply(RevisionResource rsrc, RebaseInput input)
throws AuthException, ResourceNotFoundException,
ResourceConflictException, EmailException, OrmException {
ChangeControl control = rsrc.getControl();
@@ -65,11 +77,56 @@ public class Rebase implements RestModifyView<RevisionResource, Input>,
} else if (!change.getStatus().isOpen()) {
throw new ResourceConflictException("change is "
+ change.getStatus().name().toLowerCase());
+ } else if (!hasOneParent(rsrc.getPatchSet().getId())) {
+ throw new ResourceConflictException(
+ "cannot rebase merge commits or commit with no ancestor");
+ }
+
+ String baseRev = null;
+ if (input != null && input.base != null) {
+ String base = input.base.trim();
+ do {
+ if (base.equals("")) {
+ // remove existing dependency to other patch set
+ baseRev = change.getDest().get();
+ break;
+ }
+
+ ReviewDb db = dbProvider.get();
+ PatchSet basePatchSet = parseBase(base);
+ if (basePatchSet == null) {
+ throw new ResourceConflictException("base revision is missing: " + base);
+ } else if (!rsrc.getControl().isPatchVisible(basePatchSet, db)) {
+ throw new AuthException("base revision not accessible: " + base);
+ } else if (change.getId().equals(basePatchSet.getId().getParentKey())) {
+ throw new ResourceConflictException("cannot depend on self");
+ }
+
+ Change baseChange = db.changes().get(basePatchSet.getId().getParentKey());
+ if (baseChange != null) {
+ if (!baseChange.getProject().equals(change.getProject())) {
+ throw new ResourceConflictException("base change is in wrong project: "
+ + baseChange.getProject());
+ } else if (!baseChange.getDest().equals(change.getDest())) {
+ throw new ResourceConflictException("base change is targetting wrong branch: "
+ + baseChange.getDest());
+ } else if (baseChange.getStatus() == Status.ABANDONED) {
+ throw new ResourceConflictException("base change is abandoned: "
+ + baseChange.getKey());
+ } else if (isDescendantOf(baseChange.getId(), rsrc.getPatchSet().getRevision())) {
+ throw new ResourceConflictException("base change " + baseChange.getKey()
+ + " is a descendant of the current "
+ + " change - recursion not allowed");
+ }
+ baseRev = basePatchSet.getRevision().get();
+ break;
+ }
+ } while (false); // just wanted to use the break statement
}
try {
- rebaseChange.get().rebase(rsrc.getChange(), rsrc.getPatchSet().getId(),
- rsrc.getUser());
+ rebaseChange.get().rebase(change, rsrc.getPatchSet().getId(),
+ rsrc.getUser(), baseRev);
} catch (InvalidChangeOperationException e) {
throw new ResourceConflictException(e.getMessage());
} catch (IOException e) {
@@ -81,37 +138,115 @@ public class Rebase implements RestModifyView<RevisionResource, Input>,
return json.format(change.getId());
}
+ private boolean isDescendantOf(Change.Id child, RevId ancestor)
+ throws OrmException {
+ ReviewDb db = dbProvider.get();
+
+ ArrayList<RevId> parents = new ArrayList<>();
+ parents.add(ancestor);
+ while (!parents.isEmpty()) {
+ RevId parent = parents.remove(0);
+ // get direct descendants of change
+ for (PatchSetAncestor desc : db.patchSetAncestors().descendantsOf(parent)) {
+ PatchSet descPatchSet = db.patchSets().get(desc.getPatchSet());
+ Change.Id descChangeId = descPatchSet.getId().getParentKey();
+ if (child.equals(descChangeId)) {
+ PatchSet.Id descCurrentPatchSetId =
+ db.changes().get(descChangeId).currentPatchSetId();
+ // it's only bad if the descendant patch set is current
+ return descPatchSet.getId().equals(descCurrentPatchSetId);
+ } else {
+ // process indirect descendants as well
+ parents.add(descPatchSet.getRevision());
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private PatchSet parseBase(final String base) throws OrmException {
+ ReviewDb db = dbProvider.get();
+
+ PatchSet.Id basePatchSetId = PatchSet.Id.fromRef(base);
+ if (basePatchSetId != null) {
+ // try parsing the base as a ref string
+ return db.patchSets().get(basePatchSetId);
+ }
+
+ // try parsing base as a change number (assume current patch set)
+ PatchSet basePatchSet = null;
+ try {
+ Change.Id baseChangeId = Change.Id.parse(base);
+ if (baseChangeId != null) {
+ for (PatchSet ps : db.patchSets().byChange(baseChangeId)) {
+ if (basePatchSet == null || basePatchSet.getId().get() < ps.getId().get()){
+ basePatchSet = ps;
+ }
+ }
+ }
+ } catch (NumberFormatException e) { // probably a SHA1
+ }
+
+ // try parsing as SHA1
+ if (basePatchSet == null) {
+ for (PatchSet ps : db.patchSets().byRevision(new RevId(base))) {
+ if (basePatchSet == null || basePatchSet.getId().get() < ps.getId().get()) {
+ basePatchSet = ps;
+ }
+ }
+ }
+
+ return basePatchSet;
+ }
+
+ private boolean hasOneParent(final PatchSet.Id patchSetId) {
+ try {
+ // prevent rebase of exotic changes (merge commit, no ancestor).
+ return (dbProvider.get().patchSetAncestors()
+ .ancestorsOf(patchSetId).toList().size() == 1);
+ } catch (OrmException e) {
+ log.error("Failed to get ancestors of patch set "
+ + patchSetId.toRefName(), e);
+ return false;
+ }
+ }
+
@Override
public UiAction.Description getDescription(RevisionResource resource) {
- return new UiAction.Description()
+ UiAction.Description descr = new UiAction.Description()
.setLabel("Rebase")
.setTitle("Rebase onto tip of branch or parent change")
.setVisible(resource.getChange().getStatus().isOpen()
&& resource.getControl().canRebase()
- && rebaseChange.get().canRebase(resource));
+ && hasOneParent(resource.getPatchSet().getId()));
+ if (descr.isVisible()) {
+ // Disable the rebase button in the RebaseDialog if
+ // the change cannot be rebased.
+ descr.setEnabled(rebaseChange.get().canRebase(resource));
+ }
+ return descr;
}
public static class CurrentRevision implements
- RestModifyView<ChangeResource, Input> {
- private final Provider<ReviewDb> dbProvider;
+ RestModifyView<ChangeResource, RebaseInput> {
private final Rebase rebase;
@Inject
- CurrentRevision(Provider<ReviewDb> dbProvider, Rebase rebase) {
- this.dbProvider = dbProvider;
+ CurrentRevision(Rebase rebase) {
this.rebase = rebase;
}
@Override
- public ChangeInfo apply(ChangeResource rsrc, Input input)
+ public ChangeInfo apply(ChangeResource rsrc, RebaseInput input)
throws AuthException, ResourceNotFoundException,
ResourceConflictException, EmailException, OrmException {
PatchSet ps =
- dbProvider.get().patchSets()
+ rebase.dbProvider.get().patchSets()
.get(rsrc.getChange().currentPatchSetId());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
- } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
+ } else if (!rsrc.getControl().isPatchVisible(ps, rebase.dbProvider.get())) {
throw new AuthException("current revision not accessible");
}
return rebase.apply(new RevisionResource(rsrc, ps), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
new file mode 100644
index 0000000000..1420b6c9be
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2014 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.common.base.Optional;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsPost;
+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.NotImplementedException;
+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.RestView;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class RebaseChangeEdit implements
+ ChildCollection<ChangeResource, ChangeEditResource>,
+ AcceptsPost<ChangeResource> {
+
+ private final Rebase rebase;
+
+ @Inject
+ RebaseChangeEdit(Rebase rebase) {
+ this.rebase = rebase;
+ }
+
+ @Override
+ public DynamicMap<RestView<ChangeEditResource>> views() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public RestView<ChangeResource> list() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public ChangeEditResource parse(ChangeResource parent, IdString id) {
+ throw new NotImplementedException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Rebase post(ChangeResource parent) throws RestApiException {
+ return rebase;
+ }
+
+ @Singleton
+ public static class Rebase implements RestModifyView<ChangeResource, Rebase.Input> {
+ public static class Input {
+ }
+
+ private final ChangeEditModifier editModifier;
+ private final ChangeEditUtil editUtil;
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ Rebase(ChangeEditModifier editModifier,
+ ChangeEditUtil editUtil,
+ Provider<ReviewDb> db) {
+ this.editModifier = editModifier;
+ this.editUtil = editUtil;
+ this.db = db;
+ }
+
+ @Override
+ public Response<?> apply(ChangeResource rsrc, Rebase.Input in)
+ throws AuthException, ResourceConflictException, IOException,
+ InvalidChangeOperationException, OrmException {
+ Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+ if (!edit.isPresent()) {
+ throw new ResourceConflictException(String.format(
+ "no edit exists for change %s",
+ rsrc.getChange().getChangeId()));
+ }
+
+ PatchSet current = db.get().patchSets().get(
+ rsrc.getChange().currentPatchSetId());
+ if (current.getId().equals(edit.get().getBasePatchSet().getId())) {
+ throw new ResourceConflictException(String.format(
+ "edit for change %s is already on latest patch set: %s",
+ rsrc.getChange().getChangeId(),
+ current.getId()));
+ }
+ editModifier.rebaseEdit(edit.get(), current);
+ return Response.none();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 88e2137371..cce362a1f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -18,6 +18,7 @@ import com.google.common.base.Strings;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.extensions.api.changes.RestoreInput;
+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.RestModifyView;
@@ -29,7 +30,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.ReplyToChangeSender;
import com.google.gerrit.server.mail.RestoredSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
@@ -54,7 +55,7 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
private final RestoredSender.Factory restoredSenderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson json;
- private final MergeabilityChecker mergeabilityChecker;
+ private final ChangeIndexer indexer;
private final ChangeMessagesUtil cmUtil;
private final ChangeUpdate.Factory updateFactory;
@@ -63,14 +64,14 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
RestoredSender.Factory restoredSenderFactory,
Provider<ReviewDb> dbProvider,
ChangeJson json,
- MergeabilityChecker mergeabilityChecker,
+ ChangeIndexer indexer,
ChangeMessagesUtil cmUtil,
ChangeUpdate.Factory updateFactory) {
this.hooks = hooks;
this.restoredSenderFactory = restoredSenderFactory;
this.dbProvider = dbProvider;
this.json = json;
- this.mergeabilityChecker = mergeabilityChecker;
+ this.indexer = indexer;
this.cmUtil = cmUtil;
this.updateFactory = updateFactory;
}
@@ -121,10 +122,7 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
}
update.commit();
- CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
- .addChange(change)
- .reindex()
- .runAsync();
+ CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
try {
ReplyToChangeSender cm = restoredSenderFactory.create(change);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 326c8721d9..e6846e0abe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -15,8 +15,10 @@
package com.google.gerrit.server.change;
import com.google.common.base.Strings;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RevertInput;
+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.ResourceConflictException;
@@ -27,12 +29,10 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.ssh.NoSshInfo;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
index 4612c9672b..7c10b115b1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
@@ -23,14 +23,15 @@ import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.extensions.common.AccountInfo;
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.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -47,13 +48,13 @@ public class ReviewerJson {
private final Provider<ReviewDb> db;
private final ChangeData.Factory changeDataFactory;
private final ApprovalsUtil approvalsUtil;
- private final AccountInfo.Loader.Factory accountLoaderFactory;
+ private final AccountLoader.Factory accountLoaderFactory;
@Inject
ReviewerJson(Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
ApprovalsUtil approvalsUtil,
- AccountInfo.Loader.Factory accountLoaderFactory) {
+ AccountLoader.Factory accountLoaderFactory) {
this.db = db;
this.changeDataFactory = changeDataFactory;
this.approvalsUtil = approvalsUtil;
@@ -63,12 +64,11 @@ public class ReviewerJson {
public List<ReviewerInfo> format(Collection<ReviewerResource> rsrcs)
throws OrmException {
List<ReviewerInfo> infos = Lists.newArrayListWithCapacity(rsrcs.size());
- AccountInfo.Loader loader = accountLoaderFactory.create(true);
+ AccountLoader loader = accountLoaderFactory.create(true);
for (ReviewerResource rsrc : rsrcs) {
ReviewerInfo info = format(new ReviewerInfo(
rsrc.getUser().getAccountId()),
- rsrc.getUserControl(),
- rsrc.getNotes());
+ rsrc.getUserControl());
loader.put(info);
infos.add(info);
}
@@ -80,11 +80,11 @@ public class ReviewerJson {
return format(ImmutableList.<ReviewerResource> of(rsrc));
}
- public ReviewerInfo format(ReviewerInfo out, ChangeControl ctl,
- ChangeNotes changeNotes) throws OrmException {
+ public ReviewerInfo format(ReviewerInfo out, ChangeControl ctl) throws OrmException {
PatchSet.Id psId = ctl.getChange().currentPatchSetId();
return format(out, ctl,
- approvalsUtil.byPatchSetUser(db.get(), ctl, psId, out._id));
+ approvalsUtil.byPatchSetUser(db.get(), ctl, psId,
+ new Account.Id(out._accountId)));
}
public ReviewerInfo format(ReviewerInfo out, ChangeControl ctl,
@@ -109,8 +109,11 @@ public class ReviewerJson {
ChangeData cd = changeDataFactory.create(db.get(), ctl);
PatchSet ps = cd.currentPatchSet();
if (ps != null) {
- for (SubmitRecord rec :
- ctl.canSubmit(db.get(), ps, cd, true, false, true)) {
+ for (SubmitRecord rec : new SubmitRuleEvaluator(cd)
+ .setPatchSet(ps)
+ .setFastEvalLabels(true)
+ .setAllowDraft(true)
+ .evaluate()) {
if (rec.labels == null) {
continue;
}
@@ -135,7 +138,7 @@ public class ReviewerJson {
Map<String, String> approvals;
protected ReviewerInfo(Account.Id id) {
- super(id);
+ super(id.get());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
new file mode 100644
index 0000000000..c4c918644e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
@@ -0,0 +1,187 @@
+// Copyright (C) 2014 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.common.base.Splitter;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.AccountExternalIdAccess;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.util.CharArraySet;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.IntField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.Version;
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+/**
+ * The suggest oracle may be called many times in rapid succession during the
+ * course of one operation.
+ * It would be easy to have a simple Cache<Boolean, List<Account>> with a short
+ * expiration time of 30s.
+ * Cache only has a single key we're just using Cache for the expiration behavior.
+ */
+@Singleton
+public class ReviewerSuggestionCache {
+ private static final Logger log = LoggerFactory
+ .getLogger(ReviewerSuggestionCache.class);
+
+ private static final String ID = "id";
+ private static final String NAME = "name";
+ private static final String EMAIL = "email";
+ private static final String USERNAME = "username";
+ private static final String[] ALL = {ID, NAME, EMAIL, USERNAME};
+
+ private final LoadingCache<Boolean, IndexSearcher> cache;
+ private final Provider<ReviewDb> db;
+
+ @Inject
+ ReviewerSuggestionCache(Provider<ReviewDb> db,
+ @GerritServerConfig Config cfg) {
+ this.db = db;
+ long expiration = ConfigUtil.getTimeUnit(cfg,
+ "suggest", null, "fullTextSearchRefresh",
+ TimeUnit.HOURS.toMillis(1),
+ TimeUnit.MILLISECONDS);
+ this.cache =
+ CacheBuilder.newBuilder().maximumSize(1)
+ .expireAfterWrite(expiration, TimeUnit.MILLISECONDS)
+ .build(new CacheLoader<Boolean, IndexSearcher>() {
+ @Override
+ public IndexSearcher load(Boolean key) throws Exception {
+ return index();
+ }
+ });
+ }
+
+ List<AccountInfo> search(String query, int n) throws IOException {
+ IndexSearcher searcher = get();
+ if (searcher == null) {
+ return Collections.emptyList();
+ }
+
+ List<String> segments = Splitter.on(' ').omitEmptyStrings().splitToList(
+ query.toLowerCase());
+ BooleanQuery q = new BooleanQuery();
+ for (String field : ALL) {
+ BooleanQuery and = new BooleanQuery();
+ for (String s : segments) {
+ and.add(new PrefixQuery(new Term(field, s)), Occur.MUST);
+ }
+ q.add(and, Occur.SHOULD);
+ }
+
+ TopDocs results = searcher.search(q, n);
+ ScoreDoc[] hits = results.scoreDocs;
+
+ List<AccountInfo> result = new LinkedList<>();
+
+ for (ScoreDoc h : hits) {
+ Document doc = searcher.doc(h.doc);
+
+ AccountInfo info = new AccountInfo(
+ doc.getField(ID).numericValue().intValue());
+ info.name = doc.get(NAME);
+ info.email = doc.get(EMAIL);
+ info.username = doc.get(USERNAME);
+ result.add(info);
+ }
+
+ return result;
+ }
+
+ private IndexSearcher get() {
+ try {
+ return cache.get(true);
+ } catch (ExecutionException e) {
+ log.warn("Cannot fetch reviewers from cache", e);
+ return null;
+ }
+ }
+
+ private IndexSearcher index() throws IOException, OrmException {
+ RAMDirectory idx = new RAMDirectory();
+ @SuppressWarnings("deprecation")
+ IndexWriterConfig config = new IndexWriterConfig(
+ Version.LUCENE_4_10_1,
+ new StandardAnalyzer(CharArraySet.EMPTY_SET));
+ config.setOpenMode(OpenMode.CREATE);
+
+ try (IndexWriter writer = new IndexWriter(idx, config)) {
+ for (Account a : db.get().accounts().all()) {
+ if (a.isActive()) {
+ addAccount(writer, a);
+ }
+ }
+ }
+
+ return new IndexSearcher(DirectoryReader.open(idx));
+ }
+
+ private void addAccount(IndexWriter writer, Account a)
+ throws IOException, OrmException {
+ Document doc = new Document();
+ doc.add(new IntField(ID, a.getId().get(), Store.YES));
+ if (a.getFullName() != null) {
+ doc.add(new TextField(NAME, a.getFullName(), Store.YES));
+ }
+ if (a.getPreferredEmail() != null) {
+ doc.add(new StringField(EMAIL, a.getPreferredEmail().toLowerCase(),
+ Store.YES));
+ doc.add(new TextField(EMAIL, a.getPreferredEmail(), Store.YES));
+ }
+ AccountExternalIdAccess extIdAccess = db.get().accountExternalIds();
+ String username = AccountState.getUserName(
+ extIdAccess.byAccount(a.getId()).toList());
+ if (username != null) {
+ doc.add(new StringField(USERNAME, username, Store.YES));
+ }
+ writer.addDocument(doc);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
index eb9f4eaeac..e58d1a1259 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.common.base.Optional;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestResource.HasETag;
import com.google.gerrit.extensions.restapi.RestView;
@@ -21,6 +22,7 @@ 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.IdentifiedUser;
+import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.TypeLiteral;
@@ -31,11 +33,18 @@ public class RevisionResource implements RestResource, HasETag {
private final ChangeResource change;
private final PatchSet ps;
+ private final Optional<ChangeEdit> edit;
private boolean cacheable = true;
public RevisionResource(ChangeResource change, PatchSet ps) {
+ this(change, ps, Optional.<ChangeEdit> absent());
+ }
+
+ public RevisionResource(ChangeResource change, PatchSet ps,
+ Optional<ChangeEdit> edit) {
this.change = change;
this.ps = ps;
+ this.edit = edit;
}
public boolean isCacheable() {
@@ -82,4 +91,17 @@ public class RevisionResource implements RestResource, HasETag {
cacheable = false;
return this;
}
+
+ Optional<ChangeEdit> getEdit() {
+ return edit;
+ }
+
+ @Override
+ public String toString() {
+ String s = ps.getId().toString();
+ if (edit.isPresent()) {
+ s = "edit:" + s;
+ }
+ return s;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index 239aae2bd1..bb5775be0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -14,8 +14,14 @@
package com.google.gerrit.server.change;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
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;
@@ -24,11 +30,15 @@ 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.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -36,12 +46,15 @@ import java.util.List;
public class Revisions implements ChildCollection<ChangeResource, RevisionResource> {
private final DynamicMap<RestView<RevisionResource>> views;
private final Provider<ReviewDb> dbProvider;
+ private final ChangeEditUtil editUtil;
@Inject
Revisions(DynamicMap<RestView<RevisionResource>> views,
- Provider<ReviewDb> dbProvider) {
+ Provider<ReviewDb> dbProvider,
+ ChangeEditUtil editUtil) {
this.views = views;
this.dbProvider = dbProvider;
+ this.editUtil = editUtil;
}
@Override
@@ -56,7 +69,8 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
@Override
public RevisionResource parse(ChangeResource change, IdString id)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, AuthException, OrmException,
+ IOException {
if (id.equals("current")) {
PatchSet.Id p = change.getChange().currentPatchSetId();
PatchSet ps = p != null ? dbProvider.get().patchSets().get(p) : null;
@@ -65,17 +79,23 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
}
throw new ResourceNotFoundException(id);
}
- List<PatchSet> match = Lists.newArrayListWithExpectedSize(2);
- for (PatchSet ps : find(change, id.get())) {
- Change.Id changeId = ps.getId().getParentKey();
- if (changeId.equals(change.getChange().getId()) && visible(change, ps)) {
- match.add(ps);
+
+ List<RevisionResource> match = Lists.newArrayListWithExpectedSize(2);
+ for (RevisionResource rsrc : find(change, id.get())) {
+ if (visible(change, rsrc.getPatchSet())) {
+ match.add(rsrc);
}
}
- if (match.size() != 1) {
- throw new ResourceNotFoundException(id);
+ switch (match.size()) {
+ case 0:
+ throw new ResourceNotFoundException(id);
+ case 1:
+ return match.get(0);
+ default:
+ throw new ResourceNotFoundException(
+ "Multiple patch sets for \"" + id.get() + "\": "
+ + Joiner.on("; ").join(match));
}
- return new RevisionResource(change, match.get(0));
}
private boolean visible(ChangeResource change, PatchSet ps)
@@ -83,19 +103,13 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
return change.getControl().isPatchVisible(ps, dbProvider.get());
}
- private List<PatchSet> find(ChangeResource change, String id)
- throws OrmException {
- ReviewDb db = dbProvider.get();
-
- if (id.length() < 6 && id.matches("^[1-9][0-9]{0,4}$")) {
+ private List<RevisionResource> find(ChangeResource change, String id)
+ throws OrmException, IOException, AuthException {
+ if (id.equals("0")) {
+ return loadEdit(change, null);
+ } else if (id.length() < 6 && id.matches("^[1-9][0-9]{0,4}$")) {
// Legacy patch set number syntax.
- PatchSet ps = dbProvider.get().patchSets().get(new PatchSet.Id(
- change.getChange().getId(),
- Integer.parseInt(id)));
- if (ps != null) {
- return Collections.singletonList(ps);
- }
- return Collections.emptyList();
+ return byLegacyPatchSetId(change, id);
} else if (id.length() < 4 || id.length() > RevId.LEN) {
// Require a minimum of 4 digits.
// Impossibly long identifier will never match.
@@ -107,19 +121,71 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
// for all patch sets in the change.
RevId revid = new RevId(id);
if (revid.isComplete()) {
- return db.patchSets().byRevision(revid).toList();
- } else {
- return db.patchSets().byRevisionRange(revid, revid.max()).toList();
+ List<RevisionResource> m = toResources(change, findExactMatch(revid));
+ return m.isEmpty() ? loadEdit(change, revid) : m;
}
+ return toResources(change, findByPrefix(revid));
} else {
// Chance of collision rises; look at all patch sets on the change.
- List<PatchSet> out = Lists.newArrayList();
- for (PatchSet ps : db.patchSets().byChange(change.getChange().getId())) {
+ List<RevisionResource> out = Lists.newArrayList();
+ for (PatchSet ps : dbProvider.get().patchSets()
+ .byChange(change.getChange().getId())) {
if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) {
- out.add(ps);
+ out.add(new RevisionResource(change, ps));
}
}
return out;
}
}
+
+ private List<RevisionResource> byLegacyPatchSetId(ChangeResource change,
+ String id) throws OrmException {
+ PatchSet ps = dbProvider.get().patchSets().get(new PatchSet.Id(
+ change.getChange().getId(),
+ Integer.parseInt(id)));
+ if (ps != null) {
+ return Collections.singletonList(new RevisionResource(change, ps));
+ }
+ return Collections.emptyList();
+ }
+
+ private ResultSet<PatchSet> findExactMatch(RevId revid) throws OrmException {
+ return dbProvider.get().patchSets().byRevision(revid);
+ }
+
+ private ResultSet<PatchSet> findByPrefix(RevId revid) throws OrmException {
+ return dbProvider.get().patchSets().byRevisionRange(revid, revid.max());
+ }
+
+ private List<RevisionResource> loadEdit(ChangeResource change, RevId revid)
+ throws AuthException, IOException {
+ Optional<ChangeEdit> edit = editUtil.byChange(change.getChange());
+ if (edit.isPresent()) {
+ PatchSet ps = new PatchSet(new PatchSet.Id(
+ change.getChange().getId(), 0));
+ ps.setRevision(edit.get().getRevision());
+ if (revid == null || edit.get().getRevision().equals(revid)) {
+ return Collections.singletonList(
+ new RevisionResource(change, ps, edit));
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ private static List<RevisionResource> toResources(final ChangeResource change,
+ Iterable<PatchSet> patchSets) {
+ final Change.Id changeId = change.getChange().getId();
+ return FluentIterable.from(patchSets)
+ .filter(new Predicate<PatchSet>() {
+ @Override
+ public boolean apply(PatchSet in) {
+ return changeId.equals(in.getId().getParentKey());
+ }
+ }).transform(new Function<PatchSet, RevisionResource>() {
+ @Override
+ public RevisionResource apply(PatchSet in) {
+ return new RevisionResource(change, in);
+ }
+ }).toList();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 371902871a..5f67e6e68f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -16,8 +16,9 @@ package com.google.gerrit.server.change;
import static com.google.gerrit.common.data.SubmitRecord.Status.OK;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
@@ -27,9 +28,11 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.common.data.SubmitRecord;
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.RestModifyView;
@@ -38,19 +41,17 @@ 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.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.account.AccountsCollection;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LabelNormalizer;
@@ -59,9 +60,12 @@ import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.OrmRuntimeException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -71,9 +75,13 @@ import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -81,8 +89,16 @@ import java.util.Map;
@Singleton
public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
UiAction<RevisionResource> {
+ private static final Logger log = LoggerFactory.getLogger(Submit.class);
+
private static final String DEFAULT_TOOLTIP =
"Submit patch set ${patchSet} into ${branch}";
+ private static final String DEFAULT_TOPIC_TOOLTIP =
+ "Submit all ${topicSize} changes of the same topic";
+ private static final String BLOCKED_TOPIC_TOOLTIP =
+ "Other changes in this topic are not ready";
+ private static final String BLOCKED_HIDDEN_TOPIC_TOOLTIP =
+ "Other hidden changes in this topic are not ready";
public enum Status {
SUBMITTED, MERGED
@@ -102,6 +118,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private final Provider<ReviewDb> dbProvider;
private final GitRepositoryManager repoManager;
private final IdentifiedUser.GenericFactory userFactory;
+ private final ChangeData.Factory changeDataFactory;
private final ChangeUpdate.Factory updateFactory;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
@@ -112,12 +129,17 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private final ChangesCollection changes;
private final String label;
private final ParameterizedString titlePattern;
+ private final String submitTopicLabel;
+ private final ParameterizedString submitTopicTooltip;
+ private final boolean submitWholeTopic;
+ private final Provider<InternalChangeQuery> queryProvider;
@Inject
Submit(@GerritPersonIdent PersonIdent serverIdent,
Provider<ReviewDb> dbProvider,
GitRepositoryManager repoManager,
IdentifiedUser.GenericFactory userFactory,
+ ChangeData.Factory changeDataFactory,
ChangeUpdate.Factory updateFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
@@ -126,11 +148,13 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
ChangesCollection changes,
ChangeIndexer indexer,
LabelNormalizer labelNormalizer,
- @GerritServerConfig Config cfg) {
+ @GerritServerConfig Config cfg,
+ Provider<InternalChangeQuery> queryProvider) {
this.serverIdent = serverIdent;
this.dbProvider = dbProvider;
this.repoManager = repoManager;
this.userFactory = userFactory;
+ this.changeDataFactory = changeDataFactory;
this.updateFactory = updateFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
@@ -139,12 +163,20 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
this.changes = changes;
this.indexer = indexer;
this.labelNormalizer = labelNormalizer;
- this.label = Objects.firstNonNull(
+ this.label = MoreObjects.firstNonNull(
Strings.emptyToNull(cfg.getString("change", null, "submitLabel")),
"Submit");
- this.titlePattern = new ParameterizedString(Objects.firstNonNull(
+ this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull(
cfg.getString("change", null, "submitTooltip"),
DEFAULT_TOOLTIP));
+ submitWholeTopic = cfg.getBoolean("change", null, "submitWholeTopic" , false);
+ this.submitTopicLabel = MoreObjects.firstNonNull(
+ Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
+ "Submit whole topic");
+ this.submitTopicTooltip = new ParameterizedString(MoreObjects.firstNonNull(
+ cfg.getString("change", null, "submitTopicTooltip"),
+ DEFAULT_TOPIC_TOOLTIP));
+ this.queryProvider = queryProvider;
}
@Override
@@ -200,27 +232,98 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
if (msg != null) {
throw new ResourceConflictException(msg.getMessage());
}
+ //$FALL-THROUGH$
default:
throw new ResourceConflictException("change is " + status(change));
}
}
+ /**
+ * @param changes list of changes to be submitted at once
+ * @param identifiedUser the user who is checking to submit
+ * @return a reason why any of the changes is not submittable or null
+ */
+ private String problemsForSubmittingChanges(List<ChangeData> changes,
+ IdentifiedUser identifiedUser) {
+ for (ChangeData c : changes) {
+ try {
+ ChangeControl changeControl = c.changeControl().forUser(
+ identifiedUser);
+ if (!changeControl.isVisible(dbProvider.get())) {
+ return BLOCKED_HIDDEN_TOPIC_TOOLTIP;
+ }
+ if (!changeControl.canSubmit()) {
+ return BLOCKED_TOPIC_TOOLTIP;
+ }
+ checkSubmitRule(c, c.currentPatchSet(), false);
+ } catch (OrmException e) {
+ log.error("Error checking if change is submittable", e);
+ throw new OrmRuntimeException(e);
+ } catch (ResourceConflictException e) {
+ return BLOCKED_TOPIC_TOOLTIP;
+ }
+ }
+ return null;
+ }
+
@Override
public UiAction.Description getDescription(RevisionResource resource) {
PatchSet.Id current = resource.getChange().currentPatchSetId();
- RevId revId = resource.getPatchSet().getRevision();
- Map<String, String> params = ImmutableMap.of(
- "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
- "branch", resource.getChange().getDest().getShortName(),
- "commit", ObjectId.fromString(revId.get()).abbreviate(7).name());
-
- return new UiAction.Description()
- .setLabel(label)
- .setTitle(Strings.emptyToNull(titlePattern.replace(params)))
- .setVisible(!resource.getPatchSet().isDraft()
- && resource.getChange().getStatus().isOpen()
- && resource.getPatchSet().getId().equals(current)
- && resource.getControl().canSubmit());
+ String topic = resource.getChange().getTopic();
+ boolean visible = !resource.getPatchSet().isDraft()
+ && resource.getChange().getStatus().isOpen()
+ && resource.getPatchSet().getId().equals(current)
+ && resource.getControl().canSubmit();
+
+ ReviewDb db = dbProvider.get();
+ ChangeData cd = changeDataFactory.create(db, resource.getControl());
+ if (problemsForSubmittingChanges(Arrays.asList(cd), resource.getUser())
+ != null) {
+ visible = false;
+ }
+
+ if (!visible) {
+ return new UiAction.Description()
+ .setLabel("")
+ .setTitle("")
+ .setVisible(false);
+ }
+ if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
+ List<ChangeData> changesByTopic = null;
+ try {
+ changesByTopic = queryProvider.get().byTopicOpen(topic);
+ } catch (OrmException e) {
+ throw new OrmRuntimeException(e);
+ }
+ Map<String, String> params = ImmutableMap.of(
+ "topicSize", String.valueOf(changesByTopic.size()));
+ String topicProblems = problemsForSubmittingChanges(changesByTopic,
+ resource.getUser());
+ if (topicProblems != null) {
+ return new UiAction.Description()
+ .setLabel(submitTopicLabel)
+ .setTitle(topicProblems)
+ .setVisible(true)
+ .setEnabled(false);
+ } else {
+ return new UiAction.Description()
+ .setLabel(submitTopicLabel)
+ .setTitle(Strings.emptyToNull(
+ submitTopicTooltip.replace(params)))
+ .setVisible(true)
+ .setEnabled(true);
+ }
+ } else {
+ RevId revId = resource.getPatchSet().getRevision();
+ Map<String, String> params = ImmutableMap.of(
+ "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
+ "branch", resource.getChange().getDest().getShortName(),
+ "commit", ObjectId.fromString(revId.get()).abbreviate(7).name());
+ return new UiAction.Description()
+ .setLabel(label)
+ .setTitle(Strings.emptyToNull(titlePattern.replace(params)))
+ .setVisible(true);
+ }
}
/**
@@ -242,36 +345,41 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
.orNull();
}
- public Change submit(RevisionResource rsrc, IdentifiedUser caller,
+ private Change submitToDatabase(ReviewDb db, Change.Id changeId,
+ final Timestamp timestamp) throws OrmException {
+ return db.changes().atomicUpdate(changeId,
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus().isOpen()) {
+ change.setStatus(Change.Status.SUBMITTED);
+ change.setLastUpdatedOn(timestamp);
+ return change;
+ }
+ return null;
+ }
+ });
+ }
+
+ private Change submitThisChange(RevisionResource rsrc, IdentifiedUser caller,
boolean force) throws ResourceConflictException, OrmException,
IOException {
- List<SubmitRecord> submitRecords = checkSubmitRule(rsrc, force);
+ ReviewDb db = dbProvider.get();
+ ChangeData cd = changeDataFactory.create(db, rsrc.getControl());
+ List<SubmitRecord> submitRecords = checkSubmitRule(cd,
+ rsrc.getPatchSet(), force);
+
final Timestamp timestamp = TimeUtil.nowTs();
Change change = rsrc.getChange();
ChangeUpdate update = updateFactory.create(rsrc.getControl(), timestamp);
update.submit(submitRecords);
- ReviewDb db = dbProvider.get();
db.changes().beginTransaction(change.getId());
try {
BatchMetaDataUpdate batch = approve(rsrc, update, caller, timestamp);
// Write update commit after all normalized label commits.
batch.write(update, new CommitBuilder());
-
- change = db.changes().atomicUpdate(
- change.getId(),
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isOpen()) {
- change.setStatus(Change.Status.SUBMITTED);
- change.setLastUpdatedOn(timestamp);
- ChangeUtil.computeSortKey(change);
- return change;
- }
- return null;
- }
- });
+ change = submitToDatabase(db, change.getId(), timestamp);
if (change == null) {
return null;
}
@@ -283,6 +391,62 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
return change;
}
+ private Change submitWholeTopic(RevisionResource rsrc, IdentifiedUser caller,
+ boolean force, String topic) throws ResourceConflictException, OrmException,
+ IOException {
+ Preconditions.checkNotNull(topic);
+ final Timestamp timestamp = TimeUtil.nowTs();
+
+ ReviewDb db = dbProvider.get();
+ ChangeData cd = changeDataFactory.create(db, rsrc.getControl());
+
+ List<ChangeData> changesByTopic = queryProvider.get().byTopicOpen(topic);
+ String problems = problemsForSubmittingChanges(changesByTopic, caller);
+ if (problems != null) {
+ throw new ResourceConflictException(problems);
+ }
+
+ Change change = rsrc.getChange();
+ ChangeUpdate update = updateFactory.create(rsrc.getControl(), timestamp);
+
+ List<SubmitRecord> submitRecords = checkSubmitRule(cd,
+ rsrc.getPatchSet(), force);
+ update.submit(submitRecords);
+
+ db.changes().beginTransaction(change.getId());
+ try {
+ BatchMetaDataUpdate batch = approve(rsrc, update, caller, timestamp);
+ // Write update commit after all normalized label commits.
+ batch.write(update, new CommitBuilder());
+
+ for (ChangeData c : changesByTopic) {
+ if (submitToDatabase(db, c.getId(), timestamp) == null) {
+ return null;
+ }
+ }
+ db.commit();
+ } finally {
+ db.rollback();
+ }
+ List<Change.Id> ids = new ArrayList<>(changesByTopic.size());
+ for (ChangeData c : changesByTopic) {
+ ids.add(c.getId());
+ }
+ indexer.indexAsync(ids).checkedGet();
+ return change;
+ }
+
+ public Change submit(RevisionResource rsrc, IdentifiedUser caller,
+ boolean force) throws ResourceConflictException, OrmException,
+ IOException {
+ String topic = rsrc.getChange().getTopic();
+ if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
+ return submitWholeTopic(rsrc, caller, force, topic);
+ } else {
+ return submitThisChange(rsrc, caller, force);
+ }
+ }
+
private BatchMetaDataUpdate approve(RevisionResource rsrc,
ChangeUpdate update, IdentifiedUser caller, Timestamp timestamp)
throws OrmException {
@@ -296,7 +460,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
}
PatchSetApproval submit = ApprovalsUtil.getSubmitter(psId, byKey.values());
- if (submit == null || submit.getAccountId() != caller.getAccountId()) {
+ if (submit == null
+ || !submit.getAccountId().equals(caller.getAccountId())) {
submit = new PatchSetApproval(
new PatchSetApproval.Key(
rsrc.getPatchSet().getId(),
@@ -321,7 +486,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
update.putApproval(submit.getLabel(), submit.getValue());
dbProvider.get().patchSetApprovals().upsert(normalized.getNormalized());
- dbProvider.get().patchSetApprovals().delete(normalized.getDeleted());
+ dbProvider.get().patchSetApprovals().delete(normalized.deleted());
try {
return saveToBatch(rsrc, update, normalized, timestamp);
@@ -334,11 +499,11 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
ChangeUpdate callerUpdate, LabelNormalizer.Result normalized,
Timestamp timestamp) throws IOException {
Table<Account.Id, String, Optional<Short>> byUser = HashBasedTable.create();
- for (PatchSetApproval psa : normalized.getUpdated()) {
+ for (PatchSetApproval psa : normalized.updated()) {
byUser.put(psa.getAccountId(), psa.getLabel(),
Optional.of(psa.getValue()));
}
- for (PatchSetApproval psa : normalized.getDeleted()) {
+ for (PatchSetApproval psa : normalized.deleted()) {
byUser.put(psa.getAccountId(), psa.getLabel(), Optional.<Short> absent());
}
@@ -373,11 +538,12 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
}
}
- private List<SubmitRecord> checkSubmitRule(RevisionResource rsrc,
- boolean force) throws ResourceConflictException {
- List<SubmitRecord> results = rsrc.getControl().canSubmit(
- dbProvider.get(),
- rsrc.getPatchSet());
+ private List<SubmitRecord> checkSubmitRule(ChangeData cd,
+ PatchSet patchSet, boolean force)
+ throws ResourceConflictException, OrmException {
+ List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
+ .setPatchSet(patchSet)
+ .evaluate();
Optional<SubmitRecord> ok = findOkRecord(results);
if (ok.isPresent()) {
// Rules supplied a valid solution.
@@ -386,9 +552,9 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
return results;
} else if (results.isEmpty()) {
throw new IllegalStateException(String.format(
- "ChangeControl.canSubmit returned empty list for %s in %s",
- rsrc.getPatchSet().getId(),
- rsrc.getChange().getProject().get()));
+ "SubmitRuleEvaluator.evaluate returned empty list for %s in %s",
+ patchSet.getId(),
+ cd.change().getProject().get()));
}
for (SubmitRecord record : results) {
@@ -410,17 +576,23 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
continue;
case REJECT:
- if (msg.length() > 0) msg.append("; ");
+ if (msg.length() > 0) {
+ msg.append("; ");
+ }
msg.append("blocked by ").append(lbl.label);
continue;
case NEED:
- if (msg.length() > 0) msg.append("; ");
+ if (msg.length() > 0) {
+ msg.append("; ");
+ }
msg.append("needs ").append(lbl.label);
continue;
case IMPOSSIBLE:
- if (msg.length() > 0) msg.append("; ");
+ if (msg.length() > 0) {
+ msg.append("; ");
+ }
msg.append("needs ").append(lbl.label)
.append(" (check project access)");
continue;
@@ -429,8 +601,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
throw new IllegalStateException(String.format(
"Unsupported SubmitRecord.Label %s for %s in %s",
lbl.toString(),
- rsrc.getPatchSet().getId(),
- rsrc.getChange().getProject().get()));
+ patchSet.getId(),
+ cd.change().getProject().get()));
}
}
throw new ResourceConflictException(msg.toString());
@@ -439,8 +611,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
throw new IllegalStateException(String.format(
"Unsupported SubmitRecord %s for %s in %s",
record,
- rsrc.getPatchSet().getId(),
- rsrc.getChange().getProject().get()));
+ patchSet.getId().getId(),
+ cd.change().getProject().get()));
}
}
throw new IllegalStateException();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
index b95d664e71..4561ae45bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
@@ -14,13 +14,18 @@
package com.google.gerrit.server.change;
-import com.google.common.base.Objects;
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.AccountInfo;
+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.RestReadView;
import com.google.gerrit.extensions.restapi.Url;
@@ -32,12 +37,11 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountControl;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountVisibility;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.group.GroupJson.GroupBaseInfo;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.server.OrmException;
@@ -48,19 +52,36 @@ import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
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.Set;
public class SuggestReviewers implements RestReadView<ChangeResource> {
-
private static final String MAX_SUFFIX = "\u9fa5";
- private static final int MAX = 10;
+ private static final int DEFAULT_MAX_SUGGESTED = 10;
+ private static final int DEFAULT_MAX_MATCHES = 100;
+ private static final Ordering<SuggestedReviewerInfo> ORDERING =
+ Ordering.natural().onResultOf(new Function<SuggestedReviewerInfo, String>() {
+ @Nullable
+ @Override
+ public String apply(@Nullable SuggestedReviewerInfo suggestedReviewerInfo) {
+ if (suggestedReviewerInfo == null) {
+ return null;
+ }
+ return suggestedReviewerInfo.account != null
+ ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email,
+ Strings.nullToEmpty(suggestedReviewerInfo.account.name))
+ : Strings.nullToEmpty(suggestedReviewerInfo.group.name);
+ }
+ });
- private final AccountInfo.Loader.Factory accountLoaderFactory;
- private final AccountControl.Factory accountControlFactory;
+ private final AccountLoader accountLoader;
+ private final AccountControl accountControl;
private final GroupMembers.Factory groupMembersFactory;
private final AccountCache accountCache;
private final Provider<ReviewDb> dbProvider;
@@ -72,11 +93,17 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
private final int maxAllowed;
private int limit;
private String query;
+ private boolean useFullTextSearch;
+ private final int fullTextMaxMatches;
+ private final int maxSuggestedReviewers;
+ private final ReviewerSuggestionCache reviewerSuggestionCache;
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT",
usage = "maximum number of reviewers to list")
public void setLimit(int l) {
- this.limit = l <= 0 ? MAX : Math.min(l, MAX);
+ this.limit =
+ l <= 0 ? maxSuggestedReviewers : Math.min(l,
+ maxSuggestedReviewers);
}
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY",
@@ -87,7 +114,7 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
@Inject
SuggestReviewers(AccountVisibility av,
- AccountInfo.Loader.Factory accountLoaderFactory,
+ AccountLoader.Factory accountLoaderFactory,
AccountControl.Factory accountControlFactory,
AccountCache accountCache,
GroupMembers.Factory groupMembersFactory,
@@ -95,21 +122,29 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
Provider<CurrentUser> currentUser,
Provider<ReviewDb> dbProvider,
@GerritServerConfig Config cfg,
- GroupBackend groupBackend) {
- this.accountLoaderFactory = accountLoaderFactory;
- this.accountControlFactory = accountControlFactory;
+ GroupBackend groupBackend,
+ ReviewerSuggestionCache reviewerSuggestionCache) {
+ this.accountLoader = accountLoaderFactory.create(true);
+ this.accountControl = accountControlFactory.get();
this.accountCache = accountCache;
this.groupMembersFactory = groupMembersFactory;
this.dbProvider = dbProvider;
this.identifiedUserFactory = identifiedUserFactory;
this.currentUser = currentUser;
this.groupBackend = groupBackend;
-
+ this.reviewerSuggestionCache = reviewerSuggestionCache;
+ this.maxSuggestedReviewers =
+ cfg.getInt("suggest", "maxSuggestedReviewers", DEFAULT_MAX_SUGGESTED);
+ this.limit = this.maxSuggestedReviewers;
+ this.fullTextMaxMatches =
+ cfg.getInt("suggest", "fullTextSearchMaxMatches",
+ DEFAULT_MAX_MATCHES);
String suggest = cfg.getString("suggest", null, "accounts");
if ("OFF".equalsIgnoreCase(suggest)
|| "false".equalsIgnoreCase(suggest)) {
this.suggestAccounts = false;
} else {
+ this.useFullTextSearch = cfg.getBoolean("suggest", "fullTextSearch", false);
this.suggestAccounts = (av != AccountVisibility.NONE);
}
@@ -119,7 +154,7 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
}
private interface VisibilityControl {
- boolean isVisibleTo(Account account) throws OrmException;
+ boolean isVisibleTo(Account.Id account) throws OrmException;
}
@Override
@@ -134,12 +169,18 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
}
VisibilityControl visibilityControl = getVisibility(rsrc);
- List<AccountInfo> suggestedAccounts = suggestAccount(visibilityControl);
- accountLoaderFactory.create(true).fill(suggestedAccounts);
+ List<AccountInfo> suggestedAccounts;
+ if (useFullTextSearch) {
+ suggestedAccounts = suggestAccountFullTextSearch(visibilityControl);
+ } else {
+ suggestedAccounts = suggestAccount(visibilityControl);
+ }
List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
for (AccountInfo a : suggestedAccounts) {
- reviewer.add(new SuggestedReviewerInfo(a));
+ SuggestedReviewerInfo info = new SuggestedReviewerInfo();
+ info.account = a;
+ reviewer.add(info);
}
Project p = rsrc.getControl().getProject();
@@ -149,11 +190,13 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
GroupBaseInfo info = new GroupBaseInfo();
info.id = Url.encode(g.getUUID().get());
info.name = g.getName();
- reviewer.add(new SuggestedReviewerInfo(info));
+ SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
+ suggestedReviewerInfo.group = info;
+ reviewer.add(suggestedReviewerInfo);
}
}
- Collections.sort(reviewer);
+ reviewer = ORDERING.immutableSortedCopy(reviewer);
if (reviewer.size() <= limit) {
return reviewer;
} else {
@@ -165,16 +208,16 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
if (rsrc.getControl().getRefControl().isVisibleByRegisteredUsers()) {
return new VisibilityControl() {
@Override
- public boolean isVisibleTo(Account account) throws OrmException {
+ public boolean isVisibleTo(Account.Id account) throws OrmException {
return true;
}
};
} else {
return new VisibilityControl() {
@Override
- public boolean isVisibleTo(Account account) throws OrmException {
+ public boolean isVisibleTo(Account.Id account) throws OrmException {
IdentifiedUser who =
- identifiedUserFactory.create(dbProvider, account.getId());
+ identifiedUserFactory.create(dbProvider, account);
// we can't use changeControl directly as it won't suggest reviewers
// to drafts
return rsrc.getControl().forUser(who).isRefVisible();
@@ -193,16 +236,22 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
String a = query;
String b = a + MAX_SUFFIX;
- LinkedHashMap<Account.Id, AccountInfo> r = Maps.newLinkedHashMap();
+ Map<Account.Id, AccountInfo> r = new LinkedHashMap<>();
+ Map<Account.Id, String> queryEmail = new HashMap<>();
+
for (Account p : dbProvider.get().accounts()
.suggestByFullName(a, b, limit)) {
- addSuggestion(r, p, new AccountInfo(p.getId()), visibilityControl);
+ if (p.isActive()) {
+ addSuggestion(r, p.getId(), visibilityControl);
+ }
}
if (r.size() < limit) {
for (Account p : dbProvider.get().accounts()
.suggestByPreferredEmail(a, b, limit - r.size())) {
- addSuggestion(r, p, new AccountInfo(p.getId()), visibilityControl);
+ if (p.isActive()) {
+ addSuggestion(r, p.getId(), visibilityControl);
+ }
}
}
@@ -211,26 +260,54 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
.suggestByEmailAddress(a, b, limit - r.size())) {
if (!r.containsKey(e.getAccountId())) {
Account p = accountCache.get(e.getAccountId()).getAccount();
- AccountInfo info = new AccountInfo(p.getId());
- addSuggestion(r, p, info, visibilityControl);
+ if (p.isActive()) {
+ if (addSuggestion(r, p.getId(), visibilityControl)) {
+ queryEmail.put(e.getAccountId(), e.getEmailAddress());
+ }
+ }
}
}
}
- return Lists.newArrayList(r.values());
+ accountLoader.fill();
+ for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
+ AccountInfo info = r.get(p.getKey());
+ if (info != null) {
+ info.email = p.getValue();
+ }
+ }
+ return new ArrayList<>(r.values());
}
- private void addSuggestion(Map<Account.Id, AccountInfo> map, Account account,
- AccountInfo info, VisibilityControl visibilityControl)
+ private List<AccountInfo> suggestAccountFullTextSearch(
+ VisibilityControl visibilityControl) throws IOException, OrmException {
+ List<AccountInfo> results = reviewerSuggestionCache.search(
+ query, fullTextMaxMatches);
+
+ Iterator<AccountInfo> it = results.iterator();
+ while (it.hasNext()) {
+ Account.Id accountId = new Account.Id(it.next()._accountId);
+ if (!(visibilityControl.isVisibleTo(accountId)
+ && accountControl.canSee(accountId))) {
+ it.remove();
+ }
+ }
+
+ return results;
+ }
+
+ private boolean addSuggestion(Map<Account.Id, AccountInfo> map,
+ Account.Id account, VisibilityControl visibilityControl)
throws OrmException {
- if (!map.containsKey(account.getId())
- && account.isActive()
+ if (!map.containsKey(account)
// Can the suggestion see the change?
&& visibilityControl.isVisibleTo(account)
// Can the account see the current user?
- && accountControlFactory.get().canSee(account)) {
- map.put(account.getId(), info);
+ && accountControl.canSee(account)) {
+ map.put(account, accountLoader.get(account));
+ return true;
}
+ return false;
}
private boolean suggestGroupAsReviewer(Project project,
@@ -255,7 +332,7 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
// require that at least one member in the group can see the change
for (Account account : members) {
- if (visibilityControl.isVisibleTo(account)) {
+ if (visibilityControl.isVisibleTo(account.getId())) {
return true;
}
}
@@ -267,29 +344,4 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
return false;
}
-
- public static class SuggestedReviewerInfo implements Comparable<SuggestedReviewerInfo> {
- public AccountInfo account;
- public GroupBaseInfo group;
-
- SuggestedReviewerInfo(AccountInfo a) {
- this.account = a;
- }
-
- SuggestedReviewerInfo(GroupBaseInfo g) {
- this.group = g;
- }
-
- @Override
- public int compareTo(SuggestedReviewerInfo o) {
- return getSortValue().compareTo(o.getSortValue());
- }
-
- private String getSortValue() {
- return account != null
- ? Objects.firstNonNull(account.email,
- Strings.nullToEmpty(account.name))
- : Strings.nullToEmpty(group.name);
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index 25070abffa..d3c4132e33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -14,36 +14,27 @@
package com.google.gerrit.server.change;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.SubmitRecord;
+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.DefaultInput;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.RulesCache;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.change.TestSubmitRule.Input;
-import com.google.gerrit.server.project.RuleEvalException;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.googlecode.prolog_cafe.lang.Term;
-
import org.kohsuke.args4j.Option;
-import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.Map;
@@ -61,7 +52,7 @@ public class TestSubmitRule implements RestModifyView<RevisionResource, Input> {
private final Provider<ReviewDb> db;
private final ChangeData.Factory changeDataFactory;
private final RulesCache rules;
- private final AccountInfo.Loader.Factory accountInfoFactory;
+ private final AccountLoader.Factory accountInfoFactory;
@Option(name = "--filters", usage = "impact of filters in parent projects")
private Filters filters = Filters.RUN;
@@ -70,7 +61,7 @@ public class TestSubmitRule implements RestModifyView<RevisionResource, Input> {
TestSubmitRule(Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
RulesCache rules,
- AccountInfo.Loader.Factory infoFactory) {
+ AccountLoader.Factory infoFactory) {
this.db = db;
this.changeDataFactory = changeDataFactory;
this.rules = rules;
@@ -86,60 +77,27 @@ public class TestSubmitRule implements RestModifyView<RevisionResource, Input> {
if (input.rule != null && !rules.isProjectRulesEnabled()) {
throw new AuthException("project rules are disabled");
}
- input.filters = Objects.firstNonNull(input.filters, filters);
-
+ input.filters = MoreObjects.firstNonNull(input.filters, filters);
SubmitRuleEvaluator evaluator = new SubmitRuleEvaluator(
- db.get(),
- rsrc.getPatchSet(),
- rsrc.getControl().getProjectControl(),
- rsrc.getControl(),
- rsrc.getChange(),
- changeDataFactory.create(db.get(), rsrc.getChange()),
- false,
- "locate_submit_rule", "can_submit",
- "locate_submit_filter", "filter_submit_results",
- input.filters == Filters.SKIP,
- input.rule != null
- ? new ByteArrayInputStream(input.rule.getBytes(UTF_8))
- : null);
-
- List<Term> results;
- try {
- results = eval(evaluator);
- } catch (RuleEvalException e) {
- String msg = Joiner.on(": ").skipNulls().join(Iterables.transform(
- Throwables.getCausalChain(e),
- new Function<Throwable, String>() {
- @Override
- public String apply(Throwable in) {
- return in.getMessage();
- }
- }));
- throw new BadRequestException("rule failed: " + msg);
- }
- if (results.isEmpty()) {
- throw new BadRequestException(String.format(
- "rule %s has no solutions",
- evaluator.getSubmitRule().toString()));
- }
+ changeDataFactory.create(db.get(), rsrc.getControl()));
- List<SubmitRecord> records = rsrc.getControl().resultsToSubmitRecord(
- evaluator.getSubmitRule(),
- results);
+ List<SubmitRecord> records = evaluator.setPatchSet(rsrc.getPatchSet())
+ .setLogErrors(false)
+ .setSkipSubmitFilters(input.filters == Filters.SKIP)
+ .setRule(input.rule)
+ .evaluate();
List<Record> out = Lists.newArrayListWithCapacity(records.size());
- AccountInfo.Loader accounts = accountInfoFactory.create(true);
+ AccountLoader accounts = accountInfoFactory.create(true);
for (SubmitRecord r : records) {
out.add(new Record(r, accounts));
}
+ if (!out.isEmpty()) {
+ out.get(0).prologReductionCount = evaluator.getReductionsConsumed();
+ }
accounts.fill();
return out;
}
- private static List<Term> eval(SubmitRuleEvaluator evaluator)
- throws RuleEvalException {
- return evaluator.evaluate();
- }
-
static class Record {
SubmitRecord.Status status;
String errorMessage;
@@ -148,8 +106,9 @@ public class TestSubmitRule implements RestModifyView<RevisionResource, Input> {
Map<String, None> need;
Map<String, AccountInfo> may;
Map<String, None> impossible;
+ Integer prologReductionCount;
- Record(SubmitRecord r, AccountInfo.Loader accounts) {
+ Record(SubmitRecord r, AccountLoader accounts) {
this.status = r.status;
this.errorMessage = r.errorMessage;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
index 3b7f419e3d..f6016b537a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
@@ -14,10 +14,9 @@
package com.google.gerrit.server.change;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.base.Objects;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.common.base.MoreObjects;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -26,20 +25,14 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.change.TestSubmitRule.Filters;
import com.google.gerrit.server.change.TestSubmitRule.Input;
-import com.google.gerrit.server.project.RuleEvalException;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.googlecode.prolog_cafe.lang.SymbolTerm;
-import com.googlecode.prolog_cafe.lang.Term;
-
import org.kohsuke.args4j.Option;
-import java.io.ByteArrayInputStream;
-import java.util.List;
-
public class TestSubmitType implements RestModifyView<RevisionResource, Input> {
private final Provider<ReviewDb> db;
private final ChangeData.Factory changeDataFactory;
@@ -59,60 +52,29 @@ public class TestSubmitType implements RestModifyView<RevisionResource, Input> {
@Override
public SubmitType apply(RevisionResource rsrc, Input input)
- throws AuthException, BadRequestException {
+ throws AuthException, BadRequestException, OrmException {
if (input == null) {
input = new Input();
}
if (input.rule != null && !rules.isProjectRulesEnabled()) {
throw new AuthException("project rules are disabled");
}
- input.filters = Objects.firstNonNull(input.filters, filters);
-
+ input.filters = MoreObjects.firstNonNull(input.filters, filters);
SubmitRuleEvaluator evaluator = new SubmitRuleEvaluator(
- db.get(),
- rsrc.getPatchSet(),
- rsrc.getControl().getProjectControl(),
- rsrc.getControl(),
- rsrc.getChange(),
- changeDataFactory.create(db.get(), rsrc.getChange()),
- false,
- "locate_submit_type", "get_submit_type",
- "locate_submit_type_filter", "filter_submit_type_results",
- input.filters == Filters.SKIP,
- input.rule != null
- ? new ByteArrayInputStream(input.rule.getBytes(UTF_8))
- : null);
-
- List<Term> results;
- try {
- results = evaluator.evaluate();
- } catch (RuleEvalException e) {
- throw new BadRequestException(String.format(
- "rule failed with exception: %s",
- e.getMessage()));
- }
- if (results.isEmpty()) {
- throw new BadRequestException(String.format(
- "rule %s has no solution",
- evaluator.getSubmitRule()));
- }
- Term type = results.get(0);
- if (!type.isSymbol()) {
+ changeDataFactory.create(db.get(), rsrc.getControl()));
+
+ SubmitTypeRecord rec = evaluator.setPatchSet(rsrc.getPatchSet())
+ .setLogErrors(false)
+ .setSkipSubmitFilters(input.filters == Filters.SKIP)
+ .setRule(input.rule)
+ .getSubmitType();
+ if (rec.status != SubmitTypeRecord.Status.OK) {
throw new BadRequestException(String.format(
"rule %s produced invalid result: %s",
- evaluator.getSubmitRule().toString(),
- type));
+ evaluator.getSubmitRule(), rec));
}
- String typeName = ((SymbolTerm) type).name();
- try {
- return SubmitType.valueOf(typeName.toUpperCase());
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(String.format(
- "rule %s produced invalid result: %s",
- evaluator.getSubmitRule().toString(),
- type));
- }
+ return rec.type;
}
static class Get implements RestReadView<RevisionResource> {
@@ -125,7 +87,7 @@ public class TestSubmitType implements RestModifyView<RevisionResource, Input> {
@Override
public SubmitType apply(RevisionResource resource)
- throws AuthException, BadRequestException {
+ throws AuthException, BadRequestException, OrmException {
return test.apply(resource, null);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index 77fb178a52..67e70c151b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.changedetail;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -21,20 +22,22 @@ 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.reviewdb.client.PatchSetAncestor;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeConflictException;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -98,6 +101,7 @@ public class RebaseChange {
* @param change the change to perform the rebase for
* @param patchSetId the id of the patch set
* @param uploader the user that creates the rebased patch set
+ * @param newBaseRev the commit that should be the new base
* @throws NoSuchChangeException thrown if the change to which the patch set
* belongs does not exist or is not visible to the user
* @throws EmailException thrown if sending the e-mail to notify about the new
@@ -106,9 +110,9 @@ public class RebaseChange {
* @throws IOException thrown if rebase is not possible or not needed
* @throws InvalidChangeOperationException thrown if rebase is not allowed
*/
- public void rebase(Change change, PatchSet.Id patchSetId, final IdentifiedUser uploader)
- throws NoSuchChangeException, EmailException, OrmException, IOException,
- InvalidChangeOperationException {
+ public void rebase(Change change, PatchSet.Id patchSetId, final IdentifiedUser uploader,
+ final String newBaseRev) throws NoSuchChangeException, EmailException, OrmException,
+ IOException, InvalidChangeOperationException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl changeControl =
changeControlFactory.validateFor(change, uploader);
@@ -117,18 +121,20 @@ public class RebaseChange {
"Cannot rebase: New patch sets are not allowed to be added to change: "
+ changeId.toString());
}
- Repository git = null;
- RevWalk rw = null;
- ObjectInserter inserter = null;
- try {
- git = gitManager.openRepository(change.getProject());
- rw = new RevWalk(git);
- inserter = git.newObjectInserter();
-
- final String baseRev = findBaseRevision(patchSetId, db.get(),
- change.getDest(), git, null, null, null);
- final RevCommit baseCommit =
- rw.parseCommit(ObjectId.fromString(baseRev));
+ try (Repository git = gitManager.openRepository(change.getProject());
+ RevWalk rw = new RevWalk(git);
+ ObjectInserter inserter = git.newObjectInserter()) {
+ String baseRev = newBaseRev;
+ if (baseRev == null) {
+ baseRev = findBaseRevision(patchSetId, db.get(),
+ change.getDest(), git, null, null, null);
+ }
+ ObjectId baseObjectId = git.resolve(baseRev);
+ if (baseObjectId == null) {
+ throw new InvalidChangeOperationException(
+ "Cannot rebase: Failed to resolve baseRev: " + baseRev);
+ }
+ final RevCommit baseCommit = rw.parseCommit(baseObjectId);
PersonIdent committerIdent =
uploader.newCommitterIdent(TimeUtil.nowTs(),
@@ -137,19 +143,9 @@ public class RebaseChange {
rebase(git, rw, inserter, patchSetId, change,
uploader, baseCommit, mergeUtilFactory.create(
changeControl.getProjectControl().getProjectState(), true),
- committerIdent, true, true, ValidatePolicy.GERRIT);
- } catch (PathConflictException e) {
+ committerIdent, true, ValidatePolicy.GERRIT);
+ } catch (MergeConflictException e) {
throw new IOException(e.getMessage());
- } finally {
- if (inserter != null) {
- inserter.close();
- }
- if (rw != null) {
- rw.close();
- }
- if (git != null) {
- git.close();
- }
}
}
@@ -264,7 +260,6 @@ public class RebaseChange {
* @param baseCommit the commit that should be the new base
* @param mergeUtil merge utilities for the destination project
* @param committerIdent the committer's identity
- * @param sendMail if a mail notification should be sent for the new patch set
* @param runHooks if hooks should be run for the new patch set
* @param validate if commit validation should be run for the new patch set
* @return the new patch set which is based on the given base commit
@@ -278,10 +273,10 @@ public class RebaseChange {
final ObjectInserter inserter, final PatchSet.Id patchSetId,
final Change change, final IdentifiedUser uploader, final RevCommit baseCommit,
final MergeUtil mergeUtil, PersonIdent committerIdent,
- boolean sendMail, boolean runHooks, ValidatePolicy validate)
+ boolean runHooks, ValidatePolicy validate)
throws NoSuchChangeException,
OrmException, IOException, InvalidChangeOperationException,
- PathConflictException {
+ MergeConflictException {
if (!change.currentPatchSetId().equals(patchSetId)) {
throw new InvalidChangeOperationException("patch set is not current");
}
@@ -299,11 +294,10 @@ public class RebaseChange {
PatchSetInserter patchSetInserter = patchSetInserterFactory
.create(git, revWalk, changeControl, rebasedCommit)
- .setCopyLabels(true)
.setValidatePolicy(validate)
.setDraft(originalPatchSet.isDraft())
.setUploader(uploader.getAccountId())
- .setSendMail(sendMail)
+ .setSendMail(false)
.setRunHooks(runHooks);
final PatchSet.Id newPatchSetId = patchSetInserter.getPatchSetId();
@@ -323,54 +317,62 @@ public class RebaseChange {
}
/**
- * Rebases a commit.
+ * Rebase a commit.
*
- * @param git repository to find commits in
- * @param inserter inserter to handle new trees and blobs
- * @param original The commit to rebase
- * @param base Base to rebase against
- * @param mergeUtil merge utilities for the destination project
- * @param committerIdent committer identity
- * @return the id of the rebased commit
- * @throws IOException Merged failed
- * @throws PathConflictException the rebase failed due to a path conflict
+ * @param git repository to find commits in.
+ * @param inserter inserter to handle new trees and blobs.
+ * @param original the commit to rebase.
+ * @param base base to rebase against.
+ * @param mergeUtil merge utilities for the destination project.
+ * @param committerIdent committer identity.
+ * @return the id of the rebased commit.
+ * @throws MergeConflictException the rebase failed due to a merge conflict.
+ * @throws IOException the merge failed for another reason.
*/
- private ObjectId rebaseCommit(final Repository git,
- final ObjectInserter inserter, final RevCommit original,
- final RevCommit base, final MergeUtil mergeUtil,
- final PersonIdent committerIdent) throws IOException,
- PathConflictException {
-
- final RevCommit parentCommit = original.getParent(0);
+ private ObjectId rebaseCommit(Repository git, ObjectInserter inserter,
+ RevCommit original, RevCommit base, MergeUtil mergeUtil,
+ PersonIdent committerIdent) throws MergeConflictException, IOException {
+ RevCommit parentCommit = original.getParent(0);
if (base.equals(parentCommit)) {
throw new IOException("Change is already up to date.");
}
- final ThreeWayMerger merger = mergeUtil.newThreeWayMerger(git, inserter);
+ ThreeWayMerger merger = mergeUtil.newThreeWayMerger(git, inserter);
merger.setBase(parentCommit);
merger.merge(original, base);
if (merger.getResultTreeId() == null) {
- throw new PathConflictException(
- "The change could not be rebased due to a path conflict during merge.");
+ throw new MergeConflictException(
+ "The change could not be rebased due to a conflict during merge.");
}
- final CommitBuilder cb = new CommitBuilder();
+ CommitBuilder cb = new CommitBuilder();
cb.setTreeId(merger.getResultTreeId());
cb.setParentId(base);
cb.setAuthor(original.getAuthorIdent());
cb.setMessage(original.getFullMessage());
cb.setCommitter(committerIdent);
- final ObjectId objectId = inserter.insert(cb);
+ ObjectId objectId = inserter.insert(cb);
inserter.flush();
return objectId;
}
+ public boolean canRebase(ChangeResource r) {
+ Change c = r.getChange();
+ return canRebase(c.getProject(), c.currentPatchSetId(), c.getDest());
+ }
+
public boolean canRebase(RevisionResource r) {
+ return canRebase(r.getChange().getProject(),
+ r.getPatchSet().getId(), r.getChange().getDest());
+ }
+
+ public boolean canRebase(Project.NameKey project,
+ PatchSet.Id patchSetId, Branch.NameKey branch) {
Repository git;
try {
- git = gitManager.openRepository(r.getChange().getProject());
+ git = gitManager.openRepository(project);
} catch (RepositoryNotFoundException err) {
return false;
} catch (IOException err) {
@@ -378,9 +380,9 @@ public class RebaseChange {
}
try {
findBaseRevision(
- r.getPatchSet().getId(),
+ patchSetId,
db.get(),
- r.getChange().getDest(),
+ branch,
git,
null,
null,
@@ -394,24 +396,4 @@ public class RebaseChange {
git.close();
}
}
-
- public static boolean canDoRebase(final ReviewDb db,
- final Change change, final GitRepositoryManager gitManager,
- List<PatchSetAncestor> patchSetAncestors,
- List<PatchSet> depPatchSetList, List<Change> depChangeList)
- throws OrmException, RepositoryNotFoundException, IOException {
-
- final Repository git = gitManager.openRepository(change.getProject());
-
- try {
- // If no exception is thrown, then we can do a rebase.
- findBaseRevision(change.currentPatchSetId(), db, change.getDest(), git,
- patchSetAncestors, depPatchSetList, depChangeList);
- return true;
- } catch (IOException e) {
- return false;
- } finally {
- git.close();
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java
index 73074b75a6..2f25da400c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java
@@ -33,6 +33,7 @@ public class AllProjectsNameProvider implements Provider<AllProjectsName> {
name = new AllProjectsName(n);
}
+ @Override
public AllProjectsName get() {
return name;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java
index e6ec095e58..f5aa127e7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java
@@ -33,6 +33,7 @@ public class AllUsersNameProvider implements Provider<AllUsersName> {
name = new AllUsersName(n);
}
+ @Override
public AllUsersName get() {
return name;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index c2cf95e6ca..d7138b331e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -38,6 +38,7 @@ public class AuthConfig {
private final String httpHeader;
private final String httpDisplaynameHeader;
private final String httpEmailHeader;
+ private final String httpExternalIdHeader;
private final String registerPageUrl;
private final boolean trustContainerAuth;
private final boolean enableRunAs;
@@ -61,6 +62,7 @@ public class AuthConfig {
httpHeader = cfg.getString("auth", null, "httpheader");
httpDisplaynameHeader = cfg.getString("auth", null, "httpdisplaynameheader");
httpEmailHeader = cfg.getString("auth", null, "httpemailheader");
+ httpExternalIdHeader = cfg.getString("auth", null, "httpexternalidheader");
loginUrl = cfg.getString("auth", null, "loginurl");
logoutUrl = cfg.getString("auth", null, "logouturl");
registerPageUrl = cfg.getString("auth", null, "registerPageUrl");
@@ -111,7 +113,7 @@ public class AuthConfig {
}
private static AuthType toType(final Config cfg) {
- return ConfigUtil.getEnum(cfg, "auth", null, "type", AuthType.OPENID);
+ return cfg.getEnum("auth", null, "type", AuthType.OPENID);
}
/** Type of user authentication used by this Gerrit server. */
@@ -131,6 +133,10 @@ public class AuthConfig {
return httpEmailHeader;
}
+ public String getHttpExternalIdHeader() {
+ return httpExternalIdHeader;
+ }
+
public String getLoginUrl() {
return loginUrl;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
index 289173b0ae..99a4f52875 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -24,12 +24,14 @@ public class CapabilityConstants extends TranslationBundle {
public String accessDatabase;
public String administrateServer;
+ public String batchChangesLimit;
public String createAccount;
public String createGroup;
public String createProject;
public String emailReviewers;
public String flushCaches;
public String killTask;
+ public String modifyAccount;
public String priority;
public String queryLimit;
public String runAs;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index ab290cbaea..dd4ba791b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.config;
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
import org.eclipse.jgit.lib.Config;
+
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
@@ -25,24 +26,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ConfigUtil {
- /**
- * Parse a Java enumeration from the configuration.
- *
- * @param <T> type of the enumeration object.
- * @param config the configuration file to read.
- * @param section section the key is in.
- * @param subsection subsection the key is in, or null if not in a subsection.
- * @param setting name of the setting to read.
- * @param defaultValue default value to return if the setting was not set.
- * Must not be null as the enumeration values are derived from this.
- * @return the selected enumeration value, or {@code defaultValue}.
- */
- public static <T extends Enum<?>> T getEnum(final Config config,
- final String section, final String subsection, final String setting,
- final T defaultValue) {
- final T[] all = allValuesOf(defaultValue);
- return getEnum(config, section, subsection, setting, all, defaultValue);
- }
@SuppressWarnings("unchecked")
private static <T> T[] allValuesOf(final T defaultValue) {
@@ -65,31 +48,6 @@ public class ConfigUtil {
* Parse a Java enumeration from the configuration.
*
* @param <T> type of the enumeration object.
- * @param config the configuration file to read.
- * @param section section the key is in.
- * @param subsection subsection the key is in, or null if not in a subsection.
- * @param setting name of the setting to read.
- * @param all all possible values in the enumeration which should be
- * recognized. This should be {@code EnumType.values()}.
- * @param defaultValue default value to return if the setting was not set.
- * This value may be null.
- * @return the selected enumeration value, or {@code defaultValue}.
- */
- public static <T extends Enum<?>> T getEnum(final Config config,
- final String section, final String subsection, final String setting,
- final T[] all, final T defaultValue) {
- final String valueString = config.getString(section, subsection, setting);
- if (valueString == null) {
- return defaultValue;
- }
-
- return getEnum(section, subsection, setting, valueString, all);
- }
-
- /**
- * Parse a Java enumeration from the configuration.
- *
- * @param <T> type of the enumeration object.
* @param section section the key is in.
* @param subsection subsection the key is in, or null if not in a subsection.
* @param setting name of the setting to read.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Current.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookup.java
index b16c977051..04712f96a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Current.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookup.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2014 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,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.server.config;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -20,8 +20,7 @@ import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
-/** Indicates the {@link SchemaVersion} is the current one. */
@Retention(RUNTIME)
@BindingAnnotation
-public @interface Current {
+public @interface DisableReverseDnsLookup {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookupProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookupProvider.java
new file mode 100644
index 0000000000..8c4271493c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/DisableReverseDnsLookupProvider.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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 org.eclipse.jgit.lib.Config;
+
+public class DisableReverseDnsLookupProvider implements Provider<Boolean> {
+ private final boolean disableReverseDnsLookup;
+
+ @Inject
+ DisableReverseDnsLookupProvider(@GerritServerConfig Config config) {
+ disableReverseDnsLookup =
+ config.getBoolean("gerrit", null, "disableReverseDnsLookup", false);
+ }
+
+ @Override
+ public Boolean get() {
+ return disableReverseDnsLookup;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java
index 80031c2ee2..2d9f21af4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/DownloadConfig.java
@@ -16,7 +16,6 @@ package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
-import com.google.gerrit.reviewdb.client.SystemConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -34,8 +33,7 @@ public class DownloadConfig {
private final Set<DownloadCommand> downloadCommands;
@Inject
- DownloadConfig(@GerritServerConfig final Config cfg,
- final SystemConfig s) {
+ DownloadConfig(@GerritServerConfig final Config cfg) {
List<DownloadScheme> allSchemes =
ConfigUtil.getEnumList(cfg, "download", null, "scheme",
DownloadScheme.DEFAULT_DOWNLOADS);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritConfig.java
new file mode 100644
index 0000000000..fff29fa2cf
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritConfig.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2014 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.gerrit.server.securestore.SecureStore;
+
+import org.eclipse.jgit.lib.Config;
+
+class GerritConfig extends Config {
+ private final SecureStore secureStore;
+
+ GerritConfig(Config baseConfig, SecureStore secureStore) {
+ super(baseConfig);
+ this.secureStore = secureStore;
+ }
+
+ @Override
+ public String getString(String section, String subsection, String name) {
+ String secure = secureStore.get(section, subsection, name);
+ if (secure != null) {
+ return secure;
+ }
+ return super.getString(section, subsection, name);
+ }
+
+ @Override
+ public String[] getStringList(String section, String subsection, String name) {
+ String[] secure = secureStore.getList(section, subsection, name);
+ if (secure != null && secure.length > 0) {
+ return secure;
+ }
+ return super.getStringList(section, subsection, name);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 8c686d57cb..7bdccaa78c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -18,7 +18,7 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.cache.Cache;
import com.google.gerrit.audit.AuditModule;
-import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.common.EventListener;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
@@ -32,6 +32,9 @@ 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.systemstatus.MessageOfTheDay;
+import com.google.gerrit.extensions.webui.BranchWebLink;
+import com.google.gerrit.extensions.webui.DiffWebLink;
+import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
import com.google.gerrit.extensions.webui.TopMenu;
@@ -41,12 +44,8 @@ import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.CmdLineParserModule;
-import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.PluginUser;
-import com.google.gerrit.server.WebLinks;
-import com.google.gerrit.server.WebLinksProvider;
import com.google.gerrit.server.account.AccountByEmailCacheImpl;
import com.google.gerrit.server.account.AccountCacheImpl;
import com.google.gerrit.server.account.AccountControl;
@@ -71,12 +70,10 @@ import com.google.gerrit.server.auth.UniversalAuthBackend;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gerrit.server.cache.CacheRemovalListener;
import com.google.gerrit.server.change.ChangeKindCacheImpl;
-import com.google.gerrit.server.change.MergeabilityChecker;
+import com.google.gerrit.server.change.MergeabilityCacheImpl;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.ChangeCache;
import com.google.gerrit.server.git.ChangeMergeQueue;
-import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.git.MergeUtil;
@@ -89,9 +86,12 @@ import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.git.validators.MergeValidationListener;
import com.google.gerrit.server.git.validators.MergeValidators;
import com.google.gerrit.server.git.validators.MergeValidators.ProjectConfigValidator;
+import com.google.gerrit.server.git.validators.RefOperationValidationListener;
+import com.google.gerrit.server.git.validators.RefOperationValidators;
import com.google.gerrit.server.git.validators.UploadValidationListener;
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.group.GroupModule;
+import com.google.gerrit.server.index.ReindexAfterUpdate;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.EmailModule;
@@ -102,6 +102,8 @@ import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.mail.VelocityRuntimeProvider;
+import com.google.gerrit.server.mime.FileTypeRegistry;
+import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.notedb.NoteDbModule;
import com.google.gerrit.server.patch.PatchListCacheImpl;
import com.google.gerrit.server.patch.PatchScriptFactory;
@@ -119,13 +121,14 @@ import com.google.gerrit.server.project.ProjectNode;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SectionSortCache;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ConflictsCacheImpl;
import com.google.gerrit.server.ssh.SshAddressesModule;
import com.google.gerrit.server.tools.ToolsCatalog;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
+import com.google.gerrit.server.validators.HashtagValidationListener;
+import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
import com.google.gerrit.server.validators.ProjectCreationValidationListener;
import com.google.inject.Inject;
import com.google.inject.TypeLiteral;
@@ -158,11 +161,11 @@ public class GerritGlobalModule extends FactoryModule {
install(authModule);
install(AccountByEmailCacheImpl.module());
install(AccountCacheImpl.module());
- install(ChangeCache.module());
install(ChangeKindCacheImpl.module());
install(ConflictsCacheImpl.module());
install(GroupCacheImpl.module());
install(GroupIncludeCacheImpl.module());
+ install(MergeabilityCacheImpl.module());
install(PatchListCacheImpl.module());
install(ProjectCacheImpl.module());
install(SectionSortCache.module());
@@ -184,7 +187,6 @@ public class GerritGlobalModule extends FactoryModule {
factory(AddReviewerSender.Factory.class);
factory(CapabilityControl.Factory.class);
factory(ChangeData.Factory.class);
- factory(ChangeQueryBuilder.Factory.class);
factory(CreateChangeSender.Factory.class);
factory(GroupDetailFactory.Factory.class);
factory(GroupInfoCacheFactory.Factory.class);
@@ -201,7 +203,6 @@ public class GerritGlobalModule extends FactoryModule {
factory(RegisterNewEmailSender.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
factory(PerformCreateProject.Factory.class);
- factory(GarbageCollection.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class)
.toProvider(AccountVisibilityProvider.class)
@@ -232,7 +233,8 @@ public class GerritGlobalModule extends FactoryModule {
.in(SINGLETON);
bind(FromAddressGenerator.class).toProvider(
FromAddressGeneratorProvider.class).in(SINGLETON);
- bind(WebLinks.class).toProvider(WebLinksProvider.class).in(SINGLETON);
+ bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class)
+ .toProvider(DisableReverseDnsLookupProvider.class).in(SINGLETON);
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
@@ -261,15 +263,17 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), ProjectDeletedListener.class);
DynamicSet.setOf(binder(), HeadUpdatedListener.class);
DynamicSet.setOf(binder(), UsageDataPublishedListener.class);
- DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
- DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(MergeabilityChecker.class);
+ DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ReindexAfterUpdate.class);
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
.to(ProjectConfigEntry.UpdateChecker.class);
- DynamicSet.setOf(binder(), ChangeListener.class);
+ DynamicSet.setOf(binder(), EventListener.class);
DynamicSet.setOf(binder(), CommitValidationListener.class);
+ DynamicSet.setOf(binder(), RefOperationValidationListener.class);
DynamicSet.setOf(binder(), MergeValidationListener.class);
DynamicSet.setOf(binder(), ProjectCreationValidationListener.class);
DynamicSet.setOf(binder(), GroupCreationValidationListener.class);
+ DynamicSet.setOf(binder(), HashtagValidationListener.class);
+ DynamicSet.setOf(binder(), OutgoingEmailValidationListener.class);
DynamicItem.itemOf(binder(), AvatarProvider.class);
DynamicSet.setOf(binder(), LifecycleListener.class);
DynamicSet.setOf(binder(), TopMenu.class);
@@ -278,7 +282,10 @@ public class GerritGlobalModule extends FactoryModule {
DynamicMap.mapOf(binder(), DownloadCommand.class);
DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
DynamicSet.setOf(binder(), PatchSetWebLink.class);
+ DynamicSet.setOf(binder(), FileWebLink.class);
+ DynamicSet.setOf(binder(), DiffWebLink.class);
DynamicSet.setOf(binder(), ProjectWebLink.class);
+ DynamicSet.setOf(binder(), BranchWebLink.class);
factory(UploadValidators.Factory.class);
DynamicSet.setOf(binder(), UploadValidationListener.class);
@@ -286,6 +293,7 @@ public class GerritGlobalModule extends FactoryModule {
bind(AnonymousUser.class);
factory(CommitValidators.Factory.class);
+ factory(RefOperationValidators.Factory.class);
factory(MergeValidators.Factory.class);
factory(ProjectConfigValidator.Factory.class);
factory(NotesBranchUtil.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 872c473262..cdfad8df56 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -20,7 +20,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.SubmoduleOp;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.PerRequestProjectControlCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.servlet.RequestScoped;
@@ -34,7 +33,6 @@ public class GerritRequestModule extends FactoryModule {
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
- bind(ChangeControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON);
factory(SubmoduleOp.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java
index 18a62d0db2..89331fd381 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java
@@ -16,17 +16,67 @@ package com.google.gerrit.server.config;
import static com.google.inject.Scopes.SINGLETON;
+import com.google.gerrit.server.securestore.DefaultSecureStore;
+import com.google.gerrit.server.securestore.SecureStore;
+import com.google.gerrit.server.securestore.SecureStoreProvider;
import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.ProvisionException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
/** Creates {@link GerritServerConfig}. */
public class GerritServerConfigModule extends AbstractModule {
+ public static String getSecureStoreClassName(final File sitePath) {
+ if (sitePath != null) {
+ return getSecureStoreFromGerritConfig(sitePath);
+ }
+
+ String secureStoreProperty = System.getProperty("gerrit.secure_store_class");
+ return nullToDefault(secureStoreProperty);
+ }
+
+ private static String getSecureStoreFromGerritConfig(final File sitePath) {
+ AbstractModule m = new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+ bind(SitePaths.class);
+ }
+ };
+ Injector injector = Guice.createInjector(m);
+ SitePaths site = injector.getInstance(SitePaths.class);
+ FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+ if (!cfg.getFile().exists()) {
+ return DefaultSecureStore.class.getName();
+ }
+
+ try {
+ cfg.load();
+ String className = cfg.getString("gerrit", null, "secureStoreClass");
+ return nullToDefault(className);
+ } catch (IOException | ConfigInvalidException e) {
+ throw new ProvisionException(e.getMessage(), e);
+ }
+ }
+
+ private static String nullToDefault(String className) {
+ return className != null ? className : DefaultSecureStore.class.getName();
+ }
+
@Override
protected void configure() {
bind(SitePaths.class);
bind(TrackingFooters.class).toProvider(TrackingFootersProvider.class).in(SINGLETON) ;
bind(Config.class).annotatedWith(GerritServerConfig.class).toProvider(
GerritServerConfigProvider.class).in(SINGLETON);
+ bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
index 92a2614702..aa699c518d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.config;
+import com.google.gerrit.server.securestore.SecureStore;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
@@ -33,10 +34,12 @@ class GerritServerConfigProvider implements Provider<Config> {
LoggerFactory.getLogger(GerritServerConfigProvider.class);
private final SitePaths site;
+ private final SecureStore secureStore;
@Inject
- GerritServerConfigProvider(final SitePaths site) {
+ GerritServerConfigProvider(SitePaths site, SecureStore secureStore) {
this.site = site;
+ this.secureStore = secureStore;
}
@Override
@@ -46,7 +49,7 @@ class GerritServerConfigProvider implements Provider<Config> {
if (!cfg.getFile().exists()) {
log.info("No " + site.gerrit_config.getAbsolutePath()
+ "; assuming defaults");
- return cfg;
+ return new GerritConfig(cfg, secureStore);
}
try {
@@ -57,17 +60,6 @@ class GerritServerConfigProvider implements Provider<Config> {
throw new ProvisionException(e.getMessage(), e);
}
- if (site.secure_config.exists()) {
- cfg = new FileBasedConfig(cfg, site.secure_config, FS.DETECTED);
- try {
- cfg.load();
- } catch (IOException e) {
- throw new ProvisionException(e.getMessage(), e);
- } catch (ConfigInvalidException e) {
- throw new ProvisionException(e.getMessage(), e);
- }
- }
-
- return cfg;
+ return new GerritConfig(cfg, secureStore);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
index 27fcd72e70..0600712338 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.config;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.server.git.ProjectConfig;
@@ -86,7 +86,8 @@ public class PluginConfig {
if (defaultValue == null) {
return cfg.getString(PLUGIN, pluginName, name);
} else {
- return Objects.firstNonNull(cfg.getString(PLUGIN, pluginName, name), defaultValue);
+ return MoreObjects.firstNonNull(cfg.getString(PLUGIN, pluginName, name),
+ defaultValue);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index 385570bc17..5943801dea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -185,23 +185,59 @@ public class ProjectConfigEntry {
return permittedValues;
}
+ /**
+ * @param project project state.
+ * @return whether the project is editable.
+ */
public boolean isEditable(ProjectState project) {
return true;
}
+ /**
+ * @param project project state.
+ * @return any warning associated with the project.
+ */
public String getWarning(ProjectState project) {
return null;
}
+ /**
+ * Called after a project config is updated.
+ *
+ * @param project project name.
+ * @param oldValue old entry value.
+ * @param newValue new entry value.
+ */
public void onUpdate(Project.NameKey project, String oldValue, String newValue) {
}
+ /**
+ * Called after a project config is updated.
+ *
+ * @param project project name.
+ * @param oldValue old entry value.
+ * @param newValue new entry value.
+ */
public void onUpdate(Project.NameKey project, Boolean oldValue, Boolean newValue) {
}
+ /**
+ * Called after a project config is updated.
+ *
+ * @param project project name.
+ * @param oldValue old entry value.
+ * @param newValue new entry value.
+ */
public void onUpdate(Project.NameKey project, Integer oldValue, Integer newValue) {
}
+ /**
+ * Called after a project config is updated.
+ *
+ * @param project project name.
+ * @param oldValue old entry value.
+ * @param newValue new entry value.
+ */
public void onUpdate(Project.NameKey project, Long oldValue, Long newValue) {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
index 66f0171472..d19f063b9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.config;
+import com.google.common.annotations.VisibleForTesting;
+
import org.eclipse.jgit.lib.Config;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
@@ -52,12 +54,13 @@ public class ScheduleConfig {
this(rc, section, subsection, keyInterval, keyStartTime, DateTime.now());
}
- /* For testing we need to be able to pass now */
+ @VisibleForTesting
ScheduleConfig(Config rc, String section, String subsection, DateTime now) {
this(rc, section, subsection, KEY_INTERVAL, KEY_STARTTIME, now);
}
- private ScheduleConfig(Config rc, String section, String subsection,
+ @VisibleForTesting
+ ScheduleConfig(Config rc, String section, String subsection,
String keyInterval, String keyStartTime, DateTime now) {
this.interval = interval(rc, section, subsection, keyInterval);
if (interval > 0) {
@@ -68,10 +71,18 @@ public class ScheduleConfig {
}
}
+ /**
+ * Milliseconds between constructor invocation and first event time.
+ * <p>
+ * If there is any lag between the constructor invocation and queuing the
+ * object into an executor the event will run later, as there is no method
+ * to adjust for the scheduling delay.
+ */
public long getInitialDelay() {
return initialDelay;
}
+ /** Number of milliseconds between events. */
public long getInterval() {
return interval;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
index e8b2dc5092..d97499cee5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
@@ -49,13 +49,11 @@ public class SetPreferences implements RestModifyView<ConfigResource, Input> {
|| i.useFlashClipboard != null || i.downloadScheme != null
|| i.downloadCommand != null || i.copySelfOnEmail != null
|| i.dateFormat != null || i.timeFormat != null
- || i.reversePatchSetOrder != null
|| i.relativeDateInChangeTable != null
|| i.sizeBarInChangeTable != null
|| i.legacycidInChangeTable != null
- || i.reviewCategoryStrategy != null
- || i.commentVisibilityStrategy != null || i.diffView != null
- || i.changeScreen != null) {
+ || i.muteCommonPathPrefixes != null
+ || i.reviewCategoryStrategy != null) {
throw new BadRequestException("unsupported option");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index 9d7c54a1c9..fbff7c45cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -100,12 +100,13 @@ public final class SitePaths {
if (site_path.exists()) {
final String[] contents = site_path.list();
- if (contents != null)
+ if (contents != null) {
isNew = contents.length == 0;
- else if (site_path.isDirectory())
+ } else if (site_path.isDirectory()) {
throw new FileNotFoundException("Cannot access " + site_path);
- else
+ } else {
throw new FileNotFoundException("Not a directory: " + site_path);
+ }
} else {
isNew = true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFootersProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFootersProvider.java
index a76b010dd8..4c1bde9774 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFootersProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFootersProvider.java
@@ -24,7 +24,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.regex.PatternSyntaxException;
/** Provides a list of all configured {@link TrackingFooter}s. */
@@ -43,8 +47,11 @@ public class TrackingFootersProvider implements Provider<TrackingFooters> {
for (String name : cfg.getSubsections(TRACKING_ID_TAG)) {
boolean configValid = true;
- String footer = cfg.getString(TRACKING_ID_TAG, name, FOOTER_TAG);
- if (footer == null || footer.isEmpty()) {
+ Set<String> footers = new HashSet<>(
+ Arrays.asList(cfg.getStringList(TRACKING_ID_TAG, name, FOOTER_TAG)));
+ footers.removeAll(Collections.singleton(null));
+
+ if (footers.isEmpty()) {
configValid = false;
log.error("Missing " + TRACKING_ID_TAG + "." + name + "." + FOOTER_TAG
+ " in gerrit.config");
@@ -71,7 +78,9 @@ public class TrackingFootersProvider implements Provider<TrackingFooters> {
if (configValid) {
try {
- trackingFooters.add(new TrackingFooter(footer, match, system));
+ for (String footer : footers) {
+ trackingFooters.add(new TrackingFooter(footer, match, system));
+ }
} catch (PatternSyntaxException e) {
log.error("Invalid pattern \"" + match + "\" in gerrit.config "
+ TRACKING_ID_TAG + "." + name + "." + REGEX_TAG + ": "
@@ -81,6 +90,7 @@ public class TrackingFootersProvider implements Provider<TrackingFooters> {
}
}
+ @Override
public TrackingFooters get() {
return new TrackingFooters(trackingFooters);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java
index 835dc40c85..6b195dea01 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.contact;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -35,24 +36,16 @@ import java.net.URL;
import java.security.Security;
/** Creates the {@link ContactStore} based on the configuration. */
-public class ContactStoreProvider implements Provider<ContactStore> {
- private final Config config;
- private final SitePaths site;
- private final SchemaFactory<ReviewDb> schema;
- private final ContactStoreConnection.Factory connFactory;
+public class ContactStoreModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ }
- @Inject
- ContactStoreProvider(@GerritServerConfig final Config config,
+ @Nullable
+ @Provides
+ public ContactStore provideContactStore(@GerritServerConfig final Config config,
final SitePaths site, final SchemaFactory<ReviewDb> schema,
final ContactStoreConnection.Factory connFactory) {
- this.config = config;
- this.site = site;
- this.schema = schema;
- this.connFactory = connFactory;
- }
-
- @Override
- public ContactStore get() {
final String url = config.getString("contactstore", null, "url");
if (StringUtils.isEmptyOrNull(url)) {
return new NoContactStore();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
index f1f374c935..4048748cba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.contact;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.ContactInformationStoreException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.ContactInformation;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.UrlEncoded;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.ProvisionException;
@@ -110,9 +110,7 @@ class EncryptedContactStore implements ContactStore {
try (InputStream fin = new FileInputStream(pub);
InputStream in = PGPUtil.getDecoderStream(fin)) {
return new BcPGPPublicKeyRingCollection(in);
- } catch (IOException e) {
- throw new ProvisionException("Cannot read " + pub, e);
- } catch (PGPException e) {
+ } catch (IOException | PGPException e) {
throw new ProvisionException("Cannot read " + pub, e);
}
}
@@ -130,6 +128,7 @@ class EncryptedContactStore implements ContactStore {
return null;
}
+ @Override
public void store(final Account account, final ContactInformation info)
throws ContactInformationStoreException {
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/NoContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/NoContactStore.java
index a625c0c237..98001bb4fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/NoContactStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/NoContactStore.java
@@ -18,7 +18,7 @@ import com.google.gerrit.common.errors.ContactInformationStoreException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.ContactInformation;
-class NoContactStore implements ContactStore {
+public class NoContactStore implements ContactStore {
@Override
public boolean isEnabled() {
return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
index 5f5fd33997..8c1851463f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -31,7 +31,6 @@ public class ChangeAttribute {
public Long createdOn;
public Long lastUpdated;
- public String sortKey;
public Boolean open;
public Change.Status status;
public List<MessageAttribute> comments;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/data/QueryStatsAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/data/QueryStatsAttribute.java
index 989706534a..4674037b6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/data/QueryStatsAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/data/QueryStatsAttribute.java
@@ -18,5 +18,5 @@ public class QueryStatsAttribute {
public final String type = "stats";
public int rowCount;
public long runTimeMilliseconds;
- public String resumeSortKey;
+ public boolean moreChanges;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
index af08d1e9c2..03441e7e2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
@@ -16,9 +16,11 @@ package com.google.gerrit.server.documentation;
import static org.pegdown.Extensions.ALL;
import static org.pegdown.Extensions.HARDWRAPS;
+import static org.pegdown.Extensions.SUPPRESS_ALL_HTML;
import com.google.common.base.Strings;
+import org.apache.commons.lang.StringEscapeUtils;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.pegdown.LinkRenderer;
@@ -43,7 +45,7 @@ public class MarkdownFormatter {
private static final Logger log =
LoggerFactory.getLogger(MarkdownFormatter.class);
- private static final String css;
+ private static final String defaultCss;
static {
AtomicBoolean file = new AtomicBoolean();
@@ -54,12 +56,12 @@ public class MarkdownFormatter {
log.warn("Cannot load pegdown.css", err);
src = "";
}
- css = file.get() ? null : src;
+ defaultCss = file.get() ? null : src;
}
private static String readCSS() {
- if (css != null) {
- return css;
+ if (defaultCss != null) {
+ return defaultCss;
}
try {
return readPegdownCss(new AtomicBoolean());
@@ -69,6 +71,19 @@ public class MarkdownFormatter {
}
}
+ private boolean suppressHtml;
+ private String css;
+
+ public MarkdownFormatter suppressHtml() {
+ suppressHtml = true;
+ return this;
+ }
+
+ public MarkdownFormatter setCss(String css) {
+ this.css = StringEscapeUtils.escapeHtml(css);
+ return this;
+ }
+
public byte[] markdownToDocHtml(String md, String charEnc)
throws UnsupportedEncodingException {
RootNode root = parseMarkdown(md);
@@ -80,9 +95,13 @@ public class MarkdownFormatter {
if (!Strings.isNullOrEmpty(title)) {
html.append("<title>").append(title).append("</title>");
}
- html.append("<style type=\"text/css\">\n")
- .append(readCSS())
- .append("\n</style>");
+ html.append("<style type=\"text/css\">\n");
+ if (css != null) {
+ html.append(css);
+ } else {
+ html.append(readCSS());
+ }
+ html.append("\n</style>");
html.append("</head>");
html.append("<body>\n");
html.append(new ToHtmlSerializer(new LinkRenderer()).toHtml(root));
@@ -121,7 +140,11 @@ public class MarkdownFormatter {
}
private RootNode parseMarkdown(String md) {
- return new PegDownProcessor(ALL & ~(HARDWRAPS))
+ int options = ALL & ~(HARDWRAPS);
+ if (suppressHtml) {
+ options |= SUPPRESS_ALL_HTML;
+ }
+ return new PegDownProcessor(options)
.parseMarkdown(md.toCharArray());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index b7340079b1..188e95b69e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -31,7 +31,6 @@ import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.RAMDirectory;
-import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,8 +45,6 @@ public class QueryDocumentationExecutor {
private static final Logger log =
LoggerFactory.getLogger(QueryDocumentationExecutor.class);
- private static final Version LUCENE_VERSION = Version.LUCENE_48;
-
private IndexSearcher searcher;
private QueryParser parser;
@@ -68,8 +65,7 @@ public class QueryDocumentationExecutor {
}
IndexReader reader = DirectoryReader.open(dir);
searcher = new IndexSearcher(reader);
- StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION);
- parser = new QueryParser(LUCENE_VERSION, Constants.DOC_FIELD, analyzer);
+ parser = new QueryParser(Constants.DOC_FIELD, new StandardAnalyzer());
} catch (IOException e) {
log.error("Cannot initialize documentation full text index", e);
searcher = null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
new file mode 100644
index 0000000000..b7bb36092c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2014 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.edit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+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.reviewdb.client.RevId;
+import com.google.gerrit.server.IdentifiedUser;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * A single user's edit for a change.
+ * <p>
+ * There is max. one edit per user per change. Edits are stored on refs:
+ * refs/users/UU/UUUU/edit-CCCC/P where UU/UUUU is sharded representation
+ * of user account, CCCC is change number and P is the patch set number it
+ * is based on.
+ */
+public class ChangeEdit {
+ private final IdentifiedUser user;
+ private final Change change;
+ private final Ref ref;
+ private final RevCommit editCommit;
+ private final PatchSet basePatchSet;
+
+ public ChangeEdit(IdentifiedUser user, Change change, Ref ref,
+ RevCommit editCommit, PatchSet basePatchSet) {
+ checkNotNull(user);
+ checkNotNull(change);
+ checkNotNull(ref);
+ checkNotNull(editCommit);
+ checkNotNull(basePatchSet);
+ this.user = user;
+ this.change = change;
+ this.ref = ref;
+ this.editCommit = editCommit;
+ this.basePatchSet = basePatchSet;
+ }
+
+ public Change getChange() {
+ return change;
+ }
+
+ public IdentifiedUser getUser() {
+ return user;
+ }
+
+ public Ref getRef() {
+ return ref;
+ }
+
+ public RevId getRevision() {
+ return new RevId(ObjectId.toString(ref.getObjectId()));
+ }
+
+ public String getRefName() {
+ return RefNames.refsEdit(user.getAccountId(), change.getId(),
+ basePatchSet.getId());
+ }
+
+ public RevCommit getEditCommit() {
+ return editCommit;
+ }
+
+ public PatchSet getBasePatchSet() {
+ return basePatchSet;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
new file mode 100644
index 0000000000..738d3093dd
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2014 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.edit;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.common.FetchInfo;
+import com.google.gerrit.extensions.config.DownloadCommand;
+import com.google.gerrit.extensions.config.DownloadScheme;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.server.CommonConverters;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.change.ChangeJson;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+@Singleton
+public class ChangeEditJson {
+ private final DynamicMap<DownloadCommand> downloadCommands;
+ private final DynamicMap<DownloadScheme> downloadSchemes;
+ private final Provider<CurrentUser> userProvider;
+
+ @Inject
+ ChangeEditJson(DynamicMap<DownloadCommand> downloadCommand,
+ DynamicMap<DownloadScheme> downloadSchemes,
+ Provider<CurrentUser> userProvider) {
+ this.downloadCommands = downloadCommand;
+ this.downloadSchemes = downloadSchemes;
+ this.userProvider = userProvider;
+ }
+
+ public EditInfo toEditInfo(ChangeEdit edit, boolean downloadCommands) {
+ EditInfo out = new EditInfo();
+ out.commit = fillCommit(edit.getEditCommit());
+ out.baseRevision = edit.getBasePatchSet().getRevision().get();
+ if (downloadCommands) {
+ out.fetch = fillFetchMap(edit);
+ }
+ return out;
+ }
+
+ private static CommitInfo fillCommit(RevCommit editCommit) {
+ CommitInfo commit = new CommitInfo();
+ commit.commit = editCommit.toObjectId().getName();
+ commit.author = CommonConverters.toGitPerson(editCommit.getAuthorIdent());
+ commit.committer = CommonConverters.toGitPerson(
+ editCommit.getCommitterIdent());
+ commit.subject = editCommit.getShortMessage();
+ commit.message = editCommit.getFullMessage();
+
+ commit.parents = new ArrayList<>(editCommit.getParentCount());
+ for (RevCommit p : editCommit.getParents()) {
+ CommitInfo i = new CommitInfo();
+ i.commit = p.name();
+ commit.parents.add(i);
+ }
+
+ return commit;
+ }
+
+ private Map<String, FetchInfo> fillFetchMap(ChangeEdit edit) {
+ Map<String, FetchInfo> r = Maps.newLinkedHashMap();
+ for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
+ String schemeName = e.getExportName();
+ DownloadScheme scheme = e.getProvider().get();
+ if (!scheme.isEnabled()
+ || (scheme.isAuthRequired()
+ && !userProvider.get().isIdentifiedUser())) {
+ continue;
+ }
+
+ // No fluff, just stuff
+ if (!scheme.isAuthSupported()) {
+ continue;
+ }
+
+ String projectName = edit.getChange().getProject().get();
+ String refName = edit.getRefName();
+ FetchInfo fetchInfo = new FetchInfo(scheme.getUrl(projectName), refName);
+ r.put(schemeName, fetchInfo);
+
+ ChangeJson.populateFetchMap(scheme, downloadCommands, projectName,
+ refName, fetchInfo);
+ }
+
+ return r;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
new file mode 100644
index 0000000000..67de914f17
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -0,0 +1,473 @@
+// Copyright (C) 2014 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.edit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.common.base.Strings;
+import com.google.common.io.ByteStreams;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RawInput;
+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.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;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+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.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Utility functions to manipulate change edits.
+ * <p>
+ * This class contains methods to modify edit's content.
+ * For retrieving, publishing and deleting edit see
+ * {@link ChangeEditUtil}.
+ * <p>
+ */
+@Singleton
+public class ChangeEditModifier {
+
+ private static enum TreeOperation {
+ CHANGE_ENTRY,
+ DELETE_ENTRY,
+ RENAME_ENTRY,
+ RESTORE_ENTRY
+ }
+ private final TimeZone tz;
+ private final GitRepositoryManager gitManager;
+ private final Provider<CurrentUser> currentUser;
+
+ @Inject
+ ChangeEditModifier(@GerritPersonIdent PersonIdent gerritIdent,
+ GitRepositoryManager gitManager,
+ Provider<CurrentUser> currentUser) {
+ this.gitManager = gitManager;
+ this.currentUser = currentUser;
+ this.tz = gerritIdent.getTimeZone();
+ }
+
+ /**
+ * Create new change edit.
+ *
+ * @param change to create change edit for
+ * @param ps patch set to create change edit on
+ * @return result
+ * @throws AuthException
+ * @throws IOException
+ * @throws ResourceConflictException When change edit already
+ * exists for the change
+ */
+ public RefUpdate.Result createEdit(Change change, PatchSet ps)
+ throws AuthException, IOException, ResourceConflictException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ String refPrefix = RefNames.refsEditPrefix(me.getAccountId(), change.getId());
+
+ try (Repository repo = gitManager.openRepository(change.getProject())) {
+ Map<String, Ref> refs = repo.getRefDatabase().getRefs(refPrefix);
+ if (!refs.isEmpty()) {
+ throw new ResourceConflictException("edit already exists");
+ }
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ ObjectId revision = ObjectId.fromString(ps.getRevision().get());
+ String editRefName = RefNames.refsEdit(me.getAccountId(), change.getId(),
+ ps.getId());
+ return update(repo, me, editRefName, rw, ObjectId.zeroId(), revision);
+ }
+ }
+ }
+
+ /**
+ * Rebase change edit on latest patch set
+ *
+ * @param edit change edit that contains edit to rebase
+ * @param current patch set to rebase the edit on
+ * @throws AuthException
+ * @throws ResourceConflictException thrown if rebase fails due to merge conflicts
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public void rebaseEdit(ChangeEdit edit, PatchSet current)
+ throws AuthException, ResourceConflictException,
+ InvalidChangeOperationException, IOException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ Change change = edit.getChange();
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ String refName = RefNames.refsEdit(me.getAccountId(), change.getId(),
+ current.getId());
+ try (Repository repo = gitManager.openRepository(change.getProject());
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter()) {
+ BatchRefUpdate ru = repo.getRefDatabase().newBatchUpdate();
+ RevCommit editCommit = edit.getEditCommit();
+ if (editCommit.getParentCount() == 0) {
+ throw new InvalidChangeOperationException(
+ "Rebase edit against root commit not supported");
+ }
+ RevCommit tip = rw.parseCommit(ObjectId.fromString(
+ current.getRevision().get()));
+ ThreeWayMerger m = MergeStrategy.RESOLVE.newMerger(repo, true);
+ m.setObjectInserter(inserter);
+ m.setBase(ObjectId.fromString(
+ edit.getBasePatchSet().getRevision().get()));
+
+ if (m.merge(tip, editCommit)) {
+ ObjectId tree = m.getResultTreeId();
+
+ CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(tree);
+ for (int i = 0; i < tip.getParentCount(); i++) {
+ commit.addParentId(tip.getParent(i));
+ }
+ commit.setAuthor(editCommit.getAuthorIdent());
+ commit.setCommitter(new PersonIdent(
+ editCommit.getCommitterIdent(), TimeUtil.nowTs()));
+ commit.setMessage(editCommit.getFullMessage());
+ ObjectId newEdit = inserter.insert(commit);
+ inserter.flush();
+
+ ru.addCommand(new ReceiveCommand(ObjectId.zeroId(), newEdit,
+ refName));
+ ru.addCommand(new ReceiveCommand(edit.getRef().getObjectId(),
+ ObjectId.zeroId(), edit.getRefName()));
+ ru.execute(rw, NullProgressMonitor.INSTANCE);
+ for (ReceiveCommand cmd : ru.getCommands()) {
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ throw new IOException("failed: " + cmd);
+ }
+ }
+ } else {
+ // TODO(davido): Allow to resolve conflicts inline
+ throw new ResourceConflictException("merge conflict");
+ }
+ }
+ }
+
+ /**
+ * Modify commit message in existing change edit.
+ *
+ * @param edit change edit
+ * @param msg new commit message
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ * @throws UnchangedCommitMessageException
+ */
+ public RefUpdate.Result modifyMessage(ChangeEdit edit, String msg)
+ throws AuthException, InvalidChangeOperationException, IOException,
+ UnchangedCommitMessageException {
+ checkState(!Strings.isNullOrEmpty(msg), "message cannot be null");
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ RevCommit prevEdit = edit.getEditCommit();
+ if (prevEdit.getFullMessage().equals(msg)) {
+ throw new UnchangedCommitMessageException();
+ }
+
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ Project.NameKey project = edit.getChange().getProject();
+ try (Repository repo = gitManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter()) {
+ String refName = edit.getRefName();
+ ObjectId commit = createCommit(me, inserter, prevEdit,
+ prevEdit.getTree(),
+ msg);
+ inserter.flush();
+ return update(repo, me, refName, rw, prevEdit, commit);
+ }
+ }
+
+ /**
+ * Modify file in existing change edit from its base commit.
+ *
+ * @param edit change edit
+ * @param file path to modify
+ * @param content new content
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result modifyFile(ChangeEdit edit,
+ String file, RawInput content) throws AuthException,
+ InvalidChangeOperationException, IOException {
+ return modify(TreeOperation.CHANGE_ENTRY, edit, file, null, content);
+ }
+
+ /**
+ * Delete file in existing change edit.
+ *
+ * @param edit change edit
+ * @param file path to delete
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result deleteFile(ChangeEdit edit,
+ String file) throws AuthException, InvalidChangeOperationException,
+ IOException {
+ return modify(TreeOperation.DELETE_ENTRY, edit, file, null, null);
+ }
+
+ /**
+ * Rename file in existing change edit.
+ *
+ * @param edit change edit
+ * @param file path to rename
+ * @param newFile path to rename the file to
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result renameFile(ChangeEdit edit, String file,
+ String newFile) throws AuthException, InvalidChangeOperationException,
+ IOException {
+ return modify(TreeOperation.RENAME_ENTRY, edit, file, newFile, null);
+ }
+
+ /**
+ * Restore file in existing change edit.
+ *
+ * @param edit change edit
+ * @param file path to restore
+ * @return result
+ * @throws AuthException
+ * @throws InvalidChangeOperationException
+ * @throws IOException
+ */
+ public RefUpdate.Result restoreFile(ChangeEdit edit,
+ String file) throws AuthException, InvalidChangeOperationException,
+ IOException {
+ return modify(TreeOperation.RESTORE_ENTRY, edit, file, null, null);
+ }
+
+ private RefUpdate.Result modify(TreeOperation op, ChangeEdit edit,
+ String file, @Nullable String newFile, @Nullable RawInput content)
+ throws AuthException, IOException, InvalidChangeOperationException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ Project.NameKey project = edit.getChange().getProject();
+ try (Repository repo = gitManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter();
+ ObjectReader reader = repo.newObjectReader()) {
+ String refName = edit.getRefName();
+ RevCommit prevEdit = edit.getEditCommit();
+ ObjectId newTree = writeNewTree(op,
+ rw,
+ inserter,
+ prevEdit,
+ reader,
+ file,
+ newFile,
+ toBlob(inserter, content));
+ if (ObjectId.equals(newTree, prevEdit.getTree())) {
+ throw new InvalidChangeOperationException("no changes were made");
+ }
+
+ ObjectId commit = createCommit(me, inserter, prevEdit, newTree);
+ inserter.flush();
+ return update(repo, me, refName, rw, prevEdit, commit);
+ }
+ }
+
+ private static ObjectId toBlob(ObjectInserter ins, @Nullable RawInput content)
+ throws IOException {
+ if (content == null) {
+ return null;
+ }
+
+ long len = content.getContentLength();
+ InputStream in = content.getInputStream();
+ if (len < 0) {
+ return ins.insert(OBJ_BLOB, ByteStreams.toByteArray(in));
+ }
+ return ins.insert(OBJ_BLOB, len, in);
+ }
+
+ private ObjectId createCommit(IdentifiedUser me, ObjectInserter inserter,
+ RevCommit revision, ObjectId tree) throws IOException {
+ return createCommit(me, inserter, revision, tree,
+ revision.getFullMessage());
+ }
+
+ private ObjectId createCommit(IdentifiedUser me, ObjectInserter inserter,
+ RevCommit revision, ObjectId tree, String msg)
+ throws IOException {
+ CommitBuilder builder = new CommitBuilder();
+ builder.setTreeId(tree);
+ builder.setParentIds(revision.getParents());
+ builder.setAuthor(revision.getAuthorIdent());
+ builder.setCommitter(getCommitterIdent(me));
+ builder.setMessage(msg);
+ return inserter.insert(builder);
+ }
+
+ private RefUpdate.Result update(Repository repo, IdentifiedUser me,
+ String refName, RevWalk rw, ObjectId oldObjectId, ObjectId newEdit)
+ throws IOException {
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setExpectedOldObjectId(oldObjectId);
+ ru.setNewObjectId(newEdit);
+ ru.setRefLogIdent(getRefLogIdent(me));
+ ru.setRefLogMessage("inline edit (amend)", false);
+ ru.setForceUpdate(true);
+ RefUpdate.Result res = ru.update(rw);
+ if (res != RefUpdate.Result.NEW &&
+ res != RefUpdate.Result.FORCED) {
+ throw new IOException("update failed: " + ru);
+ }
+ return res;
+ }
+
+ private static ObjectId writeNewTree(TreeOperation op, RevWalk rw,
+ ObjectInserter ins, RevCommit prevEdit, ObjectReader reader,
+ String fileName, @Nullable String newFile,
+ final @Nullable ObjectId content) throws IOException {
+ DirCache newTree = readTree(reader, prevEdit);
+ DirCacheEditor dce = newTree.editor();
+ switch (op) {
+ case DELETE_ENTRY:
+ dce.add(new DeletePath(fileName));
+ break;
+
+ case RENAME_ENTRY:
+ rw.parseHeaders(prevEdit);
+ TreeWalk tw =
+ TreeWalk.forPath(rw.getObjectReader(), fileName, prevEdit.getTree());
+ if (tw != null) {
+ dce.add(new DeletePath(fileName));
+ addFileToCommit(newFile, dce, tw);
+ }
+ break;
+
+ case CHANGE_ENTRY:
+ checkNotNull(content, "new content required");
+ dce.add(new PathEdit(fileName) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ if (ent.getRawMode() == 0) {
+ ent.setFileMode(FileMode.REGULAR_FILE);
+ }
+ ent.setObjectId(content);
+ }
+ });
+ break;
+
+ case RESTORE_ENTRY:
+ if (prevEdit.getParentCount() == 0) {
+ dce.add(new DeletePath(fileName));
+ break;
+ }
+
+ RevCommit base = prevEdit.getParent(0);
+ rw.parseHeaders(base);
+ tw = TreeWalk.forPath(rw.getObjectReader(), fileName, base.getTree());
+ if (tw == null) {
+ dce.add(new DeletePath(fileName));
+ break;
+ }
+
+ addFileToCommit(fileName, dce, tw);
+ break;
+ }
+ dce.finish();
+ return newTree.writeTree(ins);
+ }
+
+ private static void addFileToCommit(String newFile, DirCacheEditor dce,
+ TreeWalk tw) {
+ final FileMode mode = tw.getFileMode(0);
+ final ObjectId oid = tw.getObjectId(0);
+ dce.add(new PathEdit(newFile) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(mode);
+ ent.setObjectId(oid);
+ }
+ });
+ }
+
+ private static DirCache readTree(ObjectReader reader, RevCommit prevEdit)
+ throws IOException {
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, prevEdit.getTree());
+ b.finish();
+ return dc;
+ }
+
+ private PersonIdent getCommitterIdent(IdentifiedUser user) {
+ return user.newCommitterIdent(TimeUtil.nowTs(), tz);
+ }
+
+ private PersonIdent getRefLogIdent(IdentifiedUser user) {
+ return user.newRefLogIdent(TimeUtil.nowTs(), tz);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
new file mode 100644
index 0000000000..0a65527aad
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -0,0 +1,267 @@
+// Copyright (C) 2014 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.edit;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.common.TimeUtil;
+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.Change.Status;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+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.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Utility functions to manipulate change edits.
+ * <p>
+ * This class contains methods to retrieve, publish and delete edits.
+ * For changing edits see {@link ChangeEditModifier}.
+ */
+@Singleton
+public class ChangeEditUtil {
+ private final GitRepositoryManager gitManager;
+ private final PatchSetInserter.Factory patchSetInserterFactory;
+ private final ChangeControl.GenericFactory changeControlFactory;
+ private final Provider<ReviewDb> db;
+ private final Provider<CurrentUser> user;
+
+ @Inject
+ ChangeEditUtil(GitRepositoryManager gitManager,
+ PatchSetInserter.Factory patchSetInserterFactory,
+ ChangeControl.GenericFactory changeControlFactory,
+ Provider<ReviewDb> db,
+ Provider<CurrentUser> user) {
+ this.gitManager = gitManager;
+ this.patchSetInserterFactory = patchSetInserterFactory;
+ this.changeControlFactory = changeControlFactory;
+ this.db = db;
+ this.user = user;
+ }
+
+ /**
+ * Retrieve edits for a change and user. Max. one change edit can
+ * exist per user and change.
+ *
+ * @param change
+ * @return edit for this change for this user, if present.
+ * @throws AuthException
+ * @throws IOException
+ */
+ public Optional<ChangeEdit> byChange(Change change)
+ throws AuthException, IOException {
+ CurrentUser currentUser = user.get();
+ if (!currentUser.isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ return byChange(change, (IdentifiedUser)currentUser);
+ }
+
+ /**
+ * Retrieve edits for a change and user. Max. one change edit can
+ * exist per user and change.
+ *
+ * @param change
+ * @param user to retrieve change edits for
+ * @return edit for this change for this user, if present.
+ * @throws IOException
+ */
+ public Optional<ChangeEdit> byChange(Change change, IdentifiedUser user)
+ throws IOException {
+ try (Repository repo = gitManager.openRepository(change.getProject())) {
+ String editRefPrefix = RefNames.refsEditPrefix(user.getAccountId(), change.getId());
+ Map<String, Ref> refs = repo.getRefDatabase().getRefs(editRefPrefix);
+ if (refs.isEmpty()) {
+ return Optional.absent();
+ }
+
+ // TODO(davido): Rather than failing when we encounter the corrupt state
+ // where there is more than one ref, we could silently delete all but the
+ // current one.
+ Ref ref = Iterables.getOnlyElement(refs.values());
+ try (RevWalk rw = new RevWalk(repo)) {
+ RevCommit commit = rw.parseCommit(ref.getObjectId());
+ PatchSet basePs = getBasePatchSet(change, ref);
+ return Optional.of(new ChangeEdit(user, change, ref, commit, basePs));
+ }
+ }
+ }
+
+ /**
+ * Promote change edit to patch set, by squashing the edit into
+ * its parent.
+ *
+ * @param edit change edit to publish
+ * @throws AuthException
+ * @throws NoSuchChangeException
+ * @throws IOException
+ * @throws InvalidChangeOperationException
+ * @throws OrmException
+ * @throws ResourceConflictException
+ */
+ public void publish(ChangeEdit edit) throws AuthException,
+ NoSuchChangeException, IOException, InvalidChangeOperationException,
+ OrmException, ResourceConflictException {
+ Change change = edit.getChange();
+ try (Repository repo = gitManager.openRepository(change.getProject());
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter()) {
+ PatchSet basePatchSet = edit.getBasePatchSet();
+ if (!basePatchSet.getId().equals(change.currentPatchSetId())) {
+ throw new ResourceConflictException(
+ "only edit for current patch set can be published");
+ }
+
+ insertPatchSet(edit, change, repo, rw, basePatchSet,
+ squashEdit(rw, inserter, edit.getEditCommit(), basePatchSet));
+ // TODO(davido): This should happen in the same BatchRefUpdate.
+ deleteRef(repo, edit);
+ }
+ }
+
+ /**
+ * Delete change edit.
+ *
+ * @param edit change edit to delete
+ * @throws IOException
+ */
+ public void delete(ChangeEdit edit)
+ throws IOException {
+ Change change = edit.getChange();
+ Repository repo = gitManager.openRepository(change.getProject());
+ try {
+ deleteRef(repo, edit);
+ } finally {
+ repo.close();
+ }
+ }
+
+ private PatchSet getBasePatchSet(Change change, Ref ref)
+ throws IOException {
+ try {
+ int pos = ref.getName().lastIndexOf("/");
+ checkArgument(pos > 0, "invalid edit ref: %s", ref.getName());
+ String psId = ref.getName().substring(pos + 1);
+ return db.get().patchSets().get(new PatchSet.Id(
+ change.getId(), Integer.valueOf(psId)));
+ } catch (OrmException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private RevCommit squashEdit(RevWalk rw, ObjectInserter inserter,
+ RevCommit edit, PatchSet basePatchSet)
+ throws IOException, ResourceConflictException {
+ RevCommit parent = rw.parseCommit(ObjectId.fromString(
+ basePatchSet.getRevision().get()));
+ if (parent.getTree().equals(edit.getTree())
+ && edit.getFullMessage().equals(parent.getFullMessage())) {
+ throw new ResourceConflictException("identical tree and message");
+ }
+ return writeSquashedCommit(rw, inserter, parent, edit);
+ }
+
+ private void insertPatchSet(ChangeEdit edit, Change change,
+ Repository repo, RevWalk rw, PatchSet basePatchSet, RevCommit squashed)
+ throws NoSuchChangeException, InvalidChangeOperationException,
+ OrmException, IOException {
+ PatchSet ps = new PatchSet(
+ ChangeUtil.nextPatchSetId(change.currentPatchSetId()));
+ ps.setRevision(new RevId(ObjectId.toString(squashed)));
+ ps.setUploader(edit.getUser().getAccountId());
+ ps.setCreatedOn(TimeUtil.nowTs());
+
+ PatchSetInserter insr =
+ patchSetInserterFactory.create(repo, rw,
+ changeControlFactory.controlFor(change, edit.getUser()),
+ squashed);
+ insr.setPatchSet(ps)
+ .setDraft(change.getStatus() == Status.DRAFT ||
+ basePatchSet.isDraft())
+ .setMessage(
+ String.format("Patch Set %d: Published edit on patch set %d",
+ ps.getPatchSetId(),
+ basePatchSet.getPatchSetId()))
+ .insert();
+ }
+
+ private static void deleteRef(Repository repo, ChangeEdit edit)
+ throws IOException {
+ String refName = edit.getRefName();
+ RefUpdate ru = repo.updateRef(refName, true);
+ ru.setExpectedOldObjectId(edit.getRef().getObjectId());
+ ru.setForceUpdate(true);
+ RefUpdate.Result result = ru.delete();
+ switch (result) {
+ case FORCED:
+ case NEW:
+ case NO_CHANGE:
+ break;
+ default:
+ throw new IOException(String.format("Failed to delete ref %s: %s",
+ refName, result));
+ }
+ }
+
+ private static RevCommit writeSquashedCommit(RevWalk rw,
+ ObjectInserter inserter, RevCommit parent, RevCommit edit)
+ throws IOException {
+ CommitBuilder mergeCommit = new CommitBuilder();
+ for (int i = 0; i < parent.getParentCount(); i++) {
+ mergeCommit.addParentId(parent.getParent(i));
+ }
+ mergeCommit.setAuthor(parent.getAuthorIdent());
+ mergeCommit.setMessage(edit.getFullMessage());
+ mergeCommit.setCommitter(edit.getCommitterIdent());
+ mergeCommit.setTreeId(edit.getTree());
+
+ return rw.parseCommit(commit(inserter, mergeCommit));
+ }
+
+ private static ObjectId commit(ObjectInserter inserter,
+ CommitBuilder mergeCommit) throws IOException {
+ ObjectId id = inserter.insert(mergeCommit);
+ inserter.flush();
+ return id;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/UnchangedCommitMessageException.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/UnchangedCommitMessageException.java
new file mode 100644
index 0000000000..f405f8b0f3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/UnchangedCommitMessageException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 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.edit;
+
+public class UnchangedCommitMessageException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public UnchangedCommitMessageException() {
+ super("New commit message cannot be same as existing commit message");
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
index b0eb9c6b4f..c285a82bcd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class ChangeAbandonedEvent extends ChangeEvent {
- public final String type = "change-abandoned";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute abandoner;
- public String reason;
+public class ChangeAbandonedEvent extends PatchSetEvent {
+ public AccountAttribute abandoner;
+ public String reason;
+
+ public ChangeAbandonedEvent() {
+ super("change-abandoned");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java
index 904a0a0489..9a5ad8293f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java
@@ -14,5 +14,30 @@
package com.google.gerrit.server.events;
-public abstract class ChangeEvent {
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.data.ChangeAttribute;
+
+public abstract class ChangeEvent extends RefEvent {
+ public ChangeAttribute change;
+
+ protected ChangeEvent(String type) {
+ super(type);
+ }
+
+ @Override
+ public Project.NameKey getProjectNameKey() {
+ return new Project.NameKey(change.project);
+ }
+
+ @Override
+ public String getRefName() {
+ return R_HEADS + change.branch;
+ }
+
+ public Change.Key getChangeKey() {
+ return new Change.Key(change.id);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java
index 38996a5e05..41a95cb0f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class ChangeMergedEvent extends ChangeEvent {
- public final String type = "change-merged";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute submitter;
+public class ChangeMergedEvent extends PatchSetEvent {
+ public AccountAttribute submitter;
+ public String newRev;
+
+ public ChangeMergedEvent() {
+ super("change-merged");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
index e761190d6e..a575a42cee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class ChangeRestoredEvent extends ChangeEvent {
- public final String type = "change-restored";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute restorer;
- public String reason;
+public class ChangeRestoredEvent extends PatchSetEvent {
+ public AccountAttribute restorer;
+ public String reason;
+
+ public ChangeRestoredEvent () {
+ super("change-restored");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java
index 52d7409819..4391dfbdb9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java
@@ -16,14 +16,13 @@ package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.ApprovalAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class CommentAddedEvent extends ChangeEvent {
- public final String type = "comment-added";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute author;
- public ApprovalAttribute[] approvals;
- public String comment;
+public class CommentAddedEvent extends PatchSetEvent {
+ public AccountAttribute author;
+ public ApprovalAttribute[] approvals;
+ public String comment;
+
+ public CommentAddedEvent() {
+ super("comment-added");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java
index 8dd10847b5..8843dbbbd6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommitReceivedEvent.java
@@ -20,19 +20,34 @@ import com.google.gerrit.server.IdentifiedUser;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.ReceiveCommand;
-public class CommitReceivedEvent extends ChangeEvent {
- public final ReceiveCommand command;
- public final Project project;
- public final String refName;
- public final RevCommit commit;
- public final IdentifiedUser user;
+public class CommitReceivedEvent extends RefEvent {
+ public ReceiveCommand command;
+ public Project project;
+ public String refName;
+ public RevCommit commit;
+ public IdentifiedUser user;
+
+ public CommitReceivedEvent() {
+ super("commit-received");
+ }
public CommitReceivedEvent(ReceiveCommand command, Project project,
String refName, RevCommit commit, IdentifiedUser user) {
+ this();
this.command = command;
this.project = project;
this.refName = refName;
this.commit = commit;
this.user = user;
}
+
+ @Override
+ public Project.NameKey getProjectNameKey() {
+ return project.getNameKey();
+ }
+
+ @Override
+ public String getRefName() {
+ return refName;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
index 7fd033a15b..5db628f673 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
@@ -15,12 +15,11 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class DraftPublishedEvent extends ChangeEvent {
- public final String type = "draft-published";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute uploader;
+public class DraftPublishedEvent extends PatchSetEvent {
+ public AccountAttribute uploader;
+
+ public DraftPublishedEvent() {
+ super("draft-published");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/Event.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/Event.java
new file mode 100644
index 0000000000..20fbe2fc1a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/Event.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2014 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.gerrit.common.TimeUtil;
+
+public abstract class Event {
+ public final String type;
+ public long eventCreatedOn = TimeUtil.nowMs() / 1000L;
+
+ protected Event(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java
new file mode 100644
index 0000000000..3508acfd94
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventDeserializer.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 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.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+
+/**
+ * JSON deserializer for {@link Event}s.
+ * <p>
+ * Deserialized objects are of an appropriate subclass based on the value of the
+ * top-level "type" element.
+ */
+public class EventDeserializer implements JsonDeserializer<Event> {
+ @Override
+ public Event deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ if (!json.isJsonObject()) {
+ throw new JsonParseException("Not an object");
+ }
+ JsonElement typeJson = json.getAsJsonObject().get("type");
+ if (typeJson == null || !typeJson.isJsonPrimitive()
+ || !typeJson.getAsJsonPrimitive().isString()) {
+ throw new JsonParseException("Type is not a string: " + typeJson);
+ }
+ String type = typeJson.getAsJsonPrimitive().getAsString();
+ Class<?> cls = EventTypes.getClass(type);
+ if (cls == null) {
+ throw new JsonParseException("Unknown event type: " + type);
+ }
+ return context.deserialize(json, cls);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index e72d4654f2..f0c0bc1d58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -164,7 +164,6 @@ public class EventFactory {
public void extend(ChangeAttribute a, Change change) {
a.createdOn = change.getCreatedOn().getTime() / 1000L;
a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
- a.sortKey = change.getSortKey();
a.open = change.getStatus().isOpen();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
new file mode 100644
index 0000000000..908fd0a870
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2014 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 java.util.HashMap;
+import java.util.Map;
+
+/** Class for registering event types */
+public class EventTypes {
+ private static final Map<String, Class<?>> typesByString = new HashMap<>();
+
+ static {
+ registerClass(new ChangeAbandonedEvent());
+ registerClass(new ChangeMergedEvent());
+ registerClass(new ChangeRestoredEvent());
+ registerClass(new CommentAddedEvent());
+ registerClass(new CommitReceivedEvent());
+ registerClass(new DraftPublishedEvent());
+ registerClass(new HashtagsChangedEvent());
+ registerClass(new MergeFailedEvent());
+ registerClass(new RefUpdatedEvent());
+ registerClass(new RefReceivedEvent());
+ registerClass(new ReviewerAddedEvent());
+ registerClass(new PatchSetCreatedEvent());
+ registerClass(new TopicChangedEvent());
+ }
+
+ /** Register an event.
+ *
+ * @param event The event to register.
+ * registered.
+ **/
+ public static void registerClass(Event event) {
+ String type = event.getType();
+ typesByString.put(type, event.getClass());
+ }
+
+ /** Get the class for an event type.
+ *
+ * @param type The type.
+ * @return The event class, or null if no class is registered with the
+ * given type
+ **/
+ public static Class<?> getClass(String type) {
+ return typesByString.get(type);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
new file mode 100644
index 0000000000..c5919e50c9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 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.gerrit.server.data.AccountAttribute;
+
+public class HashtagsChangedEvent extends ChangeEvent {
+ public AccountAttribute editor;
+ public String[] added;
+ public String[] removed;
+ public String[] hashtags;
+
+ public HashtagsChangedEvent () {
+ super("hashtags-changed");
+ }
+} \ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java
index 599fe60ea2..75cbcb028c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class MergeFailedEvent extends ChangeEvent {
- public final String type = "merge-failed";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute submitter;
- public String reason;
+public class MergeFailedEvent extends PatchSetEvent {
+ public AccountAttribute submitter;
+ public String reason;
+
+ public MergeFailedEvent() {
+ super("merge-failed");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
index fbaf4ef1ce..e4685939d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
@@ -15,12 +15,11 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class PatchSetCreatedEvent extends ChangeEvent {
- public final String type = "patchset-created";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute uploader;
+public class PatchSetCreatedEvent extends PatchSetEvent {
+ public AccountAttribute uploader;
+
+ public PatchSetCreatedEvent() {
+ super("patchset-created");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetEvent.java
new file mode 100644
index 0000000000..cdaf601843
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetEvent.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 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.gerrit.server.data.PatchSetAttribute;
+
+public class PatchSetEvent extends ChangeEvent {
+ public PatchSetAttribute patchSet;
+
+ protected PatchSetEvent(String type) {
+ super(type);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectEvent.java
new file mode 100644
index 0000000000..cba8e90ffc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectEvent.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 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.gerrit.reviewdb.client.Project;
+
+public abstract class ProjectEvent extends Event {
+ protected ProjectEvent(String type) {
+ super(type);
+ }
+
+ public abstract Project.NameKey getProjectNameKey();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefEvent.java
new file mode 100644
index 0000000000..646bb96963
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefEvent.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 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;
+
+public abstract class RefEvent extends ProjectEvent {
+ protected RefEvent(String type) {
+ super(type);
+ }
+
+ public abstract String getRefName();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountInfoMapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefReceivedEvent.java
index 10a9116ead..38f4442241 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountInfoMapper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefReceivedEvent.java
@@ -11,27 +11,29 @@
// 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;
-package com.google.gerrit.server.api.accounts;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.extensions.common.AccountInfo;
+import org.eclipse.jgit.transport.ReceiveCommand;
-public class AccountInfoMapper {
- public static AccountInfo fromAcountInfo(
- com.google.gerrit.server.account.AccountInfo i) {
- if (i == null) {
- return null;
- }
- AccountInfo ai = new AccountInfo();
- fromAccount(i, ai);
- return ai;
+public class RefReceivedEvent extends RefEvent {
+ public ReceiveCommand command;
+ public Project project;
+ public IdentifiedUser user;
+
+ public RefReceivedEvent() {
+ super("ref-received");
+ }
+
+ @Override
+ public Project.NameKey getProjectNameKey() {
+ return project.getNameKey();
}
- public static void fromAccount(
- com.google.gerrit.server.account.AccountInfo i, AccountInfo ai) {
- ai._accountId = i._accountId;
- ai.email = i.email;
- ai.name = i.name;
- ai.username = i.username;
+ @Override
+ public String getRefName() {
+ return command.getRefName();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java
index 944c9ad65e..e5039ff425 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java
@@ -14,11 +14,25 @@
package com.google.gerrit.server.events;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.RefUpdateAttribute;
-public class RefUpdatedEvent extends ChangeEvent {
- public final String type = "ref-updated";
+public class RefUpdatedEvent extends RefEvent {
public AccountAttribute submitter;
public RefUpdateAttribute refUpdate;
+
+ public RefUpdatedEvent() {
+ super("ref-updated");
+ }
+
+ @Override
+ public Project.NameKey getProjectNameKey() {
+ return new Project.NameKey(refUpdate.project);
+ }
+
+ @Override
+ public String getRefName() {
+ return refUpdate.refName;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
index e00cc60417..b016bd9de5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
@@ -15,12 +15,11 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-public class ReviewerAddedEvent extends ChangeEvent {
- public final String type = "reviewer-added";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AccountAttribute reviewer;
+public class ReviewerAddedEvent extends PatchSetEvent {
+ public AccountAttribute reviewer;
+
+ public ReviewerAddedEvent() {
+ super("reviewer-added");
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
index e725eac3e1..7bb334fa7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
@@ -15,11 +15,12 @@
package com.google.gerrit.server.events;
import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ChangeAttribute;
public class TopicChangedEvent extends ChangeEvent {
- public final String type = "topic-changed";
- public ChangeAttribute change;
public AccountAttribute changer;
public String oldTopic;
-} \ No newline at end of file
+
+ public TopicChangedEvent() {
+ super("topic-changed");
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
index 9e48e810c6..5327448afd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
@@ -19,8 +19,10 @@ import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,6 +65,14 @@ public class GitReferenceUpdated {
}
}
+ public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate) {
+ for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
+ if (cmd.getResult() == ReceiveCommand.Result.OK) {
+ fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId());
+ }
+ }
+ }
+
private static class Event implements GitReferenceUpdatedListener.Event {
private final String projectName;
private final String ref;
@@ -96,5 +106,11 @@ public class GitReferenceUpdated {
public String getNewObjectId() {
return newObjectId;
}
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s,%s: %s -> %s]", getClass().getSimpleName(),
+ projectName, ref, oldObjectId, newObjectId);
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
index a9f161e8bb..5952568e74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
@@ -20,13 +20,13 @@ import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.WorkQueue.Executor;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.Inject;
-import com.google.inject.name.Named;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.google.inject.name.Named;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -166,7 +166,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
log.warn(String.format(
"Error in ReceiveCommits while processing changes for project %s",
rc.getProject().getName()), e);
- rc.addError("internal error while processing changes " + e.getMessage());
+ rc.addError("internal error while processing changes");
// ReceiveCommits has tried its best to catch errors, so anything at this
// point is very bad.
for (final ReceiveCommand c : commands) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
index 05ee1db4bf..d8e56b3310 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -48,15 +48,15 @@ import java.util.TimeZone;
@Singleton
public class BanCommit {
-
/**
* Loads a list of commits to reject from {@code refs/meta/reject-commits}.
*
* @param repo repository from which the rejected commits should be loaded
+ * @param walk open revwalk on repo.
* @return NoteMap of commits to be rejected, null if there are none.
* @throws IOException the map cannot be loaded.
*/
- public static NoteMap loadRejectCommitsMap(Repository repo)
+ public static NoteMap loadRejectCommitsMap(Repository repo, RevWalk walk)
throws IOException {
try {
Ref ref = repo.getRef(RefNames.REFS_REJECT_COMMITS);
@@ -64,13 +64,8 @@ public class BanCommit {
return NoteMap.newEmptyMap();
}
- RevWalk rw = new RevWalk(repo);
- try {
- RevCommit map = rw.parseCommit(ref.getObjectId());
- return NoteMap.read(rw.getObjectReader(), map);
- } finally {
- rw.close();
- }
+ RevCommit map = walk.parseCommit(ref.getObjectId());
+ return NoteMap.read(walk.getObjectReader(), map);
} catch (IOException badMap) {
throw new IOException("Cannot load " + RefNames.REFS_REJECT_COMMITS,
badMap);
@@ -79,7 +74,7 @@ public class BanCommit {
private final Provider<IdentifiedUser> currentUser;
private final GitRepositoryManager repoManager;
- private final PersonIdent gerritIdent;
+ private final TimeZone tz;
private NotesBranchUtil.Factory notesBranchUtilFactory;
@Inject
@@ -89,60 +84,55 @@ public class BanCommit {
final NotesBranchUtil.Factory notesBranchUtilFactory) {
this.currentUser = currentUser;
this.repoManager = repoManager;
- this.gerritIdent = gerritIdent;
this.notesBranchUtilFactory = notesBranchUtilFactory;
+ this.tz = gerritIdent.getTimeZone();
}
public BanCommitResult ban(final ProjectControl projectControl,
final List<ObjectId> commitsToBan, final String reason)
- throws PermissionDeniedException, IOException, InterruptedException,
- MergeException, ConcurrentRefUpdateException {
+ throws PermissionDeniedException, IOException,
+ ConcurrentRefUpdateException {
if (!projectControl.isOwner()) {
throw new PermissionDeniedException(
- "No project owner: not permitted to ban commits");
+ "Not project owner: not permitted to ban commits");
}
final BanCommitResult result = new BanCommitResult();
NoteMap banCommitNotes = NoteMap.newEmptyMap();
- // add a note for each banned commit to notes
+ // Add a note for each banned commit to notes.
final Project.NameKey project = projectControl.getProject().getNameKey();
- final Repository repo = repoManager.openRepository(project);
- try {
- final RevWalk revWalk = new RevWalk(repo);
- final ObjectInserter inserter = repo.newObjectInserter();
- try {
- for (final ObjectId commitToBan : commitsToBan) {
- try {
- revWalk.parseCommit(commitToBan);
- } catch (MissingObjectException e) {
- // ignore exception, also not existing commits can be banned
- } catch (IncorrectObjectTypeException e) {
- result.notACommit(commitToBan, e.getMessage());
- continue;
- }
- banCommitNotes.set(commitToBan, createNoteContent(reason, inserter));
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk revWalk = new RevWalk(repo);
+ ObjectInserter inserter = repo.newObjectInserter()) {
+ ObjectId noteId = null;
+ for (final ObjectId commitToBan : commitsToBan) {
+ try {
+ revWalk.parseCommit(commitToBan);
+ } catch (MissingObjectException e) {
+ // Ignore exception, non-existing commits can be banned.
+ } catch (IncorrectObjectTypeException e) {
+ result.notACommit(commitToBan);
+ continue;
}
- inserter.flush();
- NotesBranchUtil notesBranchUtil =
- notesBranchUtilFactory.create(project, repo, inserter);
- NoteMap newlyCreated =
- notesBranchUtil.commitNewNotes(banCommitNotes, REFS_REJECT_COMMITS,
- createPersonIdent(), buildCommitMessage(commitsToBan, reason));
-
- for (Note n : banCommitNotes) {
- if (newlyCreated.contains(n)) {
- result.commitBanned(n);
- } else {
- result.commitAlreadyBanned(n);
- }
+ if (noteId == null) {
+ noteId = createNoteContent(reason, inserter);
+ }
+ banCommitNotes.set(commitToBan, noteId);
+ }
+ NotesBranchUtil notesBranchUtil =
+ notesBranchUtilFactory.create(project, repo, inserter);
+ NoteMap newlyCreated =
+ notesBranchUtil.commitNewNotes(banCommitNotes, REFS_REJECT_COMMITS,
+ createPersonIdent(), buildCommitMessage(commitsToBan, reason));
+
+ for (Note n : banCommitNotes) {
+ if (newlyCreated.contains(n)) {
+ result.commitBanned(n);
+ } else {
+ result.commitAlreadyBanned(n);
}
- return result;
- } finally {
- revWalk.close();
- inserter.close();
}
- } finally {
- repo.close();
+ return result;
}
}
@@ -157,7 +147,6 @@ public class BanCommit {
private PersonIdent createPersonIdent() {
Date now = new Date();
- TimeZone tz = gerritIdent.getTimeZone();
return currentUser.get().newCommitterIdent(now, tz);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
index baae6294fe..92c3b6c64e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
@@ -16,17 +16,13 @@ package com.google.gerrit.server.git;
import org.eclipse.jgit.lib.ObjectId;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
public class BanCommitResult {
-
- private final List<ObjectId> newlyBannedCommits = new LinkedList<>();
- private final List<ObjectId> alreadyBannedCommits = new LinkedList<>();
- private final List<ObjectId> ignoredObjectIds = new LinkedList<>();
-
- public BanCommitResult() {
- }
+ private final List<ObjectId> newlyBannedCommits = new ArrayList<>(4);
+ private final List<ObjectId> alreadyBannedCommits = new ArrayList<>(4);
+ private final List<ObjectId> ignoredObjectIds = new ArrayList<>(4);
public void commitBanned(final ObjectId commitId) {
newlyBannedCommits.add(commitId);
@@ -36,7 +32,7 @@ public class BanCommitResult {
alreadyBannedCommits.add(commitId);
}
- public void notACommit(final ObjectId id, final String message) {
+ public void notACommit(final ObjectId id) {
ignoredObjectIds.add(id);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
index eb4acfc59e..391ccd07b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2015 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.
@@ -14,86 +14,11 @@
package com.google.gerrit.server.git;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
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.server.ReviewDb;
-import com.google.gerrit.server.cache.CacheModule;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Module;
-import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
-import com.google.inject.name.Named;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collections;
import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-@Singleton
-public class ChangeCache implements GitReferenceUpdatedListener {
- private static final Logger log =
- LoggerFactory.getLogger(ChangeCache.class);
- private static final String ID_CACHE = "changes";
-
- public static Module module() {
- return new CacheModule() {
- @Override
- protected void configure() {
- cache(ID_CACHE,
- Project.NameKey.class,
- new TypeLiteral<List<Change>>() {})
- .maximumWeight(0)
- .loader(Loader.class);
- }
- };
- }
-
- private final LoadingCache<Project.NameKey, List<Change>> cache;
-
- @Inject
- ChangeCache(@Named(ID_CACHE) LoadingCache<Project.NameKey, List<Change>> cache) {
- this.cache = cache;
- }
-
- List<Change> get(Project.NameKey name) {
- try {
- return cache.get(name);
- } catch (ExecutionException e) {
- log.warn("Cannot fetch changes for " + name, e);
- return Collections.emptyList();
- }
- }
-
- @Override
- public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
- if (event.getRefName().startsWith(RefNames.REFS_CHANGES)) {
- cache.invalidate(new Project.NameKey(event.getProjectName()));
- }
- }
-
- static class Loader extends CacheLoader<Project.NameKey, List<Change>> {
- private final SchemaFactory<ReviewDb> schema;
-
- @Inject
- Loader(SchemaFactory<ReviewDb> schema) {
- this.schema = schema;
- }
- @Override
- public List<Change> load(Project.NameKey key) throws Exception {
- final ReviewDb db = schema.open();
- try {
- return Collections.unmodifiableList(db.changes().byProject(key).toList());
- } finally {
- db.close();
- }
- }
- }
+public interface ChangeCache {
+ public List<Change> get(Project.NameKey name);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCacheImplModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCacheImplModule.java
new file mode 100644
index 0000000000..90109a972d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCacheImplModule.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2015 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.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.AbstractModule;
+
+public class ChangeCacheImplModule extends AbstractModule {
+ private final boolean slave;
+
+ public ChangeCacheImplModule(boolean slave) {
+ this.slave = slave;
+ }
+
+ @Override
+ protected void configure() {
+ if (slave) {
+ install(ScanningChangeCacheImpl.module());
+ } else {
+ install(SearchingChangeCacheImpl.module());
+ DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
+ .to(SearchingChangeCacheImpl.class);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
index e386bc562e..be3902cf29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.git;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -27,7 +28,6 @@ import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
index 99009cd5f3..1ff37b0848 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.common.base.Function;
+import com.google.common.collect.Ordering;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -21,12 +23,41 @@ import com.google.gerrit.server.project.ChangeControl;
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.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import java.util.List;
/** Extended commit entity with code review specific metadata. */
public class CodeReviewCommit extends RevCommit {
+ /**
+ * Default ordering when merging multiple topologically-equivalent commits.
+ * <p>
+ * Operates only on these commits and does not take ancestry into account.
+ * <p>
+ * Use this in preference to the default order, which comes from {@link
+ * AnyObjectId} and only orders on SHA-1.
+ */
+ public static final Ordering<CodeReviewCommit> ORDER = Ordering.natural()
+ .onResultOf(new Function<CodeReviewCommit, Integer>() {
+ @Override
+ public Integer apply(CodeReviewCommit in) {
+ return in.getPatchsetId() != null
+ ? in.getPatchsetId().getParentKey().get()
+ : null;
+ }
+ }).nullsFirst();
+
+ public static RevWalk newRevWalk(Repository repo) {
+ return new CodeReviewRevWalk(repo);
+ }
+
+ public static RevWalk newRevWalk(ObjectReader reader) {
+ return new CodeReviewRevWalk(reader);
+ }
+
static CodeReviewCommit revisionGone(ChangeControl ctl) {
return error(ctl, CommitMergeStatus.REVISION_GONE);
}
@@ -43,7 +74,7 @@ public class CodeReviewCommit extends RevCommit {
* #setStatusCode(CommitMergeStatus)}, enumerated in the methods above.
*
* @param ctl control for change that caused this error
- * @param CommitMergeStatus status
+ * @param s status
* @return new commit instance
*/
private static CodeReviewCommit error(ChangeControl ctl,
@@ -54,6 +85,21 @@ public class CodeReviewCommit extends RevCommit {
return r;
}
+ private static class CodeReviewRevWalk extends RevWalk {
+ private CodeReviewRevWalk(Repository repo) {
+ super(repo);
+ }
+
+ private CodeReviewRevWalk(ObjectReader reader) {
+ super(reader);
+ }
+
+ @Override
+ protected RevCommit createCommit(AnyObjectId id) {
+ return new CodeReviewCommit(id);
+ }
+ }
+
/**
* Unique key of the PatchSet entity from the code review system.
* <p>
@@ -66,13 +112,6 @@ public class CodeReviewCommit extends RevCommit {
private ChangeControl control;
/**
- * Ordinal position of this commit within the submit queue.
- * <p>
- * Only valid if {@link #patchsetId} is not null.
- */
- int originalOrder;
-
- /**
* The result status for this commit.
* <p>
* Only valid if {@link #patchsetId} is not null.
@@ -109,7 +148,6 @@ public class CodeReviewCommit extends RevCommit {
public void copyFrom(final CodeReviewCommit src) {
control = src.control;
patchsetId = src.patchsetId;
- originalOrder = src.originalOrder;
statusCode = src.statusCode;
missing = src.missing;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
index f421dcb736..3d3d9b1fe5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
@@ -28,11 +28,17 @@ public enum CommitMergeStatus {
ALREADY_MERGED(""),
/** */
- PATH_CONFLICT("The change could not be merged due to a path conflict.\n"
+ PATH_CONFLICT("Change could not be merged due to a path conflict.\n"
+ "\n"
+ "Please rebase the change locally and upload the rebased commit for review."),
/** */
+ REBASE_MERGE_CONFLICT(
+ "Change could not be merged due to a conflict.\n"
+ + "\n"
+ + "Please rebase the change locally and upload the rebased commit for review."),
+
+ /** */
MISSING_DEPENDENCY(""),
/** */
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionLogFile.java
index 7d33a36cc2..2b0d3e9260 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionLogFile.java
@@ -12,43 +12,35 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.server.git;
-import com.google.gerrit.common.Die;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.util.SystemLog;
+import com.google.inject.Inject;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import java.io.File;
-import java.io.FileNotFoundException;
-public class GarbageCollectionLogFile {
+public class GarbageCollectionLogFile implements LifecycleListener {
- public static LifecycleListener start(File sitePath)
- throws FileNotFoundException {
- File logdir = new SitePaths(sitePath).logs_dir;
- if (!logdir.exists() && !logdir.mkdirs()) {
- throw new Die("Cannot create log directory: " + logdir);
- }
+ @Inject
+ public GarbageCollectionLogFile(SitePaths sitePaths) {
if (SystemLog.shouldConfigure()) {
- initLogSystem(logdir);
+ initLogSystem(sitePaths.logs_dir);
}
+ }
- return new LifecycleListener() {
- @Override
- public void start() {
- }
+ @Override
+ public void start() {
+ }
- @Override
- public void stop() {
- LogManager.getLogger(GarbageCollection.LOG_NAME).removeAllAppenders();
- }
- };
+ @Override
+ public void stop() {
+ LogManager.getLogger(GarbageCollection.LOG_NAME).removeAllAppenders();
}
private static void initLogSystem(File logdir) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionModule.java
index b5cef608ee..aacc738211 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2015 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,23 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.server.git;
-import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.lifecycle.LifecycleModule;
-public class InheritedBoolean {
+public class GarbageCollectionModule extends LifecycleModule {
- public InheritableBoolean value;
- public boolean inheritedValue;
+ @Override
+ protected void configure() {
+ bind(GarbageCollectionLogFile.class).asEagerSingleton();
+ listener().to(GarbageCollectionLogFile.class);
- public InheritedBoolean() {
- }
-
- public void setValue(final InheritableBoolean value) {
- this.value = value;
- }
-
- public void setInheritedValue(final boolean inheritedValue) {
- this.inheritedValue = inheritedValue;
+ bind(GarbageCollectionQueue.class);
+ factory(GarbageCollection.Factory.class);
+ listener().to(GarbageCollectionRunner.Lifecycle.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java
index 5e3ca317d8..68d25d9a19 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java
@@ -18,12 +18,10 @@ import static com.google.gerrit.server.config.ScheduleConfig.MISSING_CONFIG;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GcConfig;
import com.google.gerrit.server.config.ScheduleConfig;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
-import com.google.inject.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,16 +33,6 @@ public class GarbageCollectionRunner implements Runnable {
private static final Logger gcLog = LoggerFactory
.getLogger(GarbageCollection.LOG_NAME);
- public static Module module() {
- return new LifecycleModule() {
-
- @Override
- protected void configure() {
- listener().to(Lifecycle.class);
- }
- };
- }
-
static class Lifecycle implements LifecycleListener {
private final WorkQueue queue;
private final GarbageCollectionRunner gcRunner;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index ffb91cebf7..dee2df0d31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -46,6 +46,9 @@ public interface GitRepositoryManager {
/**
* Create (and open) a repository by name.
+ * <p>
+ * If the implementation supports separate metadata repositories, this method
+ * must also create the metadata repository, but does not open it.
*
* @param name the repository name, relative to the base directory.
* @return the cached Repository instance. Caller must call {@code close()}
@@ -59,6 +62,23 @@ public interface GitRepositoryManager {
throws RepositoryCaseMismatchException, RepositoryNotFoundException,
IOException;
+ /**
+ * Open the repository storing metadata for the given project.
+ * <p>
+ * This includes any project-specific metadata <em>except</em> what is stored
+ * in {@code refs/meta/config}. Implementations may choose to store all
+ * metadata in the original project.
+ *
+ * @param name the base project name name.
+ * @return the cached metadata Repository instance. Caller must call
+ * {@code close()} when done to decrement the resource handle.
+ * @throws RepositoryNotFoundException the name does not denote an existing
+ * repository.
+ * @throws IOException the name cannot be read as a repository.
+ */
+ public abstract Repository openMetadataRepository(Project.NameKey name)
+ throws RepositoryNotFoundException, IOException;
+
/** @return set of all known projects, sorted by natural NameKey order. */
public abstract SortedSet<Project.NameKey> list();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java
new file mode 100644
index 0000000000..29948c2f40
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupList.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2014 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.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class GroupList {
+ public static final String FILE_NAME = "groups";
+ private final Map<AccountGroup.UUID, GroupReference> byUUID;
+
+ private GroupList(Map<AccountGroup.UUID, GroupReference> byUUID) {
+ this.byUUID = byUUID;
+ }
+
+ public static GroupList parse(String text, ValidationError.Sink errors) throws IOException {
+ Map<AccountGroup.UUID, GroupReference> groupsByUUID = new HashMap<>();
+
+ BufferedReader br = new BufferedReader(new StringReader(text));
+ String s;
+ for (int lineNumber = 1; (s = br.readLine()) != null; lineNumber++) {
+ if (s.isEmpty() || s.startsWith("#")) {
+ continue;
+ }
+
+ int tab = s.indexOf('\t');
+ if (tab < 0) {
+ errors.error(new ValidationError(FILE_NAME, lineNumber, "missing tab delimiter"));
+ continue;
+ }
+
+ AccountGroup.UUID uuid = new AccountGroup.UUID(s.substring(0, tab).trim());
+ String name = s.substring(tab + 1).trim();
+ GroupReference ref = new GroupReference(uuid, name);
+
+ groupsByUUID.put(uuid, ref);
+ }
+
+ return new GroupList(groupsByUUID);
+ }
+
+ public GroupReference byUUID(AccountGroup.UUID uuid) {
+ return byUUID.get(uuid);
+ }
+
+ public GroupReference resolve(GroupReference group) {
+ if (group != null) {
+ GroupReference ref = byUUID.get(group.getUUID());
+ if (ref != null) {
+ return ref;
+ }
+ byUUID.put(group.getUUID(), group);
+ }
+ return group;
+ }
+
+ public Collection<GroupReference> references() {
+ return byUUID.values();
+ }
+
+ public Set<AccountGroup.UUID> uuids() {
+ return byUUID.keySet();
+ }
+
+ public void put(UUID uuid, GroupReference reference) {
+ byUUID.put(uuid, reference);
+ }
+
+ private static String pad(int len, String src) {
+ if (len <= src.length()) {
+ return src;
+ }
+
+ StringBuilder r = new StringBuilder(len);
+ r.append(src);
+ while (r.length() < len) {
+ r.append(' ');
+ }
+ return r.toString();
+ }
+
+ private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
+ ArrayList<T> r = new ArrayList<>(m);
+ Collections.sort(r);
+ return r;
+ }
+
+ public String asText() {
+ if (byUUID.isEmpty()) {
+ return null;
+ }
+
+ final int uuidLen = 40;
+ StringBuilder buf = new StringBuilder();
+ buf.append(pad(uuidLen, "# UUID"));
+ buf.append('\t');
+ buf.append("Group Name");
+ buf.append('\n');
+
+ buf.append('#');
+ buf.append('\n');
+
+ for (GroupReference g : sort(byUUID.values())) {
+ if (g.getUUID() != null && g.getName() != null) {
+ buf.append(pad(uuidLen, g.getUUID().get()));
+ buf.append('\t');
+ buf.append(g.getName());
+ buf.append('\n');
+ }
+ }
+ return buf.toString();
+ }
+
+ public void retainUUIDs(Collection<AccountGroup.UUID> toBeRetained) {
+ byUUID.keySet().retainAll(toBeRetained);
+ }
+
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
index 12efd56d2a..0dc7aa9a15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.git;
import static com.google.common.base.Preconditions.checkArgument;
+import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -49,60 +49,25 @@ import java.util.List;
*/
@Singleton
public class LabelNormalizer {
- public static class Result {
- private final ImmutableList<PatchSetApproval> unchanged;
- private final ImmutableList<PatchSetApproval> updated;
- private final ImmutableList<PatchSetApproval> deleted;
-
+ @AutoValue
+ public abstract static class Result {
@VisibleForTesting
- Result(
+ static Result create(
List<PatchSetApproval> unchanged,
List<PatchSetApproval> updated,
List<PatchSetApproval> deleted) {
- this.unchanged = ImmutableList.copyOf(unchanged);
- this.updated = ImmutableList.copyOf(updated);
- this.deleted = ImmutableList.copyOf(deleted);
- }
-
- public ImmutableList<PatchSetApproval> getUnchanged() {
- return unchanged;
+ return new AutoValue_LabelNormalizer_Result(
+ ImmutableList.copyOf(unchanged),
+ ImmutableList.copyOf(updated),
+ ImmutableList.copyOf(deleted));
}
- public ImmutableList<PatchSetApproval> getUpdated() {
- return updated;
- }
-
- public ImmutableList<PatchSetApproval> getDeleted() {
- return deleted;
- }
+ public abstract ImmutableList<PatchSetApproval> unchanged();
+ public abstract ImmutableList<PatchSetApproval> updated();
+ public abstract ImmutableList<PatchSetApproval> deleted();
public Iterable<PatchSetApproval> getNormalized() {
- return Iterables.concat(unchanged, updated);
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof Result) {
- Result r = (Result) o;
- return Objects.equal(unchanged, r.unchanged)
- && Objects.equal(updated, r.updated)
- && Objects.equal(deleted, r.deleted);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(unchanged, updated, deleted);
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .add("unchanged", unchanged)
- .add("updated", updated)
- .add("deleted", deleted)
- .toString();
+ return Iterables.concat(unchanged(), updated());
}
}
@@ -173,7 +138,7 @@ public class LabelNormalizer {
unchanged.add(psa);
}
}
- return new Result(unchanged, updated, deleted);
+ return Result.create(unchanged, updated, deleted);
}
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 196d3e9bdd..633c3bb8d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -14,11 +14,16 @@
package com.google.gerrit.server.git;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.MoreObjects;
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.gerrit.server.notedb.NotesMigration;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -124,16 +129,25 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
}
private final File basePath;
+ private final File noteDbPath;
private final Lock namesUpdateLock;
private volatile SortedSet<Project.NameKey> names;
@Inject
- LocalDiskRepositoryManager(final SitePaths site,
- @GerritServerConfig final Config cfg) {
+ LocalDiskRepositoryManager(SitePaths site,
+ @GerritServerConfig Config cfg,
+ NotesMigration notesMigration) {
basePath = site.resolve(cfg.getString("gerrit", null, "basePath"));
if (basePath == null) {
throw new IllegalStateException("gerrit.basePath must be configured");
}
+
+ if (notesMigration.enabled()) {
+ noteDbPath = site.resolve(MoreObjects.firstNonNull(
+ cfg.getString("gerrit", null, "noteDbPath"), "notedb"));
+ } else {
+ noteDbPath = null;
+ }
namesUpdateLock = new ReentrantLock(true /* fair */);
names = list();
}
@@ -143,28 +157,31 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
return basePath;
}
- private File gitDirOf(Project.NameKey name) {
- return new File(getBasePath(), name.get());
+ @Override
+ public Repository openRepository(Project.NameKey name)
+ throws RepositoryNotFoundException {
+ return openRepository(basePath, name);
}
- public Repository openRepository(Project.NameKey name)
+ private Repository openRepository(File path, Project.NameKey name)
throws RepositoryNotFoundException {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
+ File gitDir = new File(path, name.get());
if (!names.contains(name)) {
// The this.names list does not hold the project-name but it can still exist
// on disk; for instance when the project has been created directly on the
// file-system through replication.
//
if (!name.get().endsWith(Constants.DOT_GIT_EXT)) {
- if (FileKey.resolve(gitDirOf(name), FS.DETECTED) != null) {
+ if (FileKey.resolve(gitDir, FS.DETECTED) != null) {
onCreateProject(name);
} else {
- throw new RepositoryNotFoundException(gitDirOf(name));
+ throw new RepositoryNotFoundException(gitDir);
}
} else {
- final File directory = gitDirOf(name);
+ final File directory = gitDir;
if (FileKey.isGitRepository(new File(directory, Constants.DOT_GIT),
FS.DETECTED)) {
onCreateProject(name);
@@ -172,11 +189,11 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
directory.getName() + Constants.DOT_GIT_EXT), FS.DETECTED)) {
onCreateProject(name);
} else {
- throw new RepositoryNotFoundException(gitDirOf(name));
+ throw new RepositoryNotFoundException(gitDir);
}
}
}
- final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
+ final FileKey loc = FileKey.lenient(gitDir, FS.DETECTED);
try {
return RepositoryCache.open(loc);
} catch (IOException e1) {
@@ -187,13 +204,23 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
}
}
- public Repository createRepository(final Project.NameKey name)
+ @Override
+ public Repository createRepository(Project.NameKey name)
+ throws RepositoryNotFoundException, RepositoryCaseMismatchException {
+ Repository repo = createRepository(basePath, name);
+ if (noteDbPath != null) {
+ createRepository(noteDbPath, name);
+ }
+ return repo;
+ }
+
+ private Repository createRepository(File path, Project.NameKey name)
throws RepositoryNotFoundException, RepositoryCaseMismatchException {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
- File dir = FileKey.resolve(gitDirOf(name), FS.DETECTED);
+ File dir = FileKey.resolve(new File(path, name.get()), FS.DETECTED);
FileKey loc;
if (dir != null) {
// Already exists on disk, use the repository we found.
@@ -208,7 +235,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
// of the repository name, so prefer the standard bare name.
//
String n = name.get() + Constants.DOT_GIT_EXT;
- loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
+ loc = FileKey.exact(new File(path, n), FS.DETECTED);
}
try {
@@ -220,6 +247,18 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
config.save();
+ // JGit only writes to the reflog for refs/meta/config if the log file
+ // already exists.
+ //
+ File metaConfigLog =
+ new File(db.getDirectory(), "logs/" + RefNames.REFS_CONFIG);
+ if (!metaConfigLog.getParentFile().mkdirs()
+ || !metaConfigLog.createNewFile()) {
+ log.error(String.format(
+ "Failed to create ref log for %s in repository %s",
+ RefNames.REFS_CONFIG, name));
+ }
+
onCreateProject(name);
return db;
@@ -231,6 +270,17 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
}
}
+ @Override
+ public Repository openMetadataRepository(Project.NameKey name)
+ throws RepositoryNotFoundException, IOException {
+ checkState(noteDbPath != null, "notedb disabled");
+ try {
+ return openRepository(noteDbPath, name);
+ } catch (RepositoryNotFoundException e) {
+ return createRepository(noteDbPath, name);
+ }
+ }
+
private void onCreateProject(final Project.NameKey newProjectName) {
namesUpdateLock.lock();
try {
@@ -242,6 +292,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
}
}
+ @Override
public String getProjectDescription(final Project.NameKey name)
throws RepositoryNotFoundException, IOException {
final Repository e = openRepository(name);
@@ -274,6 +325,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
return description;
}
+ @Override
public void setProjectDescription(final Project.NameKey name,
final String description) {
// Update git's description file, in case gitweb is being used
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
new file mode 100644
index 0000000000..02bc8dc19f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 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;
+
+/** Indicates that the commit cannot be merged without conflicts. */
+public class MergeConflictException extends Exception {
+ private static final long serialVersionUID = 1L;
+ public MergeConflictException(String msg) {
+ super(msg, null);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
index 54cd32980c..d3ebb9558f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
@@ -18,11 +18,15 @@ package com.google.gerrit.server.git;
public class MergeException extends Exception {
private static final long serialVersionUID = 1L;
- public MergeException(final String msg) {
- super(msg, null);
+ public MergeException(String msg) {
+ super(msg);
}
- public MergeException(final String msg, final Throwable why) {
+ public MergeException(Throwable why) {
+ super(why);
+ }
+
+ public MergeException(String msg, Throwable why) {
super(msg, why);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
new file mode 100644
index 0000000000..109fa76ff3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 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;
+
+/** Indicates that the commit is already contained in destination banch. */
+public class MergeIdenticalTreeException extends Exception {
+ private static final long serialVersionUID = 1L;
+ public MergeIdenticalTreeException(String msg) {
+ super(msg, null);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 7855b63a74..05e864b2ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -20,7 +20,6 @@ import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.eclipse.jgit.lib.RefDatabase.ALL;
-import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
@@ -28,9 +27,10 @@ import com.google.common.collect.Lists;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -63,17 +63,19 @@ import com.google.gerrit.server.project.NoSuchChangeException;
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.SubmitRuleEvaluator;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -85,6 +87,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -95,6 +98,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -128,89 +132,103 @@ public class MergeOp {
private static final long MAX_SUBMIT_WINDOW =
MILLISECONDS.convert(12, HOURS);
- private final GitRepositoryManager repoManager;
- private final SchemaFactory<ReviewDb> schemaFactory;
+ private final AccountCache accountCache;
+ private final ApprovalsUtil approvalsUtil;
+ private final ChangeControl.GenericFactory changeControlFactory;
+ private final ChangeData.Factory changeDataFactory;
+ private final ChangeHooks hooks;
+ private final ChangeIndexer indexer;
+ private final ChangeMessagesUtil cmUtil;
private final ChangeNotes.Factory notesFactory;
- private final ProjectCache projectCache;
+ private final ChangeUpdate.Factory updateFactory;
private final GitReferenceUpdated gitRefUpdated;
+ private final GitRepositoryManager repoManager;
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final MergedSender.Factory mergedSenderFactory;
private final MergeFailSender.Factory mergeFailSenderFactory;
- private final PatchSetInfoFactory patchSetInfoFactory;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
- private final ChangeControl.GenericFactory changeControlFactory;
- private final ChangeUpdate.Factory updateFactory;
private final MergeQueue mergeQueue;
private final MergeValidators.Factory mergeValidatorsFactory;
- private final ApprovalsUtil approvalsUtil;
- private final ChangeMessagesUtil cmUtil;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+ private final ProjectCache projectCache;
+ private final Provider<InternalChangeQuery> queryProvider;
+ private final RequestScopePropagator requestScopePropagator;
+ private final SchemaFactory<ReviewDb> schemaFactory;
+ private final SubmitStrategyFactory submitStrategyFactory;
+ private final SubmoduleOp.Factory subOpFactory;
+ private final TagCache tagCache;
+ private final WorkQueue workQueue;
+ private final String logPrefix;
private final Branch.NameKey destBranch;
- private ProjectState destProject;
private final ListMultimap<SubmitType, CodeReviewCommit> toMerge;
private final List<CodeReviewCommit> potentiallyStillSubmittable;
private final Map<Change.Id, CodeReviewCommit> commits;
private final List<Change> toUpdate;
+
+ private ProjectState destProject;
private ReviewDb db;
private Repository repo;
private RevWalk rw;
private RevFlag canMergeFlag;
private CodeReviewCommit branchTip;
- private CodeReviewCommit mergeTip;
+ private MergeTip mergeTip;
private ObjectInserter inserter;
private PersonIdent refLogIdent;
- private final ChangeHooks hooks;
- private final AccountCache accountCache;
- private final TagCache tagCache;
- private final SubmitStrategyFactory submitStrategyFactory;
- private final SubmoduleOp.Factory subOpFactory;
- private final WorkQueue workQueue;
- private final RequestScopePropagator requestScopePropagator;
- private final ChangeIndexer indexer;
-
@Inject
- MergeOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> sf,
- final ChangeNotes.Factory nf,
- final ProjectCache pc,
- final GitReferenceUpdated gru, final MergedSender.Factory msf,
- final MergeFailSender.Factory mfsf,
- final PatchSetInfoFactory psif, final IdentifiedUser.GenericFactory iuf,
- final ChangeControl.GenericFactory changeControlFactory,
- final ChangeUpdate.Factory updateFactory,
- final MergeQueue mergeQueue, @Assisted final Branch.NameKey branch,
- final ChangeHooks hooks, final AccountCache accountCache,
- final TagCache tagCache,
- final SubmitStrategyFactory submitStrategyFactory,
- final SubmoduleOp.Factory subOpFactory,
- final WorkQueue workQueue,
- final RequestScopePropagator requestScopePropagator,
- final ChangeIndexer indexer,
- final MergeValidators.Factory mergeValidatorsFactory,
- final ApprovalsUtil approvalsUtil,
- final ChangeMessagesUtil cmUtil) {
- repoManager = grm;
- schemaFactory = sf;
- notesFactory = nf;
- projectCache = pc;
- gitRefUpdated = gru;
- mergedSenderFactory = msf;
- mergeFailSenderFactory = mfsf;
- patchSetInfoFactory = psif;
- identifiedUserFactory = iuf;
+ MergeOp(AccountCache accountCache,
+ ApprovalsUtil approvalsUtil,
+ ChangeControl.GenericFactory changeControlFactory,
+ ChangeData.Factory changeDataFactory,
+ ChangeHooks hooks,
+ ChangeIndexer indexer,
+ ChangeMessagesUtil cmUtil,
+ ChangeNotes.Factory notesFactory,
+ ChangeUpdate.Factory updateFactory,
+ GitReferenceUpdated gitRefUpdated,
+ GitRepositoryManager repoManager,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ MergedSender.Factory mergedSenderFactory,
+ MergeFailSender.Factory mergeFailSenderFactory,
+ MergeQueue mergeQueue,
+ MergeValidators.Factory mergeValidatorsFactory,
+ PatchSetInfoFactory patchSetInfoFactory,
+ ProjectCache projectCache,
+ Provider<InternalChangeQuery> queryProvider,
+ RequestScopePropagator requestScopePropagator,
+ SchemaFactory<ReviewDb> schemaFactory,
+ SubmitStrategyFactory submitStrategyFactory,
+ SubmoduleOp.Factory subOpFactory,
+ TagCache tagCache,
+ WorkQueue workQueue,
+ @Assisted Branch.NameKey branch) {
+ this.accountCache = accountCache;
+ this.approvalsUtil = approvalsUtil;
this.changeControlFactory = changeControlFactory;
+ this.changeDataFactory = changeDataFactory;
+ this.hooks = hooks;
+ this.indexer = indexer;
+ this.cmUtil = cmUtil;
+ this.notesFactory = notesFactory;
this.updateFactory = updateFactory;
+ this.gitRefUpdated = gitRefUpdated;
+ this.repoManager = repoManager;
+ this.identifiedUserFactory = identifiedUserFactory;
+ this.mergedSenderFactory = mergedSenderFactory;
+ this.mergeFailSenderFactory = mergeFailSenderFactory;
this.mergeQueue = mergeQueue;
- this.hooks = hooks;
- this.accountCache = accountCache;
- this.tagCache = tagCache;
+ this.mergeValidatorsFactory = mergeValidatorsFactory;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.projectCache = projectCache;
+ this.queryProvider = queryProvider;
+ this.requestScopePropagator = requestScopePropagator;
+ this.schemaFactory = schemaFactory;
this.submitStrategyFactory = submitStrategyFactory;
this.subOpFactory = subOpFactory;
+ this.tagCache = tagCache;
this.workQueue = workQueue;
- this.requestScopePropagator = requestScopePropagator;
- this.indexer = indexer;
- this.mergeValidatorsFactory = mergeValidatorsFactory;
- this.approvalsUtil = approvalsUtil;
- this.cmUtil = cmUtil;
+ logPrefix = String.format("[%s@%s]: ", branch.toString(),
+ ISODateTimeFormat.hourMinuteSecond().print(TimeUtil.nowMs()));
destBranch = branch;
toMerge = ArrayListMultimap.create();
potentiallyStillSubmittable = new ArrayList<>();
@@ -231,7 +249,9 @@ public class MergeOp {
}
}
- public void merge() throws MergeException, NoSuchChangeException, IOException {
+ public void merge()
+ throws MergeException, NoSuchChangeException, IOException {
+ logDebug("Beginning merge attempt on {}", destBranch);
setDestProject();
try {
openSchema();
@@ -240,65 +260,73 @@ public class MergeOp {
RefUpdate branchUpdate = openBranch();
boolean reopen = false;
- final ListMultimap<SubmitType, Change> toSubmit =
- validateChangeList(db.changes().submitted(destBranch).toList());
- final ListMultimap<SubmitType, CodeReviewCommit> toMergeNextTurn =
+ ListMultimap<SubmitType, Change> toSubmit =
+ validateChangeList(queryProvider.get().submitted(destBranch));
+ ListMultimap<SubmitType, CodeReviewCommit> toMergeNextTurn =
ArrayListMultimap.create();
- final List<CodeReviewCommit> potentiallyStillSubmittableOnNextRun =
+ List<CodeReviewCommit> potentiallyStillSubmittableOnNextRun =
new ArrayList<>();
while (!toMerge.isEmpty()) {
+ logDebug("Beginning merge iteration with {} left to merge",
+ toMerge.size());
toMergeNextTurn.clear();
- final Set<SubmitType> submitTypes =
- new HashSet<>(toMerge.keySet());
- for (final SubmitType submitType : submitTypes) {
+ Set<SubmitType> submitTypes = new HashSet<>(toMerge.keySet());
+ for (SubmitType submitType : submitTypes) {
if (reopen) {
+ logDebug("Reopening branch");
branchUpdate = openBranch();
}
- final SubmitStrategy strategy = createStrategy(submitType);
- preMerge(strategy, toMerge.get(submitType));
+ SubmitStrategy strategy = createStrategy(submitType);
+ MergeTip mergeTip = preMerge(strategy, toMerge.get(submitType));
RefUpdate update = updateBranch(strategy, branchUpdate);
reopen = true;
- updateChangeStatus(toSubmit.get(submitType));
+ updateChangeStatus(toSubmit.get(submitType), mergeTip);
updateSubscriptions(toSubmit.get(submitType));
if (update != null) {
fireRefUpdated(update);
}
- for (final Iterator<CodeReviewCommit> it =
+ for (Iterator<CodeReviewCommit> it =
potentiallyStillSubmittable.iterator(); it.hasNext();) {
- final CodeReviewCommit commit = it.next();
+ CodeReviewCommit commit = it.next();
if (containsMissingCommits(toMerge, commit)
|| containsMissingCommits(toMergeNextTurn, commit)) {
// change has missing dependencies, but all commits which are
// missing are still attempted to be merged with another submit
// strategy, retry to merge this commit in the next turn
+ logDebug("Revision {} of patch set {} has missing dependencies"
+ + " with different submit types, reconsidering on next run",
+ commit.name(), commit.getPatchsetId());
it.remove();
commit.setStatusCode(null);
commit.missing = null;
toMergeNextTurn.put(submitType, commit);
}
}
- potentiallyStillSubmittableOnNextRun.addAll(potentiallyStillSubmittable);
+ logDebug("Adding {} changes potentially submittable on next run",
+ potentiallyStillSubmittable.size());
+ potentiallyStillSubmittableOnNextRun.addAll(
+ potentiallyStillSubmittable);
potentiallyStillSubmittable.clear();
}
toMerge.clear();
toMerge.putAll(toMergeNextTurn);
+ logDebug("Adding {} changes to merge on next run", toMerge.size());
}
- updateChangeStatus(toUpdate);
+ updateChangeStatus(toUpdate, mergeTip);
- for (final CodeReviewCommit commit : potentiallyStillSubmittableOnNextRun) {
- final Capable capable = isSubmitStillPossible(commit);
+ for (CodeReviewCommit commit : potentiallyStillSubmittableOnNextRun) {
+ Capable capable = isSubmitStillPossible(commit);
if (capable != Capable.OK) {
sendMergeFail(commit.notes(),
message(commit.change(), capable.getMessage()), false);
}
}
} catch (NoSuchProjectException noProject) {
- log.warn(String.format(
- "Project %s no longer exists, abandoning open changes",
- destBranch.getParentKey().get()));
+ logWarn("Project " + destBranch.getParentKey() + " no longer exists,"
+ + " abandoning open changes");
abandonAllOpenChanges();
} catch (OrmException e) {
throw new MergeException("Cannot query the database", e);
@@ -319,13 +347,12 @@ public class MergeOp {
}
private boolean containsMissingCommits(
- final ListMultimap<SubmitType, CodeReviewCommit> map,
- final CodeReviewCommit commit) {
+ ListMultimap<SubmitType, CodeReviewCommit> map, CodeReviewCommit commit) {
if (!isSubmitForMissingCommitsStillPossible(commit)) {
return false;
}
- for (final CodeReviewCommit missingCommit : commit.missing) {
+ for (CodeReviewCommit missingCommit : commit.missing) {
if (!map.containsValue(missingCommit)) {
return false;
}
@@ -333,8 +360,12 @@ public class MergeOp {
return true;
}
- private boolean isSubmitForMissingCommitsStillPossible(final CodeReviewCommit commit) {
+ private boolean isSubmitForMissingCommitsStillPossible(
+ CodeReviewCommit commit) {
+ PatchSet.Id psId = commit.getPatchsetId();
if (commit.missing == null || commit.missing.isEmpty()) {
+ logDebug("Patch set {} is not submittable: no list of missing commits",
+ psId);
return false;
}
@@ -342,7 +373,7 @@ public class MergeOp {
try {
loadChangeInfo(missingCommit);
} catch (NoSuchChangeException | OrmException e) {
- log.error("Cannot check if missing commits can be submitted", e);
+ logError("Cannot check if missing commits can be submitted", e);
return false;
}
@@ -350,14 +381,21 @@ public class MergeOp {
// The commit doesn't have a patch set, so it cannot be
// submitted to the branch.
//
+ logDebug("Patch set {} is not submittable: dependency {} has no"
+ + " associated patch set", psId, missingCommit.name());
return false;
}
if (!missingCommit.change().currentPatchSetId().equals(
missingCommit.getPatchsetId())) {
+ PatchSet.Id missingId = missingCommit.getPatchsetId();
// If the missing commit is not the current patch set,
// the change must be rebased to use the proper parent.
//
+ logDebug("Patch set {} is not submittable: depends on patch set {} of"
+ + " change {}, but current patch set is {}", psId, missingId,
+ missingId.getParentKey(),
+ missingCommit.change().currentPatchSetId());
return false;
}
}
@@ -365,36 +403,35 @@ public class MergeOp {
return true;
}
- private void preMerge(final SubmitStrategy strategy,
- final List<CodeReviewCommit> toMerge) throws MergeException {
+ private MergeTip preMerge(SubmitStrategy strategy,
+ List<CodeReviewCommit> toMerge) throws MergeException {
+ logDebug("Running submit strategy {} for {} commits",
+ strategy.getClass().getSimpleName(), toMerge.size());
mergeTip = strategy.run(branchTip, toMerge);
refLogIdent = strategy.getRefLogIdent();
+ logDebug("Produced {} new commits", strategy.getNewCommits().size());
commits.putAll(strategy.getNewCommits());
+ return mergeTip;
}
- private SubmitStrategy createStrategy(final SubmitType submitType)
+ private SubmitStrategy createStrategy(SubmitType submitType)
throws MergeException, NoSuchProjectException {
return submitStrategyFactory.create(submitType, db, repo, rw, inserter,
canMergeFlag, getAlreadyAccepted(branchTip), destBranch);
}
private void openRepository() throws MergeException, NoSuchProjectException {
- final Project.NameKey name = destBranch.getParentKey();
+ Project.NameKey name = destBranch.getParentKey();
try {
repo = repoManager.openRepository(name);
} catch (RepositoryNotFoundException notFound) {
throw new NoSuchProjectException(name, notFound);
} catch (IOException err) {
- final String m = "Error opening repository \"" + name.get() + '"';
+ String m = "Error opening repository \"" + name.get() + '"';
throw new MergeException(m, err);
}
- rw = new RevWalk(repo) {
- @Override
- protected RevCommit createCommit(final AnyObjectId id) {
- return new CodeReviewCommit(id);
- }
- };
+ rw = CodeReviewCommit.newRevWalk(repo);
rw.sort(RevSort.TOPO);
rw.sort(RevSort.COMMIT_TIME_DESC, true);
canMergeFlag = rw.newFlag("CAN_MERGE");
@@ -402,9 +439,10 @@ public class MergeOp {
inserter = repo.newObjectInserter();
}
- private RefUpdate openBranch() throws MergeException, OrmException, NoSuchChangeException {
+ private RefUpdate openBranch()
+ throws MergeException, OrmException, NoSuchChangeException {
try {
- final RefUpdate branchUpdate = repo.updateRef(destBranch.get());
+ RefUpdate branchUpdate = repo.updateRef(destBranch.get());
if (branchUpdate.getOldObjectId() != null) {
branchTip =
(CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
@@ -412,93 +450,107 @@ public class MergeOp {
branchTip = null;
branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
} else {
- for (final Change c : db.changes().submitted(destBranch).toList()) {
- setNew(c, message(c, "Your change could not be merged, "
- + "because the destination branch does not exist anymore."));
+ for (ChangeData cd : queryProvider.get().submitted(destBranch)) {
+ try {
+ Change c = cd.change();
+ setNew(c, message(c, "Change could not be merged, "
+ + "because the destination branch does not exist anymore."));
+ } catch (OrmException e) {
+ log.error("Error setting change new", e);
+ }
}
}
+ logDebug("Opened branch {}: {}", destBranch.get(), branchTip);
return branchUpdate;
} catch (IOException e) {
throw new MergeException("Cannot open branch", e);
}
}
- private Set<RevCommit> getAlreadyAccepted(final CodeReviewCommit branchTip)
+ private Set<RevCommit> getAlreadyAccepted(CodeReviewCommit branchTip)
throws MergeException {
- final Set<RevCommit> alreadyAccepted = new HashSet<>();
+ Set<RevCommit> alreadyAccepted = new HashSet<>();
if (branchTip != null) {
alreadyAccepted.add(branchTip);
}
try {
- for (final Ref r : repo.getRefDatabase().getRefs(ALL).values()) {
- if (r.getName().startsWith(Constants.R_HEADS)) {
- try {
- alreadyAccepted.add(rw.parseCommit(r.getObjectId()));
- } catch (IncorrectObjectTypeException iote) {
- // Not a commit? Skip over it.
- }
+ for (Ref r : repo.getRefDatabase().getRefs(Constants.R_HEADS).values()) {
+ try {
+ alreadyAccepted.add(rw.parseCommit(r.getObjectId()));
+ } catch (IncorrectObjectTypeException iote) {
+ // Not a commit? Skip over it.
}
}
} catch (IOException e) {
- throw new MergeException("Failed to determine already accepted commits.", e);
+ throw new MergeException(
+ "Failed to determine already accepted commits.", e);
}
+ logDebug("Found {} existing heads", alreadyAccepted.size());
return alreadyAccepted;
}
private ListMultimap<SubmitType, Change> validateChangeList(
- final List<Change> submitted) throws MergeException, NoSuchChangeException {
- final ListMultimap<SubmitType, Change> toSubmit =
- ArrayListMultimap.create();
+ List<ChangeData> submitted) throws MergeException {
+ logDebug("Validating {} changes", submitted.size());
+ ListMultimap<SubmitType, Change> toSubmit = ArrayListMultimap.create();
- final Map<String, Ref> allRefs;
+ Map<String, Ref> allRefs;
try {
allRefs = repo.getRefDatabase().getRefs(ALL);
} catch (IOException e) {
throw new MergeException(e.getMessage(), e);
}
- final Set<ObjectId> tips = new HashSet<>();
- for (final Ref r : allRefs.values()) {
+ Set<ObjectId> tips = new HashSet<>();
+ for (Ref r : allRefs.values()) {
tips.add(r.getObjectId());
}
- int commitOrder = 0;
- for (final Change chg : submitted) {
+ for (ChangeData cd : submitted) {
ChangeControl ctl;
+ Change chg;
try {
- ctl = changeControlFactory.controlFor(chg,
- identifiedUserFactory.create(chg.getOwner()));
- } catch (NoSuchChangeException e) {
+ ctl = cd.changeControl();
+ // Reload change in case index was stale.
+ chg = cd.reloadChange();
+ } catch (OrmException e) {
throw new MergeException("Failed to validate changes", e);
}
- final Change.Id changeId = chg.getId();
+ Change.Id changeId = cd.getId();
+ if (chg.getStatus() != Change.Status.SUBMITTED) {
+ logDebug("Change {} is not submitted: {}", changeId, chg.getStatus());
+ continue;
+ }
if (chg.currentPatchSetId() == null) {
+ logError("Missing current patch set on change " + changeId);
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
toUpdate.add(chg);
continue;
}
- final PatchSet ps;
+ PatchSet ps;
try {
- ps = db.patchSets().get(chg.currentPatchSetId());
+ ps = cd.currentPatchSet();
} catch (OrmException e) {
throw new MergeException("Cannot query the database", e);
}
if (ps == null || ps.getRevision() == null
|| ps.getRevision().get() == null) {
+ logError("Missing patch set or revision on change " + changeId);
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
toUpdate.add(chg);
continue;
}
- final String idstr = ps.getRevision().get();
- final ObjectId id;
+ String idstr = ps.getRevision().get();
+ ObjectId id;
try {
id = ObjectId.fromString(idstr);
} catch (IllegalArgumentException iae) {
+ logError("Invalid revision on patch set " + ps.getId());
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
toUpdate.add(chg);
continue;
@@ -514,30 +566,36 @@ public class MergeOp {
// want to merge the issue. We can't safely do that if the
// tip is not reachable.
//
+ logError("Revision " + idstr + " of patch set " + ps.getId()
+ + " is not contained in any ref");
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
toUpdate.add(chg);
continue;
}
- final CodeReviewCommit commit;
+ CodeReviewCommit commit;
try {
commit = (CodeReviewCommit) rw.parseCommit(id);
} catch (IOException e) {
- log.error("Invalid commit " + id.name() + " on " + chg.getKey(), e);
+ logError(
+ "Invalid commit " + idstr + " on patch set " + ps.getId(), e);
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
toUpdate.add(chg);
continue;
}
+ // TODO(dborowitz): Consider putting ChangeData in CodeReviewCommit.
commit.setControl(ctl);
commit.setPatchsetId(ps.getId());
- commit.originalOrder = commitOrder++;
commits.put(changeId, commit);
MergeValidators mergeValidators = mergeValidatorsFactory.create();
try {
- mergeValidators.validatePreMerge(repo, commit, destProject, destBranch, ps.getId());
+ mergeValidators.validatePreMerge(
+ repo, commit, destProject, destBranch, ps.getId());
} catch (MergeValidationException mve) {
+ logDebug("Revision {} of patch set {} failed validation: {}",
+ idstr, ps.getId(), mve.getStatus());
commit.setStatusCode(mve.getStatus());
toUpdate.add(chg);
continue;
@@ -550,11 +608,13 @@ public class MergeOp {
//
try {
if (rw.isMergedInto(commit, branchTip)) {
+ logDebug("Revision {} of patch set {} is already merged",
+ idstr, ps.getId());
commit.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
try {
- setMerged(chg, null);
+ setMerged(chg, null, commit);
} catch (OrmException e) {
- log.error("Cannot mark change " + chg.getId() + " merged", e);
+ logError("Cannot mark change " + chg.getId() + " merged", e);
}
continue;
}
@@ -563,8 +623,11 @@ public class MergeOp {
}
}
- SubmitType submitType = getSubmitType(commit.getControl(), ps);
+ SubmitType submitType;
+ submitType = getSubmitType(commit.getControl(), ps);
if (submitType == null) {
+ logError("No submit type for revision " + idstr + " of patch set "
+ + ps.getId());
commit.setStatusCode(CommitMergeStatus.NO_SUBMIT_TYPE);
toUpdate.add(chg);
continue;
@@ -574,58 +637,77 @@ public class MergeOp {
toMerge.put(submitType, commit);
toSubmit.put(submitType, chg);
}
+ logDebug("Submitting on this run: {}", toSubmit);
return toSubmit;
}
private SubmitType getSubmitType(ChangeControl ctl, PatchSet ps) {
- SubmitTypeRecord r = ctl.getSubmitTypeRecord(db, ps);
- if (r.status != SubmitTypeRecord.Status.OK) {
- log.error("Failed to get submit type for " + ctl.getChange().getKey());
+ try {
+ ChangeData cd = changeDataFactory.create(db, ctl);
+ SubmitTypeRecord r = new SubmitRuleEvaluator(cd).setPatchSet(ps)
+ .getSubmitType();
+ if (r.status != SubmitTypeRecord.Status.OK) {
+ logError("Failed to get submit type for " + ctl.getChange().getKey());
+ return null;
+ }
+ return r.type;
+ } catch (OrmException e) {
+ logError("Failed to get submit type for " + ctl.getChange().getKey(), e);
return null;
}
- return r.type;
}
- private RefUpdate updateBranch(final SubmitStrategy strategy,
- final RefUpdate branchUpdate) throws MergeException {
- if (branchTip == mergeTip || mergeTip == null) {
- // nothing to do
+ private RefUpdate updateBranch(SubmitStrategy strategy,
+ RefUpdate branchUpdate) throws MergeException {
+ CodeReviewCommit currentTip =
+ mergeTip != null ? mergeTip.getCurrentTip() : null;
+ if (Objects.equals(branchTip, currentTip)) {
+ logDebug("Branch already at merge tip {}, no update to perform",
+ currentTip.name());
+ return null;
+ } else if (currentTip == null) {
+ logDebug("No merge tip, no update to perform");
return null;
}
if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
+ logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
try {
ProjectConfig cfg =
new ProjectConfig(destProject.getProject().getNameKey());
- cfg.load(repo, mergeTip);
+ cfg.load(repo, currentTip);
} catch (Exception e) {
throw new MergeException("Submit would store invalid"
- + " project configuration " + mergeTip.name() + " for "
+ + " project configuration " + currentTip.name() + " for "
+ destProject.getProject().getName(), e);
}
}
branchUpdate.setRefLogIdent(refLogIdent);
branchUpdate.setForceUpdate(false);
- branchUpdate.setNewObjectId(mergeTip);
+ branchUpdate.setNewObjectId(currentTip);
branchUpdate.setRefLogMessage("merged", true);
try {
- switch (branchUpdate.update(rw)) {
+ RefUpdate.Result result = branchUpdate.update(rw);
+ logDebug("Update of {}: {}..{} returned status {}",
+ branchUpdate.getName(), branchUpdate.getOldObjectId(),
+ branchUpdate.getNewObjectId(), result);
+ switch (result) {
case NEW:
case FAST_FORWARD:
if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) {
tagCache.updateFastForward(destBranch.getParentKey(),
branchUpdate.getName(),
branchUpdate.getOldObjectId(),
- mergeTip);
+ currentTip);
}
if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
- projectCache.evict(destProject.getProject());
- destProject = projectCache.get(destProject.getProject().getNameKey());
+ Project p = destProject.getProject();
+ projectCache.evict(p);
+ destProject = projectCache.get(p.getNameKey());
repoManager.setProjectDescription(
- destProject.getProject().getNameKey(),
- destProject.getProject().getDescription());
+ p.getNameKey(), p.getDescription());
}
return branchUpdate;
@@ -639,9 +721,12 @@ public class MergeOp {
} else {
msg = "will not retry";
}
- throw new IOException(branchUpdate.getResult().name() + ", " + msg);
+ // TODO(dborowitz): Implement RefUpdate.toString().
+ throw new IOException(branchUpdate.getResult().name() + ", " + msg
+ + '\n' + branchUpdate);
default:
- throw new IOException(branchUpdate.getResult().name());
+ throw new IOException(branchUpdate.getResult().name()
+ + '\n' + branchUpdate);
}
} catch (IOException e) {
throw new MergeException("Cannot update " + branchUpdate.getName(), e);
@@ -649,8 +734,10 @@ public class MergeOp {
}
private void fireRefUpdated(RefUpdate branchUpdate) {
+ logDebug("Firing ref updated hooks for {}", branchUpdate.getName());
gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
- hooks.doRefUpdatedHook(destBranch, branchUpdate, getAccount(mergeTip));
+ hooks.doRefUpdatedHook(destBranch, branchUpdate,
+ getAccount(mergeTip.getCurrentTip()));
}
private Account getAccount(CodeReviewCommit codeReviewCommit) {
@@ -671,33 +758,42 @@ public class MergeOp {
return "";
}
- private void updateChangeStatus(final List<Change> submitted) throws NoSuchChangeException {
- for (final Change c : submitted) {
- final CodeReviewCommit commit = commits.get(c.getId());
- final CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
+ private void updateChangeStatus(List<Change> submitted, MergeTip mergeTip)
+ throws NoSuchChangeException {
+ logDebug("Updating change status for {} changes", submitted.size());
+ for (Change c : submitted) {
+ CodeReviewCommit commit = commits.get(c.getId());
+ CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
if (s == null) {
// Shouldn't ever happen, but leave the change alone. We'll pick
// it up on the next pass.
//
+ logDebug("Submitted change {} did not appear in set of new commits"
+ + " produced by merge strategy", c.getId());
continue;
}
- final String txt = s.getMessage();
-
+ String txt = s.getMessage();
+ logDebug("Status of change {} ({}) on {}: {}", c.getId(), commit.name(),
+ c.getDest(), s);
+ // If mergeTip is null merge failed and mergeResultRev will not be read.
+ ObjectId mergeResultRev =
+ mergeTip != null ? mergeTip.getMergeResults().get(commit) : null;
try {
switch (s) {
case CLEAN_MERGE:
- setMerged(c, message(c, txt + getByAccountName(commit)));
+ setMerged(c, message(c, txt + getByAccountName(commit)),
+ mergeResultRev);
break;
case CLEAN_REBASE:
case CLEAN_PICK:
setMerged(c, message(c, txt + " as " + commit.name()
- + getByAccountName(commit)));
+ + getByAccountName(commit)), mergeResultRev);
break;
case ALREADY_MERGED:
- setMerged(c, null);
+ setMerged(c, null, mergeResultRev);
break;
case PATH_CONFLICT:
@@ -714,54 +810,64 @@ public class MergeOp {
break;
case MISSING_DEPENDENCY:
+ logDebug("Change {} is missing dependency", c.getId());
potentiallyStillSubmittable.add(commit);
break;
default:
- setNew(commit, message(c, "Unspecified merge failure: " + s.name()));
+ setNew(commit,
+ message(c, "Unspecified merge failure: " + s.name()));
break;
}
} catch (OrmException err) {
- log.warn("Error updating change status for " + c.getId(), err);
+ logWarn("Error updating change status for " + c.getId(), err);
} catch (IOException err) {
- log.warn("Error updating change status for " + c.getId(), err);
+ logWarn("Error updating change status for " + c.getId(), err);
}
}
}
- private void updateSubscriptions(final List<Change> submitted) {
- if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
+ private void updateSubscriptions(List<Change> submitted) {
+ if (mergeTip != null
+ && (branchTip == null || branchTip != mergeTip.getCurrentTip())) {
+ logDebug("Updating submodule subscriptions for {} changes",
+ submitted.size());
SubmoduleOp subOp =
- subOpFactory.create(destBranch, mergeTip, rw, repo,
+ subOpFactory.create(destBranch, mergeTip.getCurrentTip(), rw, repo,
destProject.getProject(), submitted, commits,
- getAccount(mergeTip));
+ getAccount(mergeTip.getCurrentTip()));
try {
subOp.update();
} catch (SubmoduleException e) {
- log
- .error("The gitLinks were not updated according to the subscriptions "
- + e.getMessage());
+ logError(
+ "The gitLinks were not updated according to the subscriptions" , e);
}
}
}
- private Capable isSubmitStillPossible(final CodeReviewCommit commit) {
- final Capable capable;
- final Change c = commit.change();
- final boolean submitStillPossible = isSubmitForMissingCommitsStillPossible(commit);
- final long now = TimeUtil.nowMs();
- final long waitUntil = c.getLastUpdatedOn().getTime() + DEPENDENCY_DELAY;
+ private Capable isSubmitStillPossible(CodeReviewCommit commit) {
+ Capable capable;
+ Change c = commit.change();
+ boolean submitStillPossible =
+ isSubmitForMissingCommitsStillPossible(commit);
+ long now = TimeUtil.nowMs();
+ long waitUntil = c.getLastUpdatedOn().getTime() + DEPENDENCY_DELAY;
if (submitStillPossible && now < waitUntil) {
+ long recheckIn = waitUntil - now;
+ logDebug("Submit for {} is still possible; rechecking in {}ms",
+ c.getId(), recheckIn);
// If we waited a short while we might still be able to get
// this change submitted. Reschedule an attempt in a bit.
//
- mergeQueue.recheckAfter(destBranch, waitUntil - now, MILLISECONDS);
+ mergeQueue.recheckAfter(destBranch, recheckIn, MILLISECONDS);
capable = Capable.OK;
} else if (submitStillPossible) {
// It would be possible to submit the change if the missing
// dependencies are also submitted. Perhaps the user just
// forgot to submit those.
//
+ logDebug("Submit for {} is still possible after missing dependencies",
+ c.getId());
StringBuilder m = new StringBuilder();
m.append("Change could not be merged because of a missing dependency.");
m.append("\n");
@@ -781,20 +887,23 @@ public class MergeOp {
// needs to rebase it in order to work around the missing
// dependencies.
//
+ logDebug("Submit for {} is not possible", c.getId());
StringBuilder m = new StringBuilder();
m.append("Change cannot be merged due to unsatisfiable dependencies.\n");
m.append("\n");
m.append("The following dependency errors were found:\n");
m.append("\n");
for (CodeReviewCommit missingCommit : commit.missing) {
- if (missingCommit.getPatchsetId() != null) {
+ PatchSet.Id missingPsId = missingCommit.getPatchsetId();
+ if (missingPsId != null) {
m.append("* Depends on patch set ");
- m.append(missingCommit.getPatchsetId().get());
+ m.append(missingPsId.get());
m.append(" of ");
m.append(missingCommit.change().getKey().abbreviate());
- if (missingCommit.getPatchsetId().get() != missingCommit.change().currentPatchSetId().get()) {
+ PatchSet.Id currPsId = missingCommit.change().currentPatchSetId();
+ if (!missingPsId.equals(currPsId)) {
m.append(", however the current patch set is ");
- m.append(missingCommit.change().currentPatchSetId().get());
+ m.append(currPsId.get());
}
m.append(".\n");
@@ -812,21 +921,21 @@ public class MergeOp {
return capable;
}
- private void loadChangeInfo(final CodeReviewCommit commit)
+ private void loadChangeInfo(CodeReviewCommit commit)
throws NoSuchChangeException, OrmException {
if (commit.getControl() == null) {
List<PatchSet> matches =
db.patchSets().byRevision(new RevId(commit.name())).toList();
if (matches.size() == 1) {
- PatchSet ps = matches.get(0);
- commit.setPatchsetId(ps.getId());
- commit.setControl(changeControl(db.changes().get(ps.getId().getParentKey())));
+ PatchSet.Id psId = matches.get(0).getId();
+ commit.setPatchsetId(psId);
+ commit.setControl(changeControl(db.changes().get(psId.getParentKey())));
}
}
}
- private ChangeMessage message(final Change c, final String body) {
- final String uuid;
+ private ChangeMessage message(Change c, String body) {
+ String uuid;
try {
uuid = ChangeUtil.messageUUID(db);
} catch (OrmException e) {
@@ -838,19 +947,22 @@ public class MergeOp {
return m;
}
- private void setMerged(Change c, ChangeMessage msg)
- throws OrmException, IOException, NoSuchChangeException {
+ private void setMerged(Change c, ChangeMessage msg, ObjectId mergeResultRev)
+ throws OrmException, IOException {
+ logDebug("Setting change {} merged", c.getId());
ChangeUpdate update = null;
+ PatchSetApproval submitter;
+ PatchSet merged;
try {
db.changes().beginTransaction(c.getId());
// We must pull the patchset out of commits, because the patchset ID is
// modified when using the cherry-pick merge strategy.
CodeReviewCommit commit = commits.get(c.getId());
- PatchSet.Id merged = commit.change().currentPatchSetId();
- c = setMergedPatchSet(c.getId(), merged);
- PatchSetApproval submitter =
- approvalsUtil.getSubmitter(db, commit.notes(), merged);
+ PatchSet.Id mergedId = commit.change().currentPatchSetId();
+ merged = db.patchSets().get(mergedId);
+ c = setMergedPatchSet(c.getId(), mergedId);
+ submitter = approvalsUtil.getSubmitter(db, commit.notes(), mergedId);
ChangeControl control = commit.getControl();
update = updateFactory.create(control, c.getLastUpdatedOn());
@@ -862,22 +974,21 @@ public class MergeOp {
}
db.commit();
- sendMergedEmail(c, submitter);
- indexer.index(db, c);
- if (submitter != null) {
- try {
- hooks.doChangeMergedHook(c,
- accountCache.get(submitter.getAccountId()).getAccount(),
- db.patchSets().get(merged), db);
- } catch (OrmException ex) {
- log.error("Cannot run hook for submitted patch set " + c.getId(), ex);
- }
- }
} finally {
db.rollback();
}
- indexer.index(db, c);
update.commit();
+ sendMergedEmail(c, submitter);
+ indexer.index(db, c);
+ if (submitter != null && mergeResultRev != null) {
+ try {
+ hooks.doChangeMergedHook(c,
+ accountCache.get(submitter.getAccountId()).getAccount(),
+ merged, db, mergeResultRev.name());
+ } catch (OrmException ex) {
+ logError("Cannot run hook for submitted patch set " + c.getId(), ex);
+ }
+ }
}
private Change setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged)
@@ -886,10 +997,6 @@ public class MergeOp {
@Override
public Change update(Change c) {
c.setStatus(Change.Status.MERGED);
- // It could be possible that the change being merged
- // has never had its mergeability tested. So we insure
- // merged changes has mergeable field true.
- c.setMergeable(true);
if (!merged.equals(c.currentPatchSetId())) {
// Uncool; the patch set changed after we merged it.
// Go back to the patch set that was actually merged.
@@ -897,7 +1004,7 @@ public class MergeOp {
try {
c.setCurrentPatchSet(patchSetInfoFactory.get(db, merged));
} catch (PatchSetInfoNotAvailableException e1) {
- log.error("Cannot read merged patch set " + merged, e1);
+ logError("Cannot read merged patch set " + merged, e1);
}
}
ChangeUtil.updated(c);
@@ -920,7 +1027,7 @@ public class MergeOp {
reviewDb.close();
}
} catch (Exception e) {
- log.error("Cannot send email for submitted patch set " + c.getId(), e);
+ logError("Cannot send email for submitted patch set " + c.getId(), e);
return;
}
@@ -932,7 +1039,7 @@ public class MergeOp {
cm.setPatchSet(patchSet);
cm.send();
} catch (Exception e) {
- log.error("Cannot send email for submitted patch set " + c.getId(), e);
+ logError("Cannot send email for submitted patch set " + c.getId(), e);
}
}
@@ -948,11 +1055,13 @@ public class MergeOp {
c, identifiedUserFactory.create(c.getOwner()));
}
- private void setNew(CodeReviewCommit c, ChangeMessage msg) throws NoSuchChangeException, IOException {
+ private void setNew(CodeReviewCommit c, ChangeMessage msg)
+ throws NoSuchChangeException, IOException {
sendMergeFail(c.notes(), msg, true);
}
- private void setNew(Change c, ChangeMessage msg) throws OrmException, NoSuchChangeException, IOException {
+ private void setNew(Change c, ChangeMessage msg)
+ throws NoSuchChangeException, IOException {
sendMergeFail(notesFactory.create(c), msg, true);
}
@@ -964,39 +1073,57 @@ public class MergeOp {
@Nullable PatchSetApproval submitter,
ChangeMessage msg,
ChangeNotes notes) {
- if (submitter != null
- && TimeUtil.nowMs() - submitter.getGranted().getTime()
- > MAX_SUBMIT_WINDOW) {
- return RetryStatus.UNSUBMIT;
+ Change.Id id = notes.getChangeId();
+ if (submitter != null) {
+ long sinceMs = TimeUtil.nowMs() - submitter.getGranted().getTime();
+ if (sinceMs > MAX_SUBMIT_WINDOW) {
+ logDebug("Change {} submitted {}ms ago, unsubmitting", id, sinceMs);
+ return RetryStatus.UNSUBMIT;
+ } else {
+ logDebug("Change {} submitted {}ms ago, within window", id, sinceMs);
+ }
+ } else {
+ logDebug("No submitter for change {}", id);
}
try {
ChangeMessage last = Iterables.getLast(cmUtil.byChange(db, notes));
if (last != null) {
- if (Objects.equal(last.getAuthor(), msg.getAuthor())
- && Objects.equal(last.getMessage(), msg.getMessage())) {
+ if (Objects.equals(last.getAuthor(), msg.getAuthor())
+ && Objects.equals(last.getMessage(), msg.getMessage())) {
long lastMs = last.getWrittenOn().getTime();
long msgMs = msg.getWrittenOn().getTime();
- return msgMs - lastMs > MAX_SUBMIT_WINDOW
- ? RetryStatus.UNSUBMIT
- : RetryStatus.RETRY_NO_MESSAGE;
+ long sinceMs = msgMs - lastMs;
+ if (sinceMs > MAX_SUBMIT_WINDOW) {
+ logDebug("Last message for change {} was {}ms ago, unsubmitting",
+ id, sinceMs);
+ return RetryStatus.UNSUBMIT;
+ } else {
+ logDebug("Last message for change {} was {}ms ago, within window",
+ id, sinceMs);
+ return RetryStatus.RETRY_NO_MESSAGE;
+ }
+ } else {
+ logDebug("Last message for change {} differed, adding message", id);
}
}
return RetryStatus.RETRY_ADD_MESSAGE;
} catch (OrmException err) {
- log.warn("Cannot check previous merge failure, unsubmitting", err);
+ logWarn("Cannot check previous merge failure, unsubmitting", err);
return RetryStatus.UNSUBMIT;
}
}
private void sendMergeFail(ChangeNotes notes, final ChangeMessage msg,
boolean makeNew) throws NoSuchChangeException, IOException {
+ logDebug("Possibly sending merge failure notification for {}",
+ notes.getChangeId());
PatchSetApproval submitter = null;
try {
submitter = approvalsUtil.getSubmitter(
db, notes, notes.getChange().currentPatchSetId());
} catch (Exception e) {
- log.error("Cannot get submitter", e);
+ logError("Cannot get submitter for change " + notes.getChangeId(), e);
}
if (!makeNew) {
@@ -1041,7 +1168,7 @@ public class MergeOp {
db.rollback();
}
} catch (OrmException err) {
- log.warn("Cannot record merge failure message", err);
+ logWarn("Cannot record merge failure message", err);
}
if (update != null) {
update.commit();
@@ -1067,12 +1194,12 @@ public class MergeOp {
reviewDb.close();
}
} catch (Exception e) {
- log.error("Cannot send email notifications about merge failure", e);
+ logError("Cannot send email notifications about merge failure", e);
return;
}
try {
- final MergeFailSender cm = mergeFailSenderFactory.create(c);
+ MergeFailSender cm = mergeFailSenderFactory.create(c);
if (from != null) {
cm.setFrom(from.getAccountId());
}
@@ -1080,7 +1207,7 @@ public class MergeOp {
cm.setChangeMessage(msg);
cm.send();
} catch (Exception e) {
- log.error("Cannot send email notifications about merge failure", e);
+ logError("Cannot send email notifications about merge failure", e);
}
}
@@ -1094,7 +1221,7 @@ public class MergeOp {
try {
indexFuture.checkedGet();
} catch (IOException e) {
- log.error("Failed to index new change message", e);
+ logError("Failed to index new change message", e);
}
}
@@ -1104,7 +1231,7 @@ public class MergeOp {
accountCache.get(submitter.getAccountId()).getAccount(),
db.patchSets().get(c.currentPatchSetId()), msg.getMessage(), db);
} catch (OrmException ex) {
- log.error("Cannot run hook for merge failed " + c.getId(), ex);
+ logError("Cannot run hook for merge failed " + c.getId(), ex);
}
}
}
@@ -1113,8 +1240,9 @@ public class MergeOp {
Exception err = null;
try {
openSchema();
- for (Change c : db.changes().byProjectOpenAll(destBranch.getParentKey())) {
- abandonOneChange(c);
+ for (ChangeData cd
+ : queryProvider.get().byProjectOpen(destBranch.getParentKey())) {
+ abandonOneChange(cd.change());
}
db.close();
db = null;
@@ -1124,9 +1252,8 @@ public class MergeOp {
err = e;
}
if (err != null) {
- log.warn(String.format(
- "Cannot abandon changes for deleted project %s",
- destBranch.getParentKey().get()), err);
+ logWarn("Cannot abandon changes for deleted project "
+ + destBranch.getParentKey().get(), err);
}
}
@@ -1172,4 +1299,34 @@ public class MergeOp {
}
update.commit();
}
+
+ private void logDebug(String msg, Object... args) {
+ if (log.isDebugEnabled()) {
+ log.debug(logPrefix + msg, args);
+ }
+ }
+
+ private void logWarn(String msg, Throwable t) {
+ if (log.isWarnEnabled()) {
+ log.warn(logPrefix + msg, t);
+ }
+ }
+
+ private void logWarn(String msg) {
+ if (log.isWarnEnabled()) {
+ log.warn(logPrefix + msg);
+ }
+ }
+
+ private void logError(String msg, Throwable t) {
+ if (log.isErrorEnabled()) {
+ log.error(logPrefix + msg, t);
+ }
+ }
+
+ private void logError(String msg) {
+ if (log.isErrorEnabled()) {
+ log.error(logPrefix + msg);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java
new file mode 100644
index 0000000000..ba92651220
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2014 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.base.Preconditions.checkArgument;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.Nullable;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Class describing a merge tip during merge operation.
+ * <p>
+ * The current tip of a {@link MergeTip} may be null if the merge operation is
+ * against an unborn branch, and has not yet been attempted. This is distinct
+ * from a null {@link MergeTip} instance, which may be used to indicate that a
+ * merge failed or another error state.
+ */
+public class MergeTip {
+ private CodeReviewCommit branchTip;
+ private Map<ObjectId, ObjectId> mergeResults;
+
+ /**
+ * @param initial Tip before the merge operation; may be null, indicating an
+ * unborn branch.
+ * @param toMerge List of CodeReview commits to be merged in merge operation;
+ * may not be null or empty.
+ */
+ public MergeTip(@Nullable CodeReviewCommit initial,
+ Collection<CodeReviewCommit> toMerge) {
+ checkArgument(toMerge != null && !toMerge.isEmpty(),
+ "toMerge may not be null or empty: %s", toMerge);
+ this.mergeResults = Maps.newHashMap();
+ this.branchTip = initial;
+ // Assume fast-forward merge until opposite is proven.
+ for (CodeReviewCommit commit : toMerge) {
+ mergeResults.put(commit.copy(), commit.copy());
+ }
+ }
+
+ /**
+ * Moves this MergeTip to newTip and appends mergeResult.
+ *
+ * @param newTip The new tip; may not be null.
+ * @param mergedFrom The result of the merge of {@code newTip}.
+ */
+ public void moveTipTo(CodeReviewCommit newTip, ObjectId mergedFrom) {
+ checkArgument(newTip != null);
+ branchTip = newTip;
+ mergeResults.put(mergedFrom, newTip.copy());
+ }
+
+ /**
+ * The merge results of all the merges of this merge operation.
+ *
+ * @return The merge results of the merge operation as a map of SHA-1 to be
+ * merged to SHA-1 of the merge result.
+ */
+ public Map<ObjectId, ObjectId> getMergeResults() {
+ return mergeResults;
+ }
+
+ /**
+ * @return The current tip of the current merge operation; may be null,
+ * indicating an unborn branch.
+ */
+ @Nullable
+ public CodeReviewCommit getCurrentTip() {
+ return branchTip;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index cf348ad9c8..84be4a16b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -20,12 +20,13 @@ import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -71,7 +72,6 @@ import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
@@ -80,8 +80,6 @@ import java.util.Set;
import java.util.TimeZone;
public class MergeUtil {
- public static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
- private static final FooterKey REVIEWED_ON = new FooterKey("Reviewed-on");
private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
private static final String R_HEADS_MASTER =
Constants.R_HEADS + Constants.MASTER;
@@ -154,23 +152,16 @@ public class MergeUtil {
return mergeTip;
}
- public void reduceToMinimalMerge(final MergeSorter mergeSorter,
- final List<CodeReviewCommit> toSort) throws MergeException {
- final Collection<CodeReviewCommit> heads;
+ public List<CodeReviewCommit> reduceToMinimalMerge(MergeSorter mergeSorter,
+ Collection<CodeReviewCommit> toSort) throws MergeException {
+ List<CodeReviewCommit> result = new ArrayList<>();
try {
- heads = mergeSorter.sort(toSort);
+ result.addAll(mergeSorter.sort(toSort));
} catch (IOException e) {
throw new MergeException("Branch head sorting failed", e);
}
-
- toSort.clear();
- toSort.addAll(heads);
- Collections.sort(toSort, new Comparator<CodeReviewCommit>() {
- @Override
- public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
- return a.originalOrder - b.originalOrder;
- }
- });
+ Collections.sort(result, CodeReviewCommit.ORDER);
+ return result;
}
public PatchSetApproval getSubmitter(CodeReviewCommit c) {
@@ -180,7 +171,8 @@ public class MergeUtil {
public RevCommit createCherryPickFromCommit(Repository repo,
ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
PersonIdent cherryPickCommitterIdent, String commitMsg, RevWalk rw)
- throws MissingObjectException, IncorrectObjectTypeException, IOException {
+ throws MissingObjectException, IncorrectObjectTypeException, IOException,
+ MergeIdenticalTreeException, MergeConflictException {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
@@ -188,7 +180,7 @@ public class MergeUtil {
if (m.merge(mergeTip, originalCommit)) {
ObjectId tree = m.getResultTreeId();
if (tree.equals(mergeTip.getTree())) {
- return null;
+ throw new MergeIdenticalTreeException("identical tree");
}
CommitBuilder mergeCommit = new CommitBuilder();
@@ -199,7 +191,7 @@ public class MergeUtil {
mergeCommit.setMessage(commitMsg);
return rw.parseCommit(commit(inserter, mergeCommit));
} else {
- return null;
+ throw new MergeConflictException("merge conflict");
}
}
@@ -223,8 +215,8 @@ public class MergeUtil {
msgbuf.append('\n');
}
- if (!contains(footers, CHANGE_ID, n.change().getKey().get())) {
- msgbuf.append(CHANGE_ID.getName());
+ if (!contains(footers, FooterConstants.CHANGE_ID, n.change().getKey().get())) {
+ msgbuf.append(FooterConstants.CHANGE_ID.getName());
msgbuf.append(": ");
msgbuf.append(n.change().getKey().get());
msgbuf.append('\n');
@@ -233,8 +225,8 @@ public class MergeUtil {
final String siteUrl = urlProvider.get();
if (siteUrl != null) {
final String url = siteUrl + n.getPatchsetId().getParentKey().get();
- if (!contains(footers, REVIEWED_ON, url)) {
- msgbuf.append(REVIEWED_ON.getName());
+ if (!contains(footers, FooterConstants.REVIEWED_ON, url)) {
+ msgbuf.append(FooterConstants.REVIEWED_ON.getName());
msgbuf.append(": ");
msgbuf.append(url);
msgbuf.append('\n');
@@ -379,8 +371,7 @@ public class MergeUtil {
final Timestamp dt = submitter.getGranted();
final TimeZone tz = myIdent.getTimeZone();
- if (emails.size() == 1
- && who.getEmailAddresses().contains(emails.iterator().next())) {
+ if (emails.size() == 1 && who.hasEmailAddress(emails.iterator().next())) {
authorIdent =
new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
} else {
@@ -612,9 +603,11 @@ public class MergeUtil {
}
if (topics.size() == 1) {
- return String.format("Merge topic '%s'", Iterables.getFirst(topics, null));
+ return String.format("Merge changes from topic '%s'",
+ Iterables.getFirst(topics, null));
} else if (topics.size() > 1) {
- return String.format("Merge topics '%s'", Joiner.on("', '").join(topics));
+ return String.format("Merge changes from topics '%s'",
+ Joiner.on("', '").join(topics));
} else {
return String.format("Merge changes %s%s",
Joiner.on(',').join(Iterables.transform(
@@ -631,8 +624,11 @@ public class MergeUtil {
public ThreeWayMerger newThreeWayMerger(final Repository repo,
final ObjectInserter inserter) {
- return newThreeWayMerger(repo, inserter,
- mergeStrategyName(useContentMerge, useRecursiveMerge));
+ return newThreeWayMerger(repo, inserter, mergeStrategyName());
+ }
+
+ public String mergeStrategyName() {
+ return mergeStrategyName(useContentMerge, useRecursiveMerge);
}
public static String mergeStrategyName(boolean useContentMerge,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index b48028e337..c71c94fda9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
@@ -21,8 +22,10 @@ import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@@ -59,7 +62,42 @@ public class MetaDataUpdate {
public MetaDataUpdate create(Project.NameKey name, IdentifiedUser user)
throws RepositoryNotFoundException, IOException {
- MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
+ return create(name, user, null);
+ }
+
+ /**
+ * Create an update using an existing batch ref update.
+ * <p>
+ * This allows batching together updates to multiple metadata refs. For making
+ * multiple commits to a single metadata ref, see
+ * {@link VersionedMetaData#openUpdate(MetaDataUpdate)}.
+ *
+ * @param name project name.
+ * @param user user for the update.
+ * @param batch batch update to use; the caller is responsible for committing
+ * the update.
+ */
+ public MetaDataUpdate create(Project.NameKey name, IdentifiedUser user,
+ BatchRefUpdate batch) throws RepositoryNotFoundException, IOException {
+ return create(name, mgr.openRepository(name), user, batch);
+ }
+
+ /**
+ * Create an update using an existing batch ref update.
+ * <p>
+ * This allows batching together updates to multiple metadata refs. For making
+ * multiple commits to a single metadata ref, see
+ * {@link VersionedMetaData#openUpdate(MetaDataUpdate)}.
+ *
+ * @param name project name.
+ * @param repository GIT respository
+ * @param user user for the update.
+ * @param batch batch update to use; the caller is responsible for committing
+ * the update.
+ */
+ public MetaDataUpdate create(Project.NameKey name, Repository repository,
+ IdentifiedUser user, BatchRefUpdate batch) {
+ MetaDataUpdate md = factory.create(name, repository, batch);
md.getCommitBuilder().setAuthor(createPersonIdent(user));
md.getCommitBuilder().setCommitter(serverIdent);
return md;
@@ -86,7 +124,13 @@ public class MetaDataUpdate {
public MetaDataUpdate create(Project.NameKey name)
throws RepositoryNotFoundException, IOException {
- MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
+ return create(name, null);
+ }
+
+ /** @see User#create(Project.NameKey, IdentifiedUser, BatchRefUpdate) */
+ public MetaDataUpdate create(Project.NameKey name, BatchRefUpdate batch)
+ throws RepositoryNotFoundException, IOException {
+ MetaDataUpdate md = factory.create(name, mgr.openRepository(name), batch);
md.getCommitBuilder().setAuthor(serverIdent);
md.getCommitBuilder().setCommitter(serverIdent);
return md;
@@ -95,24 +139,32 @@ public class MetaDataUpdate {
interface InternalFactory {
MetaDataUpdate create(@Assisted Project.NameKey projectName,
- @Assisted Repository db);
+ @Assisted Repository db, @Assisted @Nullable BatchRefUpdate batch);
}
private final GitReferenceUpdated gitRefUpdated;
private final Project.NameKey projectName;
private final Repository db;
+ private final BatchRefUpdate batch;
private final CommitBuilder commit;
private boolean allowEmpty;
- @Inject
+ @AssistedInject
public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
- @Assisted Project.NameKey projectName, @Assisted Repository db) {
+ @Assisted Project.NameKey projectName, @Assisted Repository db,
+ @Assisted @Nullable BatchRefUpdate batch) {
this.gitRefUpdated = gitRefUpdated;
this.projectName = projectName;
this.db = db;
+ this.batch = batch;
this.commit = new CommitBuilder();
}
+ public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
+ Project.NameKey projectName, Repository db) {
+ this(gitRefUpdated, projectName, db, null);
+ }
+
/** Set the commit message used when committing the update. */
public void setMessage(String message) {
getCommitBuilder().setMessage(message);
@@ -128,6 +180,11 @@ public class MetaDataUpdate {
this.allowEmpty = allowEmpty;
}
+ /** @return batch in which to run the update, or {@code null} for no batch. */
+ BatchRefUpdate getBatch() {
+ return batch;
+ }
+
/** Close the cached Repository handle. */
public void close() {
getRepository().close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index 4279b317ec..d081fe6bd7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -25,14 +25,13 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
-
+import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.List;
/**
* Progress reporting interface that multiplexes multiple sub-tasks.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 351394249c..95f5108d10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -19,7 +19,7 @@ import static com.google.gerrit.common.data.Permission.isPermission;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -39,9 +39,9 @@ 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.RefConfigSection;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
@@ -58,9 +58,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.StringUtils;
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -75,7 +73,7 @@ import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
-public class ProjectConfig extends VersionedMetaData {
+public class ProjectConfig extends VersionedMetaData implements ValidationError.Sink {
public static final String COMMENTLINK = "commentlink";
private static final String KEY_MATCH = "match";
private static final String KEY_HTML = "html";
@@ -83,7 +81,6 @@ public class ProjectConfig extends VersionedMetaData {
private static final String KEY_ENABLED = "enabled";
public static final String PROJECT_CONFIG = "project.config";
- private static final String GROUP_LIST = "groups";
private static final String PROJECT = "project";
private static final String KEY_DESCRIPTION = "description";
@@ -115,6 +112,8 @@ public class ProjectConfig extends VersionedMetaData {
private static final String RECEIVE = "receive";
private static final String KEY_REQUIRE_SIGNED_OFF_BY = "requireSignedOffBy";
private static final String KEY_REQUIRE_CHANGE_ID = "requireChangeId";
+ private static final String KEY_USE_ALL_NOT_IN_TARGET =
+ "createNewChangeForAllNotInTarget";
private static final String KEY_MAX_OBJECT_SIZE_LIMIT = "maxObjectSizeLimit";
private static final String KEY_REQUIRE_CONTRIBUTOR_AGREEMENT =
"requireContributorAgreement";
@@ -135,7 +134,8 @@ public class ProjectConfig extends VersionedMetaData {
private static final String KEY_COPY_MIN_SCORE = "copyMinScore";
private static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
private static final String KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE = "copyAllScoresOnTrivialRebase";
- private static final String KEY_COPY_ALL_SCORES_IF_NO_CHANGE = "copyAllScoresIfNoCodeChange";
+ private static final String KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE = "copyAllScoresIfNoCodeChange";
+ private static final String KEY_COPY_ALL_SCORES_IF_NO_CHANGE = "copyAllScoresIfNoChange";
private static final String KEY_VALUE = "value";
private static final String KEY_CAN_OVERRIDE = "canOverride";
private static final String KEY_Branch = "branch";
@@ -152,7 +152,7 @@ public class ProjectConfig extends VersionedMetaData {
private Project.NameKey projectName;
private Project project;
private AccountsSection accountsSection;
- private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
+ private GroupList groupList;
private Map<String, AccessSection> accessSections;
private BranchOrderSection branchOrderSection;
private Map<String, ContributorAgreement> contributorAgreements;
@@ -220,6 +220,10 @@ public class ProjectConfig extends VersionedMetaData {
this.projectName = projectName;
}
+ public Project.NameKey getName() {
+ return projectName;
+ }
+
public Project getProject() {
return project;
}
@@ -318,24 +322,17 @@ public class ProjectConfig extends VersionedMetaData {
}
public GroupReference resolve(GroupReference group) {
- if (group != null) {
- GroupReference ref = groupsByUUID.get(group.getUUID());
- if (ref != null) {
- return ref;
- }
- groupsByUUID.put(group.getUUID(), group);
- }
- return group;
+ return groupList.resolve(group);
}
/** @return the group reference, if the group is used by at least one rule. */
public GroupReference getGroup(AccountGroup.UUID uuid) {
- return groupsByUUID.get(uuid);
+ return groupList.byUUID(uuid);
}
/** @return set of all groups used by this configuration. */
public Set<AccountGroup.UUID> getAllGroupUUIDs() {
- return Collections.unmodifiableSet(groupsByUUID.keySet());
+ return groupList.uuids();
}
/**
@@ -369,7 +366,7 @@ public class ProjectConfig extends VersionedMetaData {
*/
public boolean updateGroupNames(GroupBackend groupBackend) {
boolean dirty = false;
- for (GroupReference ref : groupsByUUID.values()) {
+ for (GroupReference ref : groupList.references()) {
GroupDescription.Basic g = groupBackend.get(ref.getUUID());
if (g != null && !g.getName().equals(ref.getName())) {
dirty = true;
@@ -399,7 +396,8 @@ public class ProjectConfig extends VersionedMetaData {
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
- Map<String, GroupReference> groupsByName = readGroupList();
+ readGroupList();
+ Map<String, GroupReference> groupsByName = mapGroupReferences();
rulesId = getObjectId("rules.pl");
Config rc = readConfig(PROJECT_CONFIG);
@@ -415,6 +413,7 @@ public class ProjectConfig extends VersionedMetaData {
p.setUseContributorAgreements(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, InheritableBoolean.INHERIT));
p.setUseSignedOffBy(getEnum(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, InheritableBoolean.INHERIT));
p.setRequireChangeID(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, InheritableBoolean.INHERIT));
+ p.setCreateNewChangeForAllNotInTarget(getEnum(rc, RECEIVE, null, KEY_USE_ALL_NOT_IN_TARGET, InheritableBoolean.INHERIT));
p.setMaxObjectSizeLimit(rc.getString(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT));
p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
@@ -508,8 +507,7 @@ public class ProjectConfig extends VersionedMetaData {
NOTIFY, sectionName, KEY_TYPE,
NotifyType.ALL));
n.setTypes(types);
- n.setHeader(ConfigUtil.getEnum(rc,
- NOTIFY, sectionName, KEY_HEADER,
+ n.setHeader(rc.getEnum(NOTIFY, sectionName, KEY_HEADER,
NotifyConfig.Header.BCC));
for (String dst : rc.getStringList(NOTIFY, sectionName, KEY_EMAIL)) {
@@ -524,7 +522,7 @@ public class ProjectConfig extends VersionedMetaData {
n.addEmail(ref);
} else {
error(new ValidationError(PROJECT_CONFIG,
- "group \"" + ref.getName() + "\" not in " + GROUP_LIST));
+ "group \"" + ref.getName() + "\" not in " + GroupList.FILE_NAME));
}
} else if (dst.startsWith("user ")) {
error(new ValidationError(PROJECT_CONFIG, dst + " not supported"));
@@ -620,7 +618,7 @@ public class ProjectConfig extends VersionedMetaData {
ref = rule.getGroup();
groupsByName.put(ref.getName(), ref);
error(new ValidationError(PROJECT_CONFIG,
- "group \"" + ref.getName() + "\" not in " + GROUP_LIST));
+ "group \"" + ref.getName() + "\" not in " + GroupList.FILE_NAME));
}
rule.setGroup(ref);
@@ -641,7 +639,7 @@ public class ProjectConfig extends VersionedMetaData {
valueText);
}
- private void loadLabelSections(Config rc) throws IOException {
+ private void loadLabelSections(Config rc) {
Map<String, String> lowerNames = Maps.newHashMapWithExpectedSize(2);
labelSections = Maps.newLinkedHashMap();
for (String name : rc.getSubsections(LABEL)) {
@@ -673,7 +671,7 @@ public class ProjectConfig extends VersionedMetaData {
continue;
}
- String functionName = Objects.firstNonNull(
+ String functionName = MoreObjects.firstNonNull(
rc.getString(LABEL, name, KEY_FUNCTION), "MaxWithBlock");
if (LABEL_FUNCTIONS.contains(functionName)) {
label.setFunctionName(functionName);
@@ -695,15 +693,23 @@ public class ProjectConfig extends VersionedMetaData {
}
}
label.setCopyMinScore(
- rc.getBoolean(LABEL, name, KEY_COPY_MIN_SCORE, false));
+ rc.getBoolean(LABEL, name, KEY_COPY_MIN_SCORE,
+ LabelType.DEF_COPY_MIN_SCORE));
label.setCopyMaxScore(
- rc.getBoolean(LABEL, name, KEY_COPY_MAX_SCORE, false));
+ rc.getBoolean(LABEL, name, KEY_COPY_MAX_SCORE,
+ LabelType.DEF_COPY_MAX_SCORE));
label.setCopyAllScoresOnTrivialRebase(
- rc.getBoolean(LABEL, name, KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE, false));
+ rc.getBoolean(LABEL, name, KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
+ LabelType.DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE));
label.setCopyAllScoresIfNoCodeChange(
- rc.getBoolean(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE, false));
+ rc.getBoolean(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
+ LabelType.DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE));
+ label.setCopyAllScoresIfNoChange(
+ rc.getBoolean(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
+ LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE));
label.setCanOverride(
- rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, true));
+ rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE,
+ LabelType.DEF_CAN_OVERRIDE));
label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_Branch));
labelSections.put(name, label);
}
@@ -769,31 +775,18 @@ public class ProjectConfig extends VersionedMetaData {
return new PluginConfig(pluginName, pluginConfig, this);
}
- private Map<String, GroupReference> readGroupList() throws IOException {
- groupsByUUID = new HashMap<>();
- Map<String, GroupReference> groupsByName = new HashMap<>();
-
- BufferedReader br = new BufferedReader(new StringReader(readUTF8(GROUP_LIST)));
- String s;
- for (int lineNumber = 1; (s = br.readLine()) != null; lineNumber++) {
- if (s.isEmpty() || s.startsWith("#")) {
- continue;
- }
-
- int tab = s.indexOf('\t');
- if (tab < 0) {
- error(new ValidationError(GROUP_LIST, lineNumber, "missing tab delimiter"));
- continue;
- }
-
- AccountGroup.UUID uuid = new AccountGroup.UUID(s.substring(0, tab).trim());
- String name = s.substring(tab + 1).trim();
- GroupReference ref = new GroupReference(uuid, name);
+ private void readGroupList() throws IOException {
+ groupList = GroupList.parse(readUTF8(GroupList.FILE_NAME), this);
+ }
- groupsByUUID.put(uuid, ref);
- groupsByName.put(name, ref);
+ private Map<String, GroupReference> mapGroupReferences() {
+ Collection<GroupReference> references = groupList.references();
+ Map<String, GroupReference> result = new HashMap<>(references.size());
+ for (GroupReference ref : references) {
+ result.put(ref.getName(), ref);
}
- return groupsByName;
+
+ return result;
}
@Override
@@ -816,6 +809,7 @@ public class ProjectConfig extends VersionedMetaData {
set(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, p.getUseContributorAgreements(), InheritableBoolean.INHERIT);
set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, p.getUseSignedOffBy(), InheritableBoolean.INHERIT);
set(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, p.getRequireChangeID(), InheritableBoolean.INHERIT);
+ set(rc, RECEIVE, null, KEY_USE_ALL_NOT_IN_TARGET, p.getCreateNewChangeForAllNotInTarget(), InheritableBoolean.INHERIT);
set(rc, RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
@@ -831,7 +825,7 @@ public class ProjectConfig extends VersionedMetaData {
saveContributorAgreements(rc, keepGroups);
saveAccessSections(rc, keepGroups);
saveNotifySections(rc, keepGroups);
- groupsByUUID.keySet().retainAll(keepGroups);
+ groupList.retainUUIDs(keepGroups);
saveLabelSections(rc);
savePluginSections(rc);
@@ -1048,32 +1042,22 @@ public class ProjectConfig extends VersionedMetaData {
toUnset.remove(name);
rc.setString(LABEL, name, KEY_FUNCTION, label.getFunctionName());
rc.setInt(LABEL, name, KEY_DEFAULT_VALUE, label.getDefaultValue());
- if (label.isCopyMinScore()) {
- rc.setBoolean(LABEL, name, KEY_COPY_MIN_SCORE, true);
- } else {
- rc.unset(LABEL, name, KEY_COPY_MIN_SCORE);
- }
- if (label.isCopyMaxScore()) {
- rc.setBoolean(LABEL, name, KEY_COPY_MAX_SCORE, true);
- } else {
- rc.unset(LABEL, name, KEY_COPY_MAX_SCORE);
- }
- if (label.isCopyAllScoresOnTrivialRebase()) {
- rc.setBoolean(LABEL, name, KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE, true);
- } else {
- rc.unset(LABEL, name, KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
- }
- if (label.isCopyAllScoresIfNoCodeChange()) {
- rc.setBoolean(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE, true);
- } else {
- rc.unset(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
- }
- if (!label.canOverride()) {
- rc.setBoolean(LABEL, name, KEY_CAN_OVERRIDE, false);
- } else {
- rc.unset(LABEL, name, KEY_CAN_OVERRIDE);
- }
+ setBooleanConfigKey(rc, name, KEY_COPY_MIN_SCORE, label.isCopyMinScore(),
+ LabelType.DEF_COPY_MIN_SCORE);
+ setBooleanConfigKey(rc, name, KEY_COPY_MAX_SCORE, label.isCopyMaxScore(),
+ LabelType.DEF_COPY_MAX_SCORE);
+ setBooleanConfigKey(rc, name, KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
+ label.isCopyAllScoresOnTrivialRebase(),
+ LabelType.DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
+ setBooleanConfigKey(rc, name, KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
+ label.isCopyAllScoresIfNoCodeChange(),
+ LabelType.DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
+ setBooleanConfigKey(rc, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
+ label.isCopyAllScoresIfNoChange(),
+ LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE);
+ setBooleanConfigKey(rc, name, KEY_CAN_OVERRIDE, label.canOverride(),
+ LabelType.DEF_CAN_OVERRIDE);
List<String> values =
Lists.newArrayListWithCapacity(label.getValues().size());
for (LabelValue value : label.getValues()) {
@@ -1087,6 +1071,15 @@ public class ProjectConfig extends VersionedMetaData {
}
}
+ private static void setBooleanConfigKey(
+ Config rc, String name, String key, boolean value, boolean defaultValue) {
+ if (value == defaultValue) {
+ rc.unset(LABEL, name, key);
+ } else {
+ rc.setBoolean(LABEL, name, key, value);
+ }
+ }
+
private void savePluginSections(Config rc) {
List<String> existing = Lists.newArrayList(rc.getSubsections(PLUGIN));
for (String name : existing) {
@@ -1104,30 +1097,7 @@ public class ProjectConfig extends VersionedMetaData {
}
private void saveGroupList() throws IOException {
- if (groupsByUUID.isEmpty()) {
- saveFile(GROUP_LIST, null);
- return;
- }
-
- final int uuidLen = 40;
- StringBuilder buf = new StringBuilder();
- buf.append(pad(uuidLen, "# UUID"));
- buf.append('\t');
- buf.append("Group Name");
- buf.append('\n');
-
- buf.append('#');
- buf.append('\n');
-
- for (GroupReference g : sort(groupsByUUID.values())) {
- if (g.getUUID() != null && g.getName() != null) {
- buf.append(pad(uuidLen, g.getUUID().get()));
- buf.append('\t');
- buf.append(g.getName());
- buf.append('\n');
- }
- }
- saveUTF8(GROUP_LIST, buf.toString());
+ saveUTF8(GroupList.FILE_NAME, groupList.asText());
}
private <E extends Enum<?>> E getEnum(Config rc, String section,
@@ -1140,26 +1110,14 @@ public class ProjectConfig extends VersionedMetaData {
}
}
- private void error(ValidationError error) {
+ @Override
+ public void error(ValidationError error) {
if (validationErrors == null) {
validationErrors = new ArrayList<>(4);
}
validationErrors.add(error);
}
- private static String pad(int len, String src) {
- if (len <= src.length()) {
- return src;
- }
-
- StringBuilder r = new StringBuilder(len);
- r.append(src);
- while (r.length() < len) {
- r.append(' ');
- }
- return r.toString();
- }
-
private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
ArrayList<T> r = new ArrayList<>(m);
Collections.sort(r);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index cedf11a432..57849b4425 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -14,7 +14,9 @@
package com.google.gerrit.server.git;
+import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+import static com.google.gerrit.server.change.HashtagsUtil.cleanupHashtag;
import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromReviewers;
@@ -28,11 +30,13 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_RE
import com.google.common.base.Function;
import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
@@ -40,6 +44,7 @@ import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.CheckedFuture;
@@ -49,6 +54,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.ChangeHookRunner.HookResult;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
@@ -72,7 +78,6 @@ import com.google.gerrit.server.ApprovalCopier;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResolver;
@@ -80,13 +85,14 @@ import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeKind;
import com.google.gerrit.server.change.ChangeKindCache;
import com.google.gerrit.server.change.ChangesCollection;
-import com.google.gerrit.server.change.MergeabilityChecker;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
@@ -94,11 +100,12 @@ 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.index.ChangeIndexer;
-import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -107,11 +114,11 @@ import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.LabelVote;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
@@ -131,7 +138,6 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
@@ -176,8 +182,6 @@ public class ReceiveCommits {
public static final Pattern NEW_PATCHSET = Pattern.compile(
"^" + REFS_CHANGES + "(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
- private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
-
private static final String COMMAND_REJECTION_MESSAGE_FOOTER =
"Please read the documentation and contact an administrator\n"
+ "if you feel the configuration is incorrect";
@@ -271,12 +275,12 @@ public class ReceiveCommits {
private final IdentifiedUser currentUser;
private final ReviewDb db;
+ private final Provider<InternalChangeQuery> queryProvider;
private final ChangeData.Factory changeDataFactory;
private final ChangeUpdate.Factory updateFactory;
private final SchemaFactory<ReviewDb> schemaFactory;
private final AccountResolver accountResolver;
private final CmdLineParser.Factory optionParserFactory;
- private final CreateChangeSender.Factory createChangeSenderFactory;
private final MergedSender.Factory mergedSenderFactory;
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final GitReferenceUpdated gitRefUpdated;
@@ -297,7 +301,6 @@ public class ReceiveCommits {
private final ListeningExecutorService changeUpdateExector;
private final RequestScopePropagator requestScopePropagator;
private final ChangeIndexer indexer;
- private final MergeabilityChecker mergeabilityChecker;
private final SshInfo sshInfo;
private final AllProjectsName allProjectsName;
private final ReceiveConfig receiveConfig;
@@ -310,12 +313,11 @@ public class ReceiveCommits {
private final ReceivePack rp;
private final NoteMap rejectCommits;
private MagicBranchInput magicBranch;
+ private boolean newChangeForAllNotInTarget;
private List<CreateRequest> newChanges = Collections.emptyList();
private final Map<Change.Id, ReplaceRequest> replaceByChange =
new HashMap<>();
- private final Map<RevCommit, ReplaceRequest> replaceByCommit =
- new HashMap<>();
private final Set<RevCommit> validCommits = new HashSet<>();
private ListMultimap<Change.Id, Ref> refsByChange;
@@ -326,6 +328,8 @@ public class ReceiveCommits {
private final Provider<Submit> submitProvider;
private final MergeQueue mergeQueue;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
+ private final NotesMigration notesMigration;
+ private final ChangeEditUtil editUtil;
private final List<CommitValidationMessage> messages = new ArrayList<>();
private ListMultimap<Error, String> errors = LinkedListMultimap.create();
@@ -338,12 +342,12 @@ public class ReceiveCommits {
@Inject
ReceiveCommits(final ReviewDb db,
+ final Provider<InternalChangeQuery> queryProvider,
final SchemaFactory<ReviewDb> schemaFactory,
final ChangeData.Factory changeDataFactory,
final ChangeUpdate.Factory updateFactory,
final AccountResolver accountResolver,
final CmdLineParser.Factory optionParserFactory,
- final CreateChangeSender.Factory createChangeSenderFactory,
final MergedSender.Factory mergedSenderFactory,
final ReplacePatchSetSender.Factory replacePatchSetFactory,
final GitReferenceUpdated gitRefUpdated,
@@ -361,12 +365,10 @@ public class ReceiveCommits {
final ChangeInserter.Factory changeInserterFactory,
final CommitValidators.Factory commitValidatorsFactory,
@CanonicalWebUrl final String canonicalWebUrl,
- @GerritPersonIdent final PersonIdent gerritIdent,
final WorkQueue workQueue,
@ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
final RequestScopePropagator requestScopePropagator,
final ChangeIndexer indexer,
- final MergeabilityChecker mergeabilityChecker,
final SshInfo sshInfo,
final AllProjectsName allProjectsName,
ReceiveConfig config,
@@ -376,15 +378,17 @@ public class ReceiveCommits {
final Provider<Submit> submitProvider,
final MergeQueue mergeQueue,
final ChangeKindCache changeKindCache,
- final DynamicMap<ProjectConfigEntry> pluginConfigEntries) throws IOException {
+ final DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+ final NotesMigration notesMigration,
+ final ChangeEditUtil editUtil) throws IOException {
this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
this.db = db;
+ this.queryProvider = queryProvider;
this.changeDataFactory = changeDataFactory;
this.updateFactory = updateFactory;
this.schemaFactory = schemaFactory;
this.accountResolver = accountResolver;
this.optionParserFactory = optionParserFactory;
- this.createChangeSenderFactory = createChangeSenderFactory;
this.mergedSenderFactory = mergedSenderFactory;
this.replacePatchSetFactory = replacePatchSetFactory;
this.gitRefUpdated = gitRefUpdated;
@@ -405,7 +409,6 @@ public class ReceiveCommits {
this.changeUpdateExector = changeUpdateExector;
this.requestScopePropagator = requestScopePropagator;
this.indexer = indexer;
- this.mergeabilityChecker = mergeabilityChecker;
this.sshInfo = sshInfo;
this.allProjectsName = allProjectsName;
this.receiveConfig = config;
@@ -416,17 +419,21 @@ public class ReceiveCommits {
this.project = projectControl.getProject();
this.repo = repo;
this.rp = new ReceivePack(repo);
- this.rejectCommits = BanCommit.loadRejectCommitsMap(repo);
+ this.rejectCommits = BanCommit.loadRejectCommitsMap(repo, rp.getRevWalk());
this.subOpFactory = subOpFactory;
this.submitProvider = submitProvider;
this.mergeQueue = mergeQueue;
this.pluginConfigEntries = pluginConfigEntries;
+ this.notesMigration = notesMigration;
+
+ this.editUtil = editUtil;
this.messageSender = new ReceivePackMessageSender();
ProjectState ps = projectControl.getProjectState();
+ this.newChangeForAllNotInTarget = ps.isCreateNewChangeForAllNotInTarget();
rp.setAllowCreates(true);
rp.setAllowDeletes(true);
rp.setAllowNonFastForwards(true);
@@ -476,7 +483,7 @@ public class ReceiveCommits {
});
advHooks.add(rp.getAdvertiseRefsHook());
advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
- db, projectControl.getProject().getNameKey()));
+ db, queryProvider, projectControl.getProject().getNameKey()));
rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
}
@@ -554,12 +561,16 @@ public class ReceiveCommits {
parseCommands(commands);
if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
- newChanges = selectNewChanges();
+ selectNewAndReplacedChangesFromMagicBranch();
}
preparePatchSetsForReplace();
if (!batch.getCommands().isEmpty()) {
try {
+ if (!batch.isAllowNonFastForwards() && magicBranch != null
+ && magicBranch.edit) {
+ batch.setAllowNonFastForwards(true);
+ }
batch.execute(rp.getRevWalk(), commandProgress);
} catch (IOException err) {
int cnt = 0;
@@ -588,28 +599,19 @@ public class ReceiveCommits {
for (final ReceiveCommand c : commands) {
if (c.getResult() == OK) {
- try {
+ if (c.getType() == ReceiveCommand.Type.UPDATE) { // aka fast-forward
+ tagCache.updateFastForward(project.getNameKey(),
+ c.getRefName(),
+ c.getOldId(),
+ c.getNewId());
+ }
+
+ if (isHead(c) || isConfig(c)) {
switch (c.getType()) {
case CREATE:
- if (isHead(c) || isConfig(c)) {
- autoCloseChanges(c);
- }
- break;
-
- case UPDATE: // otherwise known as a fast-forward
- tagCache.updateFastForward(project.getNameKey(),
- c.getRefName(),
- c.getOldId(),
- c.getNewId());
- if (isHead(c) || isConfig(c)) {
- autoCloseChanges(c);
- }
- break;
-
+ case UPDATE:
case UPDATE_NONFASTFORWARD:
- if (isHead(c) || isConfig(c)) {
- autoCloseChanges(c);
- }
+ autoCloseChanges(c);
break;
case DELETE:
@@ -626,36 +628,36 @@ public class ReceiveCommits {
}
break;
}
+ }
- if (isConfig(c)) {
- projectCache.evict(project);
- ProjectState ps = projectCache.get(project.getNameKey());
- repoManager.setProjectDescription(project.getNameKey(), //
- ps.getProject().getDescription());
- }
+ if (isConfig(c)) {
+ projectCache.evict(project);
+ ProjectState ps = projectCache.get(project.getNameKey());
+ repoManager.setProjectDescription(project.getNameKey(), //
+ ps.getProject().getDescription());
+ }
- if (!MagicBranch.isMagicBranch(c.getRefName())) {
- // We only fire gitRefUpdated for direct refs updates.
- // Events for change refs are fired when they are created.
- //
- gitRefUpdated.fire(project.getNameKey(), c.getRefName(),
- c.getOldId(), c.getNewId());
- hooks.doRefUpdatedHook(
- new Branch.NameKey(project.getNameKey(), c.getRefName()),
- c.getOldId(),
- c.getNewId(),
- currentUser.getAccount());
- }
- } catch (NoSuchChangeException e) {
- c.setResult(REJECTED_OTHER_REASON,
- "No such change: " + e.getMessage());
+ if (!MagicBranch.isMagicBranch(c.getRefName())) {
+ // We only fire gitRefUpdated for direct refs updates.
+ // Events for change refs are fired when they are created.
+ //
+ gitRefUpdated.fire(project.getNameKey(), c.getRefName(),
+ c.getOldId(), c.getNewId());
+ hooks.doRefUpdatedHook(
+ new Branch.NameKey(project.getNameKey(), c.getRefName()),
+ c.getOldId(),
+ c.getNewId(),
+ currentUser.getAccount());
}
}
}
closeProgress.end();
commandProgress.end();
progress.end();
+ reportMessages();
+ }
+ private void reportMessages() {
Iterable<CreateRequest> created =
Iterables.filter(newChanges, new Predicate<CreateRequest>() {
@Override
@@ -667,30 +669,39 @@ public class ReceiveCommits {
addMessage("");
addMessage("New Changes:");
for (CreateRequest c : created) {
- addMessage(formatChangeUrl(canonicalWebUrl, c.change));
+ addMessage(formatChangeUrl(canonicalWebUrl, c.change, false));
}
addMessage("");
}
- Iterable<ReplaceRequest> updated =
- Iterables.filter(replaceByChange.values(),
- new Predicate<ReplaceRequest>() {
+ List<ReplaceRequest> updated = FluentIterable
+ .from(replaceByChange.values())
+ .filter(new Predicate<ReplaceRequest>() {
+ @Override
+ public boolean apply(ReplaceRequest input) {
+ return !input.skip && input.inputCommand.getResult() == OK;
+ }
+ })
+ .toSortedList(Ordering.natural().onResultOf(
+ new Function<ReplaceRequest, Integer>() {
@Override
- public boolean apply(ReplaceRequest input) {
- return !input.skip && input.inputCommand.getResult() == OK;
+ public Integer apply(ReplaceRequest in) {
+ return in.change.getId().get();
}
- });
- if (!Iterables.isEmpty(updated)) {
+ }));
+ if (!updated.isEmpty()) {
addMessage("");
addMessage("Updated Changes:");
+ boolean edit = magicBranch != null && magicBranch.edit;
for (ReplaceRequest u : updated) {
- addMessage(formatChangeUrl(canonicalWebUrl, u.change));
+ addMessage(formatChangeUrl(canonicalWebUrl, u.change, edit));
}
addMessage("");
}
}
- private static String formatChangeUrl(String url, Change change) {
+ private static String formatChangeUrl(String url, Change change,
+ boolean edit) {
StringBuilder m = new StringBuilder()
.append(" ")
.append(url)
@@ -700,6 +711,9 @@ public class ReceiveCommits {
if (change.getStatus() == Change.Status.DRAFT) {
m.append(" [DRAFT]");
}
+ if (edit) {
+ m.append(" [EDIT]");
+ }
return m.toString();
}
@@ -1003,7 +1017,8 @@ public class ReceiveCommits {
}
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
- if (ctl.canCreate(rp.getRevWalk(), obj, allRefs.values().contains(obj))) {
+ rp.getRevWalk().reset();
+ if (ctl.canCreate(db, rp.getRevWalk(), obj)) {
validateNewCommits(ctl, cmd);
batch.addCommand(cmd);
} else {
@@ -1107,6 +1122,8 @@ public class ReceiveCommits {
List<RevCommit> baseCommit;
LabelTypes labelTypes;
CmdLineParser clp;
+ Set<String> hashtags = new HashSet<>();
+ NotesMigration notesMigration;
@Option(name = "--base", metaVar = "BASE", usage = "merge base of changes")
List<ObjectId> base;
@@ -1117,10 +1134,14 @@ public class ReceiveCommits {
@Option(name = "--draft", usage = "mark new/updated changes as draft")
boolean draft;
+ @Option(name = "--edit", aliases = {"-e"}, usage = "upload as change edit")
+ boolean edit;
+
@Option(name = "--submit", usage = "immediately submit the change")
boolean submit;
- @Option(name = "-r", metaVar = "EMAIL", usage = "add reviewer to changes")
+ @Option(name = "--reviewer", aliases = {"-r"}, metaVar = "EMAIL",
+ usage = "add reviewer to changes")
void reviewer(Account.Id id) {
reviewer.add(id);
}
@@ -1135,41 +1156,45 @@ public class ReceiveCommits {
draft = !publish;
}
- @Option(name = "-l", metaVar = "LABEL+VALUE",
+ @Option(name = "--label", aliases = {"-l"}, metaVar = "LABEL+VALUE",
usage = "label(s) to assign (defaults to +1 if no value provided")
void addLabel(final String token) throws CmdLineException {
LabelVote v = LabelVote.parse(token);
try {
- LabelType.checkName(v.getLabel());
- ApprovalsUtil.checkLabel(labelTypes, v.getLabel(), v.getValue());
+ LabelType.checkName(v.label());
+ ApprovalsUtil.checkLabel(labelTypes, v.label(), v.value());
} catch (IllegalArgumentException e) {
throw clp.reject(e.getMessage());
}
- labels.put(v.getLabel(), v.getValue());
+ labels.put(v.label(), v.value());
}
- MagicBranchInput(ReceiveCommand cmd, LabelTypes labelTypes) {
+ @Option(name = "--hashtag", aliases = {"-t"}, metaVar = "HASHTAG",
+ usage = "add hashtag to changes")
+ void addHashtag(String token) throws CmdLineException {
+ if (!notesMigration.enabled()) {
+ throw clp.reject("cannot add hashtags; noteDb is disabled");
+ }
+ String hashtag = cleanupHashtag(token);
+ if (!hashtag.isEmpty()) {
+ hashtags.add(hashtag);
+ }
+ //TODO(dpursehouse): validate hashtags
+ }
+
+ @Inject
+ MagicBranchInput(ReceiveCommand cmd, LabelTypes labelTypes,
+ NotesMigration notesMigration) {
this.cmd = cmd;
this.draft = cmd.getRefName().startsWith(MagicBranch.NEW_DRAFT_CHANGE);
this.labelTypes = labelTypes;
- }
-
- boolean isDraft() {
- return draft;
- }
-
- boolean isSubmit() {
- return submit;
+ this.notesMigration = notesMigration;
}
MailRecipients getMailRecipients() {
return new MailRecipients(reviewer, cc);
}
- Map<String, Short> getLabels() {
- return labels;
- }
-
String parse(CmdLineParser clp, Repository repo, Set<String> refs)
throws CmdLineException {
String ref = MagicBranch.getDestBranchName(cmd.getRefName());
@@ -1212,10 +1237,6 @@ public class ReceiveCommits {
}
return ref.substring(0, split);
}
-
- void setCmdLineParser(CmdLineParser clp) {
- this.clp = clp;
- }
}
private void parseMagicBranch(final ReceiveCommand cmd) {
@@ -1225,13 +1246,13 @@ public class ReceiveCommits {
return;
}
- magicBranch = new MagicBranchInput(cmd, labelTypes);
+ magicBranch = new MagicBranchInput(cmd, labelTypes, notesMigration);
magicBranch.reviewer.addAll(reviewersFromCommandLine);
magicBranch.cc.addAll(ccFromCommandLine);
String ref;
CmdLineParser clp = optionParserFactory.create(magicBranch);
- magicBranch.setCmdLineParser(clp);
+ magicBranch.clp = clp;
try {
ref = magicBranch.parse(clp, repo, rp.getAdvertisedRefs().keySet());
} catch (CmdLineException e) {
@@ -1266,13 +1287,17 @@ public class ReceiveCommits {
return;
}
- if (magicBranch.isDraft()
- && (!receiveConfig.allowDrafts
- || projectControl.controlForRef("refs/drafts/" + ref)
- .isBlocked(Permission.PUSH))) {
- errors.put(Error.CODE_REVIEW, ref);
- reject(cmd, "cannot upload drafts");
- return;
+ if (magicBranch.draft) {
+ if (!receiveConfig.allowDrafts) {
+ errors.put(Error.CODE_REVIEW, ref);
+ reject(cmd, "draft workflow is disabled");
+ return;
+ } else if (projectControl.controlForRef("refs/drafts/" + ref)
+ .isBlocked(Permission.PUSH)) {
+ errors.put(Error.CODE_REVIEW, ref);
+ reject(cmd, "cannot upload drafts");
+ return;
+ }
}
if (!magicBranch.ctl.canUpload()) {
@@ -1281,18 +1306,35 @@ public class ReceiveCommits {
return;
}
- if (magicBranch.isDraft() && magicBranch.isSubmit()) {
+ if (magicBranch.draft && magicBranch.submit) {
reject(cmd, "cannot submit draft");
return;
}
- if (magicBranch.isSubmit() && !projectControl.controlForRef(
+ if (magicBranch.submit && !projectControl.controlForRef(
MagicBranch.NEW_CHANGE + ref).canSubmit()) {
reject(cmd, "submit not allowed");
return;
}
RevWalk walk = rp.getRevWalk();
+ RevCommit tip;
+ try {
+ tip = walk.parseCommit(magicBranch.cmd.getNewId());
+ } catch (IOException ex) {
+ magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
+ log.error("Invalid pack upload; one or more objects weren't sent", ex);
+ return;
+ }
+
+ // If tip is a merge commit, or the root commit or
+ // if %base was specified, ignore newChangeForAllNotInTarget
+ if (tip.getParentCount() > 1
+ || magicBranch.base != null
+ || tip.getParentCount() == 0) {
+ newChangeForAllNotInTarget = false;
+ }
+
if (magicBranch.base != null) {
magicBranch.baseCommit = Lists.newArrayListWithCapacity(
magicBranch.base.size());
@@ -1313,6 +1355,18 @@ public class ReceiveCommits {
return;
}
}
+ } else if (newChangeForAllNotInTarget) {
+ String destBranch = magicBranch.dest.get();
+ try {
+ ObjectId baseHead = repo.getRef(destBranch).getObjectId();
+ magicBranch.baseCommit =
+ Collections.singletonList(walk.parseCommit(baseHead));
+ } catch (IOException ex) {
+ log.warn(String.format("Project %s cannot read %s", project.getName(),
+ destBranch), ex);
+ reject(cmd, "internal server error");
+ return;
+ }
}
// Validate that the new commits are connected with the target
@@ -1321,7 +1375,6 @@ public class ReceiveCommits {
// commits and the target branch head.
//
try {
- final RevCommit tip = walk.parseCommit(magicBranch.cmd.getNewId());
Ref targetRef = rp.getAdvertisedRefs().get(magicBranch.ctl.getRefName());
if (targetRef == null || targetRef.getObjectId() == null) {
// The destination branch does not yet exist. Assume the
@@ -1408,29 +1461,22 @@ public class ReceiveCommits {
reject(cmd, "duplicate request");
return false;
}
- if (replaceByCommit.containsKey(req.newCommit)) {
- reject(cmd, "duplicate request");
- return false;
- }
replaceByChange.put(req.ontoChange, req);
- replaceByCommit.put(req.newCommit, req);
return true;
}
- private List<CreateRequest> selectNewChanges() {
- final List<CreateRequest> newChanges = Lists.newArrayList();
+ private void selectNewAndReplacedChangesFromMagicBranch() {
+ newChanges = Lists.newArrayList();
final RevWalk walk = rp.getRevWalk();
walk.reset();
walk.sort(RevSort.TOPO);
walk.sort(RevSort.REVERSE, true);
try {
- Set<ObjectId> existing = Sets.newHashSet();
walk.markStart(walk.parseCommit(magicBranch.cmd.getNewId()));
if (magicBranch.baseCommit != null) {
for (RevCommit c : magicBranch.baseCommit) {
walk.markUninteresting(c);
}
- assert magicBranch.ctl != null;
Ref targetRef = allRefs.get(magicBranch.ctl.getRefName());
if (targetRef != null) {
walk.markUninteresting(walk.parseCommit(targetRef.getObjectId()));
@@ -1438,27 +1484,34 @@ public class ReceiveCommits {
} else {
markHeadsAsUninteresting(
walk,
- existing,
magicBranch.ctl != null ? magicBranch.ctl.getRefName() : null);
}
+ Set<ObjectId> existing = changeRefsById().keySet();
List<ChangeLookup> pending = Lists.newArrayList();
final Set<Change.Key> newChangeIds = new HashSet<>();
+ final int maxBatchChanges =
+ receiveConfig.getEffectiveMaxBatchChangesLimit(currentUser);
for (;;) {
final RevCommit c = walk.next();
if (c == null) {
break;
}
- if (existing.contains(c) || replaceByCommit.containsKey(c)) {
- // This commit was already scheduled to replace an existing PatchSet.
- //
+ if (existing.contains(c)) { // Commit is already tracked.
continue;
}
if (!validCommit(magicBranch.ctl, magicBranch.cmd, c)) {
// Not a change the user can propose? Abort as early as possible.
- //
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
+ }
+
+ // 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");
}
Change.Key changeKey = new Change.Key("I" + c.name());
@@ -1472,20 +1525,30 @@ public class ReceiveCommits {
if (idStr.matches("^I00*$")) {
// Reject this invalid line from EGit.
reject(magicBranch.cmd, "invalid Change-Id");
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
}
changeKey = new Change.Key(idStr);
pending.add(new ChangeLookup(c, changeKey));
+ if (maxBatchChanges != 0
+ && pending.size() + newChanges.size() > maxBatchChanges) {
+ reject(magicBranch.cmd,
+ "the number of pushed changes in a batch exceeds the max limit "
+ + maxBatchChanges);
+ newChanges = Collections.emptyList();
+ return;
+ }
}
for (ChangeLookup p : pending) {
if (newChangeIds.contains(p.changeKey)) {
reject(magicBranch.cmd, "squash commits first");
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
}
- List<Change> changes = p.changes.toList();
+ List<ChangeData> changes = p.destChanges;
if (changes.size() > 1) {
// WTF, multiple changes in this project have the same key?
// Since the commit is new, the user should recreate it with
@@ -1493,23 +1556,27 @@ public class ReceiveCommits {
// this error message as Change-Id should be unique.
//
reject(magicBranch.cmd, p.changeKey.get() + " has duplicates");
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
}
if (changes.size() == 1) {
// Schedule as a replacement to this one matching change.
//
- if (requestReplace(magicBranch.cmd, false, changes.get(0), p.commit)) {
+ if (requestReplace(
+ magicBranch.cmd, false, changes.get(0).change(), p.commit)) {
continue;
} else {
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
}
}
if (changes.size() == 0) {
if (!isValidChangeId(p.changeKey.get())) {
reject(magicBranch.cmd, "invalid Change-Id");
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
}
newChangeIds.add(p.changeKey);
@@ -1522,40 +1589,37 @@ public class ReceiveCommits {
//
magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
log.error("Invalid pack upload; one or more objects weren't sent", e);
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
} catch (OrmException e) {
log.error("Cannot query database to locate prior changes", e);
reject(magicBranch.cmd, "database error");
- return Collections.emptyList();
+ newChanges = Collections.emptyList();
+ return;
}
if (newChanges.isEmpty() && replaceByChange.isEmpty()) {
reject(magicBranch.cmd, "no new changes");
- return Collections.emptyList();
+ return;
+ }
+ if (!newChanges.isEmpty() && magicBranch.edit) {
+ reject(magicBranch.cmd, "edit is not supported for new changes");
+ return;
}
for (CreateRequest create : newChanges) {
batch.addCommand(create.cmd);
}
- return newChanges;
}
- private void markHeadsAsUninteresting(
- final RevWalk walk,
- Set<ObjectId> existing,
- @Nullable String forRef) {
+ private void markHeadsAsUninteresting(RevWalk rw, @Nullable String forRef) {
for (Ref ref : allRefs.values()) {
- if (ref.getObjectId() == null) {
- continue;
- } else if (ref.getName().startsWith(REFS_CHANGES)) {
- existing.add(ref.getObjectId());
- } else if (ref.getName().startsWith(R_HEADS)
- || (forRef != null && forRef.equals(ref.getName()))) {
+ if ((ref.getName().startsWith(R_HEADS) || ref.getName().equals(forRef))
+ && ref.getObjectId() != null) {
try {
- walk.markUninteresting(walk.parseCommit(ref.getObjectId()));
+ rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
} catch (IOException e) {
log.warn(String.format("Invalid ref %s in %s",
ref.getName(), project.getName()), e);
- continue;
}
}
}
@@ -1568,12 +1632,12 @@ public class ReceiveCommits {
private class ChangeLookup {
final RevCommit commit;
final Change.Key changeKey;
- final ResultSet<Change> changes;
+ final List<ChangeData> destChanges;
ChangeLookup(RevCommit c, Change.Key key) throws OrmException {
commit = c;
changeKey = key;
- changes = db.changes().byBranchKey(magicBranch.dest, key);
+ destChanges = queryProvider.get().byBranchKey(magicBranch.dest, key);
}
}
@@ -1593,8 +1657,8 @@ public class ReceiveCommits {
magicBranch.dest,
TimeUtil.nowTs());
change.setTopic(magicBranch.topic);
- ins = changeInserterFactory.create(ctl, change, c)
- .setDraft(magicBranch.isDraft());
+ ins = changeInserterFactory.create(ctl.getProjectControl(), change, c)
+ .setDraft(magicBranch.draft);
cmd = new ReceiveCommand(ObjectId.zeroId(), c,
ins.getPatchSet().getRefName());
}
@@ -1634,7 +1698,8 @@ public class ReceiveCommits {
Map<String, Short> approvals = new HashMap<>();
if (magicBranch != null) {
recipients.add(magicBranch.getMailRecipients());
- approvals = magicBranch.getLabels();
+ approvals = magicBranch.labels;
+ ins.setHashtags(magicBranch.hashtags);
}
recipients.add(getRecipientsFromFooters(accountResolver, ps, footerLines));
recipients.remove(me);
@@ -1646,36 +1711,15 @@ public class ReceiveCommits {
ins
.setReviewers(recipients.getReviewers())
+ .setExtraCC(recipients.getCcOnly())
.setApprovals(approvals)
.setMessage(msg)
- .setSendMail(false)
+ .setRequestScopePropagator(requestScopePropagator)
+ .setSendMail(true)
.insert();
created = true;
- workQueue.getDefaultQueue()
- .submit(requestScopePropagator.wrap(new Runnable() {
- @Override
- public void run() {
- try {
- CreateChangeSender cm =
- createChangeSenderFactory.create(change);
- cm.setFrom(me);
- cm.setPatchSet(ps, ins.getPatchSetInfo());
- cm.addReviewers(recipients.getReviewers());
- cm.addExtraCC(recipients.getCcOnly());
- cm.send();
- } catch (Exception e) {
- log.error("Cannot send email for new change " + change.getId(), e);
- }
- }
-
- @Override
- public String toString() {
- return "send-email newchange";
- }
- }));
-
- if (magicBranch != null && magicBranch.isSubmit()) {
+ if (magicBranch != null && magicBranch.submit) {
submit(projectControl.controlFor(change), ps);
}
}
@@ -1712,6 +1756,7 @@ public class ReceiveCommits {
addMessage("Change " + c.getChangeId() + ": " + msg.getMessage());
break;
}
+ //$FALL-THROUGH$
default:
addMessage("change " + c.getChangeId() + " is "
+ c.getStatus().name().toLowerCase());
@@ -1729,7 +1774,6 @@ public class ReceiveCommits {
req.validate(false);
if (req.skip && req.cmd == null) {
itr.remove();
- replaceByCommit.remove(req.newCommit);
}
}
}
@@ -1755,6 +1799,9 @@ public class ReceiveCommits {
for (ReplaceRequest req : replaceByChange.values()) {
if (req.inputCommand.getResult() == NOT_ATTEMPTED && req.cmd != null) {
+ if (req.prev != null) {
+ batch.addCommand(req.prev);
+ }
batch.addCommand(req.cmd);
}
}
@@ -1795,6 +1842,7 @@ public class ReceiveCommits {
ChangeControl changeCtl;
BiMap<RevCommit, PatchSet.Id> revisions;
PatchSet newPatchSet;
+ ReceiveCommand prev;
ReceiveCommand cmd;
PatchSetInfo info;
ChangeMessage msg;
@@ -1917,13 +1965,66 @@ public class ReceiveCommits {
}
}
+ if (magicBranch != null && magicBranch.edit) {
+ return newEdit();
+ }
+
+ newPatchSet();
+ return true;
+ }
+
+ private boolean newEdit() {
+ newPatchSet = new PatchSet(change.currentPatchSetId());
+ Optional<ChangeEdit> edit = null;
+
+ try {
+ edit = editUtil.byChange(change, currentUser);
+ } catch (IOException e) {
+ log.error("Cannt retrieve edit", e);
+ return false;
+ }
+
+ if (edit.isPresent()) {
+ if (edit.get().getBasePatchSet().getId().equals(newPatchSet.getId())) {
+ // replace edit
+ cmd = new ReceiveCommand(
+ edit.get().getRef().getObjectId(),
+ newCommit,
+ edit.get().getRefName());
+ } else {
+ // delete old edit ref on rebase
+ prev = new ReceiveCommand(
+ edit.get().getRef().getObjectId(),
+ ObjectId.zeroId(),
+ edit.get().getRefName());
+ createEditCommand();
+ }
+ } else {
+ createEditCommand();
+ }
+
+ return true;
+ }
+
+ private void createEditCommand() {
+ // create new edit
+ cmd = new ReceiveCommand(
+ ObjectId.zeroId(),
+ newCommit,
+ RefNames.refsEdit(
+ currentUser.getAccountId(),
+ change.getId(),
+ newPatchSet.getId()));
+ }
+
+ private void newPatchSet() {
PatchSet.Id id =
ChangeUtil.nextPatchSetId(allRefs, change.currentPatchSetId());
newPatchSet = new PatchSet(id);
newPatchSet.setCreatedOn(TimeUtil.nowTs());
newPatchSet.setUploader(currentUser.getAccountId());
newPatchSet.setRevision(toRevId(newCommit));
- if (magicBranch != null && magicBranch.isDraft()) {
+ if (magicBranch != null && magicBranch.draft) {
newPatchSet.setDraft(true);
}
info = patchSetInfoFactory.get(newCommit, newPatchSet.getId());
@@ -1931,7 +2032,6 @@ public class ReceiveCommits {
ObjectId.zeroId(),
newCommit,
newPatchSet.getRefName());
- return true;
}
CheckedFuture<PatchSet.Id, InsertException> insertPatchSet()
@@ -1944,7 +2044,9 @@ public class ReceiveCommits {
@Override
public PatchSet.Id call() throws OrmException, IOException, NoSuchChangeException {
try {
- if (caller == Thread.currentThread()) {
+ if (magicBranch != null && magicBranch.edit) {
+ return upsertEdit();
+ } else if (caller == Thread.currentThread()) {
return insertPatchSet(db);
} else {
ReviewDb db = schemaFactory.open();
@@ -1964,17 +2066,16 @@ public class ReceiveCommits {
return Futures.makeChecked(future, INSERT_EXCEPTION);
}
- private ChangeMessage newChangeMessage(ReviewDb db) throws OrmException {
+ private ChangeMessage newChangeMessage(ReviewDb db, ChangeKind changeKind)
+ throws OrmException {
msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
.messageUUID(db)), currentUser.getAccountId(), newPatchSet.getCreatedOn(),
newPatchSet.getId());
- RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
- ChangeKind changeKind = changeKindCache.getChangeKind(
- projectControl.getProjectState(), repo, priorCommit, newCommit);
String message = "Uploaded patch set " + newPatchSet.getPatchSetId();
switch (changeKind) {
case TRIVIAL_REBASE:
+ case NO_CHANGE:
message += ": Patch Set " + priorPatchSet.get() + " was rebased";
break;
case NO_CODE_CHANGE:
@@ -1988,20 +2089,37 @@ public class ReceiveCommits {
return msg;
}
+ PatchSet.Id upsertEdit() {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.execute(rp);
+ }
+ return newPatchSet.getId();
+ }
+
PatchSet.Id insertPatchSet(ReviewDb db) throws OrmException, IOException {
final Account.Id me = currentUser.getAccountId();
final List<FooterLine> footerLines = newCommit.getFooterLines();
final MailRecipients recipients = new MailRecipients();
Map<String, Short> approvals = new HashMap<>();
+ ChangeUpdate update = updateFactory.create(
+ changeCtl, newPatchSet.getCreatedOn());
+ update.setPatchSetId(newPatchSet.getId());
+
if (magicBranch != null) {
recipients.add(magicBranch.getMailRecipients());
- approvals = magicBranch.getLabels();
+ approvals = magicBranch.labels;
+ Set<String> hashtags = magicBranch.hashtags;
+ if (!hashtags.isEmpty()) {
+ ChangeNotes notes = changeCtl.getNotes().load();
+ hashtags.addAll(notes.getHashtags());
+ update.setHashtags(hashtags);
+ }
}
recipients.add(getRecipientsFromFooters(accountResolver, newPatchSet, footerLines));
recipients.remove(me);
- ChangeUpdate update = updateFactory.create(changeCtl, newPatchSet.getCreatedOn());
db.changes().beginTransaction(change.getId());
+ ChangeKind changeKind = ChangeKind.REWORK;
try {
change = db.changes().get(change.getId());
if (change == null || change.getStatus().isClosed()) {
@@ -2024,10 +2142,14 @@ public class ReceiveCommits {
approvalsUtil.addReviewers(db, update, labelTypes, change, newPatchSet,
info, recipients.getReviewers(), oldRecipients.getAll());
approvalsUtil.addApprovals(db, update, labelTypes, newPatchSet, info,
- change, changeCtl, approvals);
+ changeCtl, approvals);
recipients.add(oldRecipients);
- cmUtil.addChangeMessage(db, update, newChangeMessage(db));
+ RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
+ changeKind = changeKindCache.getChangeKind(
+ projectControl.getProjectState(), repo, priorCommit, newCommit);
+
+ cmUtil.addChangeMessage(db, update, newChangeMessage(db, changeKind));
if (mergedIntoRef == null) {
// Change should be new, so it can go through review again.
@@ -2052,7 +2174,6 @@ public class ReceiveCommits {
} else {
change.setStatus(Change.Status.NEW);
}
- change.setLastSha1MergeTested(null);
change.setCurrentPatchSet(info);
final List<String> idList = newCommit.getFooterLines(CHANGE_ID);
@@ -2089,36 +2210,35 @@ public class ReceiveCommits {
if (cmd.getResult() == NOT_ATTEMPTED) {
cmd.execute(rp);
}
- CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
- .addChange(change)
- .reindex()
- .runAsync();
- workQueue.getDefaultQueue()
- .submit(requestScopePropagator.wrap(new Runnable() {
- @Override
- public void run() {
- try {
- ReplacePatchSetSender cm =
- replacePatchSetFactory.create(change);
- cm.setFrom(me);
- cm.setPatchSet(newPatchSet, info);
- cm.setChangeMessage(msg);
- cm.addReviewers(recipients.getReviewers());
- cm.addExtraCC(recipients.getCcOnly());
- cm.send();
- } catch (Exception e) {
- log.error("Cannot send email for new patch set " + newPatchSet.getId(), e);
- }
- if (mergedIntoRef != null) {
- sendMergedEmail(ReplaceRequest.this);
+ CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
+ if (changeKind != ChangeKind.TRIVIAL_REBASE) {
+ workQueue.getDefaultQueue()
+ .submit(requestScopePropagator.wrap(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ReplacePatchSetSender cm =
+ replacePatchSetFactory.create(change);
+ cm.setFrom(me);
+ cm.setPatchSet(newPatchSet, info);
+ cm.setChangeMessage(msg);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot send email for new patch set " + newPatchSet.getId(), e);
+ }
+ if (mergedIntoRef != null) {
+ sendMergedEmail(ReplaceRequest.this);
+ }
}
- }
- @Override
- public String toString() {
- return "send-email newpatchset";
- }
- }));
+ @Override
+ public String toString() {
+ return "send-email newpatchset";
+ }
+ }));
+ }
f.checkedGet();
gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
@@ -2126,10 +2246,10 @@ public class ReceiveCommits {
hooks.doPatchsetCreatedHook(change, newPatchSet, db);
if (mergedIntoRef != null) {
hooks.doChangeMergedHook(
- change, currentUser.getAccount(), newPatchSet, db);
+ change, currentUser.getAccount(), newPatchSet, db, newCommit.getName());
}
- if (magicBranch != null && magicBranch.isSubmit()) {
+ if (magicBranch != null && magicBranch.submit) {
submit(changeCtl, newPatchSet);
}
@@ -2138,18 +2258,37 @@ public class ReceiveCommits {
}
private List<Ref> refs(Change.Id changeId) {
+ return refsByChange().get(changeId);
+ }
+
+ private void initChangeRefMaps() {
if (refsByChange == null) {
int estRefsPerChange = 4;
+ refsById = HashMultimap.create();
refsByChange = ArrayListMultimap.create(
allRefs.size() / estRefsPerChange,
estRefsPerChange);
for (Ref ref : allRefs.values()) {
- if (ref.getObjectId() != null && PatchSet.isRef(ref.getName())) {
- refsByChange.put(Change.Id.fromRef(ref.getName()), ref);
+ ObjectId obj = ref.getObjectId();
+ if (obj != null) {
+ PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
+ if (psId != null) {
+ refsById.put(obj, ref);
+ refsByChange.put(psId.getParentKey(), ref);
+ }
}
}
}
- return refsByChange.get(changeId);
+ }
+
+ private ListMultimap<Change.Id, Ref> refsByChange() {
+ initChangeRefMaps();
+ return refsByChange;
+ }
+
+ private SetMultimap<ObjectId, Ref> changeRefsById() {
+ initChangeRefMaps();
+ return refsById;
}
static boolean parentsEqual(RevCommit a, RevCommit b) {
@@ -2233,19 +2372,21 @@ public class ReceiveCommits {
walk.reset();
walk.sort(RevSort.NONE);
try {
- Set<ObjectId> existing = Sets.newHashSet();
- walk.markStart(walk.parseCommit(cmd.getNewId()));
- markHeadsAsUninteresting(walk, existing, cmd.getRefName());
-
- RevCommit c;
- while ((c = walk.next()) != null) {
+ RevObject parsedObject = walk.parseAny(cmd.getNewId());
+ if (!(parsedObject instanceof RevCommit)) {
+ return;
+ }
+ walk.markStart((RevCommit)parsedObject);
+ markHeadsAsUninteresting(walk, cmd.getRefName());
+ Set<ObjectId> existing = changeRefsById().keySet();
+ for (RevCommit c; (c = walk.next()) != null;) {
if (existing.contains(c)) {
continue;
} else if (!validCommit(ctl, cmd, c)) {
break;
}
- if (defaultName && currentUser.getEmailAddresses().contains(
+ if (defaultName && currentUser.hasEmailAddress(
c.getCommitterIdent().getEmailAddress())) {
try {
Account a = db.accounts().get(currentUser.getAccountId());
@@ -2269,7 +2410,7 @@ public class ReceiveCommits {
}
private boolean validCommit(final RefControl ctl, final ReceiveCommand cmd,
- final RevCommit c) throws MissingObjectException, IOException {
+ final RevCommit c) {
if (validCommits.contains(c)) {
return true;
@@ -2281,7 +2422,8 @@ public class ReceiveCommits {
commitValidatorsFactory.create(ctl, sshInfo, repo);
try {
- messages.addAll(commitValidators.validateForReceiveCommits(receiveEvent));
+ messages.addAll(commitValidators.validateForReceiveCommits(
+ receiveEvent, rejectCommits));
} catch (CommitValidationException e) {
messages.addAll(e.getMessages());
reject(cmd, e.getMessage());
@@ -2291,40 +2433,47 @@ public class ReceiveCommits {
return true;
}
- private void autoCloseChanges(final ReceiveCommand cmd) throws NoSuchChangeException {
+ private void autoCloseChanges(final ReceiveCommand cmd) {
final RevWalk rw = rp.getRevWalk();
try {
+ RevCommit newTip = rw.parseCommit(cmd.getNewId());
+ Branch.NameKey branch =
+ new Branch.NameKey(project.getNameKey(), cmd.getRefName());
+
rw.reset();
- rw.markStart(rw.parseCommit(cmd.getNewId()));
+ rw.markStart(newTip);
if (!ObjectId.zeroId().equals(cmd.getOldId())) {
rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
}
final SetMultimap<ObjectId, Ref> byCommit = changeRefsById();
- final Map<Change.Key, Change.Id> byKey = openChangesByKey(
- new Branch.NameKey(project.getNameKey(), cmd.getRefName()));
+ Map<Change.Key, Change> byKey = null;
final List<ReplaceRequest> toClose = new ArrayList<>();
- RevCommit c;
- while ((c = rw.next()) != null) {
- final Set<Ref> refs = byCommit.get(c.copy());
- for (Ref ref : refs) {
- if (ref != null) {
- rw.parseBody(c);
- Change.Key closedChange =
- closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
- closeProgress.update(1);
- if (closedChange != null) {
- byKey.remove(closedChange);
+ for (RevCommit c; (c = rw.next()) != null;) {
+ rw.parseBody(c);
+
+ for (Ref ref : byCommit.get(c.copy())) {
+ Change.Key closedChange =
+ closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
+ closeProgress.update(1);
+ if (closedChange != null) {
+ if (byKey == null) {
+ byKey = openChangesByBranch(branch);
}
+ byKey.remove(closedChange);
}
}
- rw.parseBody(c);
for (final String changeId : c.getFooterLines(CHANGE_ID)) {
- final Change.Id onto = byKey.get(new Change.Key(changeId.trim()));
+ if (byKey == null) {
+ byKey = openChangesByBranch(branch);
+ }
+
+ final Change onto = byKey.get(new Change.Key(changeId.trim()));
if (onto != null) {
- final ReplaceRequest req = new ReplaceRequest(onto, c, cmd, false);
- req.change = db.changes().get(onto);
+ final ReplaceRequest req =
+ new ReplaceRequest(onto.getId(), c, cmd, false);
+ req.change = onto;
toClose.add(req);
break;
}
@@ -2341,18 +2490,12 @@ public class ReceiveCommits {
}
}
- // It handles gitlinks if required.
-
- rw.reset();
- final RevCommit codeReviewCommit = rw.parseCommit(cmd.getNewId());
-
- final SubmoduleOp subOp =
- subOpFactory.create(
- new Branch.NameKey(project.getNameKey(), cmd.getRefName()),
- codeReviewCommit, rw, repo, project, new ArrayList<Change>(),
- new HashMap<Change.Id, CodeReviewCommit>(),
- currentUser.getAccount());
- subOp.update();
+ // Update superproject gitlinks if required.
+ subOpFactory.create(
+ branch, newTip, rw, repo, project,
+ new ArrayList<Change>(),
+ new HashMap<Change.Id, CodeReviewCommit>(),
+ currentUser.getAccount()).update();
} catch (InsertException e) {
log.error("Can't insert patchset", e);
} catch (IOException e) {
@@ -2393,28 +2536,16 @@ public class ReceiveCommits {
result.mergedIntoRef = refName;
markChangeMergedByPush(db, result, result.changeCtl);
hooks.doChangeMergedHook(
- change, currentUser.getAccount(), result.newPatchSet, db);
+ change, currentUser.getAccount(), result.newPatchSet, db, commit.getName());
sendMergedEmail(result);
return change.getKey();
}
- private SetMultimap<ObjectId, Ref> changeRefsById() throws IOException {
- if (refsById == null) {
- refsById = HashMultimap.create();
- for (Ref r : repo.getRefDatabase().getRefs(REFS_CHANGES).values()) {
- if (PatchSet.isRef(r.getName())) {
- refsById.put(r.getObjectId(), r);
- }
- }
- }
- return refsById;
- }
-
- private Map<Change.Key, Change.Id> openChangesByKey(Branch.NameKey branch)
+ private Map<Change.Key, Change> openChangesByBranch(Branch.NameKey branch)
throws OrmException {
- final Map<Change.Key, Change.Id> r = new HashMap<>();
- for (Change c : db.changes().byBranchOpenAll(branch)) {
- r.put(c.getKey(), c.getId());
+ final Map<Change.Key, Change> r = new HashMap<>();
+ for (ChangeData cd : queryProvider.get().byBranchOpen(branch)) {
+ r.put(cd.change().getKey(), cd.change());
}
return r;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index 7ae8271db7..7095552e4c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -18,13 +18,15 @@ import static org.eclipse.jgit.lib.RefDatabase.ALL;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
-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.reviewdb.server.ReviewDb;
+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.gwtorm.server.OrmException;
+import com.google.inject.Provider;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -48,11 +50,14 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
.getLogger(ReceiveCommitsAdvertiseRefsHook.class);
private final ReviewDb db;
+ private final Provider<InternalChangeQuery> queryProvider;
private final Project.NameKey projectName;
public ReceiveCommitsAdvertiseRefsHook(ReviewDb db,
+ Provider<InternalChangeQuery> queryProvider,
Project.NameKey projectName) {
this.db = db;
+ this.queryProvider = queryProvider;
this.projectName = projectName;
}
@@ -93,10 +98,14 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
Set<ObjectId> toInclude = Sets.newHashSet();
// Advertise some recent open changes, in case a commit is based one.
+ final int limit = 32;
try {
- Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(32);
- for (Change c : db.changes().byProjectOpenNext(projectName, "z", 32)) {
- PatchSet.Id id = c.currentPatchSetId();
+ Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(limit);
+ for (ChangeData cd : queryProvider.get()
+ .enforceVisibility(true)
+ .setLimit(limit)
+ .byProjectOpen(projectName)) {
+ PatchSet.Id id = cd.change().currentPatchSetId();
if (id != null) {
toGet.add(id);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
index 81ce05d8b4..25fbfb982a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
@@ -60,7 +60,7 @@ public class ReceiveCommitsExecutorModule extends AbstractModule {
public ListeningExecutorService createChangeUpdateExecutor(@GerritServerConfig Config config) {
int poolSize = config.getInt("receive", null, "changeUpdateThreads", 1);
if (poolSize <= 1) {
- return MoreExecutors.sameThreadExecutor();
+ return MoreExecutors.newDirectExecutorService();
}
return MoreExecutors.listeningDecorator(
MoreExecutors.getExitingExecutorService(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
index 2efc94c002..ac6116c7ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.git;
+import static com.google.gerrit.common.data.GlobalCapability.BATCH_CHANGES_LIMIT;
+
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -25,6 +28,7 @@ class ReceiveConfig {
final boolean checkMagicRefs;
final boolean checkReferencedObjectsAreReachable;
final boolean allowDrafts;
+ private final int systemMaxBatchChanges;
@Inject
ReceiveConfig(@GerritServerConfig Config config) {
@@ -37,5 +41,13 @@ class ReceiveConfig {
allowDrafts = config.getBoolean(
"change", null, "allowDrafts",
true);
+ systemMaxBatchChanges = config.getInt("receive", "maxBatchChanges", 0);
+ }
+
+ public int getEffectiveMaxBatchChangesLimit(CurrentUser user) {
+ if (user.getCapabilities().canPerform(BATCH_CHANGES_LIMIT)) {
+ return user.getCapabilities().getRange(BATCH_CHANGES_LIMIT).getMax();
+ }
+ return systemMaxBatchChanges;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReloadSubmitQueueOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReloadSubmitQueueOp.java
index dd7a85b44f..734512fbb3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReloadSubmitQueueOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReloadSubmitQueueOp.java
@@ -15,11 +15,12 @@
package com.google.gerrit.server.git;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.slf4j.Logger;
@@ -32,34 +33,39 @@ public class ReloadSubmitQueueOp extends DefaultQueueOp {
private static final Logger log =
LoggerFactory.getLogger(ReloadSubmitQueueOp.class);
- private final SchemaFactory<ReviewDb> schema;
+ private final OneOffRequestContext requestContext;
+ private final Provider<InternalChangeQuery> queryProvider;
private final MergeQueue mergeQueue;
@Inject
- ReloadSubmitQueueOp(final WorkQueue wq, final SchemaFactory<ReviewDb> sf,
- final MergeQueue mq) {
+ ReloadSubmitQueueOp(
+ OneOffRequestContext rc,
+ WorkQueue wq,
+ Provider<InternalChangeQuery> qp,
+ MergeQueue mq) {
super(wq);
- schema = sf;
+ requestContext = rc;
+ queryProvider = qp;
mergeQueue = mq;
}
+ @Override
public void run() {
- final HashSet<Branch.NameKey> pending = new HashSet<>();
- try {
- final ReviewDb c = schema.open();
- try {
- for (final Change change : c.changes().allSubmitted()) {
- pending.add(change.getDest());
+ try (AutoCloseable ctx = requestContext.open()) {
+ HashSet<Branch.NameKey> pending = new HashSet<>();
+ for (ChangeData cd : queryProvider.get().allSubmitted()) {
+ try {
+ pending.add(cd.change().getDest());
+ } catch (OrmException e) {
+ log.error("Error reading submitted change", e);
}
- } finally {
- c.close();
}
- } catch (OrmException e) {
- log.error("Cannot reload MergeQueue", e);
- }
- for (final Branch.NameKey branch : pending) {
- mergeQueue.schedule(branch);
+ for (Branch.NameKey branch : pending) {
+ mergeQueue.schedule(branch);
+ }
+ } catch (Exception e) {
+ log.error("Cannot reload MergeQueue", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
index 2b3994c8c0..a89a89be68 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
@@ -17,7 +17,6 @@ package com.google.gerrit.server.git;
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.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -74,7 +73,9 @@ public class RenameGroupOp extends DefaultQueueOp {
@Override
public void run() {
- Iterable<NameKey> names = tryingAgain ? retryOn : projectCache.all();
+ Iterable<Project.NameKey> names = tryingAgain
+ ? retryOn
+ : projectCache.all();
for (Project.NameKey projectName : names) {
ProjectConfig config = projectCache.get(projectName).getConfig();
GroupReference ref = config.getGroup(uuid);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
index 60f1d0dc98..8b6da7b178 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
@@ -43,6 +43,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.notes.NoteMerger;
import org.eclipse.jgit.util.io.UnionInputStream;
@@ -51,6 +52,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
class ReviewNoteMerger implements NoteMerger {
+ @Override
public Note merge(Note base, Note ours, Note theirs, ObjectReader reader,
ObjectInserter inserter) throws IOException {
if (ours == null) {
@@ -66,12 +68,13 @@ class ReviewNoteMerger implements NoteMerger {
ObjectLoader lo = reader.open(ours.getData());
byte[] sep = new byte[] {'\n'};
ObjectLoader lt = reader.open(theirs.getData());
- UnionInputStream union = new UnionInputStream(
- lo.openStream(),
- new ByteArrayInputStream(sep),
- lt.openStream());
- ObjectId noteData = inserter.insert(Constants.OBJ_BLOB,
- lo.getSize() + sep.length + lt.getSize(), union);
- return new Note(ours, noteData);
+ try (ObjectStream os = lo.openStream();
+ ByteArrayInputStream b = new ByteArrayInputStream(sep);
+ ObjectStream ts = lt.openStream();
+ UnionInputStream union = new UnionInputStream(os, b, ts)) {
+ ObjectId noteData = inserter.insert(Constants.OBJ_BLOB,
+ lo.getSize() + sep.length + lt.getSize(), union);
+ return new Note(ours, noteData);
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java
new file mode 100644
index 0000000000..65808fcda2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2015 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.gerrit.server.git.SearchingChangeCacheImpl.ID_CACHE;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Iterables;
+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.server.ReviewDb;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+@Singleton
+public class ScanningChangeCacheImpl implements ChangeCache {
+ private static final Logger log =
+ LoggerFactory.getLogger(ScanningChangeCacheImpl.class);
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ bind(ChangeCache.class).to(ScanningChangeCacheImpl.class);
+ cache(ID_CACHE,
+ Project.NameKey.class,
+ new TypeLiteral<List<Change>>() {})
+ .maximumWeight(0)
+ .loader(Loader.class);
+ }
+ };
+ }
+
+ private final LoadingCache<Project.NameKey, List<Change>> cache;
+
+ @Inject
+ ScanningChangeCacheImpl(
+ @Named(ID_CACHE) LoadingCache<Project.NameKey, List<Change>> cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public List<Change> get(Project.NameKey name) {
+ try {
+ return cache.get(name);
+ } catch (ExecutionException e) {
+ log.warn("Cannot fetch changes for " + name, e);
+ return Collections.emptyList();
+ }
+ }
+
+ static class Loader extends CacheLoader<Project.NameKey, List<Change>> {
+ private final GitRepositoryManager repoManager;
+ private final OneOffRequestContext requestContext;
+
+ @Inject
+ Loader(GitRepositoryManager repoManager,
+ OneOffRequestContext requestContext) {
+ this.repoManager = repoManager;
+ this.requestContext = requestContext;
+ }
+
+ @Override
+ public List<Change> load(Project.NameKey key) throws Exception {
+ Repository repo = repoManager.openRepository(key);
+ try (ManualRequestContext ctx = requestContext.open()) {
+ ReviewDb db = ctx.getReviewDbProvider().get();
+ Map<String, Ref> refs =
+ repo.getRefDatabase().getRefs(RefNames.REFS_CHANGES);
+ Set<Change.Id> ids = new LinkedHashSet<>();
+ for (Ref r : refs.values()) {
+ Change.Id id = Change.Id.fromRef(r.getName());
+ if (id != null) {
+ ids.add(id);
+ }
+ }
+ List<Change> changes = new ArrayList<>(ids.size());
+ // A batch size of N may overload get(Iterable), so use something smaller,
+ // but still >1.
+ for (List<Change.Id> batch : Iterables.partition(ids, 30)) {
+ Iterables.addAll(changes, db.changes().get(batch));
+ }
+ return changes;
+ } finally {
+ repo.close();
+ }
+ }
+
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
new file mode 100644
index 0000000000..233a892850
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2012 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.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+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.cache.CacheModule;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+@Singleton
+public class SearchingChangeCacheImpl
+ implements ChangeCache, GitReferenceUpdatedListener {
+ private static final Logger log =
+ LoggerFactory.getLogger(SearchingChangeCacheImpl.class);
+ static final String ID_CACHE = "changes";
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ bind(ChangeCache.class).to(SearchingChangeCacheImpl.class);
+ cache(ID_CACHE,
+ Project.NameKey.class,
+ new TypeLiteral<List<Change>>() {})
+ .maximumWeight(0)
+ .loader(Loader.class);
+ }
+ };
+ }
+
+ private final LoadingCache<Project.NameKey, List<Change>> cache;
+
+ @Inject
+ SearchingChangeCacheImpl(
+ @Named(ID_CACHE) LoadingCache<Project.NameKey, List<Change>> cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public List<Change> get(Project.NameKey name) {
+ try {
+ return cache.get(name);
+ } catch (ExecutionException e) {
+ log.warn("Cannot fetch changes for " + name, e);
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
+ if (event.getRefName().startsWith(RefNames.REFS_CHANGES)) {
+ cache.invalidate(new Project.NameKey(event.getProjectName()));
+ }
+ }
+
+ static class Loader extends CacheLoader<Project.NameKey, List<Change>> {
+ private final OneOffRequestContext requestContext;
+ private final Provider<InternalChangeQuery> queryProvider;
+
+ @Inject
+ Loader(OneOffRequestContext requestContext,
+ Provider<InternalChangeQuery> queryProvider) {
+ this.requestContext = requestContext;
+ this.queryProvider = queryProvider;
+ }
+
+ @Override
+ public List<Change> load(Project.NameKey key) throws Exception {
+ try (AutoCloseable ctx = requestContext.open()) {
+ return Collections.unmodifiableList(
+ ChangeData.asChanges(queryProvider.get().byProject(key)));
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 89d32029a2..3cf9fed69d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -207,11 +207,11 @@ public class SubmoduleOp {
schema.submoduleSubscriptions().bySubmodule(updatedBranch).toList();
if (!subscribers.isEmpty()) {
- String msgbuf = msg;
- if (msgbuf == null) {
- // Initialize the message buffer
- msgbuf = "";
-
+ // Initialize the message buffer
+ StringBuilder sb = new StringBuilder();
+ if (msg != null) {
+ sb.append(msg);
+ } else {
// The first updatedBranch on a cascade event of automatic
// updates of repos is added to updatedSubscribers set so
// if we face a situation having
@@ -227,8 +227,8 @@ public class SubmoduleOp {
&& (c.getStatusCode() == CommitMergeStatus.CLEAN_MERGE
|| c.getStatusCode() == CommitMergeStatus.CLEAN_PICK
|| c.getStatusCode() == CommitMergeStatus.CLEAN_REBASE)) {
- msgbuf += "\n";
- msgbuf += c.getFullMessage();
+ sb.append("\n")
+ .append(c.getFullMessage());
}
}
}
@@ -246,7 +246,7 @@ public class SubmoduleOp {
Map<Branch.NameKey, String> paths = new HashMap<>(1);
paths.put(updatedBranch, s.getPath());
- updateGitlinks(s.getSuperProject(), myRw, modules, paths, msgbuf);
+ updateGitlinks(s.getSuperProject(), myRw, modules, paths, sb.toString());
}
} catch (SubmoduleException e) {
log.warn("Cannot update gitlinks for " + s + " due to " + e.getMessage());
@@ -332,6 +332,7 @@ public class SubmoduleOp {
DirCacheEditor ed = dc.editor();
for (final Map.Entry<Branch.NameKey, ObjectId> me : modules.entrySet()) {
ed.add(new PathEdit(paths.get(me.getKey())) {
+ @Override
public void apply(DirCacheEntry ent) {
ent.setFileMode(FileMode.GITLINK);
ent.setObjectId(me.getValue().copy());
@@ -357,9 +358,7 @@ public class SubmoduleOp {
rfu.setForceUpdate(false);
rfu.setNewObjectId(commitId);
rfu.setExpectedOldObjectId(currentCommitId);
- rfu
- .setRefLogMessage("Submit to " + subscriber.getParentKey().get(),
- true);
+ rfu.setRefLogMessage("Submit to " + subscriber.getParentKey().get(), true);
switch (rfu.update()) {
case NEW:
@@ -393,8 +392,7 @@ public class SubmoduleOp {
private static DirCache readTree(final Repository pdb, final Ref branch)
throws MissingObjectException, IncorrectObjectTypeException, IOException {
- final RevWalk rw = new RevWalk(pdb);
- try {
+ try (RevWalk rw = new RevWalk(pdb)) {
final DirCache dc = DirCache.newInCore();
final DirCacheBuilder b = dc.builder();
b.addTree(new byte[0], // no prefix path
@@ -402,8 +400,6 @@ public class SubmoduleOp {
pdb.newObjectReader(), rw.parseTree(branch.getObjectId()));
b.finish();
return dc;
- } finally {
- rw.close();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
index 6519bd992c..a003235483 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
@@ -157,9 +157,8 @@ class TagSet {
return;
}
- TagWalk rw = new TagWalk(git);
- rw.setRetainBody(false);
- try {
+ try (TagWalk rw = new TagWalk(git)) {
+ rw.setRetainBody(false);
for (Ref ref : git.getRefDatabase().getRefs(RefDatabase.ALL).values()) {
if (skip(ref)) {
continue;
@@ -188,8 +187,6 @@ class TagSet {
}
} catch (IOException e) {
log.warn("Error building tags for repository " + projectName, e);
- } finally {
- rw.close();
}
}
@@ -332,7 +329,7 @@ class TagSet {
}
}
- private static boolean skip(Ref ref) {
+ static boolean skip(Ref ref) {
return ref.isSymbolic() || ref.getObjectId() == null
|| PatchSet.isRef(ref.getName());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
index d923e51ee5..5260aab464 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.lib.Ref;
@@ -43,6 +45,13 @@ class TagSetHolder {
}
TagMatcher matcher(TagCache cache, Repository db, Collection<Ref> include) {
+ include = FluentIterable.from(include).filter(new Predicate<Ref>() {
+ @Override
+ public boolean apply(Ref ref) {
+ return !TagSet.skip(ref);
+ }
+ }).toList();
+
TagSet tags = this.tags;
if (tags == null) {
tags = build(cache, db);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java
index e1ab41d1c3..ad84046e35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java
@@ -38,4 +38,8 @@ public class ValidationError {
public String toString() {
return "ValidationError[" + message + "]";
}
+
+ public interface Sink {
+ void error(ValidationError error);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index de3721588c..b905f67c87 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -26,6 +26,7 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -40,10 +41,14 @@ 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.transport.ReceiveCommand;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.RawParseUtils;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.StringReader;
+import java.util.Objects;
/**
* Support for metadata stored within a version controlled branch.
@@ -175,11 +180,27 @@ public abstract class VersionedMetaData {
void write(CommitBuilder commit) throws IOException;
void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
RevCommit createRef(String refName) throws IOException;
+ void removeRef(String refName) throws IOException;
RevCommit commit() throws IOException;
RevCommit commitAt(ObjectId revision) throws IOException;
void close();
}
+ /**
+ * Open a batch of updates to the same metadata ref.
+ * <p>
+ * This allows making multiple commits to a single metadata ref, at the end of
+ * which is a single ref update. For batching together updates to multiple
+ * refs (each consisting of one or more commits against their respective
+ * refs), create the {@link MetaDataUpdate} with a {@link BatchRefUpdate}.
+ * <p>
+ * A ref update produced by this {@link BatchMetaDataUpdate} is only committed
+ * if there is no associated {@link BatchRefUpdate}. As a result, the
+ * configured ref updated event is not fired if there is an associated batch.
+ *
+ * @param update helper info about the update.
+ * @throws IOException if the update failed.
+ */
public BatchMetaDataUpdate openUpdate(final MetaDataUpdate update) throws IOException {
final Repository db = update.getRepository();
@@ -222,13 +243,23 @@ public abstract class VersionedMetaData {
return;
}
- ObjectId res = newTree.writeTree(inserter);
+ // Reuse tree from parent commit unless there are contents in newTree or
+ // there is no tree for a parent commit.
+ ObjectId res = newTree.getEntryCount() != 0 || srcTree == null
+ ? newTree.writeTree(inserter) : srcTree.copy();
if (res.equals(srcTree) && !update.allowEmpty()
&& (commit.getTreeId() == null)) {
// If there are no changes to the content, don't create the commit.
return;
}
+ // If changes are made to the DirCache and those changes are written as
+ // a commit and then the tree ID is set for the CommitBuilder, then
+ // those previous DirCache changes will be ignored and the commit's
+ // tree will be replaced with the ID in the CommitBuilder. The same is
+ // true if you explicitly set tree ID in a commit and then make changes
+ // to the DirCache; that tree ID will be ignored and replaced by that of
+ // the tree for the updated DirCache.
if (commit.getTreeId() == null) {
commit.setTreeId(res);
} else {
@@ -246,23 +277,26 @@ public abstract class VersionedMetaData {
@Override
public RevCommit createRef(String refName) throws IOException {
- if (Objects.equal(src, revision)) {
+ if (Objects.equals(src, revision)) {
return revision;
}
+ return updateRef(ObjectId.zeroId(), src, refName);
+ }
+ @Override
+ public void removeRef(String refName) throws IOException {
RefUpdate ru = db.updateRef(refName);
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(src);
- ru.disableRefLog();
- inserter.flush();
- RefUpdate.Result result = ru.update();
+ ru.setForceUpdate(true);
+ if (revision != null) {
+ ru.setExpectedOldObjectId(revision);
+ }
+ RefUpdate.Result result = ru.delete();
switch (result) {
- case NEW:
- revision = rw.parseCommit(ru.getNewObjectId());
+ case FORCED:
update.fireGitRefUpdatedEvent(ru);
- return revision;
+ return;
default:
- throw new IOException("Cannot update " + ru.getName() + " in "
+ throw new IOException("Cannot delete " + ru.getName() + " in "
+ db.getDirectory() + ": " + ru.getResult());
}
}
@@ -274,37 +308,18 @@ public abstract class VersionedMetaData {
@Override
public RevCommit commitAt(ObjectId expected) throws IOException {
- if (Objects.equal(src, expected)) {
+ if (Objects.equals(src, expected)) {
return revision;
}
-
- RefUpdate ru = db.updateRef(getRefName());
- if (expected != null) {
- ru.setExpectedOldObjectId(expected);
- } else {
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- }
- ru.setNewObjectId(src);
- ru.disableRefLog();
- inserter.flush();
-
- switch (ru.update(rw)) {
- case NEW:
- case FAST_FORWARD:
- revision = rw.parseCommit(ru.getNewObjectId());
- update.fireGitRefUpdatedEvent(ru);
- return revision;
-
- default:
- throw new IOException("Cannot update " + ru.getName() + " in "
- + db.getDirectory() + ": " + ru.getResult());
- }
+ return updateRef(MoreObjects.firstNonNull(expected, ObjectId.zeroId()),
+ src, getRefName());
}
@Override
public void close() {
newTree = null;
+ rw.close();
if (inserter != null) {
inserter.close();
inserter = null;
@@ -315,6 +330,44 @@ public abstract class VersionedMetaData {
reader = null;
}
}
+
+ private RevCommit updateRef(AnyObjectId oldId, AnyObjectId newId,
+ String refName) throws IOException {
+ BatchRefUpdate bru = update.getBatch();
+ if (bru != null) {
+ bru.addCommand(new ReceiveCommand(
+ oldId.toObjectId(), newId.toObjectId(), refName));
+ inserter.flush();
+ revision = rw.parseCommit(newId);
+ return revision;
+ }
+
+ RefUpdate ru = db.updateRef(refName);
+ ru.setExpectedOldObjectId(oldId);
+ ru.setNewObjectId(src);
+ ru.setRefLogIdent(update.getCommitBuilder().getAuthor());
+ String message = update.getCommitBuilder().getMessage();
+ if (message == null) {
+ message = "meta data update";
+ }
+ try (BufferedReader reader = new BufferedReader(
+ new StringReader(message))) {
+ // read the subject line and use it as reflog message
+ ru.setRefLogMessage("commit: " + reader.readLine(), true);
+ }
+ inserter.flush();
+ RefUpdate.Result result = ru.update();
+ switch (result) {
+ case NEW:
+ case FAST_FORWARD:
+ revision = rw.parseCommit(ru.getNewObjectId());
+ update.fireGitRefUpdatedEvent(ru);
+ return revision;
+ default:
+ throw new IOException("Cannot update " + ru.getName() + " in "
+ + db.getDirectory() + ": " + ru.getResult());
+ }
+ }
};
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index a913601312..9ccf153e03 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -16,11 +16,12 @@ package com.google.gerrit.server.git;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
+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.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.server.OrmException;
@@ -53,40 +54,66 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
private final Project.NameKey projectName;
private final ProjectControl projectCtl;
private final ReviewDb reviewDb;
- private final boolean showChanges;
+ private final boolean showMetadata;
- public VisibleRefFilter(final TagCache tagCache, final ChangeCache changeCache,
- final Repository db,
- final ProjectControl projectControl, final ReviewDb reviewDb,
- final boolean showChanges) {
+ public VisibleRefFilter(TagCache tagCache, ChangeCache changeCache,
+ Repository db, ProjectControl projectControl, ReviewDb reviewDb,
+ boolean showMetadata) {
this.tagCache = tagCache;
this.changeCache = changeCache;
this.db = db;
this.projectName = projectControl.getProject().getNameKey();
this.projectCtl = projectControl;
this.reviewDb = reviewDb;
- this.showChanges = showChanges;
+ this.showMetadata = showMetadata;
}
public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeperately) {
- if (projectCtl.allRefsAreVisibleExcept(
- ImmutableSet.of(RefNames.REFS_CONFIG))) {
+ if (projectCtl.allRefsAreVisible(ImmutableSet.of(RefNames.REFS_CONFIG))) {
Map<String, Ref> r = Maps.newHashMap(refs);
- r.remove(RefNames.REFS_CONFIG);
+ if (!projectCtl.controlForRef(RefNames.REFS_CONFIG).isVisible()) {
+ r.remove(RefNames.REFS_CONFIG);
+ }
return r;
}
- final Set<Change.Id> visibleChanges = visibleChanges();
- final Map<String, Ref> result = new HashMap<>();
- final List<Ref> deferredTags = new ArrayList<>();
+ Account.Id currAccountId;
+ boolean canViewMetadata;
+ if (projectCtl.getCurrentUser().isIdentifiedUser()) {
+ IdentifiedUser user = ((IdentifiedUser) projectCtl.getCurrentUser());
+ currAccountId = user.getAccountId();
+ canViewMetadata = user.getCapabilities().canAccessDatabase();
+ } else {
+ currAccountId = null;
+ canViewMetadata = false;
+ }
+
+ Set<Change.Id> visibleChanges = visibleChanges();
+ Map<String, Ref> result = new HashMap<>();
+ List<Ref> deferredTags = new ArrayList<>();
for (Ref ref : refs.values()) {
+ Change.Id changeId;
+ Account.Id accountId;
if (ref.getName().startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
continue;
- } else if (PatchSet.isRef(ref.getName())) {
- // Reference to a patch set is visible if the change is visible.
+ } else if ((accountId = Account.Id.fromRef(ref.getName())) != null) {
+ // Reference related to an account is visible only for the current
+ // account.
+ //
+ // TODO(dborowitz): If a ref matches an account and a change, verify
+ // both (to exclude e.g. edits on changes that the user has lost access
+ // to).
+ if (showMetadata
+ && (canViewMetadata || accountId.equals(currAccountId))) {
+ result.put(ref.getName(), ref);
+ }
+
+ } else if ((changeId = Change.Id.fromRef(ref.getName())) != null) {
+ // Reference related to a change is visible if the change is visible.
//
- if (showChanges && visibleChanges.contains(Change.Id.fromRef(ref.getName()))) {
+ if (showMetadata
+ && (canViewMetadata || visibleChanges.contains(changeId))) {
result.put(ref.getName(), ref);
}
@@ -143,7 +170,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
}
private Set<Change.Id> visibleChanges() {
- if (!showChanges) {
+ if (!showMetadata) {
return Collections.emptySet();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
index 161fa9e72a..ca9b9923d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -18,7 +18,7 @@ import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.util.IdGenerator;
import com.google.inject.Inject;
@@ -314,6 +314,7 @@ public class WorkQueue {
return startTime;
}
+ @Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (task.cancel(mayInterruptIfRunning)) {
// Tiny abuse of running: if the task needs to know it was
@@ -335,35 +336,43 @@ public class WorkQueue {
}
}
+ @Override
public int compareTo(Delayed o) {
return task.compareTo(o);
}
+ @Override
public V get() throws InterruptedException, ExecutionException {
return task.get();
}
+ @Override
public V get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return task.get(timeout, unit);
}
+ @Override
public long getDelay(TimeUnit unit) {
return task.getDelay(unit);
}
+ @Override
public boolean isCancelled() {
return task.isCancelled();
}
+ @Override
public boolean isDone() {
return task.isDone();
}
+ @Override
public boolean isPeriodic() {
return task.isPeriodic();
}
+ @Override
public void run() {
if (running.compareAndSet(false, true)) {
try {
@@ -419,7 +428,7 @@ public class WorkQueue {
}
@Override
- public NameKey getProjectNameKey() {
+ public Project.NameKey getProjectNameKey() {
return runnable.getProjectNameKey();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index b89d91f49d..60af300305 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git.strategy;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
@@ -26,10 +27,12 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeConflictException;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
+import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.lib.ObjectId;
@@ -39,6 +42,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -49,9 +53,9 @@ public class CherryPick extends SubmitStrategy {
private final GitReferenceUpdated gitRefUpdated;
private final Map<Change.Id, CodeReviewCommit> newCommits;
- CherryPick(final SubmitStrategy.Arguments args,
- final PatchSetInfoFactory patchSetInfoFactory,
- final GitReferenceUpdated gitRefUpdated) {
+ CherryPick(SubmitStrategy.Arguments args,
+ PatchSetInfoFactory patchSetInfoFactory,
+ GitReferenceUpdated gitRefUpdated) {
super(args);
this.patchSetInfoFactory = patchSetInfoFactory;
@@ -60,68 +64,22 @@ public class CherryPick extends SubmitStrategy {
}
@Override
- protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
- final List<CodeReviewCommit> toMerge) throws MergeException {
- while (!toMerge.isEmpty()) {
- final CodeReviewCommit n = toMerge.remove(0);
-
+ protected MergeTip _run(CodeReviewCommit branchTip,
+ Collection<CodeReviewCommit> toMerge) throws MergeException {
+ MergeTip mergeTip = new MergeTip(branchTip, toMerge);
+ List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit n = sorted.remove(0);
try {
- if (mergeTip == null) {
- // The branch is unborn. Take a fast-forward resolution to
- // create the branch.
- //
- mergeTip = n;
- n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
-
+ if (mergeTip.getCurrentTip() == null) {
+ cherryPickUnbornRoot(n, mergeTip);
} else if (n.getParentCount() == 0) {
- // Refuse to merge a root commit into an existing branch,
- // we cannot obtain a delta for the cherry-pick to apply.
- //
- n.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
-
+ cherryPickRootOntoBranch(n);
} else if (n.getParentCount() == 1) {
- // If there is only one parent, a cherry-pick can be done by
- // taking the delta relative to that one parent and redoing
- // that on the current merge tip.
- //
-
- mergeTip = writeCherryPickCommit(mergeTip, n);
-
- if (mergeTip != null) {
- newCommits.put(mergeTip.getPatchsetId().getParentKey(), mergeTip);
- } else {
- n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
- }
-
+ cherryPickOne(n, mergeTip);
} else {
- // There are multiple parents, so this is a merge commit. We
- // don't want to cherry-pick the merge as clients can't easily
- // rebase their history with that merge present and replaced
- // by an equivalent merge with a different first parent. So
- // instead behave as though MERGE_IF_NECESSARY was configured.
- //
- if (!args.mergeUtil.hasMissingDependencies(args.mergeSorter, n)) {
- if (args.rw.isMergedInto(mergeTip, n)) {
- mergeTip = n;
- } else {
- mergeTip =
- args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo,
- args.rw, args.inserter, args.canMergeFlag,
- args.destBranch, mergeTip, n);
- }
- final PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
- mergeTip, args.alreadyAccepted);
- setRefLogIdent(submitApproval);
-
- } else {
- // One or more dependencies were not met. The status was
- // already marked on the commit so we have nothing further
- // to perform at this time.
- //
- }
+ cherryPickMultipleParents(n, mergeTip);
}
-
} catch (NoSuchChangeException | IOException | OrmException e) {
throw new MergeException("Cannot merge " + n.name(), e);
}
@@ -129,13 +87,72 @@ public class CherryPick extends SubmitStrategy {
return mergeTip;
}
+ private void cherryPickUnbornRoot(CodeReviewCommit n, MergeTip mergeTip) {
+ // The branch is unborn. Take fast-forward resolution to create the branch.
+ mergeTip.moveTipTo(n, n);
+ n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
+ }
+
+ private void cherryPickRootOntoBranch(CodeReviewCommit n) {
+ // Refuse to merge a root commit into an existing branch, we cannot obtain a
+ // delta for the cherry-pick to apply.
+ n.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
+ }
+
+ private void cherryPickOne(CodeReviewCommit n, MergeTip mergeTip)
+ throws NoSuchChangeException, OrmException, IOException {
+ // If there is only one parent, a cherry-pick can be done by taking the
+ // delta relative to that one parent and redoing that on the current merge
+ // tip.
+ //
+ // Keep going in the case of a single merge failure; the goal is to
+ // cherry-pick as many commits as possible.
+ try {
+ CodeReviewCommit merge =
+ writeCherryPickCommit(mergeTip.getCurrentTip(), n);
+ mergeTip.moveTipTo(merge, merge);
+ newCommits.put(mergeTip.getCurrentTip().getPatchsetId()
+ .getParentKey(), mergeTip.getCurrentTip());
+ } catch (MergeConflictException mce) {
+ n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
+ } catch (MergeIdenticalTreeException mie) {
+ n.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
+ }
+ }
+
+ private void cherryPickMultipleParents(CodeReviewCommit n, MergeTip mergeTip)
+ throws IOException, MergeException {
+ // There are multiple parents, so this is a merge commit. We don't want
+ // to cherry-pick the merge as clients can't easily rebase their history
+ // with that merge present and replaced by an equivalent merge with a
+ // different first parent. So instead behave as though MERGE_IF_NECESSARY
+ // was configured.
+ if (!args.mergeUtil.hasMissingDependencies(args.mergeSorter, n)) {
+ if (args.rw.isMergedInto(mergeTip.getCurrentTip(), n)) {
+ mergeTip.moveTipTo(n, n);
+ } else {
+ CodeReviewCommit result = args.mergeUtil.mergeOneCommit(
+ args.serverIdent.get(), args.repo, args.rw, args.inserter,
+ args.canMergeFlag, args.destBranch, mergeTip.getCurrentTip(), n);
+ mergeTip.moveTipTo(result, n);
+ }
+ PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(args.rw,
+ args.canMergeFlag, mergeTip.getCurrentTip(), args.alreadyAccepted);
+ setRefLogIdent(submitApproval);
+ } else {
+ // One or more dependencies were not met. The status was already marked on
+ // the commit so we have nothing further to perform at this time.
+ }
+ }
+
private CodeReviewCommit writeCherryPickCommit(CodeReviewCommit mergeTip,
CodeReviewCommit n) throws IOException, OrmException,
- NoSuchChangeException {
+ NoSuchChangeException, MergeConflictException,
+ MergeIdenticalTreeException {
args.rw.parseBody(n);
- final PatchSetApproval submitAudit = args.mergeUtil.getSubmitter(n);
+ PatchSetApproval submitAudit = args.mergeUtil.getSubmitter(n);
IdentifiedUser cherryPickUser;
PersonIdent serverNow = args.serverIdent.get();
@@ -150,25 +167,21 @@ public class CherryPick extends SubmitStrategy {
cherryPickCommitterIdent = serverNow;
}
- final String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
+ String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
- final CodeReviewCommit newCommit =
+ CodeReviewCommit newCommit =
(CodeReviewCommit) args.mergeUtil.createCherryPickFromCommit(args.repo,
args.inserter, mergeTip, n, cherryPickCommitterIdent,
cherryPickCmtMsg, args.rw);
- if (newCommit == null) {
- return null;
- }
-
PatchSet.Id id =
ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
- final PatchSet ps = new PatchSet(id);
+ PatchSet ps = new PatchSet(id);
ps.setCreatedOn(TimeUtil.nowTs());
ps.setUploader(cherryPickUser.getAccountId());
ps.setRevision(new RevId(newCommit.getId().getName()));
- final RefUpdate ru;
+ RefUpdate ru;
args.db.changes().beginTransaction(n.change().getId());
try {
@@ -178,9 +191,9 @@ public class CherryPick extends SubmitStrategy {
.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
args.db.changes().update(Collections.singletonList(n.change()));
- final List<PatchSetApproval> approvals = Lists.newArrayList();
- for (PatchSetApproval a
- : args.approvalsUtil.byPatchSet(args.db, n.getControl(), n.getPatchsetId())) {
+ List<PatchSetApproval> approvals = Lists.newArrayList();
+ for (PatchSetApproval a : args.approvalsUtil.byPatchSet(
+ args.db, n.getControl(), n.getPatchsetId())) {
approvals.add(new PatchSetApproval(ps.getId(), a));
}
args.db.patchSetApprovals().insert(approvals);
@@ -204,15 +217,16 @@ public class CherryPick extends SubmitStrategy {
newCommit.copyFrom(n);
newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
- newCommit.setControl(args.changeControlFactory.controlFor(n.change(), cherryPickUser));
+ newCommit.setControl(
+ args.changeControlFactory.controlFor(n.change(), cherryPickUser));
newCommits.put(newCommit.getPatchsetId().getParentKey(), newCommit);
setRefLogIdent(submitAudit);
return newCommit;
}
- private static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
- throws OrmException {
- final int cnt = src.getParentCount();
+ private static void insertAncestors(ReviewDb db, PatchSet.Id id,
+ RevCommit src) throws OrmException {
+ int cnt = src.getParentCount();
List<PatchSetAncestor> toInsert = new ArrayList<>(cnt);
for (int p = 0; p < cnt; p++) {
PatchSetAncestor a;
@@ -230,8 +244,8 @@ public class CherryPick extends SubmitStrategy {
}
@Override
- public boolean dryRun(final CodeReviewCommit mergeTip,
- final CodeReviewCommit toMerge) throws MergeException {
+ public boolean dryRun(CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
+ throws MergeException {
return args.mergeUtil.canCherryPick(args.mergeSorter, args.repo,
mergeTip, args.rw, toMerge);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
index 0b18c0fc76..7ff2107196 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
@@ -18,33 +18,37 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeTip;
+import java.util.Collection;
import java.util.List;
public class FastForwardOnly extends SubmitStrategy {
-
- FastForwardOnly(final SubmitStrategy.Arguments args) {
+ FastForwardOnly(SubmitStrategy.Arguments args) {
super(args);
}
@Override
- protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
- final List<CodeReviewCommit> toMerge) throws MergeException {
- args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
- final CodeReviewCommit newMergeTip =
- args.mergeUtil.getFirstFastForward(mergeTip, args.rw, toMerge);
-
- while (!toMerge.isEmpty()) {
- final CodeReviewCommit n = toMerge.remove(0);
+ protected MergeTip _run(final CodeReviewCommit branchTip,
+ final Collection<CodeReviewCommit> toMerge) throws MergeException {
+ MergeTip mergeTip = new MergeTip(branchTip, toMerge);
+ List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(
+ args.mergeSorter, toMerge);
+ final CodeReviewCommit newMergeTipCommit =
+ args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
+ mergeTip.moveTipTo(newMergeTipCommit, newMergeTipCommit);
+
+ while (!sorted.isEmpty()) {
+ final CodeReviewCommit n = sorted.remove(0);
n.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
}
- final PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, newMergeTip,
+ PatchSetApproval submitApproval =
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, newMergeTipCommit,
args.alreadyAccepted);
setRefLogIdent(submitApproval);
- return newMergeTip;
+ return mergeTip;
}
@Override
@@ -52,8 +56,9 @@ public class FastForwardOnly extends SubmitStrategy {
return false;
}
- public boolean dryRun(final CodeReviewCommit mergeTip,
- final CodeReviewCommit toMerge) throws MergeException {
+ @Override
+ public boolean dryRun(CodeReviewCommit mergeTip,
+ CodeReviewCommit toMerge) throws MergeException {
return args.mergeUtil.canFastForward(args.mergeSorter, mergeTip, args.rw,
toMerge);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
index 9023623ef0..3c13af9cab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
@@ -17,34 +17,40 @@ package com.google.gerrit.server.git.strategy;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeTip;
+import java.util.Collection;
import java.util.List;
public class MergeAlways extends SubmitStrategy {
-
- MergeAlways(final SubmitStrategy.Arguments args) {
+ MergeAlways(SubmitStrategy.Arguments args) {
super(args);
}
@Override
- protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
- List<CodeReviewCommit> toMerge) throws MergeException {
- args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
-
- if (mergeTip == null) {
+ protected MergeTip _run(CodeReviewCommit branchTip,
+ Collection<CodeReviewCommit> toMerge) throws MergeException {
+ List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
+ MergeTip mergeTip;
+ if (branchTip == null) {
// The branch is unborn. Take a fast-forward resolution to
// create the branch.
- mergeTip = toMerge.remove(0);
+ mergeTip = new MergeTip(sorted.get(0), toMerge);
+ sorted.remove(0);
+ } else {
+ mergeTip = new MergeTip(branchTip, toMerge);
}
- while (!toMerge.isEmpty()) {
- mergeTip =
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit mergedFrom = sorted.remove(0);
+ CodeReviewCommit newTip =
args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo, args.rw,
- args.inserter, args.canMergeFlag, args.destBranch, mergeTip,
- toMerge.remove(0));
+ args.inserter, args.canMergeFlag, args.destBranch, mergeTip.getCurrentTip(),
+ mergedFrom);
+ mergeTip.moveTipTo(newTip, mergedFrom);
}
final PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, mergeTip,
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, mergeTip.getCurrentTip(),
args.alreadyAccepted);
setRefLogIdent(submitApproval);
@@ -52,8 +58,8 @@ public class MergeAlways extends SubmitStrategy {
}
@Override
- public boolean dryRun(final CodeReviewCommit mergeTip,
- final CodeReviewCommit toMerge) throws MergeException {
+ public boolean dryRun(CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
+ throws MergeException {
return args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip,
toMerge);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index b84586f682..b49cb0a8ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -17,47 +17,55 @@ package com.google.gerrit.server.git.strategy;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeTip;
+import java.util.Collection;
import java.util.List;
public class MergeIfNecessary extends SubmitStrategy {
-
- MergeIfNecessary(final SubmitStrategy.Arguments args) {
+ MergeIfNecessary(SubmitStrategy.Arguments args) {
super(args);
}
@Override
- protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
- List<CodeReviewCommit> toMerge) throws MergeException {
- args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
-
- if (mergeTip == null) {
+ protected MergeTip _run(CodeReviewCommit branchTip,
+ Collection<CodeReviewCommit> toMerge) throws MergeException {
+ List<CodeReviewCommit> sorted =
+ args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
+ MergeTip mergeTip;
+ if (branchTip == null) {
// The branch is unborn. Take a fast-forward resolution to
// create the branch.
- mergeTip = toMerge.remove(0);
+ mergeTip = new MergeTip(sorted.get(0), toMerge);
+ branchTip = sorted.remove(0);
+ } else {
+ mergeTip = new MergeTip(branchTip, toMerge);
+ branchTip =
+ args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
}
-
- mergeTip =
- args.mergeUtil.getFirstFastForward(mergeTip, args.rw, toMerge);
+ mergeTip.moveTipTo(branchTip, branchTip);
// For every other commit do a pair-wise merge.
- while (!toMerge.isEmpty()) {
- mergeTip =
- args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo, args.rw,
- args.inserter, args.canMergeFlag, args.destBranch, mergeTip,
- toMerge.remove(0));
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit mergedFrom = sorted.remove(0);
+ branchTip =
+ args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo,
+ args.rw, args.inserter, args.canMergeFlag, args.destBranch,
+ branchTip, mergedFrom);
+ mergeTip.moveTipTo(branchTip, mergedFrom);
}
- final PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(
- args.rw, args.canMergeFlag, mergeTip, args.alreadyAccepted);
+ final PatchSetApproval submitApproval =
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, branchTip,
+ args.alreadyAccepted);
setRefLogIdent(submitApproval);
return mergeTip;
}
@Override
- public boolean dryRun(final CodeReviewCommit mergeTip,
- final CodeReviewCommit toMerge) throws MergeException {
+ public boolean dryRun(CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
+ throws MergeException {
return args.mergeUtil.canFastForward(
args.mergeSorter, mergeTip, args.rw, toMerge)
|| args.mergeUtil.canMerge(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index 130d170dc5..acd32c7c50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -20,11 +20,12 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
-import com.google.gerrit.server.changedetail.PathConflictException;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeConflictException;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.git.RebaseSorter;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -34,6 +35,8 @@ import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -53,20 +56,19 @@ public class RebaseIfNecessary extends SubmitStrategy {
}
@Override
- protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
- final List<CodeReviewCommit> toMerge) throws MergeException {
- CodeReviewCommit newMergeTip = mergeTip;
- sort(toMerge);
-
- while (!toMerge.isEmpty()) {
- final CodeReviewCommit n = toMerge.remove(0);
-
- if (newMergeTip == null) {
+ protected MergeTip _run(final CodeReviewCommit branchTip,
+ final Collection<CodeReviewCommit> toMerge) throws MergeException {
+ MergeTip mergeTip = new MergeTip(branchTip, toMerge);
+ List<CodeReviewCommit> sorted = sort(toMerge);
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit n = sorted.remove(0);
+
+ if (mergeTip.getCurrentTip() == null) {
// The branch is unborn. Take a fast-forward resolution to
// create the branch.
//
- newMergeTip = n;
n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
+ mergeTip.moveTipTo(n, n);
} else if (n.getParentCount() == 0) {
// Refuse to merge a root commit into an existing branch,
@@ -75,42 +77,47 @@ public class RebaseIfNecessary extends SubmitStrategy {
n.setStatusCode(CommitMergeStatus.CANNOT_REBASE_ROOT);
} else if (n.getParentCount() == 1) {
- if (args.mergeUtil.canFastForward(
- args.mergeSorter, newMergeTip, args.rw, n)) {
- newMergeTip = n;
+ if (args.mergeUtil.canFastForward(args.mergeSorter,
+ mergeTip.getCurrentTip(), args.rw, n)) {
n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
+ mergeTip.moveTipTo(n, n);
} else {
try {
- final IdentifiedUser uploader = args.identifiedUserFactory.create(
- args.mergeUtil.getSubmitter(n).getAccountId());
- final PatchSet newPatchSet =
+ IdentifiedUser uploader =
+ args.identifiedUserFactory.create(args.mergeUtil
+ .getSubmitter(n).getAccountId());
+ PatchSet newPatchSet =
rebaseChange.rebase(args.repo, args.rw, args.inserter,
n.getPatchsetId(), n.change(), uploader,
- newMergeTip, args.mergeUtil, args.serverIdent.get(),
- false, false, ValidatePolicy.NONE);
+ mergeTip.getCurrentTip(), args.mergeUtil,
+ args.serverIdent.get(), false, ValidatePolicy.NONE);
List<PatchSetApproval> approvals = Lists.newArrayList();
- for (PatchSetApproval a : args.approvalsUtil.byPatchSet(
- args.db, n.getControl(), n.getPatchsetId())) {
+ for (PatchSetApproval a : args.approvalsUtil.byPatchSet(args.db,
+ n.getControl(), n.getPatchsetId())) {
approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
}
// rebaseChange.rebase() may already have copied some approvals,
// use upsert, not insert, to avoid constraint violation on database
args.db.patchSetApprovals().upsert(approvals);
- newMergeTip =
- (CodeReviewCommit) args.rw.parseCommit(ObjectId
- .fromString(newPatchSet.getRevision().get()));
+ CodeReviewCommit newTip = (CodeReviewCommit) args.rw.parseCommit(
+ ObjectId.fromString(newPatchSet.getRevision().get()));
+ mergeTip.moveTipTo(newTip, newTip);
n.change().setCurrentPatchSet(
- patchSetInfoFactory.get(newMergeTip, newPatchSet.getId()));
- newMergeTip.copyFrom(n);
- newMergeTip.setControl(args.changeControlFactory.controlFor(n.change(), uploader));
- newMergeTip.setPatchsetId(newPatchSet.getId());
- newMergeTip.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
- newCommits.put(newPatchSet.getId().getParentKey(), newMergeTip);
+ patchSetInfoFactory.get(mergeTip.getCurrentTip(),
+ newPatchSet.getId()));
+ mergeTip.getCurrentTip().copyFrom(n);
+ mergeTip.getCurrentTip().setControl(
+ args.changeControlFactory.controlFor(n.change(), uploader));
+ mergeTip.getCurrentTip().setPatchsetId(newPatchSet.getId());
+ mergeTip.getCurrentTip().setStatusCode(
+ CommitMergeStatus.CLEAN_REBASE);
+ newCommits.put(newPatchSet.getId().getParentKey(),
+ mergeTip.getCurrentTip());
setRefLogIdent(args.mergeUtil.getSubmitter(n));
- } catch (PathConflictException e) {
- n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
+ } catch (MergeConflictException e) {
+ n.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
} catch (NoSuchChangeException | OrmException | IOException
| InvalidChangeOperationException e) {
throw new MergeException("Cannot rebase " + n.name(), e);
@@ -125,34 +132,36 @@ public class RebaseIfNecessary extends SubmitStrategy {
// instead behave as though MERGE_IF_NECESSARY was configured.
//
try {
- if (args.rw.isMergedInto(newMergeTip, n)) {
- newMergeTip = n;
+ if (args.rw.isMergedInto(mergeTip.getCurrentTip(), n)) {
+ mergeTip.moveTipTo(n, n);
} else {
- newMergeTip = args.mergeUtil.mergeOneCommit(
- args.serverIdent.get(), args.repo, args.rw, args.inserter,
- args.canMergeFlag, args.destBranch, newMergeTip, n);
+ mergeTip.moveTipTo(
+ args.mergeUtil.mergeOneCommit(args.serverIdent.get(),
+ args.repo, args.rw, args.inserter, args.canMergeFlag,
+ args.destBranch, mergeTip.getCurrentTip(), n), n);
}
- final PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(
- args.rw, args.canMergeFlag, newMergeTip, args.alreadyAccepted);
+ PatchSetApproval submitApproval =
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ mergeTip.getCurrentTip(), args.alreadyAccepted);
setRefLogIdent(submitApproval);
} catch (IOException e) {
throw new MergeException("Cannot merge " + n.name(), e);
}
}
- args.alreadyAccepted.add(newMergeTip);
+ args.alreadyAccepted.add(mergeTip.getCurrentTip());
}
- return newMergeTip;
+ return mergeTip;
}
- private void sort(final List<CodeReviewCommit> toSort) throws MergeException {
+ private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
+ throws MergeException {
try {
- final List<CodeReviewCommit> sorted =
- new RebaseSorter(args.rw, args.alreadyAccepted, args.canMergeFlag)
- .sort(toSort);
- toSort.clear();
- toSort.addAll(sorted);
+ List<CodeReviewCommit> result = new RebaseSorter(
+ args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
+ Collections.sort(result, CodeReviewCommit.ORDER);
+ return result;
} catch (IOException e) {
throw new MergeException("Commit sorting failed", e);
}
@@ -164,8 +173,8 @@ public class RebaseIfNecessary extends SubmitStrategy {
}
@Override
- public boolean dryRun(final CodeReviewCommit mergeTip,
- final CodeReviewCommit toMerge) throws MergeException {
+ public boolean dryRun(CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
+ throws MergeException {
return !args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)
&& args.mergeUtil.canCherryPick(args.mergeSorter, args.repo, mergeTip,
args.rw, toMerge);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index a864b6c380..b25b17e425 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git.strategy;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -24,6 +24,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeSorter;
+import com.google.gerrit.server.git.MergeTip;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl;
@@ -37,20 +38,18 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevWalk;
+import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Map;
import java.util.Set;
/**
- * Base class that submit strategies must extend. A submit strategy for a
- * certain {@link SubmitType} defines how the submitted commits should be
- * merged.
+ * Base class that submit strategies must extend.
+ * <p>
+ * A submit strategy for a certain {@link SubmitType} defines how the submitted
+ * commits should be merged.
*/
public abstract class SubmitStrategy {
-
- private PersonIdent refLogIdent;
-
static class Arguments {
protected final IdentifiedUser.GenericFactory identifiedUserFactory;
protected final Provider<PersonIdent> serverIdent;
@@ -68,13 +67,13 @@ public abstract class SubmitStrategy {
protected final ChangeIndexer indexer;
protected final MergeSorter mergeSorter;
- Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
- final Provider<PersonIdent> serverIdent, final ReviewDb db,
- final ChangeControl.GenericFactory changeControlFactory,
- final Repository repo, final RevWalk rw, final ObjectInserter inserter,
- final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
- final Branch.NameKey destBranch, final ApprovalsUtil approvalsUtil,
- final MergeUtil mergeUtil, final ChangeIndexer indexer) {
+ Arguments(IdentifiedUser.GenericFactory identifiedUserFactory,
+ Provider<PersonIdent> serverIdent, ReviewDb db,
+ ChangeControl.GenericFactory changeControlFactory, Repository repo,
+ RevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag,
+ Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch,
+ ApprovalsUtil approvalsUtil, MergeUtil mergeUtil,
+ ChangeIndexer indexer) {
this.identifiedUserFactory = identifiedUserFactory;
this.serverIdent = serverIdent;
this.db = db;
@@ -95,48 +94,42 @@ public abstract class SubmitStrategy {
protected final Arguments args;
- SubmitStrategy(final Arguments args) {
+ private PersonIdent refLogIdent;
+
+ SubmitStrategy(Arguments args) {
this.args = args;
}
/**
- * Runs this submit strategy. If possible the provided commits will be merged
- * with this submit strategy.
+ * Runs this submit strategy.
+ * <p>
+ * If possible, the provided commits will be merged with this submit strategy.
*
- * @param mergeTip the mergeTip
+ * @param currentTip the mergeTip
* @param toMerge the list of submitted commits that should be merged using
- * this submit strategy
- * @return the new mergeTip
+ * this submit strategy. Implementations are responsible for ordering
+ * of commits, and should not modify the input in place.
+ * @return the new merge tip.
* @throws MergeException
*/
- public final CodeReviewCommit run(final CodeReviewCommit mergeTip,
- final List<CodeReviewCommit> toMerge) throws MergeException {
+ public final MergeTip run(final CodeReviewCommit currentTip,
+ final Collection<CodeReviewCommit> toMerge) throws MergeException {
refLogIdent = null;
- return _run(mergeTip, toMerge);
+ return _run(currentTip, toMerge);
}
- /**
- * Runs this submit strategy. If possible the provided commits will be merged
- * with this submit strategy.
- *
- * @param mergeTip the mergeTip
- * @param toMerge the list of submitted commits that should be merged using
- * this submit strategy
- * @return the new mergeTip
- * @throws MergeException
- */
- protected abstract CodeReviewCommit _run(CodeReviewCommit mergeTip,
- List<CodeReviewCommit> toMerge) throws MergeException;
+ /** @see #run(CodeReviewCommit, Collection) */
+ protected abstract MergeTip _run(CodeReviewCommit currentTip,
+ Collection<CodeReviewCommit> toMerge) throws MergeException;
/**
* Checks whether the given commit can be merged.
*
- * Subclasses must ensure that invoking this method does neither modify the
+ * Implementations must ensure that invoking this method modifies neither the
* git repository nor the Gerrit database.
*
- * @param mergeTip the mergeTip
- * @param toMerge the commit for which it should be checked whether it can be
- * merged or not
+ * @param mergeTip the merge tip.
+ * @param toMerge the commit that should be checked.
* @return {@code true} if the given commit can be merged, otherwise
* {@code false}
* @throws MergeException
@@ -145,14 +138,13 @@ public abstract class SubmitStrategy {
CodeReviewCommit toMerge) throws MergeException;
/**
- * Returns the PersonIdent that should be used for the ref log entries when
- * updating the destination branch. The ref log identity may be set after the
- * {@link #run(CodeReviewCommit, List)} method finished.
+ * Returns the identity that should be used for reflog entries when updating
+ * the destination branch.
+ * <p>
+ * The reflog identity may only be set during {@link #run(CodeReviewCommit,
+ * Collection)}, and this method is invalid to call beforehand.
*
- * Do only call this method after the {@link #run(CodeReviewCommit, List)}
- * method has been invoked.
- *
- * @return the ref log identity, may be {@code null}
+ * @return the ref log identity, which may be {@code null}.
*/
public final PersonIdent getRefLogIdent() {
return refLogIdent;
@@ -161,24 +153,24 @@ public abstract class SubmitStrategy {
/**
* Returns all commits that have been newly created for the changes that are
* getting merged.
+ * <p>
+ * By default this method returns an empty map, but subclasses may override
+ * this method to provide any newly created commits.
*
- * By default this method is returning an empty map, but subclasses may
- * overwrite this method to provide newly created commits.
- *
- * Do only call this method after the {@link #run(CodeReviewCommit, List)}
- * method has been invoked.
+ * This method may only be called after {@link #run(CodeReviewCommit,
+ * Collection)}.
*
- * @return new commits created for changes that are getting merged
+ * @return new commits created for changes that were merged.
*/
public Map<Change.Id, CodeReviewCommit> getNewCommits() {
return Collections.emptyMap();
}
/**
- * Returns whether a merge that failed with
- * {@link Result#LOCK_FAILURE} should be retried.
- *
- * May be overwritten by subclasses.
+ * Returns whether a merge that failed with {@link Result#LOCK_FAILURE} should
+ * be retried.
+ * <p>
+ * May be overridden by subclasses.
*
* @return {@code true} if a merge that failed with
* {@link Result#LOCK_FAILURE} should be retried, otherwise
@@ -189,15 +181,14 @@ public abstract class SubmitStrategy {
}
/**
- * Sets the ref log identity if it wasn't set yet.
+ * Set the ref log identity if it wasn't set yet.
*
* @param submitApproval the approval that submitted the patch set
*/
- protected final void setRefLogIdent(final PatchSetApproval submitApproval) {
+ protected final void setRefLogIdent(PatchSetApproval submitApproval) {
if (refLogIdent == null && submitApproval != null) {
- refLogIdent =
- args.identifiedUserFactory.create(submitApproval.getAccountId())
- .newRefLogIdent();
+ refLogIdent = args.identifiedUserFactory.create(
+ submitApproval.getAccountId()) .newRefLogIdent();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index 091523bdc5..ac65f8d360 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git.strategy;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationMessage.java
index ab86317f9a..a77848293e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationMessage.java
@@ -14,20 +14,8 @@
package com.google.gerrit.server.git.validators;
-public class CommitValidationMessage {
- private final String message;
- private final boolean isError;
-
- public CommitValidationMessage(final String message, final boolean isError) {
- this.message = message;
- this.isError = isError;
- }
-
- public String getMessage() {
- return message;
- }
-
- public boolean isError() {
- return isError;
+public class CommitValidationMessage extends ValidationMessage {
+ public CommitValidationMessage(String message, boolean isError) {
+ super(message, isError);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 9d0eb665c1..d030a553e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git.validators;
import com.google.common.base.CharMatcher;
+import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -24,7 +25,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.CommitReceivedEvent;
-import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ReceiveCommits;
import com.google.gerrit.server.git.ValidationError;
@@ -60,8 +60,6 @@ public class CommitValidators {
private static final Logger log = LoggerFactory
.getLogger(CommitValidators.class);
- private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
-
public interface Factory {
CommitValidators create(RefControl refControl, SshInfo sshInfo,
Repository repo);
@@ -93,7 +91,8 @@ public class CommitValidators {
}
public List<CommitValidationMessage> validateForReceiveCommits(
- CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ CommitReceivedEvent receiveEvent, NoteMap rejectCommits)
+ throws CommitValidationException {
List<CommitValidationListener> validators = new LinkedList<>();
@@ -102,7 +101,7 @@ public class CommitValidators {
refControl, gerritIdent));
validators.add(new AuthorUploaderValidator(refControl, canonicalWebUrl));
validators.add(new CommitterUploaderValidator(refControl, canonicalWebUrl));
- validators.add(new SignedOffByValidator(refControl, canonicalWebUrl));
+ validators.add(new SignedOffByValidator(refControl));
if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName())
|| ReceiveCommits.NEW_PATCHSET.matcher(
receiveEvent.command.getRefName()).matches()) {
@@ -110,7 +109,7 @@ public class CommitValidators {
installCommitMsgHookCommand, sshInfo));
}
validators.add(new ConfigValidator(refControl, repo));
- validators.add(new BannedCommitsValidator(repo));
+ validators.add(new BannedCommitsValidator(rejectCommits));
validators.add(new PluginCommitValidationListener(commitValidationListeners));
List<CommitValidationMessage> messages = new LinkedList<>();
@@ -136,7 +135,7 @@ public class CommitValidators {
validators.add(new AmendedGerritMergeCommitValidationListener(
refControl, gerritIdent));
validators.add(new AuthorUploaderValidator(refControl, canonicalWebUrl));
- validators.add(new SignedOffByValidator(refControl, canonicalWebUrl));
+ validators.add(new SignedOffByValidator(refControl));
if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName())
|| ReceiveCommits.NEW_PATCHSET.matcher(
receiveEvent.command.getRefName()).matches()) {
@@ -179,14 +178,15 @@ public class CommitValidators {
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
- final List<String> idList = receiveEvent.commit.getFooterLines(CHANGE_ID);
+ final List<String> idList = receiveEvent.commit.getFooterLines(
+ FooterConstants.CHANGE_ID);
List<CommitValidationMessage> messages = new LinkedList<>();
if (idList.isEmpty()) {
if (projectControl.getProjectState().isRequireChangeID()) {
String shortMsg = receiveEvent.commit.getShortMessage();
- String changeIdPrefix = CHANGE_ID.getName() + ":";
+ String changeIdPrefix = FooterConstants.CHANGE_ID.getName() + ":";
if (shortMsg.startsWith(changeIdPrefix)
&& shortMsg.substring(changeIdPrefix.length()).trim()
.matches("^I[0-9a-f]{8,}.*$")) {
@@ -194,7 +194,7 @@ public class CommitValidators {
"missing subject; Change-Id must be in commit message footer");
} else {
String errMsg = "missing Change-Id in commit message footer";
- messages.add(getFixedCommitMsgWithChangeId(
+ messages.add(getMissingChangeIdErrorMsg(
errMsg, receiveEvent.commit));
throw new CommitValidationException(errMsg, messages);
}
@@ -208,49 +208,25 @@ public class CommitValidators {
final String errMsg =
"missing or invalid Change-Id line format in commit message footer";
messages.add(
- getFixedCommitMsgWithChangeId(errMsg, receiveEvent.commit));
+ getMissingChangeIdErrorMsg(errMsg, receiveEvent.commit));
throw new CommitValidationException(errMsg, messages);
}
}
return Collections.emptyList();
}
- /**
- * We handle 3 cases:
- * 1. No change id in the commit message at all.
- * 2. Change id last in the commit message but missing empty line to create the footer.
- * 3. There is a change-id somewhere in the commit message, but we ignore it.
- *
- * @return The fixed up commit message
- */
- private CommitValidationMessage getFixedCommitMsgWithChangeId(
+ private CommitValidationMessage getMissingChangeIdErrorMsg(
final String errMsg, final RevCommit c) {
final String changeId = "Change-Id:";
StringBuilder sb = new StringBuilder();
sb.append("ERROR: ").append(errMsg);
- sb.append('\n');
- sb.append("Suggestion for commit message:\n");
- if (c.getFullMessage().indexOf(changeId) == -1) {
- sb.append(c.getFullMessage());
- sb.append('\n');
- sb.append(changeId).append(" I").append(c.name());
- } else {
+ if (c.getFullMessage().indexOf(changeId) >= 0) {
String lines[] = c.getFullMessage().trim().split("\n");
String lastLine = lines.length > 0 ? lines[lines.length - 1] : "";
- if (lastLine.indexOf(changeId) == 0) {
- for (int i = 0; i < lines.length - 1; i++) {
- sb.append(lines[i]);
- sb.append('\n');
- }
-
- sb.append('\n');
- sb.append(lastLine);
- } else {
- sb.append(c.getFullMessage());
+ if (lastLine.indexOf(changeId) == -1) {
sb.append('\n');
- sb.append(changeId).append(" I").append(c.name());
sb.append('\n');
sb.append("Hint: A potential Change-Id was found, but it was not in the ");
sb.append("footer (last paragraph) of the commit message.");
@@ -259,8 +235,10 @@ public class CommitValidators {
sb.append('\n');
sb.append('\n');
sb.append("Hint: To automatically insert Change-Id, install the hook:\n");
- sb.append(getCommitMessageHookInstallationHint()).append('\n');
+ sb.append(getCommitMessageHookInstallationHint());
sb.append('\n');
+ sb.append("And then amend the commit:\n");
+ sb.append(" git commit --amend\n");
return new CommitValidationMessage(sb.toString(), false);
}
@@ -396,7 +374,7 @@ public class CommitValidators {
public static class SignedOffByValidator implements CommitValidationListener {
private final RefControl refControl;
- public SignedOffByValidator(RefControl refControl, String canonicalWebUrl) {
+ public SignedOffByValidator(RefControl refControl) {
this.refControl = refControl;
}
@@ -416,7 +394,7 @@ public class CommitValidators {
if (e != null) {
sboAuthor |= author.getEmailAddress().equals(e);
sboCommitter |= committer.getEmailAddress().equals(e);
- sboMe |= currentUser.getEmailAddresses().contains(e);
+ sboMe |= currentUser.hasEmailAddress(e);
}
}
}
@@ -447,7 +425,7 @@ public class CommitValidators {
IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
final PersonIdent author = receiveEvent.commit.getAuthorIdent();
- if (!currentUser.getEmailAddresses().contains(author.getEmailAddress())
+ if (!currentUser.hasEmailAddress(author.getEmailAddress())
&& !refControl.canForgeAuthor()) {
List<CommitValidationMessage> messages = new LinkedList<>();
@@ -476,8 +454,7 @@ public class CommitValidators {
CommitReceivedEvent receiveEvent) throws CommitValidationException {
IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
- if (!currentUser.getEmailAddresses()
- .contains(committer.getEmailAddress())
+ if (!currentUser.hasEmailAddress(committer.getEmailAddress())
&& !refControl.canForgeCommitter()) {
List<CommitValidationMessage> messages = new LinkedList<>();
messages.add(getInvalidEmailError(receiveEvent.commit, "committer", committer,
@@ -522,24 +499,25 @@ public class CommitValidators {
/** Reject banned commits. */
public static class BannedCommitsValidator implements
CommitValidationListener {
- private final Repository repo;
+ private final NoteMap rejectCommits;
- public BannedCommitsValidator(Repository repo) {
- this.repo = repo;
+ public BannedCommitsValidator(NoteMap rejectCommits) {
+ this.rejectCommits = rejectCommits;
}
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
try {
- NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo);
if (rejectCommits.contains(receiveEvent.commit)) {
throw new CommitValidationException("contains banned commit "
+ receiveEvent.commit.getName());
}
return Collections.emptyList();
} catch (IOException e) {
- throw new CommitValidationException(e.getMessage(), e);
+ String m = "error checking banned commits";
+ log.warn(m, e);
+ throw new CommitValidationException(m, e);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 76998b7536..6f70d46c75 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.git.validators;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java
new file mode 100644
index 0000000000..5864833942
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 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.validators;
+
+import com.google.gerrit.server.validators.ValidationException;
+
+public class RefOperationValidationException extends ValidationException {
+ private static final long serialVersionUID = 1L;
+ private final Iterable<ValidationMessage> messages;
+
+ public RefOperationValidationException(String reason,
+ Iterable<ValidationMessage> messages) {
+ super(reason);
+ this.messages = messages;
+ }
+
+ public Iterable<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();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationListener.java
new file mode 100644
index 0000000000..5d04e2a3b8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidationListener.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 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.validators;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.server.events.RefReceivedEvent;
+import com.google.gerrit.server.validators.ValidationException;
+
+import java.util.List;
+
+/**
+ * Listener to provide validation on operation that is going to be performed on
+ * given ref
+ */
+@ExtensionPoint
+public interface RefOperationValidationListener {
+ /**
+ * Validate a ref operation before it is performed.
+ *
+ * @param refEvent ref operation specification
+ * @return empty list or informational messages on success
+ * @throws ValidationException if the ref operation fails to validate
+ */
+ List<ValidationMessage> onRefOperation(RefReceivedEvent refEvent)
+ throws ValidationException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
new file mode 100644
index 0000000000..6fd0f5cc5b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
@@ -0,0 +1,100 @@
+// Copyright (C) 2014 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.validators;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.RefReceivedEvent;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class RefOperationValidators {
+ private static final GetErrorMessages GET_ERRORS = new GetErrorMessages();
+ private static final Logger LOG = LoggerFactory
+ .getLogger(RefOperationValidators.class);
+
+ public interface Factory {
+ RefOperationValidators create(Project project, IdentifiedUser user, ReceiveCommand cmd);
+ }
+
+ public static ReceiveCommand getCommand(RefUpdate update, ReceiveCommand.Type type) {
+ return new ReceiveCommand(update.getOldObjectId(), update.getNewObjectId(),
+ update.getName(), type);
+ }
+
+ private final RefReceivedEvent event;
+ private final DynamicSet<RefOperationValidationListener> refOperationValidationListeners;
+
+ @Inject
+ RefOperationValidators(
+ DynamicSet<RefOperationValidationListener> refOperationValidationListeners,
+ @Assisted Project project, @Assisted IdentifiedUser user,
+ @Assisted ReceiveCommand cmd) {
+ this.refOperationValidationListeners = refOperationValidationListeners;
+ event = new RefReceivedEvent();
+ event.command = cmd;
+ event.project = project;
+ event.user = user;
+ }
+
+ public List<ValidationMessage> validateForRefOperation()
+ throws RefOperationValidationException {
+
+ List<ValidationMessage> messages = Lists.newArrayList();
+ boolean withException = false;
+ try {
+ for (RefOperationValidationListener listener : refOperationValidationListeners) {
+ messages.addAll(listener.onRefOperation(event));
+ }
+ } catch (ValidationException e) {
+ messages.add(new ValidationMessage(e.getMessage(), true));
+ withException = true;
+ }
+
+ if (withException) {
+ throwException(messages, event);
+ }
+
+ return messages;
+ }
+
+ private void throwException(Iterable<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());
+ LOG.error(header);
+ throw new RefOperationValidationException(header, errors);
+ }
+
+ private static class GetErrorMessages implements Predicate<ValidationMessage> {
+ @Override
+ public boolean apply(ValidationMessage input) {
+ return input.isError();
+ }
+ }
+} \ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidators.java
index 1735d2817f..eb2e136924 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidators.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.git.validators;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.ObjectId;
@@ -27,8 +28,6 @@ import org.eclipse.jgit.transport.UploadPack;
import java.util.Collection;
-import javax.inject.Inject;
-
public class UploadValidators implements PreUploadHook {
private final DynamicSet<UploadValidationListener> uploadValidationListeners;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/ValidationMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/ValidationMessage.java
new file mode 100644
index 0000000000..e1098aa099
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/ValidationMessage.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2014 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.validators;
+
+public class ValidationMessage {
+ private final String message;
+ private final boolean isError;
+
+ public ValidationMessage(String message, boolean isError) {
+ this.message = message;
+ this.isError = isError;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public boolean isError() {
+ return isError;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
index 163b3356d4..9a3f02fc75 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
@@ -18,6 +18,7 @@ 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.gerrit.audit.AuditService;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -27,14 +28,12 @@ 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.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.group.AddIncludedGroups.Input;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -75,15 +74,17 @@ public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
private final GroupIncludeCache groupIncludeCache;
private final Provider<ReviewDb> db;
private final GroupJson json;
+ private final AuditService auditService;
@Inject
public AddIncludedGroups(GroupsCollection groupsCollection,
- GroupIncludeCache groupIncludeCache,
- Provider<ReviewDb> db, GroupJson json) {
+ GroupIncludeCache groupIncludeCache, Provider<ReviewDb> db,
+ GroupJson json, AuditService auditService) {
this.groupsCollection = groupsCollection;
this.groupIncludeCache = groupIncludeCache;
this.db = db;
this.json = json;
+ this.auditService = auditService;
}
@Override
@@ -98,13 +99,12 @@ public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
GroupControl control = resource.getControl();
Map<AccountGroup.UUID, AccountGroupById> newIncludedGroups = Maps.newHashMap();
- List<AccountGroupByIdAud> newIncludedGroupsAudits = Lists.newLinkedList();
List<GroupInfo> result = Lists.newLinkedList();
Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId();
for (String includedGroup : input.groups) {
GroupDescription.Basic d = groupsCollection.parse(includedGroup);
- if (!control.canAddGroup(d.getGroupUUID())) {
+ if (!control.canAddGroup()) {
throw new AuthException(String.format("Cannot add group: %s",
d.getName()));
}
@@ -117,15 +117,13 @@ public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
if (agi == null) {
agi = new AccountGroupById(agiKey);
newIncludedGroups.put(d.getGroupUUID(), agi);
- newIncludedGroupsAudits.add(
- new AccountGroupByIdAud(agi, me, TimeUtil.nowTs()));
}
}
result.add(json.format(d));
}
if (!newIncludedGroups.isEmpty()) {
- db.get().accountGroupByIdAud().insert(newIncludedGroupsAudits);
+ auditService.dispatchAddGroupsToGroup(me, newIncludedGroups.values());
db.get().accountGroupById().insert(newIncludedGroups.values());
for (AccountGroupById agi : newIncludedGroups.values()) {
groupIncludeCache.evictParentGroupsOf(agi.getIncludeUUID());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index df58c8f262..5c7fcbca23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -17,6 +17,8 @@ package com.google.gerrit.server.group;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
+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.MethodNotAllowedException;
@@ -25,13 +27,12 @@ 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.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
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.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.AccountsCollection;
@@ -39,7 +40,6 @@ import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.group.AddMembers.Input;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -80,8 +80,9 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
private final AccountsCollection accounts;
private final AccountResolver accountResolver;
private final AccountCache accountCache;
- private final AccountInfo.Loader.Factory infoFactory;
+ private final AccountLoader.Factory infoFactory;
private final Provider<ReviewDb> db;
+ private final AuditService auditService;
@Inject
AddMembers(AccountManager accountManager,
@@ -89,9 +90,11 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
AccountsCollection accounts,
AccountResolver accountResolver,
AccountCache accountCache,
- AccountInfo.Loader.Factory infoFactory,
- Provider<ReviewDb> db) {
+ AccountLoader.Factory infoFactory,
+ Provider<ReviewDb> db,
+ AuditService auditService) {
this.accountManager = accountManager;
+ this.auditService = auditService;
this.authType = authConfig.getAuthType();
this.accounts = accounts;
this.accountResolver = accountResolver;
@@ -112,10 +115,9 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
GroupControl control = resource.getControl();
Map<Account.Id, AccountGroupMember> newAccountGroupMembers = Maps.newHashMap();
- List<AccountGroupMemberAudit> newAccountGroupMemberAudits = Lists.newLinkedList();
List<AccountInfo> result = Lists.newLinkedList();
Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId();
- AccountInfo.Loader loader = infoFactory.create(true);
+ AccountLoader loader = infoFactory.create(true);
for (String nameOrEmail : input.members) {
Account a = findAccount(nameOrEmail);
@@ -124,7 +126,7 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
"Account Inactive: %s", nameOrEmail));
}
- if (!control.canAddMember(a.getId())) {
+ if (!control.canAddMember()) {
throw new AuthException("Cannot add member: " + a.getFullName());
}
@@ -135,14 +137,12 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
if (m == null) {
m = new AccountGroupMember(key);
newAccountGroupMembers.put(m.getAccountId(), m);
- newAccountGroupMemberAudits.add(
- new AccountGroupMemberAudit(m, me, TimeUtil.nowTs()));
}
}
result.add(loader.get(a.getId()));
}
- db.get().accountGroupMembersAudit().insert(newAccountGroupMemberAudits);
+ auditService.dispatchAddAccountsToGroup(me, newAccountGroupMembers.values());
db.get().accountGroupMembers().insert(newAccountGroupMembers.values());
for (AccountGroupMember m : newAccountGroupMembers.values()) {
accountCache.evict(m.getAccountId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
index 4ca0200b9a..cb8070291f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.group;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescription;
@@ -101,7 +101,8 @@ public class CreateGroup implements RestModifyView<TopLevelResource, Input> {
CreateGroupArgs args = new CreateGroupArgs();
args.setGroupName(name);
args.groupDescription = Strings.emptyToNull(input.description);
- args.visibleToAll = Objects.firstNonNull(input.visibleToAll, defaultVisibleToAll);
+ args.visibleToAll = MoreObjects.firstNonNull(input.visibleToAll,
+ defaultVisibleToAll);
args.ownerGroupId = ownerId;
args.initialMembers = ownerId == null
? Collections.singleton(self.get().getAccountId())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
new file mode 100644
index 0000000000..9e261f921c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
@@ -0,0 +1,220 @@
+// Copyright (C) 2014 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.group;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.gerrit.audit.GroupMemberAuditListener;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.UniversalGroupBackend;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.slf4j.Logger;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+class DbGroupMemberAuditListener implements GroupMemberAuditListener {
+ private static final Logger log = org.slf4j.LoggerFactory
+ .getLogger(DbGroupMemberAuditListener.class);
+
+ private final SchemaFactory<ReviewDb> schema;
+ private final AccountCache accountCache;
+ private final GroupCache groupCache;
+ private final UniversalGroupBackend groupBackend;
+
+ @Inject
+ public DbGroupMemberAuditListener(SchemaFactory<ReviewDb> schema,
+ AccountCache accountCache, GroupCache groupCache,
+ UniversalGroupBackend groupBackend) {
+ this.schema = schema;
+ this.accountCache = accountCache;
+ this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
+ }
+
+ @Override
+ public void onAddAccountsToGroup(Account.Id me,
+ Collection<AccountGroupMember> added) {
+ List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
+ for (AccountGroupMember m : added) {
+ AccountGroupMemberAudit audit =
+ new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
+ auditInserts.add(audit);
+ }
+ try {
+ ReviewDb db = schema.open();
+ try {
+ db.accountGroupMembersAudit().insert(auditInserts);
+ } finally {
+ db.close();
+ }
+ } catch (OrmException e) {
+ logOrmExceptionForAccounts(
+ "Cannot log add accounts to group event performed by user", me,
+ added, e);
+ }
+ }
+
+ @Override
+ public void onDeleteAccountsFromGroup(Account.Id me,
+ Collection<AccountGroupMember> removed) {
+ List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
+ List<AccountGroupMemberAudit> auditUpdates = Lists.newLinkedList();
+ try {
+ ReviewDb db = schema.open();
+ try {
+ for (AccountGroupMember m : removed) {
+ AccountGroupMemberAudit audit = null;
+ for (AccountGroupMemberAudit a : db.accountGroupMembersAudit()
+ .byGroupAccount(m.getAccountGroupId(), m.getAccountId())) {
+ if (a.isActive()) {
+ audit = a;
+ break;
+ }
+ }
+
+ if (audit != null) {
+ audit.removed(me, TimeUtil.nowTs());
+ auditUpdates.add(audit);
+ } else {
+ audit = new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
+ audit.removedLegacy();
+ auditInserts.add(audit);
+ }
+ }
+ db.accountGroupMembersAudit().update(auditUpdates);
+ db.accountGroupMembersAudit().insert(auditInserts);
+ } finally {
+ db.close();
+ }
+ } catch (OrmException e) {
+ logOrmExceptionForAccounts(
+ "Cannot log delete accounts from group event performed by user", me,
+ removed, e);
+ }
+ }
+
+ @Override
+ public void onAddGroupsToGroup(Account.Id me,
+ Collection<AccountGroupById> added) {
+ List<AccountGroupByIdAud> includesAudit = new ArrayList<>();
+ for (AccountGroupById groupInclude : added) {
+ AccountGroupByIdAud audit =
+ new AccountGroupByIdAud(groupInclude, me, TimeUtil.nowTs());
+ includesAudit.add(audit);
+ }
+ try {
+ ReviewDb db = schema.open();
+ try {
+ db.accountGroupByIdAud().insert(includesAudit);
+ } finally {
+ db.close();
+ }
+ } catch (OrmException e) {
+ logOrmExceptionForGroups(
+ "Cannot log add groups to group event performed by user", me, added,
+ e);
+ }
+ }
+
+ @Override
+ public void onDeleteGroupsFromGroup(Account.Id me,
+ Collection<AccountGroupById> removed) {
+ final List<AccountGroupByIdAud> auditUpdates = Lists.newLinkedList();
+ try {
+ ReviewDb db = schema.open();
+ try {
+ for (final AccountGroupById g : removed) {
+ AccountGroupByIdAud audit = null;
+ for (AccountGroupByIdAud a : db.accountGroupByIdAud()
+ .byGroupInclude(g.getGroupId(), g.getIncludeUUID())) {
+ if (a.isActive()) {
+ audit = a;
+ break;
+ }
+ }
+
+ if (audit != null) {
+ audit.removed(me, TimeUtil.nowTs());
+ auditUpdates.add(audit);
+ }
+ }
+ db.accountGroupByIdAud().update(auditUpdates);
+ } finally {
+ db.close();
+ }
+ } catch (OrmException e) {
+ logOrmExceptionForGroups(
+ "Cannot log delete groups from group event performed by user", me,
+ removed, e);
+ }
+ }
+
+ private void logOrmExceptionForAccounts(String header, Account.Id me,
+ Collection<AccountGroupMember> values, OrmException e) {
+ List<String> descriptions = new ArrayList<>();
+ for (AccountGroupMember m : values) {
+ Account.Id accountId = m.getAccountId();
+ String userName = accountCache.get(accountId).getUserName();
+ AccountGroup.Id groupId = m.getAccountGroupId();
+ String groupName = groupCache.get(groupId).getName();
+
+ descriptions.add(MessageFormat.format("account {0}/{1}, group {2}/{3}",
+ accountId, userName, groupId, groupName));
+ }
+ logOrmException(header, me, descriptions, e);
+ }
+
+ private void logOrmExceptionForGroups(String header, Account.Id me,
+ Collection<AccountGroupById> values, OrmException e) {
+ List<String> descriptions = new ArrayList<>();
+ for (AccountGroupById m : values) {
+ AccountGroup.UUID groupUuid = m.getIncludeUUID();
+ String groupName = groupBackend.get(groupUuid).getName();
+ AccountGroup.Id targetGroupId = m.getGroupId();
+ String targetGroupName = groupCache.get(targetGroupId).getName();
+
+ descriptions.add(MessageFormat.format("group {0}/{1}, group {2}/{3}",
+ groupUuid, groupName, targetGroupId, targetGroupName));
+ }
+ logOrmException(header, me, descriptions, e);
+ }
+
+ private void logOrmException(String header, Account.Id me,
+ Iterable<?> values, OrmException e) {
+ StringBuilder message = new StringBuilder(header);
+ message.append(" ");
+ message.append(me);
+ message.append("/");
+ message.append(accountCache.get(me).getUserName());
+ message.append(": ");
+ message.append(Joiner.on("; ").join(values));
+ log.error(message.toString(), e);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
index 8cc1a4a64b..3d99565aed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.group;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -26,14 +27,12 @@ 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.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.group.AddIncludedGroups.Input;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -48,16 +47,17 @@ public class DeleteIncludedGroups implements RestModifyView<GroupResource, Input
private final GroupIncludeCache groupIncludeCache;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
+ private final AuditService auditService;
@Inject
DeleteIncludedGroups(GroupsCollection groupsCollection,
- GroupIncludeCache groupIncludeCache,
- Provider<ReviewDb> db,
- Provider<CurrentUser> self) {
+ GroupIncludeCache groupIncludeCache, Provider<ReviewDb> db,
+ Provider<CurrentUser> self, AuditService auditService) {
this.groupsCollection = groupsCollection;
this.groupIncludeCache = groupIncludeCache;
this.db = db;
this.self = self;
+ this.auditService = auditService;
}
@Override
@@ -76,7 +76,7 @@ public class DeleteIncludedGroups implements RestModifyView<GroupResource, Input
for (final String includedGroup : input.groups) {
GroupDescription.Basic d = groupsCollection.parse(includedGroup);
- if (!control.canRemoveGroup(d.getGroupUUID())) {
+ if (!control.canRemoveGroup()) {
throw new AuthException(String.format("Cannot delete group: %s",
d.getName()));
}
@@ -109,27 +109,9 @@ public class DeleteIncludedGroups implements RestModifyView<GroupResource, Input
return groups;
}
- private void writeAudits(final List<AccountGroupById> toBeRemoved)
- throws OrmException {
+ private void writeAudits(final List<AccountGroupById> toRemoved) {
final Account.Id me = ((IdentifiedUser) self.get()).getAccountId();
- final List<AccountGroupByIdAud> auditUpdates = Lists.newLinkedList();
- for (final AccountGroupById g : toBeRemoved) {
- AccountGroupByIdAud audit = null;
- for (AccountGroupByIdAud a : db.get()
- .accountGroupByIdAud().byGroupInclude(g.getGroupId(),
- g.getIncludeUUID())) {
- if (a.isActive()) {
- audit = a;
- break;
- }
- }
-
- if (audit != null) {
- audit.removed(me, TimeUtil.nowTs());
- auditUpdates.add(audit);
- }
- }
- db.get().accountGroupByIdAud().update(auditUpdates);
+ auditService.dispatchDeleteGroupsFromGroup(me, toRemoved);
}
@Singleton
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
index 654ad884b4..3047994178 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.group;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.audit.AuditService;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
@@ -24,7 +25,6 @@ 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.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -32,7 +32,6 @@ import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.AddMembers.Input;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -47,15 +46,18 @@ public class DeleteMembers implements RestModifyView<GroupResource, Input> {
private final AccountCache accountCache;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
+ private final AuditService auditService;
@Inject
DeleteMembers(AccountsCollection accounts,
AccountCache accountCache, Provider<ReviewDb> db,
- Provider<CurrentUser> self) {
+ Provider<CurrentUser> self,
+ AuditService auditService) {
this.accounts = accounts;
this.accountCache = accountCache;
this.db = db;
this.self = self;
+ this.auditService = auditService;
}
@Override
@@ -75,7 +77,7 @@ public class DeleteMembers implements RestModifyView<GroupResource, Input> {
for (final String nameOrEmail : input.members) {
Account a = accounts.parse(nameOrEmail).getAccount();
- if (!control.canRemoveMember(a.getId())) {
+ if (!control.canRemoveMember()) {
throw new AuthException("Cannot delete member: " + a.getFullName());
}
@@ -94,32 +96,9 @@ public class DeleteMembers implements RestModifyView<GroupResource, Input> {
return Response.none();
}
- private void writeAudits(final List<AccountGroupMember> toBeRemoved)
- throws OrmException {
+ private void writeAudits(final List<AccountGroupMember> toRemove) {
final Account.Id me = ((IdentifiedUser) self.get()).getAccountId();
- final List<AccountGroupMemberAudit> auditUpdates = Lists.newLinkedList();
- final List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
- for (final AccountGroupMember m : toBeRemoved) {
- AccountGroupMemberAudit audit = null;
- for (AccountGroupMemberAudit a : db.get().accountGroupMembersAudit()
- .byGroupAccount(m.getAccountGroupId(), m.getAccountId())) {
- if (a.isActive()) {
- audit = a;
- break;
- }
- }
-
- if (audit != null) {
- audit.removed(me, TimeUtil.nowTs());
- auditUpdates.add(audit);
- } else {
- audit = new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
- audit.removedLegacy();
- auditInserts.add(audit);
- }
- }
- db.get().accountGroupMembersAudit().update(auditUpdates);
- db.get().accountGroupMembersAudit().insert(auditInserts);
+ auditService.dispatchDeleteAccountsFromGroup(me, toRemove);
}
private Map<Account.Id, AccountGroupMember> getMembers(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetMember.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetMember.java
index 2782932e06..9d270ec689 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetMember.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetMember.java
@@ -14,24 +14,25 @@
package com.google.gerrit.server.group;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class GetMember implements RestReadView<MemberResource> {
- private final AccountInfo.Loader.Factory infoFactory;
+ private final AccountLoader.Factory infoFactory;
@Inject
- GetMember(AccountInfo.Loader.Factory infoFactory) {
+ GetMember(AccountLoader.Factory infoFactory) {
this.infoFactory = infoFactory;
}
@Override
public AccountInfo apply(MemberResource rsrc) throws OrmException {
- AccountInfo.Loader loader = infoFactory.create(true);
+ AccountLoader loader = infoFactory.create(true);
AccountInfo info = loader.get(rsrc.getMember().getAccountId());
loader.fill();
return info;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java
index 9f851f4648..96b4234138 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java
@@ -21,10 +21,10 @@ import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.groups.ListGroupsOption;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl;
import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupResource.java
index 89b99a11d4..7975f241e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupResource.java
@@ -25,7 +25,8 @@ public class IncludedGroupResource extends GroupResource {
private final GroupDescription.Basic member;
- IncludedGroupResource(GroupResource group, GroupDescription.Basic member) {
+ public IncludedGroupResource(GroupResource group,
+ GroupDescription.Basic member) {
super(group);
this.member = member;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupsCollection.java
index 72f17b4ad0..8d0831d15b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/IncludedGroupsCollection.java
@@ -73,7 +73,7 @@ public class IncludedGroupsCollection implements
GroupDescription.Basic member =
groupsCollection.parse(TopLevelResource.INSTANCE, id).getGroup();
if (isMember(parent, member)
- && resource.getControl().canSeeGroup(member.getGroupUUID())) {
+ && resource.getControl().canSeeGroup()) {
return new IncludedGroupResource(resource, member);
}
throw new ResourceNotFoundException(id);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 09986021e5..40d0420f1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.group;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -135,7 +135,7 @@ public class ListGroups implements RestReadView<TopLevelResource> {
public Object apply(TopLevelResource resource) throws OrmException {
final Map<String, GroupInfo> output = Maps.newTreeMap();
for (GroupInfo info : get()) {
- output.put(Objects.firstNonNull(
+ output.put(MoreObjects.firstNonNull(
info.name,
"Group " + Url.decode(info.id)), info);
info.name = null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
index c4b2ae2a40..aab9efeafc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -20,13 +20,14 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
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.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gwtorm.server.OrmException;
@@ -43,7 +44,7 @@ import java.util.Map;
public class ListMembers implements RestReadView<GroupResource> {
private final GroupCache groupCache;
private final GroupDetailFactory.Factory groupDetailFactory;
- private final AccountInfo.Loader accountLoader;
+ private final AccountLoader accountLoader;
@Option(name = "--recursive", usage = "to resolve included groups recursively")
private boolean recursive;
@@ -51,7 +52,7 @@ public class ListMembers implements RestReadView<GroupResource> {
@Inject
protected ListMembers(GroupCache groupCache,
GroupDetailFactory.Factory groupDetailFactory,
- AccountInfo.Loader.Factory accountLoaderFactory) {
+ AccountLoader.Factory accountLoaderFactory) {
this.groupCache = groupCache;
this.groupDetailFactory = groupDetailFactory;
this.accountLoader = accountLoaderFactory.create(true);
@@ -72,12 +73,12 @@ public class ListMembers implements RestReadView<GroupResource> {
}
public List<AccountInfo> apply(AccountGroup group)
- throws MethodNotAllowedException, OrmException {
+ throws OrmException {
return apply(group.getGroupUUID());
}
public List<AccountInfo> apply(AccountGroup.UUID groupId)
- throws MethodNotAllowedException, OrmException {
+ throws OrmException {
final Map<Account.Id, AccountInfo> members =
getMembers(groupId, new HashSet<AccountGroup.UUID>());
final List<AccountInfo> memberInfos = Lists.newArrayList(members.values());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
index 97338f1f65..9b5d9abae0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java
@@ -18,7 +18,9 @@ import static com.google.gerrit.server.group.GroupResource.GROUP_KIND;
import static com.google.gerrit.server.group.IncludedGroupResource.INCLUDED_GROUP_KIND;
import static com.google.gerrit.server.group.MemberResource.MEMBER_KIND;
+import com.google.gerrit.audit.GroupMemberAuditListener;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.group.AddIncludedGroups.UpdateIncludedGroup;
import com.google.gerrit.server.group.AddMembers.UpdateMember;
@@ -65,5 +67,9 @@ public class Module extends RestApiModule {
delete(INCLUDED_GROUP_KIND).to(DeleteIncludedGroup.class);
install(new FactoryModuleBuilder().build(CreateGroup.Factory.class));
+
+ DynamicSet.bind(binder(), GroupMemberAuditListener.class).to(
+ DbGroupMemberAuditListener.class);
+
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
index c888f73722..59ca27278c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -22,7 +22,7 @@ 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.server.IdentifiedUser;
-import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.AbstractGroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.project.ProjectControl;
@@ -36,7 +36,7 @@ import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
-public class SystemGroupBackend implements GroupBackend {
+public class SystemGroupBackend extends AbstractGroupBackend {
/** Common UUID assigned to the "Anonymous Users" group. */
public static final AccountGroup.UUID ANONYMOUS_USERS =
new AccountGroup.UUID("global:Anonymous-Users");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index f067e27296..2993739f9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -14,8 +14,13 @@
package com.google.gerrit.server.index;
-import com.google.common.base.Objects;
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+import com.google.common.base.Function;
import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Account;
@@ -24,17 +29,17 @@ import com.google.gerrit.reviewdb.client.ChangeMessage;
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.server.ChangeUtil;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
-import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.OrmException;
import com.google.protobuf.CodedOutputStream;
+import org.eclipse.jgit.revwalk.FooterLine;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
@@ -68,7 +73,11 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().getKey().get();
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getKey().get();
}
};
@@ -79,8 +88,11 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return ChangeStatusPredicate.VALUES.get(
- input.change().getStatus());
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return ChangeStatusPredicate.canonicalize(c.getStatus());
}
};
@@ -91,7 +103,11 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().getProject().get();
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getProject().get();
}
};
@@ -102,7 +118,11 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().getProject().get();
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getProject().get();
}
};
@@ -113,19 +133,11 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().getDest().get();
- }
- };
-
- @Deprecated
- /** Topic, a short annotation on the branch. */
- public static final FieldDef<ChangeData, String> LEGACY_TOPIC =
- new FieldDef.Single<ChangeData, String>(
- ChangeQueryBuilder.FIELD_TOPIC, FieldType.EXACT, false) {
- @Override
- public String get(ChangeData input, FillArgs args)
- throws OrmException {
- return input.change().getTopic();
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getDest().get();
}
};
@@ -136,24 +148,14 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return Objects.firstNonNull(input.change().getTopic(), "");
- }
- };
-
- // Same value as UPDATED, but implementations truncated to minutes.
- @Deprecated
- /** Last update time since January 1, 1970. */
- public static final FieldDef<ChangeData, Timestamp> LEGACY_UPDATED =
- new FieldDef.Single<ChangeData, Timestamp>(
- "updated", FieldType.TIMESTAMP, true) {
- @Override
- public Timestamp get(ChangeData input, FillArgs args)
- throws OrmException {
- return input.change().getLastUpdatedOn();
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return firstNonNull(c.getTopic(), "");
}
};
-
/** Last update time since January 1, 1970. */
public static final FieldDef<ChangeData, Timestamp> UPDATED =
new FieldDef.Single<ChangeData, Timestamp>(
@@ -161,44 +163,11 @@ public class ChangeField {
@Override
public Timestamp get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().getLastUpdatedOn();
- }
- };
-
- @Deprecated
- public static long legacyParseSortKey(String sortKey) {
- if ("z".equals(sortKey)) {
- return Long.MAX_VALUE;
- }
- return Long.parseLong(sortKey.substring(0, 8), 16);
- }
-
- /** Legacy sort key field. */
- @Deprecated
- public static final FieldDef<ChangeData, Long> LEGACY_SORTKEY =
- new FieldDef.Single<ChangeData, Long>(
- "sortkey", FieldType.LONG, true) {
- @Override
- public Long get(ChangeData input, FillArgs args)
- throws OrmException {
- return legacyParseSortKey(input.change().getSortKey());
- }
- };
-
- /**
- * Sort key field.
- * <p>
- * Redundant with {@link #UPDATED} and {@link #LEGACY_ID}, but secondary index
- * implementations may not be able to search over tuples of values.
- */
- @Deprecated
- public static final FieldDef<ChangeData, Long> SORTKEY =
- new FieldDef.Single<ChangeData, Long>(
- "sortkey2", FieldType.LONG, true) {
- @Override
- public Long get(ChangeData input, FillArgs args)
- throws OrmException {
- return ChangeUtil.parseSortKey(input.change().getSortKey());
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getLastUpdatedOn();
}
};
@@ -210,14 +179,19 @@ public class ChangeField {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
- return input.currentFilePaths();
+ return firstNonNull(input.currentFilePaths(),
+ ImmutableList.<String> of());
}
};
public static Set<String> getFileParts(ChangeData cd) throws OrmException {
+ List<String> paths = cd.currentFilePaths();
+ if (paths == null) {
+ return ImmutableSet.of();
+ }
Splitter s = Splitter.on('/').omitEmptyStrings();
Set<String> r = Sets.newHashSet();
- for (String path : cd.currentFilePaths()) {
+ for (String path : paths) {
for (String part : s.split(path)) {
r.add(part);
}
@@ -225,6 +199,25 @@ public class ChangeField {
return r;
}
+ /** Hashtags tied to a change */
+ public static final FieldDef<ChangeData, Iterable<String>> HASHTAG =
+ new FieldDef.Repeatable<ChangeData, String>(
+ "hashtag", FieldType.EXACT, false) {
+ @Override
+ public Iterable<String> get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return ImmutableSet.copyOf(Iterables.transform(input.notes().load()
+ .getHashtags(), new Function<String, String>() {
+
+ @Override
+ public String apply(String input) {
+ return input.toLowerCase();
+ }
+
+ }));
+ }
+ };
+
/** Components of each file path modified in the current patch set. */
public static final FieldDef<ChangeData, Iterable<String>> FILE_PART =
new FieldDef.Repeatable<ChangeData, String>(
@@ -243,7 +236,11 @@ public class ChangeField {
@Override
public Integer get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().getOwner().get();
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getOwner().get();
}
};
@@ -254,9 +251,12 @@ public class ChangeField {
@Override
public Iterable<Integer> get(ChangeData input, FillArgs args)
throws OrmException {
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
Set<Integer> r = Sets.newHashSet();
- if (!args.allowsDrafts &&
- input.change().getStatus() == Change.Status.DRAFT) {
+ if (!args.allowsDrafts && c.getStatus() == Change.Status.DRAFT) {
return r;
}
for (PatchSetApproval a : input.approvals().values()) {
@@ -291,9 +291,13 @@ public class ChangeField {
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
try {
- return Sets.newHashSet(args.trackingFooters.extract(
- input.commitFooters()).values());
- } catch (NoSuchChangeException | IOException e) {
+ List<FooterLine> footers = input.commitFooters();
+ if (footers == null) {
+ return null;
+ }
+ return Sets.newHashSet(
+ args.trackingFooters.extract(footers).values());
+ } catch (IOException e) {
throw new OrmException(e);
}
}
@@ -347,7 +351,11 @@ public class ChangeField {
@Override
public byte[] get(ChangeData input, FieldDef.FillArgs args)
throws OrmException {
- return CODEC.encodeToByteArray(input.change());
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return CODEC.encodeToByteArray(c);
}
}
@@ -394,7 +402,7 @@ public class ChangeField {
public String get(ChangeData input, FillArgs args) throws OrmException {
try {
return input.commitMessage();
- } catch (NoSuchChangeException | IOException e) {
+ } catch (IOException e) {
throw new OrmException(e);
}
}
@@ -408,7 +416,7 @@ public class ChangeField {
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
Set<String> r = Sets.newHashSet();
- for (PatchLineComment c : input.comments()) {
+ for (PatchLineComment c : input.publishedComments()) {
r.add(c.getMessage());
}
for (ChangeMessage m : input.messages()) {
@@ -419,13 +427,33 @@ public class ChangeField {
};
/** Whether the change is mergeable. */
- public static final FieldDef<ChangeData, String> MERGEABLE =
+ @Deprecated
+ public static final FieldDef<ChangeData, String> LEGACY_MERGEABLE =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_MERGEABLE, FieldType.EXACT, false) {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
- return input.change().isMergeable() ? "1" : null;
+ Boolean m = input.isMergeable();
+ if (m == null) {
+ return null;
+ }
+ return m ? "1" : null;
+ }
+ };
+
+ /** Whether the change is mergeable. */
+ public static final FieldDef<ChangeData, String> MERGEABLE =
+ new FieldDef.Single<ChangeData, String>(
+ "mergeable2", FieldType.EXACT, true) {
+ @Override
+ public String get(ChangeData input, FillArgs args)
+ throws OrmException {
+ Boolean m = input.isMergeable();
+ if (m == null) {
+ return null;
+ }
+ return m ? "1" : "0";
}
};
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
index 167367898a..3b04f05718 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.index;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -39,23 +40,12 @@ public interface ChangeIndex {
public void close();
/**
- * Insert a change document into the index.
- * <p>
- * Results may not be immediately visible to searchers, but should be visible
- * within a reasonable amount of time.
- *
- * @param cd change document
- *
- * @throws IOException if the change could not be inserted.
- */
- public void insert(ChangeData cd) throws IOException;
-
- /**
* Update a change document in the index.
* <p>
* Semantically equivalent to deleting the document and reinserting it with
- * new field values. Results may not be immediately visible to searchers, but
- * should be visible within a reasonable amount of time.
+ * new field values. A document that does not already exist is created. Results
+ * may not be immediately visible to searchers, but should be visible within a
+ * reasonable amount of time.
*
* @param cd change document
*
@@ -64,22 +54,13 @@ public interface ChangeIndex {
public void replace(ChangeData cd) throws IOException;
/**
- * Delete a change document from the index.
- *
- * @param cd change document
- *
- * @throws IOException
- */
- public void delete(ChangeData cd) throws IOException;
-
- /**
* Delete a change document from the index by id.
*
- * @param id change document id
+ * @param id change id
*
* @throws IOException
*/
- public void delete(int id) throws IOException;
+ public void delete(Change.Id id) throws IOException;
/**
* Delete all change documents from the index.
@@ -103,10 +84,7 @@ public interface ChangeIndex {
* @param start offset in results list at which to start returning results.
* @param limit maximum number of results to return.
* @return a source of documents matching the predicate. Documents must be
- * returned in descending sort key order, unless a {@code sortkey_after}
- * predicate (with a cut point not at {@link Long#MAX_VALUE}) is provided,
- * in which case the source should return documents in ascending sort key
- * order starting from the sort key cut point.
+ * returned in descending updated timestamp order.
*
* @throws QueryParseException if the predicate could not be converted to an
* indexed data source.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
index 72cb88c90a..5cb5c65d12 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
@@ -18,6 +18,7 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.Atomics;
import com.google.common.util.concurrent.CheckedFuture;
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.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -38,8 +39,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -55,8 +58,9 @@ public class ChangeIndexer {
LoggerFactory.getLogger(ChangeIndexer.class);
public interface Factory {
- ChangeIndexer create(ChangeIndex index);
- ChangeIndexer create(IndexCollection indexes);
+ ChangeIndexer create(ListeningExecutorService executor, ChangeIndex index);
+ ChangeIndexer create(ListeningExecutorService executor,
+ IndexCollection indexes);
}
private static final Function<Exception, IOException> MAPPER =
@@ -82,10 +86,10 @@ public class ChangeIndexer {
private final ListeningExecutorService executor;
@AssistedInject
- ChangeIndexer(@IndexExecutor ListeningExecutorService executor,
- SchemaFactory<ReviewDb> schemaFactory,
+ ChangeIndexer(SchemaFactory<ReviewDb> schemaFactory,
ChangeData.Factory changeDataFactory,
ThreadLocalRequestContext context,
+ @Assisted ListeningExecutorService executor,
@Assisted ChangeIndex index) {
this.executor = executor;
this.schemaFactory = schemaFactory;
@@ -96,10 +100,10 @@ public class ChangeIndexer {
}
@AssistedInject
- ChangeIndexer(@IndexExecutor ListeningExecutorService executor,
- SchemaFactory<ReviewDb> schemaFactory,
+ ChangeIndexer(SchemaFactory<ReviewDb> schemaFactory,
ChangeData.Factory changeDataFactory,
ThreadLocalRequestContext context,
+ @Assisted ListeningExecutorService executor,
@Assisted IndexCollection indexes) {
this.executor = executor;
this.schemaFactory = schemaFactory;
@@ -117,11 +121,29 @@ public class ChangeIndexer {
*/
public CheckedFuture<?, IOException> indexAsync(Change.Id id) {
return executor != null
- ? submit(new Task(id, false))
+ ? submit(new IndexTask(id))
: Futures.<Object, IOException> immediateCheckedFuture(null);
}
/**
+ * Start indexing multiple changes in parallel.
+ *
+ * @param ids changes to index.
+ * @return future for completing indexing of all changes.
+ */
+ public CheckedFuture<?, IOException> indexAsync(Collection<Change.Id> ids) {
+ List<ListenableFuture<?>> futures = new ArrayList<>(ids.size());
+ for (Change.Id id : ids) {
+ futures.add(indexAsync(id));
+ }
+ // allAsList propagates the first seen exception, wrapped in
+ // ExecutionException, so we can reuse the same mapper as for a single
+ // future. Assume the actual contents of the exception are not useful to
+ // callers. All exceptions are already logged by IndexTask.
+ return Futures.makeChecked(Futures.allAsList(futures), MAPPER);
+ }
+
+ /**
* Synchronously index a change.
*
* @param cd change to index.
@@ -150,40 +172,17 @@ public class ChangeIndexer {
*/
public CheckedFuture<?, IOException> deleteAsync(Change.Id id) {
return executor != null
- ? submit(new Task(id, true))
+ ? submit(new DeleteTask(id))
: Futures.<Object, IOException> immediateCheckedFuture(null);
}
/**
* Synchronously delete a change.
*
- * @param cd change to delete.
- */
- public void delete(ChangeData cd) throws IOException {
- for (ChangeIndex i : getWriteIndexes()) {
- i.delete(cd);
- }
- }
-
- /**
- * Synchronously delete a change by id.
- *
- * @param id change to delete.
+ * @param id change ID to delete.
*/
- public void delete(int id) throws IOException {
- for (ChangeIndex i : getWriteIndexes()) {
- i.delete(id);
- }
- }
-
- /**
- * Synchronously delete a change.
- *
- * @param change change to delete.
- * @param db review database.
- */
- public void delete(ReviewDb db, Change change) throws IOException {
- delete(changeDataFactory.create(db, change));
+ public void delete(Change.Id id) throws IOException {
+ new DeleteTask(id).call();
}
private Collection<ChangeIndex> getWriteIndexes() {
@@ -196,13 +195,11 @@ public class ChangeIndexer {
return Futures.makeChecked(executor.submit(task), MAPPER);
}
- private class Task implements Callable<Void> {
+ private class IndexTask implements Callable<Void> {
private final Change.Id id;
- private final boolean delete;
- private Task(Change.Id id, boolean delete) {
+ private IndexTask(Change.Id id) {
this.id = id;
- this.delete = delete;
}
@Override
@@ -237,14 +234,8 @@ public class ChangeIndexer {
try {
ChangeData cd = changeDataFactory.create(
newCtx.getReviewDbProvider().get(), id);
- if (delete) {
- for (ChangeIndex i : getWriteIndexes()) {
- i.delete(cd);
- }
- } else {
- for (ChangeIndex i : getWriteIndexes()) {
- i.replace(cd);
- }
+ for (ChangeIndex i : getWriteIndexes()) {
+ i.replace(cd);
}
return null;
} finally {
@@ -265,4 +256,23 @@ public class ChangeIndexer {
return "index-change-" + id.get();
}
}
+
+ private class DeleteTask implements Callable<Void> {
+ private final Change.Id id;
+
+ private DeleteTask(Change.Id id) {
+ this.id = id;
+ }
+
+ @Override
+ public Void call() throws IOException {
+ // Don't bother setting a RequestContext to provide the DB.
+ // Implementations should not need to access the DB in order to delete a
+ // change ID.
+ for (ChangeIndex i : getWriteIndexes()) {
+ i.delete(id);
+ }
+ return null;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index 8bb8f0bb31..05bf9bde6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.index;
import static com.google.common.base.Preconditions.checkArgument;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
@@ -24,200 +25,13 @@ import com.google.gerrit.server.query.change.ChangeData;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/** Secondary index schemas for changes. */
public class ChangeSchemas {
@SuppressWarnings("deprecation")
- static final Schema<ChangeData> V1 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.LEGACY_UPDATED,
- ChangeField.LEGACY_SORTKEY,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT);
-
- @SuppressWarnings("deprecation")
- static final Schema<ChangeData> V2 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.LEGACY_UPDATED,
- ChangeField.LEGACY_SORTKEY,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL);
-
- @SuppressWarnings("deprecation")
- static final Schema<ChangeData> V3 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.LEGACY_UPDATED,
- ChangeField.SORTKEY,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL);
-
- // For upgrade to Lucene 4.4.0 index format only.
- static final Schema<ChangeData> V4 = release(V3.getFields().values());
-
- @SuppressWarnings("deprecation")
- static final Schema<ChangeData> V5 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.LEGACY_UPDATED,
- ChangeField.SORTKEY,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL,
- ChangeField.MERGEABLE);
-
- // For upgrade to Lucene 4.6.0 index format only.
- static final Schema<ChangeData> V6 = release(V5.getFields().values());
-
- @SuppressWarnings("deprecation")
- static final Schema<ChangeData> V7 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.LEGACY_UPDATED,
- ChangeField.SORTKEY,
- ChangeField.FILE_PART,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL,
- ChangeField.MERGEABLE);
-
- @SuppressWarnings("deprecation")
- static final Schema<ChangeData> V8 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.UPDATED,
- ChangeField.FILE_PART,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL,
- ChangeField.MERGEABLE);
-
- @SuppressWarnings("deprecation")
- static final Schema<ChangeData> V9 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.PROJECTS,
- ChangeField.REF,
- ChangeField.LEGACY_TOPIC,
- ChangeField.UPDATED,
- ChangeField.FILE_PART,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL,
- ChangeField.MERGEABLE);
-
- static final Schema<ChangeData> V10 = release(
- ChangeField.LEGACY_ID,
- ChangeField.ID,
- ChangeField.STATUS,
- ChangeField.PROJECT,
- ChangeField.PROJECTS,
- ChangeField.REF,
- ChangeField.TOPIC,
- ChangeField.UPDATED,
- ChangeField.FILE_PART,
- ChangeField.PATH,
- ChangeField.OWNER,
- ChangeField.REVIEWER,
- ChangeField.COMMIT,
- ChangeField.TR,
- ChangeField.LABEL,
- ChangeField.REVIEWED,
- ChangeField.COMMIT_MESSAGE,
- ChangeField.COMMENT,
- ChangeField.CHANGE,
- ChangeField.APPROVAL,
- ChangeField.MERGEABLE);
-
- static final Schema<ChangeData> V11 = release(
+ static final Schema<ChangeData> V11 = schema(
ChangeField.LEGACY_ID,
ChangeField.ID,
ChangeField.STATUS,
@@ -238,26 +52,76 @@ public class ChangeSchemas {
ChangeField.COMMENT,
ChangeField.CHANGE,
ChangeField.APPROVAL,
- ChangeField.MERGEABLE,
+ ChangeField.LEGACY_MERGEABLE,
ChangeField.ADDED,
ChangeField.DELETED,
ChangeField.DELTA);
+ // For upgrade to Lucene 4.10.0 index format only.
+ static final Schema<ChangeData> V12 = schema(V11.getFields().values());
-
- private static Schema<ChangeData> release(Collection<FieldDef<ChangeData, ?>> fields) {
- return new Schema<>(true, fields);
- }
-
- @SafeVarargs
- private static Schema<ChangeData> release(FieldDef<ChangeData, ?>... fields) {
- return release(Arrays.asList(fields));
+ @SuppressWarnings("deprecation")
+ static final Schema<ChangeData> V13 = schema(
+ ChangeField.LEGACY_ID,
+ ChangeField.ID,
+ ChangeField.STATUS,
+ ChangeField.PROJECT,
+ ChangeField.PROJECTS,
+ ChangeField.REF,
+ ChangeField.TOPIC,
+ ChangeField.UPDATED,
+ ChangeField.FILE_PART,
+ ChangeField.PATH,
+ ChangeField.OWNER,
+ ChangeField.REVIEWER,
+ ChangeField.COMMIT,
+ ChangeField.TR,
+ ChangeField.LABEL,
+ ChangeField.REVIEWED,
+ ChangeField.COMMIT_MESSAGE,
+ ChangeField.COMMENT,
+ ChangeField.CHANGE,
+ ChangeField.APPROVAL,
+ ChangeField.LEGACY_MERGEABLE,
+ ChangeField.ADDED,
+ ChangeField.DELETED,
+ ChangeField.DELTA,
+ ChangeField.HASHTAG);
+
+ static final Schema<ChangeData> V14 = schema(
+ ChangeField.LEGACY_ID,
+ ChangeField.ID,
+ ChangeField.STATUS,
+ ChangeField.PROJECT,
+ ChangeField.PROJECTS,
+ ChangeField.REF,
+ ChangeField.TOPIC,
+ ChangeField.UPDATED,
+ ChangeField.FILE_PART,
+ ChangeField.PATH,
+ ChangeField.OWNER,
+ ChangeField.REVIEWER,
+ ChangeField.COMMIT,
+ ChangeField.TR,
+ ChangeField.LABEL,
+ ChangeField.REVIEWED,
+ ChangeField.COMMIT_MESSAGE,
+ ChangeField.COMMENT,
+ ChangeField.CHANGE,
+ ChangeField.APPROVAL,
+ ChangeField.MERGEABLE,
+ ChangeField.ADDED,
+ ChangeField.DELETED,
+ ChangeField.DELTA,
+ ChangeField.HASHTAG);
+
+ private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
+ return new Schema<>(ImmutableList.copyOf(fields));
}
@SafeVarargs
- @SuppressWarnings("unused")
- private static Schema<ChangeData> developer(FieldDef<ChangeData, ?>... fields) {
- return new Schema<>(false, Arrays.asList(fields));
+ private static Schema<ChangeData> schema(FieldDef<ChangeData, ?>... fields) {
+ return schema(ImmutableList.copyOf(fields));
}
public static final ImmutableMap<Integer, Schema<ChangeData>> ALL;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndex.java
index ff554ff0ef..09226bd9ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndex.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.index;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -22,7 +23,6 @@ import com.google.gerrit.server.query.change.ChangeDataSource;
import java.io.IOException;
public class DummyIndex implements ChangeIndex {
-
@Override
public Schema<ChangeData> getSchema() {
throw new UnsupportedOperationException();
@@ -33,15 +33,11 @@ public class DummyIndex implements ChangeIndex {
}
@Override
- public void insert(ChangeData cd) throws IOException {
- }
-
- @Override
public void replace(ChangeData cd) throws IOException {
}
@Override
- public void delete(ChangeData cd) throws IOException {
+ public void delete(Change.Id id) throws IOException {
}
@Override
@@ -58,7 +54,7 @@ public class DummyIndex implements ChangeIndex {
public void markReady(boolean ready) throws IOException {
}
- @Override
- public void delete(int id) throws IOException {
+ public int getMaxLimit() {
+ return Integer.MAX_VALUE;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
index dd7d2d89ce..d14a2f7baa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
@@ -21,6 +21,7 @@ public class DummyIndexModule extends AbstractModule {
@Override
protected void configure() {
install(new IndexModule(1));
+ bind(IndexConfig.class).toInstance(IndexConfig.createDefault());
bind(ChangeIndex.class).toInstance(new DummyIndex());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
index 872179d9d1..557faebefa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
@@ -32,7 +32,7 @@ import org.eclipse.jgit.lib.Config;
*/
public abstract class FieldDef<I, T> {
/** Definition of a single (non-repeatable) field. */
- public static abstract class Single<I, T> extends FieldDef<I, T> {
+ public abstract static class Single<I, T> extends FieldDef<I, T> {
Single(String name, FieldType<T> type, boolean stored) {
super(name, type, stored);
}
@@ -44,7 +44,7 @@ public abstract class FieldDef<I, T> {
}
/** Definition of a repeatable field. */
- public static abstract class Repeatable<I, T>
+ public abstract static class Repeatable<I, T>
extends FieldDef<I, Iterable<T>> {
Repeatable(String name, FieldType<T> type, boolean stored) {
super(name, type, stored);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
index 4c40769cd9..dce8a2059e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
@@ -21,35 +21,35 @@ import java.sql.Timestamp;
public class FieldType<T> {
/** A single integer-valued field. */
public static final FieldType<Integer> INTEGER =
- new FieldType<Integer>("INTEGER");
+ new FieldType<>("INTEGER");
/** A single-integer-valued field matched using range queries. */
public static final FieldType<Integer> INTEGER_RANGE =
- new FieldType<Integer>("INTEGER_RANGE");
+ new FieldType<>("INTEGER_RANGE");
/** A single integer-valued field. */
public static final FieldType<Long> LONG =
- new FieldType<Long>("LONG");
+ new FieldType<>("LONG");
/** A single date/time-valued field. */
public static final FieldType<Timestamp> TIMESTAMP =
- new FieldType<Timestamp>("TIMESTAMP");
+ new FieldType<>("TIMESTAMP");
/** A string field searched using exact-match semantics. */
public static final FieldType<String> EXACT =
- new FieldType<String>("EXACT");
+ new FieldType<>("EXACT");
/** A string field searched using prefix. */
public static final FieldType<String> PREFIX =
- new FieldType<String>("PREFIX");
+ new FieldType<>("PREFIX");
/** A string field searched using fuzzy-match semantics. */
public static final FieldType<String> FULL_TEXT =
- new FieldType<String>("FULL_TEXT");
+ new FieldType<>("FULL_TEXT");
/** A field that is only stored as raw bytes and cannot be queried. */
public static final FieldType<byte[]> STORED_ONLY =
- new FieldType<byte[]>("STORED_ONLY");
+ new FieldType<>("STORED_ONLY");
private final String name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
new file mode 100644
index 0000000000..1857e557ef
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2015 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.index;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Implementation-specific configuration for secondary indexes.
+ * <p>
+ * Contains configuration that is tied to a specific index implementation but is
+ * otherwise global, i.e. not tied to a specific {@link ChangeIndex} and schema
+ * version.
+ */
+@AutoValue
+public abstract class IndexConfig {
+ public static IndexConfig createDefault() {
+ return create(Integer.MAX_VALUE);
+ }
+
+ public static IndexConfig create(int maxLimit) {
+ checkArgument(maxLimit > 0, "maxLimit must be positive: %s", maxLimit);
+ return new AutoValue_IndexConfig(maxLimit);
+ }
+
+ public abstract int maxLimit();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java
index 0a96d1d16a..eb97fdc12a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.index;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.server.git.QueueProvider.QueueType;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
@@ -28,4 +29,5 @@ import java.lang.annotation.Retention;
@Retention(RUNTIME)
@BindingAnnotation
public @interface IndexExecutor {
+ QueueType value();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 67d0fef04f..0cfc6599e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.index;
+import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
+import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
+
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.gerrit.lifecycle.LifecycleModule;
@@ -21,7 +24,6 @@ import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.query.change.BasicChangeRewrites;
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
-import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
@@ -48,16 +50,20 @@ public class IndexModule extends LifecycleModule {
}
private final int threads;
- private final ListeningExecutorService indexExecutor;
+ private final ListeningExecutorService interactiveExecutor;
+ private final ListeningExecutorService batchExecutor;
public IndexModule(int threads) {
this.threads = threads;
- this.indexExecutor = null;
+ this.interactiveExecutor = null;
+ this.batchExecutor = null;
}
- public IndexModule(ListeningExecutorService indexExecutor) {
+ public IndexModule(ListeningExecutorService interactiveExecutor,
+ ListeningExecutorService batchExecutor) {
this.threads = -1;
- this.indexExecutor = indexExecutor;
+ this.interactiveExecutor = interactiveExecutor;
+ this.batchExecutor = batchExecutor;
}
@Override
@@ -67,49 +73,60 @@ public class IndexModule extends LifecycleModule {
bind(IndexCollection.class);
listener().to(IndexCollection.class);
factory(ChangeIndexer.Factory.class);
-
- if (indexExecutor != null) {
- bind(ListeningExecutorService.class)
- .annotatedWith(IndexExecutor.class)
- .toInstance(indexExecutor);
- } else {
- install(new IndexExecutorModule(threads));
- }
}
@Provides
+ @Singleton
ChangeIndexer getChangeIndexer(
+ @IndexExecutor(INTERACTIVE) ListeningExecutorService executor,
ChangeIndexer.Factory factory,
IndexCollection indexes) {
- return factory.create(indexes);
+ // Bind default indexer to interactive executor; callers who need a
+ // different executor can use the factory directly.
+ return factory.create(executor, indexes);
}
- private static class IndexExecutorModule extends AbstractModule {
- private final int threads;
-
- private IndexExecutorModule(int threads) {
- this.threads = threads;
+ @Provides
+ @Singleton
+ @IndexExecutor(INTERACTIVE)
+ ListeningExecutorService getInteractiveIndexExecutor(
+ @GerritServerConfig Config config,
+ WorkQueue workQueue) {
+ if (interactiveExecutor != null) {
+ return interactiveExecutor;
}
-
- @Override
- public void configure() {
+ int threads = this.threads;
+ if (threads <= 0) {
+ threads = config.getInt("index", null, "threads", 0);
}
+ if (threads <= 0) {
+ threads =
+ config.getInt("changeMerge", null, "interactiveThreadPoolSize", 0);
+ }
+ if (threads <= 0) {
+ return MoreExecutors.newDirectExecutorService();
+ }
+ return MoreExecutors.listeningDecorator(
+ workQueue.createQueue(threads, "Index-Interactive"));
+ }
- @Provides
- @Singleton
- @IndexExecutor
- ListeningExecutorService getIndexExecutor(
- @GerritServerConfig Config config,
- WorkQueue workQueue) {
- int threads = this.threads;
- if (threads <= 0) {
- threads = config.getInt("index", null, "threads", 0);
- }
- if (threads <= 0) {
- return MoreExecutors.sameThreadExecutor();
- }
- return MoreExecutors.listeningDecorator(
- workQueue.createQueue(threads, "index"));
+ @Provides
+ @Singleton
+ @IndexExecutor(BATCH)
+ ListeningExecutorService getBatchIndexExecutor(
+ @GerritServerConfig Config config,
+ WorkQueue workQueue) {
+ if (batchExecutor != null) {
+ return batchExecutor;
+ }
+ int threads = config.getInt("index", null, "batchThreads", 0);
+ if (threads <= 0) {
+ threads = config.getInt("changeMerge", null, "threadPoolSize", 0);
+ }
+ if (threads <= 0) {
+ threads = Runtime.getRuntime().availableProcessors();
}
+ return MoreExecutors.listeningDecorator(
+ workQueue.createQueue(threads, "Index-Batch"));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
index e451f60a83..7fbddfbb08 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
@@ -14,9 +14,8 @@
package com.google.gerrit.server.index;
-import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
+import static com.google.common.base.Preconditions.checkArgument;
-import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Change;
@@ -29,9 +28,9 @@ import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.AndSource;
import com.google.gerrit.server.query.change.BasicChangeRewrites;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gerrit.server.query.change.LimitPredicate;
import com.google.gerrit.server.query.change.OrSource;
import com.google.inject.Inject;
@@ -129,16 +128,14 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
}
@Override
- public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
- throws QueryParseException {
+ public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start,
+ int limit) throws QueryParseException {
+ checkArgument(limit > 0, "limit must be positive: %s", limit);
ChangeIndex index = indexes.getSearchIndex();
in = basicRewrites.rewrite(in);
- int limit = Objects.firstNonNull(
- ChangeQueryBuilder.getLimit(in), DEFAULT_MAX_QUERY_LIMIT);
// Increase the limit rather than skipping, since we don't know how many
// skipped results would have been filtered out by the enclosing AndSource.
limit += start;
- limit = Math.max(limit, 1);
Predicate<ChangeData> out = rewriteImpl(in, index, limit);
if (in == out || out instanceof IndexPredicate) {
@@ -168,6 +165,9 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
ChangeIndex index, int limit) throws QueryParseException {
if (isIndexPredicate(in, index)) {
return in;
+ } else if (in instanceof LimitPredicate) {
+ // Replace any limits with the limit provided by the caller.
+ return new LimitPredicate(limit);
} else if (!isRewritePossible(in)) {
return null; // magic to indicate "in" cannot be rewritten
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
index 09d66e5445..34d37d873c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -14,18 +14,15 @@
package com.google.gerrit.server.index;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gerrit.server.query.change.Paginated;
-import com.google.gerrit.server.query.change.SortKeyPredicate;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -44,40 +41,6 @@ import java.util.List;
public class IndexedChangeQuery extends Predicate<ChangeData>
implements ChangeDataSource, Paginated {
- /**
- * Replace all {@link SortKeyPredicate}s in a tree.
- * <p>
- * Strictly speaking this should replace only the {@link SortKeyPredicate} at
- * the top-level AND node, but this implementation is simpler, and the
- * behavior of having multiple sortkey operators is undefined anyway.
- *
- * @param p predicate to replace in.
- * @param newValue new cut value to replace all sortkey operators with.
- * @return a copy of {@code p} with all sortkey predicates replaced; or p
- * itself.
- */
- @VisibleForTesting
- static Predicate<ChangeData> replaceSortKeyPredicates(
- Predicate<ChangeData> p, String newValue) {
- if (p instanceof SortKeyPredicate) {
- return ((SortKeyPredicate) p).copy(newValue);
- } else if (p.getChildCount() > 0) {
- List<Predicate<ChangeData>> newChildren =
- Lists.newArrayListWithCapacity(p.getChildCount());
- boolean replaced = false;
- for (Predicate<ChangeData> c : p.getChildren()) {
- Predicate<ChangeData> nc = replaceSortKeyPredicates(c, newValue);
- newChildren.add(nc);
- if (nc != c) {
- replaced = true;
- }
- }
- return replaced ? p.copy(newChildren) : p;
- } else {
- return p;
- }
- }
-
private final ChangeIndex index;
private final int limit;
@@ -163,27 +126,13 @@ public class IndexedChangeQuery extends Predicate<ChangeData>
}
@Override
- public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
- pred = replaceSortKeyPredicates(pred, last.change().getSortKey());
- try {
- source = index.getSource(pred, 0, limit);
- } catch (QueryParseException e) {
- // Don't need to show this exception to the user; the only thing that
- // changed about pred was its SortKeyPredicates, and any other QPEs
- // that might happen should have already thrown from the constructor.
- throw new OrmException(e);
- }
- return read();
- }
-
- @Override
public ResultSet<ChangeData> restart(int start) throws OrmException {
try {
source = index.getSource(pred, start, limit);
} catch (QueryParseException e) {
// Don't need to show this exception to the user; the only thing that
- // changed about pred was its SortKeyPredicates, and any other QPEs
- // that might happen should have already thrown from the constructor.
+ // changed about pred was its start, and any other QPEs that might happen
+ // should have already thrown from the constructor.
throw new OrmException(e);
}
return read();
@@ -225,7 +174,7 @@ public class IndexedChangeQuery extends Predicate<ChangeData>
@Override
public String toString() {
- return Objects.toStringHelper("index")
+ return MoreObjects.toStringHelper("index")
.add("p", pred)
.add("limit", limit)
.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
new file mode 100644
index 0000000000..982f8973c0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2014 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.index;
+
+import static com.google.gerrit.server.query.change.ChangeData.asChanges;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AsyncFunction;
+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.extensions.events.GitReferenceUpdatedListener;
+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.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.QueueProvider.QueueType;
+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.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+public class ReindexAfterUpdate implements GitReferenceUpdatedListener {
+ private static final Logger log = LoggerFactory
+ .getLogger(ReindexAfterUpdate.class);
+
+ private final OneOffRequestContext requestContext;
+ private final Provider<InternalChangeQuery> queryProvider;
+ private final ChangeIndexer.Factory indexerFactory;
+ private final IndexCollection indexes;
+ private final ListeningExecutorService executor;
+
+ @Inject
+ ReindexAfterUpdate(
+ OneOffRequestContext requestContext,
+ Provider<InternalChangeQuery> queryProvider,
+ ChangeIndexer.Factory indexerFactory,
+ IndexCollection indexes,
+ @IndexExecutor(QueueType.BATCH) ListeningExecutorService executor) {
+ this.requestContext = requestContext;
+ this.queryProvider = queryProvider;
+ this.indexerFactory = indexerFactory;
+ this.indexes = indexes;
+ this.executor = executor;
+ }
+
+ @Override
+ public void onGitReferenceUpdated(final Event event) {
+ Futures.transform(
+ executor.submit(new GetChanges(event)),
+ new AsyncFunction<List<Change>, List<Void>>() {
+ @Override
+ public ListenableFuture<List<Void>> apply(List<Change> changes) {
+ List<ListenableFuture<Void>> result =
+ Lists.newArrayListWithCapacity(changes.size());
+ for (Change c : changes) {
+ result.add(executor.submit(new Index(event, c.getId())));
+ }
+ return Futures.allAsList(result);
+ }
+ });
+ }
+
+ private abstract class Task<V> implements Callable<V> {
+ protected Event event;
+
+ protected Task(Event event) {
+ this.event = event;
+ }
+
+ @Override
+ public final V call() throws Exception {
+ try (ManualRequestContext ctx = requestContext.open()) {
+ return impl(ctx);
+ } catch (Exception e) {
+ log.error("Failed to reindex changes after " + event, e);
+ throw e;
+ }
+ }
+
+ protected abstract V impl(RequestContext ctx) throws Exception;
+ }
+
+ private class GetChanges extends Task<List<Change>> {
+ private GetChanges(Event event) {
+ super(event);
+ }
+
+ @Override
+ protected List<Change> impl(RequestContext ctx) throws OrmException {
+ String ref = event.getRefName();
+ Project.NameKey project = new Project.NameKey(event.getProjectName());
+ if (ref.equals(RefNames.REFS_CONFIG)) {
+ return asChanges(queryProvider.get().byProjectOpen(project));
+ } else {
+ return asChanges(queryProvider.get().byBranchOpen(
+ new Branch.NameKey(project, ref)));
+ }
+ }
+ }
+
+ 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 OrmException, IOException {
+ // Reload change, as some time may have passed since GetChanges.
+ ReviewDb db = ctx.getReviewDbProvider().get();
+ Change c = db.changes().get(id);
+ indexerFactory.create(executor, indexes).index(db, c);
+ return null;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
index 0de13791d1..c0eb276d75 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.index;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
@@ -50,19 +50,16 @@ public class Schema<T> {
}
}
- private final boolean release;
private final ImmutableMap<String, FieldDef<T, ?>> fields;
private int version;
- protected Schema(boolean release, Iterable<FieldDef<T, ?>> fields) {
- this(0, release, fields);
+ protected Schema(Iterable<FieldDef<T, ?>> fields) {
+ this(0, fields);
}
@VisibleForTesting
- public Schema(int version, boolean release,
- Iterable<FieldDef<T, ?>> fields) {
+ public Schema(int version, Iterable<FieldDef<T, ?>> fields) {
this.version = version;
- this.release = release;
ImmutableMap.Builder<String, FieldDef<T, ?>> b = ImmutableMap.builder();
for (FieldDef<T, ?> f : fields) {
b.put(f.getName(), f);
@@ -70,10 +67,6 @@ public class Schema<T> {
this.fields = b.build();
}
- public final boolean isRelease() {
- return release;
- }
-
public final int getVersion() {
return version;
}
@@ -123,7 +116,7 @@ public class Schema<T> {
@Override
public String toString() {
- return Objects.toStringHelper(this)
+ return MoreObjects.toStringHelper(this)
.addValue(fields.keySet())
.toString();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
index 4779674ec0..c0522d5ea2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.index;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import static org.eclipse.jgit.lib.RefDatabase.ALL;
import com.google.common.base.Stopwatch;
@@ -27,12 +29,11 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
-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.server.ReviewDb;
-import com.google.gerrit.server.change.MergeabilityChecker;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.ChangeCache;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MultiProgressMonitor;
@@ -65,6 +66,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -75,9 +77,9 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-public class ChangeBatchIndexer {
+public class SiteIndexer {
private static final Logger log =
- LoggerFactory.getLogger(ChangeBatchIndexer.class);
+ LoggerFactory.getLogger(SiteIndexer.class);
public static class Result {
private final long elapsedNanos;
@@ -110,44 +112,59 @@ public class ChangeBatchIndexer {
}
private final SchemaFactory<ReviewDb> schemaFactory;
+ private final ChangeCache changeCache;
private final ChangeData.Factory changeDataFactory;
private final GitRepositoryManager repoManager;
private final ListeningExecutorService executor;
private final ChangeIndexer.Factory indexerFactory;
- private final MergeabilityChecker mergeabilityChecker;
private final ThreeWayMergeStrategy mergeStrategy;
+ private int numChanges = -1;
+ private OutputStream progressOut = NullOutputStream.INSTANCE;
+ private PrintWriter verboseWriter =
+ new PrintWriter(NullOutputStream.INSTANCE);
+
@Inject
- ChangeBatchIndexer(SchemaFactory<ReviewDb> schemaFactory,
+ SiteIndexer(SchemaFactory<ReviewDb> schemaFactory,
+ ChangeCache changeCache,
ChangeData.Factory changeDataFactory,
GitRepositoryManager repoManager,
- @IndexExecutor ListeningExecutorService executor,
+ @IndexExecutor(BATCH) ListeningExecutorService executor,
ChangeIndexer.Factory indexerFactory,
- @GerritServerConfig Config config,
- @Nullable MergeabilityChecker mergeabilityChecker) {
+ @GerritServerConfig Config config) {
this.schemaFactory = schemaFactory;
+ this.changeCache = changeCache;
this.changeDataFactory = changeDataFactory;
this.repoManager = repoManager;
this.executor = executor;
this.indexerFactory = indexerFactory;
- this.mergeabilityChecker = mergeabilityChecker;
this.mergeStrategy = MergeUtil.getMergeStrategy(config);
}
- public Result indexAll(ChangeIndex index, Iterable<Project.NameKey> projects,
- int numProjects, int numChanges, OutputStream progressOut,
- OutputStream verboseOut) {
- if (progressOut == null) {
- progressOut = NullOutputStream.INSTANCE;
- }
- PrintWriter verboseWriter = verboseOut != null ? new PrintWriter(verboseOut)
- : null;
+ public SiteIndexer setNumChanges(int num) {
+ numChanges = num;
+ return this;
+ }
+
+ public SiteIndexer setProgressOut(OutputStream out) {
+ progressOut = checkNotNull(out);
+ return this;
+ }
+ public SiteIndexer setVerboseOut(OutputStream out) {
+ verboseWriter = new PrintWriter(checkNotNull(out));
+ return this;
+ }
+
+ public Result indexAll(ChangeIndex index,
+ Iterable<Project.NameKey> projects) {
Stopwatch sw = Stopwatch.createStarted();
final MultiProgressMonitor mpm =
new MultiProgressMonitor(progressOut, "Reindexing changes");
final Task projTask = mpm.beginSubTask("projects",
- numProjects >= 0 ? numProjects : MultiProgressMonitor.UNKNOWN);
+ (projects instanceof Collection)
+ ? ((Collection<?>) projects).size()
+ : MultiProgressMonitor.UNKNOWN);
final Task doneTask = mpm.beginSubTask(null,
numChanges >= 0 ? numChanges : MultiProgressMonitor.UNKNOWN);
final Task failedTask = mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
@@ -156,9 +173,8 @@ public class ChangeBatchIndexer {
final AtomicBoolean ok = new AtomicBoolean(true);
for (final Project.NameKey project : projects) {
- updateMergeable(project);
final ListenableFuture<?> future = executor.submit(reindexProject(
- indexerFactory.create(index), project, doneTask, failedTask,
+ indexerFactory.create(executor, index), project, doneTask, failedTask,
verboseWriter));
futures.add(future);
future.addListener(new Runnable() {
@@ -166,13 +182,13 @@ public class ChangeBatchIndexer {
public void run() {
try {
future.get();
- } catch (InterruptedException e) {
- fail(project, e);
- } catch (ExecutionException e) {
+ } catch (ExecutionException | InterruptedException e) {
fail(project, e);
} catch (RuntimeException e) {
failAndThrow(project, e);
} catch (Error e) {
+ // Can't join with RuntimeException because "RuntimeException |
+ // Error" becomes Throwable, which messes with signatures.
failAndThrow(project, e);
} finally {
projTask.update(1);
@@ -193,7 +209,7 @@ public class ChangeBatchIndexer {
fail(project, e);
throw e;
}
- }, MoreExecutors.sameThreadExecutor());
+ }, MoreExecutors.directExecutor());
}
try {
@@ -212,18 +228,6 @@ public class ChangeBatchIndexer {
return new Result(sw, ok.get(), doneTask.getCount(), failedTask.getCount());
}
- private boolean updateMergeable(Project.NameKey project) {
- if (mergeabilityChecker != null) {
- try {
- mergeabilityChecker.newCheck().addProject(project).run();
- } catch (Exception e) {
- log.error("Error in mergeability checker", e);
- return false;
- }
- }
- return true;
- }
-
private Callable<Void> reindexProject(final ChangeIndexer indexer,
final Project.NameKey project, final Task done, final Task failed,
final PrintWriter verboseWriter) {
@@ -237,7 +241,7 @@ public class ChangeBatchIndexer {
repo = repoManager.openRepository(project);
Map<String, Ref> refs = repo.getRefDatabase().getRefs(ALL);
db = schemaFactory.open();
- for (Change c : db.changes().byProject(project)) {
+ for (Change c : changeCache.get(project)) {
Ref r = refs.get(c.currentPatchSetId().toRefName());
if (r != null) {
byId.put(r.getObjectId(), changeDataFactory.create(db, c));
@@ -268,7 +272,7 @@ public class ChangeBatchIndexer {
};
}
- public static class ProjectIndexer implements Callable<Void> {
+ private static class ProjectIndexer implements Callable<Void> {
private final ChangeIndexer indexer;
private final ThreeWayMergeStrategy mergeStrategy;
private final Multimap<ObjectId, ChangeData> byId;
@@ -326,34 +330,29 @@ public class ChangeBatchIndexer {
private void getPathsAndIndex(ObjectId b) throws Exception {
List<ChangeData> cds = Lists.newArrayList(byId.get(b));
- try {
+ try (DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
RevCommit bCommit = walk.parseCommit(b);
RevTree bTree = bCommit.getTree();
RevTree aTree = aFor(bCommit, walk);
- DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
- try {
- df.setRepository(repo);
- if (!cds.isEmpty()) {
- List<String> paths = (aTree != null)
- ? getPaths(df.scan(aTree, bTree))
- : Collections.<String>emptyList();
- Iterator<ChangeData> cdit = cds.iterator();
- for (ChangeData cd ; cdit.hasNext(); cdit.remove()) {
- cd = cdit.next();
- try {
- cd.setCurrentFilePaths(paths);
- indexer.index(cd);
- done.update(1);
- if (verboseWriter != null) {
- verboseWriter.println("Reindexed change " + cd.getId());
- }
- } catch (Exception e) {
- fail("Failed to index change " + cd.getId(), true, e);
+ df.setRepository(repo);
+ if (!cds.isEmpty()) {
+ List<String> paths = (aTree != null)
+ ? getPaths(df.scan(aTree, bTree))
+ : Collections.<String>emptyList();
+ Iterator<ChangeData> cdit = cds.iterator();
+ for (ChangeData cd ; cdit.hasNext(); cdit.remove()) {
+ cd = cdit.next();
+ try {
+ cd.setCurrentFilePaths(paths);
+ indexer.index(cd);
+ done.update(1);
+ if (verboseWriter != null) {
+ verboseWriter.println("Reindexed change " + cd.getId());
}
+ } catch (Exception e) {
+ fail("Failed to index change " + cd.getId(), true, e);
}
}
- } finally {
- df.close();
}
} catch (Exception e) {
fail("Failed to index commit " + b.name(), false, e);
@@ -392,13 +391,10 @@ public class ChangeBatchIndexer {
}
private ObjectId emptyTree() throws IOException {
- ObjectInserter oi = repo.newObjectInserter();
- try {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
oi.flush();
return id;
- } finally {
- oi.close();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
index f6110528ce..8ba7df92a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
@@ -14,12 +14,7 @@
package com.google.gerrit.server.index;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.server.index.ChangeField.UPDATED;
-
import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
import java.sql.Timestamp;
@@ -27,22 +22,6 @@ import java.util.Date;
// TODO: Migrate this to IntegerRangePredicate
public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
- @SuppressWarnings({"deprecation", "unchecked"})
- protected static FieldDef<ChangeData, Timestamp> updatedField(
- Schema<ChangeData> schema) {
- if (schema == null) {
- return ChangeField.LEGACY_UPDATED;
- }
- FieldDef<ChangeData, ?> f = schema.getFields().get(UPDATED.getName());
- if (f == null) {
- f = schema.getFields().get(ChangeField.LEGACY_UPDATED.getName());
- checkNotNull(f, "schema missing updated field, found: %s", schema);
- }
- checkArgument(f.getType() == FieldType.TIMESTAMP,
- "expected %s to be TIMESTAMP, found %s", f.getName(), f.getType());
- return (FieldDef<ChangeData, Timestamp>) f;
- }
-
protected static Timestamp parse(String value) throws QueryParseException {
try {
return JavaSqlTimestampHelper.parseTimestamp(value);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
index 8387f51090..adcf242d72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
@@ -24,6 +24,7 @@ import com.google.inject.assistedinject.Assisted;
public class AbandonedSender extends ReplyToChangeSender {
public static interface Factory extends
ReplyToChangeSender.Factory<AbandonedSender> {
+ @Override
AbandonedSender create(Change change);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index b3c0d6112f..ac2345565a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -72,6 +72,7 @@ public abstract class ChangeEmail extends NotificationEmail {
emailOnlyAuthors = false;
}
+ @Override
public void setFrom(final Account.Id id) {
super.setFrom(id);
@@ -94,6 +95,7 @@ public abstract class ChangeEmail extends NotificationEmail {
}
/** Format the message body by calling {@link #appendText(String)}. */
+ @Override
protected void format() throws EmailException {
formatChange();
appendText(velocifyFile("ChangeFooter.vm"));
@@ -114,11 +116,16 @@ public abstract class ChangeEmail extends NotificationEmail {
/** Format the message body by calling {@link #appendText(String)}. */
protected abstract void formatChange() throws EmailException;
- /** Format the message footer by calling {@link #appendText(String)}. */
+ /**
+ * Format the message footer by calling {@link #appendText(String)}.
+ *
+ * @throws EmailException if an error occurred.
+ */
protected void formatFooter() throws EmailException {
}
/** Setup the message headers and envelope (TO, CC, BCC). */
+ @Override
protected void init() throws EmailException {
if (args.projectCache != null) {
projectState = args.projectCache.get(change.getProject());
@@ -324,12 +331,14 @@ public abstract class ChangeEmail extends NotificationEmail {
}
}
+ @Override
protected void add(final RecipientType rt, final Account.Id to) {
if (! emailOnlyAuthors || authors.contains(to)) {
super.add(rt, to);
}
}
+ @Override
protected boolean isVisibleTo(final Account.Id to) throws OrmException {
return projectState == null
|| projectState.controlFor(args.identifiedUserFactory.create(to))
@@ -387,28 +396,28 @@ public abstract class ChangeEmail extends NotificationEmail {
TemporaryBuffer.Heap buf =
new TemporaryBuffer.Heap(args.settings.maximumDiffSize);
- DiffFormatter fmt = new DiffFormatter(buf);
- Repository git;
- try {
- git = args.server.openRepository(change.getProject());
- } catch (IOException e) {
- log.error("Cannot open repository to format patch", e);
- return "";
- }
- try {
- fmt.setRepository(git);
- fmt.setDetectRenames(true);
- fmt.format(patchList.getOldId(), patchList.getNewId());
- return RawParseUtils.decode(buf.toByteArray());
- } catch (IOException e) {
- if (JGitText.get().inMemoryBufferLimitExceeded.equals(e.getMessage())) {
+ try (DiffFormatter fmt = new DiffFormatter(buf)) {
+ Repository git;
+ try {
+ git = args.server.openRepository(change.getProject());
+ } catch (IOException e) {
+ log.error("Cannot open repository to format patch", e);
return "";
}
- log.error("Cannot format patch", e);
- return "";
- } finally {
- fmt.close();
- git.close();
+ try {
+ fmt.setRepository(git);
+ fmt.setDetectRenames(true);
+ fmt.format(patchList.getOldId(), patchList.getNewId());
+ return RawParseUtils.decode(buf.toByteArray());
+ } catch (IOException e) {
+ if (JGitText.get().inMemoryBufferLimitExceeded.equals(e.getMessage())) {
+ return "";
+ }
+ log.error("Cannot format patch", e);
+ return "";
+ } finally {
+ git.close();
+ }
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 2beb49fbd7..b587791f37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.errors.EmailException;
@@ -24,6 +25,7 @@ import com.google.gerrit.reviewdb.client.CommentRange;
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.server.PatchLineCommentsUtil;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -53,13 +55,16 @@ public class CommentSender extends ReplyToChangeSender {
private final NotifyHandling notify;
private List<PatchLineComment> inlineComments = Collections.emptyList();
+ private final PatchLineCommentsUtil plcUtil;
@Inject
public CommentSender(EmailArguments ea,
@Assisted NotifyHandling notify,
- @Assisted Change c) {
+ @Assisted Change c,
+ PatchLineCommentsUtil plcUtil) {
super(ea, c, "comment");
this.notify = notify;
+ this.plcUtil = plcUtil;
}
public void setPatchLineComments(final List<PatchLineComment> plc)
@@ -116,7 +121,7 @@ public class CommentSender extends ReplyToChangeSender {
try {
patchList = getPatchList();
} catch (PatchListNotAvailableException e) {
- patchList = null;
+ log.error("Failed to get patch list", e);
}
}
@@ -232,17 +237,19 @@ public class CommentSender extends ReplyToChangeSender {
private void appendQuotedParent(StringBuilder out, PatchLineComment child) {
if (child.getParentUuid() != null) {
- PatchLineComment parent;
+ Optional<PatchLineComment> parent;
+ PatchLineComment.Key key = new PatchLineComment.Key(
+ child.getKey().getParentKey(),
+ child.getParentUuid());
try {
- parent = args.db.get().patchComments().get(
- new PatchLineComment.Key(
- child.getKey().getParentKey(),
- child.getParentUuid()));
+ parent = plcUtil.get(args.db.get(), changeData.notes(), key);
} catch (OrmException e) {
- parent = null;
+ log.warn("Could not find the parent of this comment: "
+ + child.toString());
+ parent = Optional.absent();
}
- if (parent != null) {
- String msg = parent.getMessage().trim();
+ if (parent.isPresent()) {
+ String msg = parent.get().getMessage().trim();
if (msg.length() > 75) {
msg = msg.substring(0, 75);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index ebf5ac7ee9..c1ab58aba7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ApprovalsUtil;
@@ -35,6 +36,7 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
+import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -62,11 +64,12 @@ public class EmailArguments {
final AllProjectsName allProjectsName;
final List<String> sshAddresses;
- final ChangeQueryBuilder.Factory queryBuilder;
+ final ChangeQueryBuilder queryBuilder;
final Provider<ReviewDb> db;
final ChangeData.Factory changeDataFactory;
final RuntimeInstance velocityRuntime;
final EmailSettings settings;
+ final DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners;
@Inject
EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
@@ -83,12 +86,13 @@ public class EmailArguments {
@AnonymousCowardName String anonymousCowardName,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
AllProjectsName allProjectsName,
- ChangeQueryBuilder.Factory queryBuilder,
+ ChangeQueryBuilder queryBuilder,
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
RuntimeInstance velocityRuntime,
EmailSettings settings,
- @SshAdvertisedAddresses List<String> sshAddresses) {
+ @SshAdvertisedAddresses List<String> sshAddresses,
+ DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners) {
this.server = server;
this.projectCache = projectCache;
this.groupBackend = groupBackend;
@@ -112,5 +116,6 @@ public class EmailArguments {
this.velocityRuntime = velocityRuntime;
this.settings = settings;
this.sshAddresses = sshAddresses;
+ this.outgoingEmailValidationListeners = outgoingEmailValidationListeners;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
index 26cccb8b3e..c6e59eb44d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.common.collect.Multimap;
+import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -31,8 +32,6 @@ import java.util.List;
import java.util.Set;
public class MailUtil {
- private static final FooterKey REVIEWED_BY = new FooterKey("Reviewed-by");
- private static final FooterKey TESTED_BY = new FooterKey("Tested-by");
public static MailRecipients getRecipientsFromFooters(
final AccountResolver accountResolver, final PatchSet ps,
@@ -77,8 +76,8 @@ public class MailUtil {
private static boolean isReviewer(final FooterLine candidateFooterLine) {
return candidateFooterLine.matches(FooterKey.SIGNED_OFF_BY)
|| candidateFooterLine.matches(FooterKey.ACKED_BY)
- || candidateFooterLine.matches(REVIEWED_BY)
- || candidateFooterLine.matches(TESTED_BY);
+ || candidateFooterLine.matches(FooterConstants.REVIEWED_BY)
+ || candidateFooterLine.matches(FooterConstants.TESTED_BY);
}
public static class MailRecipients {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index 49acda88fa..8a3133ff0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -20,6 +20,8 @@ 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.mail.EmailHeader.AddressList;
+import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
import org.apache.commons.lang.StringUtils;
@@ -121,14 +123,32 @@ public abstract class OutgoingEmail {
}
}
- args.emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString());
+ OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args();
+ va.messageClass = messageClass;
+ va.smtpFromAddress = smtpFromAddress;
+ va.smtpRcptTo = smtpRcptTo;
+ va.headers = headers;
+ va.body = body.toString();
+ for (OutgoingEmailValidationListener validator : args.outgoingEmailValidationListeners) {
+ try {
+ validator.validateOutgoingEmail(va);
+ } catch (ValidationException e) {
+ return;
+ }
+ }
+
+ args.emailSender.send(va.smtpFromAddress, va.smtpRcptTo, va.headers, va.body);
}
}
/** Format the message body by calling {@link #appendText(String)}. */
protected abstract void format() throws EmailException;
- /** Setup the message headers and envelope (TO, CC, BCC). */
+ /**
+ * Setup the message headers and envelope (TO, CC, BCC).
+ *
+ * @throws EmailException if an error occurred.
+ */
protected void init() throws EmailException {
setupVelocityContext();
@@ -315,6 +335,11 @@ public abstract class OutgoingEmail {
}
}
+ /**
+ * @param to account.
+ * @throws OrmException
+ * @return whether this email is visible to the given account.
+ */
protected boolean isVisibleTo(final Account.Id to) throws OrmException {
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
index b847a9151e..53efa1eaff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -16,7 +16,6 @@ package com.google.gerrit.server.mail;
import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
-import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -29,12 +28,10 @@ import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -65,14 +62,12 @@ public class PatchSetNotificationSender {
@Inject
public PatchSetNotificationSender(Provider<ReviewDb> db,
- ChangeHooks hooks,
GitRepositoryManager repoManager,
PatchSetInfoFactory patchSetInfoFactory,
ApprovalsUtil approvalsUtil,
AccountResolver accountResolver,
CreateChangeSender.Factory createChangeSenderFactory,
- ReplacePatchSetSender.Factory replacePatchSetFactory,
- ChangeIndexer indexer) {
+ ReplacePatchSetSender.Factory replacePatchSetFactory) {
this.db = db;
this.repoManager = repoManager;
this.patchSetInfoFactory = patchSetInfoFactory;
@@ -86,16 +81,12 @@ public class PatchSetNotificationSender {
final boolean newChange, final IdentifiedUser currentUser,
final Change updatedChange, final PatchSet updatedPatchSet,
final LabelTypes labelTypes)
- throws OrmException, IOException, PatchSetInfoNotAvailableException {
- final Repository git = repoManager.openRepository(updatedChange.getProject());
- try {
- final RevWalk revWalk = new RevWalk(git);
+ throws OrmException, IOException {
+ try (Repository git = repoManager.openRepository(updatedChange.getProject())) {
final RevCommit commit;
- try {
+ try (RevWalk revWalk = new RevWalk(git)) {
commit = revWalk.parseCommit(ObjectId.fromString(
updatedPatchSet.getRevision().get()));
- } finally {
- revWalk.close();
}
final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
final List<FooterLine> footerLines = commit.getFooterLines();
@@ -139,8 +130,6 @@ public class PatchSetNotificationSender {
log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
}
}
- } finally {
- git.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 872b84a181..63709c60c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -68,16 +68,16 @@ public class ProjectWatch {
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(project)) {
- if (w.isNotify(type)) {
+ if (add(matching, w, type)) {
+ // We only want to prevent matching All-Projects if this filter hits
projectWatchers.add(w.getAccountId());
- add(matching, w);
}
}
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
.byProject(args.allProjectsName)) {
- if (!projectWatchers.contains(w.getAccountId()) && w.isNotify(type)) {
- add(matching, w);
+ if (!projectWatchers.contains(w.getAccountId())) {
+ add(matching, w, type);
}
}
@@ -85,7 +85,7 @@ public class ProjectWatch {
for (NotifyConfig nc : state.getConfig().getNotifyConfigs()) {
if (nc.isNotify(type)) {
try {
- add(matching, nc, state.getProject().getNameKey());
+ add(matching, nc);
} catch (QueryParseException e) {
log.warn(String.format(
"Project %s has invalid notify %s filter \"%s\"",
@@ -121,7 +121,7 @@ public class ProjectWatch {
}
}
- private void add(Watchers matching, NotifyConfig nc, Project.NameKey project)
+ private void add(Watchers matching, NotifyConfig nc)
throws OrmException, QueryParseException {
for (GroupReference ref : nc.getGroups()) {
CurrentUser user = new SingleGroupUser(args.capabilityControlFactory,
@@ -174,18 +174,24 @@ public class ProjectWatch {
}
}
- private void add(Watchers matching, AccountProjectWatch w)
+ private boolean add(Watchers matching, AccountProjectWatch w, NotifyType type)
throws OrmException {
IdentifiedUser user =
args.identifiedUserFactory.create(args.db, w.getAccountId());
try {
if (filterMatch(user, w.getFilter())) {
- matching.bcc.accounts.add(w.getAccountId());
+ // If we are set to notify on this type, add the user.
+ // Otherwise, still return true to stop notifications for this user.
+ if (w.isNotify(type)) {
+ matching.bcc.accounts.add(w.getAccountId());
+ }
+ return true;
}
} catch (QueryParseException e) {
// Ignore broken filter expressions.
}
+ return false;
}
private boolean filterMatch(CurrentUser user, String filter)
@@ -194,9 +200,9 @@ public class ProjectWatch {
Predicate<ChangeData> p = null;
if (user == null) {
- qb = args.queryBuilder.create(args.anonymousUser);
+ qb = args.queryBuilder.asUser(args.anonymousUser);
} else {
- qb = args.queryBuilder.create(user);
+ qb = args.queryBuilder.asUser(user);
p = qb.is_visible();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
index 7f464f7e27..4f65ab4e71 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
@@ -24,6 +24,7 @@ import com.google.inject.assistedinject.Assisted;
public class RestoredSender extends ReplyToChangeSender {
public static interface Factory extends
ReplyToChangeSender.Factory<RestoredSender> {
+ @Override
RestoredSender create(Change change);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 92fffde025..d74eaebf46 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -46,6 +46,7 @@ public class SignedTokenEmailTokenVerifier implements EmailTokenVerifier {
emailRegistrationToken = config.getEmailRegistrationToken();
}
+ @Override
public String encode(Account.Id accountId, String emailAddress) {
try {
String payload = String.format("%s:%s", accountId, emailAddress);
@@ -59,6 +60,7 @@ public class SignedTokenEmailTokenVerifier implements EmailTokenVerifier {
}
}
+ @Override
public ParsedToken decode(String tokenString) throws InvalidTokenException {
ValidToken token;
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
index b1c5955bd4..2f8f75d2f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
@@ -15,11 +15,11 @@
package com.google.gerrit.server.mail;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -86,8 +86,7 @@ public class SmtpEmailSender implements EmailSender {
}
smtpEncryption =
- ConfigUtil.getEnum(cfg, "sendemail", null, "smtpencryption",
- Encryption.NONE);
+ cfg.getEnum("sendemail", null, "smtpencryption", Encryption.NONE);
sslVerify = cfg.getBoolean("sendemail", null, "sslverify", true);
final int defaultPort;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java
index 32098d5ddf..ace1f5b148 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java
@@ -37,6 +37,7 @@ public class VelocityRuntimeProvider implements Provider<RuntimeInstance> {
this.site = site;
}
+ @Override
public RuntimeInstance get() {
String rl = "resource.loader";
String pkg = "org.apache.velocity.runtime.resource.loader";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java
index 61bd39ef4c..8c6bb3e72b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server;
+package com.google.gerrit.server.mime;
import com.google.common.collect.ImmutableMap;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/FileTypeRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java
index 83f87f92ba..43d53f0a0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/FileTypeRegistry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server;
+package com.google.gerrit.server.mime;
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil2;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtil2Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtil2Module.java
new file mode 100644
index 0000000000..61b01af8c3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtil2Module.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2015 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.mime;
+
+import com.google.gerrit.server.util.HostPlatform;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+import eu.medsea.mimeutil.MimeUtil2;
+import eu.medsea.mimeutil.detector.ExtensionMimeDetector;
+import eu.medsea.mimeutil.detector.MagicMimeMimeDetector;
+
+public class MimeUtil2Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ }
+
+ @Provides
+ @Singleton
+ MimeUtil2 provideMimeUtil2() {
+ MimeUtil2 m = new MimeUtil2();
+ m.registerMimeDetector(ExtensionMimeDetector.class.getName());
+ m.registerMimeDetector(MagicMimeMimeDetector.class.getName());
+ if (HostPlatform.isWin32()) {
+ m.registerMimeDetector("eu.medsea.mimeutil.detector.WindowsRegistryMimeDetector");
+ }
+ m.registerMimeDetector(DefaultFileExtensionRegistry.class.getName());
+ return m;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java
index 5263c6b38d..2387200200 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mime/MimeUtilFileTypeRegistry.java
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server;
+package com.google.gerrit.server.mime;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.util.HostPlatform;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -43,22 +42,12 @@ public class MimeUtilFileTypeRegistry implements FileTypeRegistry {
LoggerFactory.getLogger(MimeUtilFileTypeRegistry.class);
private final Config cfg;
- private MimeUtil2 mimeUtil;
+ private final MimeUtil2 mimeUtil;
@Inject
- MimeUtilFileTypeRegistry(@GerritServerConfig final Config gsc) {
+ MimeUtilFileTypeRegistry(@GerritServerConfig Config gsc, MimeUtil2 mu2) {
cfg = gsc;
- mimeUtil = new MimeUtil2();
- register("eu.medsea.mimeutil.detector.ExtensionMimeDetector");
- register("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
- if (HostPlatform.isWin32()) {
- register("eu.medsea.mimeutil.detector.WindowsRegistryMimeDetector");
- }
- register(DefaultFileExtensionRegistry.class.getName());
- }
-
- private void register(String name) {
- mimeUtil.registerMimeDetector(name);
+ mimeUtil = mu2;
}
/**
@@ -92,6 +81,7 @@ public class MimeUtilFileTypeRegistry implements FileTypeRegistry {
return mimeType.getSpecificity();
}
+ @Override
@SuppressWarnings("unchecked")
public MimeType getMimeType(final String path, final byte[] content) {
Set<MimeType> mimeTypes = new HashSet<>();
@@ -122,6 +112,7 @@ public class MimeUtilFileTypeRegistry implements FileTypeRegistry {
return types.get(0);
}
+ @Override
public boolean isSafeInline(final MimeType type) {
if (MimeUtil2.UNKNOWN_MIME_TYPE.equals(type)) {
// Most browsers perform content type sniffing when they get told
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 37094fda3b..80d45049be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -21,49 +21,79 @@ import com.google.gerrit.server.git.VersionedMetaData;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
/** View of contents at a single ref related to some change. **/
public abstract class AbstractChangeNotes<T> extends VersionedMetaData {
- private boolean loaded;
protected final GitRepositoryManager repoManager;
- private final Change change;
+ protected final NotesMigration migration;
+ private final Change.Id changeId;
+
+ private boolean loaded;
- AbstractChangeNotes(GitRepositoryManager repoManager, Change change) {
+ AbstractChangeNotes(GitRepositoryManager repoManager,
+ NotesMigration migration, Change.Id changeId) {
this.repoManager = repoManager;
- this.change = new Change(change);
+ this.migration = migration;
+ this.changeId = changeId;
}
public Change.Id getChangeId() {
- return change.getId();
+ return changeId;
}
- public Change getChange() {
- return change;
+ public T load() throws OrmException {
+ if (loaded) {
+ return self();
+ }
+ if (!migration.enabled()) {
+ loadDefaults();
+ return self();
+ }
+ Repository repo;
+ try {
+ repo = repoManager.openMetadataRepository(getProjectName());
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ try {
+ load(repo);
+ loaded = true;
+ } catch (ConfigInvalidException | IOException e) {
+ throw new OrmException(e);
+ } finally {
+ repo.close();
+ }
+ return self();
}
- public T load() throws OrmException {
- if (!loaded) {
- Repository repo;
- try {
- repo = repoManager.openRepository(getProjectName());
- } catch (IOException e) {
- throw new OrmException(e);
- }
+ public ObjectId loadRevision() throws OrmException {
+ if (loaded) {
+ return getRevision();
+ } else if (!migration.enabled()) {
+ return null;
+ }
+ Repository repo;
+ try {
+ repo = repoManager.openMetadataRepository(getProjectName());
try {
- load(repo);
- loaded = true;
- } catch (ConfigInvalidException | IOException e) {
- throw new OrmException(e);
+ Ref ref = repo.getRef(getRefName());
+ return ref != null ? ref.getObjectId() : null;
} finally {
repo.close();
}
+ } catch (IOException e) {
+ throw new OrmException(e);
}
- return self();
}
+ /** Load default values for any instance variables when notedb is disabled. */
+ protected abstract void loadDefaults();
+
/**
* @return the NameKey for the project where the notes should be stored,
* which is not necessarily the same as the change's project.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index 0d637f5343..fbca668a06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -26,10 +25,13 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.VersionedMetaData;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -43,6 +45,7 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
protected final GitRepositoryManager repoManager;
protected final MetaDataUpdate.User updateFactory;
protected final ChangeControl ctl;
+ protected final String anonymousCowardName;
protected final PersonIdent serverIdent;
protected final Date when;
protected PatchSet.Id psId;
@@ -50,15 +53,22 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
AbstractChangeUpdate(NotesMigration migration,
GitRepositoryManager repoManager,
MetaDataUpdate.User updateFactory, ChangeControl ctl,
- PersonIdent serverIdent, Date when) {
+ PersonIdent serverIdent,
+ String anonymousCowardName,
+ Date when) {
this.migration = migration;
this.repoManager = repoManager;
this.updateFactory = updateFactory;
this.ctl = ctl;
this.serverIdent = serverIdent;
+ this.anonymousCowardName = anonymousCowardName;
this.when = when;
}
+ public ChangeNotes getChangeNotes() {
+ return ctl.getNotes();
+ }
+
public Change getChange() {
return ctl.getChange();
}
@@ -71,6 +81,10 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
return (IdentifiedUser) ctl.getCurrentUser();
}
+ public PatchSet.Id getPatchSetId() {
+ return psId;
+ }
+
public void setPatchSetId(PatchSet.Id psId) {
checkArgument(psId == null
|| psId.getParentKey().equals(getChange().getId()));
@@ -78,8 +92,8 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
}
private void load() throws IOException {
- if (migration.write() && getRevision() == null) {
- Repository repo = repoManager.openRepository(getProjectName());
+ if (migration.writeChanges() && getRevision() == null) {
+ Repository repo = repoManager.openMetadataRepository(getProjectName());
try {
load(repo);
} catch (ConfigInvalidException e) {
@@ -90,16 +104,27 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
}
}
+ public void setInserter(ObjectInserter inserter) {
+ this.inserter = inserter;
+ }
+
@Override
public BatchMetaDataUpdate openUpdate(MetaDataUpdate update) throws IOException {
throw new UnsupportedOperationException("use openUpdate()");
}
public BatchMetaDataUpdate openUpdate() throws IOException {
- if (migration.write()) {
+ return openUpdateInBatch(null);
+ }
+
+ public BatchMetaDataUpdate openUpdateInBatch(BatchRefUpdate bru)
+ throws IOException {
+ if (migration.writeChanges()) {
load();
MetaDataUpdate md =
- updateFactory.create(getProjectName(), getUser());
+ updateFactory.create(getProjectName(),
+ repoManager.openMetadataRepository(getProjectName()), getUser(),
+ bru);
md.setAllowEmpty(true);
return super.openUpdate(md);
}
@@ -120,6 +145,11 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
}
@Override
+ public void removeRef(String refName) {
+ // Do nothing.
+ }
+
+ @Override
public RevCommit commit() {
return null;
}
@@ -147,15 +177,17 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
}
protected PersonIdent newIdent(Account author, Date when) {
- return new PersonIdent(
- author.getFullName(),
- author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
- when, serverIdent.getTimeZone());
+ return ChangeNoteUtil.newIdent(author, when, serverIdent,
+ anonymousCowardName);
}
+ /** Writes commit to a BatchMetaDataUpdate without committing the batch. */
+ public abstract void writeCommit(BatchMetaDataUpdate batch)
+ throws OrmException, IOException;
+
/**
* @return the NameKey for the project where the update will be stored,
* which is not necessarily the same as the change's project.
*/
- abstract protected Project.NameKey getProjectName();
+ protected abstract Project.NameKey getProjectName();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
new file mode 100644
index 0000000000..3b51bb454b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -0,0 +1,319 @@
+// Copyright (C) 2014 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 com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Table;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+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.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A single delta to apply atomically to a change.
+ * <p>
+ * This delta contains only draft comments on a single patch set of a change by
+ * a single author. This delta will become a single commit in the All-Users
+ * repository.
+ * <p>
+ * This class is not thread safe.
+ */
+public class ChangeDraftUpdate extends AbstractChangeUpdate {
+ public interface Factory {
+ ChangeDraftUpdate create(ChangeControl ctl, Date when);
+ }
+
+ private final AllUsersName draftsProject;
+ private final Account.Id accountId;
+ private final CommentsInNotesUtil commentsUtil;
+ private final ChangeNotes changeNotes;
+ private final DraftCommentNotes draftNotes;
+
+ private List<PatchLineComment> upsertComments;
+ private List<PatchLineComment> deleteComments;
+
+ @AssistedInject
+ private ChangeDraftUpdate(
+ @GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
+ GitRepositoryManager repoManager,
+ NotesMigration migration,
+ MetaDataUpdate.User updateFactory,
+ DraftCommentNotes.Factory draftNotesFactory,
+ AllUsersName allUsers,
+ CommentsInNotesUtil commentsUtil,
+ @Assisted ChangeControl ctl,
+ @Assisted Date when) throws OrmException {
+ super(migration, repoManager, updateFactory, ctl, serverIdent,
+ anonymousCowardName, when);
+ this.draftsProject = allUsers;
+ this.commentsUtil = commentsUtil;
+ checkState(ctl.getCurrentUser().isIdentifiedUser(),
+ "Current user must be identified");
+ IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+ this.accountId = user.getAccountId();
+ this.changeNotes = getChangeNotes().load();
+ this.draftNotes = draftNotesFactory.create(ctl.getChange().getId(),
+ user.getAccountId());
+
+ this.upsertComments = Lists.newArrayList();
+ this.deleteComments = Lists.newArrayList();
+ }
+
+ public void insertComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ checkArgument(c.getStatus() == Status.DRAFT,
+ "Cannot insert a published comment into a ChangeDraftUpdate");
+ if (migration.readChanges()) {
+ checkArgument(!changeNotes.containsComment(c),
+ "A comment already exists with the same key,"
+ + " so the following comment cannot be inserted: %s", c);
+ }
+ upsertComments.add(c);
+ }
+
+ public void upsertComment(PatchLineComment c) {
+ verifyComment(c);
+ checkArgument(c.getStatus() == Status.DRAFT,
+ "Cannot upsert a published comment into a ChangeDraftUpdate");
+ upsertComments.add(c);
+ }
+
+ public void updateComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ checkArgument(c.getStatus() == Status.DRAFT,
+ "Cannot update a published comment into a ChangeDraftUpdate");
+ // Here, we check to see if this comment existed previously as a draft.
+ // However, this could cause a race condition if there is a delete and an
+ // update operation happening concurrently (or two deletes) and they both
+ // believe that the comment exists. If a delete happens first, then
+ // the update will fail. However, this is an acceptable risk since the
+ // caller wanted the comment deleted anyways, so the end result will be the
+ // same either way.
+ if (migration.readChanges()) {
+ checkArgument(draftNotes.load().containsComment(c),
+ "Cannot update this comment because it didn't exist previously");
+ }
+ upsertComments.add(c);
+ }
+
+ public void deleteComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ // See the comment above about potential race condition.
+ if (migration.readChanges()) {
+ checkArgument(draftNotes.load().containsComment(c),
+ "Cannot delete this comment because it didn't previously exist as a"
+ + " draft");
+ }
+ if (migration.writeChanges()) {
+ if (draftNotes.load().containsComment(c)) {
+ deleteComments.add(c);
+ }
+ }
+ }
+
+ /**
+ * Deletes a PatchLineComment from the list of drafts only if it existed
+ * previously as a draft. If it wasn't a draft previously, this is a no-op.
+ */
+ public void deleteCommentIfPresent(PatchLineComment c) throws OrmException {
+ if (draftNotes.load().containsComment(c)) {
+ verifyComment(c);
+ deleteComments.add(c);
+ }
+ }
+
+ private void verifyComment(PatchLineComment comment) {
+ checkState(psId != null,
+ "setPatchSetId must be called first");
+ checkArgument(getCommentPsId(comment).equals(psId),
+ "Comment on %s does not match configured patch set %s",
+ getCommentPsId(comment), psId);
+ if (migration.writeChanges()) {
+ checkArgument(comment.getRevId() != null);
+ }
+ checkArgument(comment.getAuthor().equals(accountId),
+ "The author for the following comment does not match the author of"
+ + " this ChangeDraftUpdate (%s): %s", accountId, comment);
+ }
+
+ /** @return the tree id for the updated tree */
+ private ObjectId storeCommentsInNotes(AtomicBoolean removedAllComments)
+ throws OrmException, IOException {
+ if (isEmpty()) {
+ return null;
+ }
+
+ NoteMap noteMap = draftNotes.load().getNoteMap();
+ if (noteMap == null) {
+ noteMap = NoteMap.newEmptyMap();
+ }
+
+ Table<PatchSet.Id, String, PatchLineComment> baseDrafts =
+ draftNotes.getDraftBaseComments();
+ Table<PatchSet.Id, String, PatchLineComment> psDrafts =
+ draftNotes.getDraftPsComments();
+
+ boolean draftsEmpty = baseDrafts.isEmpty() && psDrafts.isEmpty();
+
+ // There is no need to rewrite the note for one of the sides of the patch
+ // set if all of the modifications were made to the comments of one side,
+ // so we set these flags to potentially save that extra work.
+ boolean baseSideChanged = false;
+ boolean revisionSideChanged = false;
+
+ // We must define these RevIds so that if this update deletes all
+ // remaining comments on a given side, then we can remove that note.
+ // However, if this update doesn't delete any comments, it is okay for these
+ // to be null because they won't be used.
+ RevId baseRevId = null;
+ RevId psRevId = null;
+
+ for (PatchLineComment c : deleteComments) {
+ if (c.getSide() == (short) 0) {
+ baseSideChanged = true;
+ baseRevId = c.getRevId();
+ baseDrafts.remove(psId, c.getKey().get());
+ } else {
+ revisionSideChanged = true;
+ psRevId = c.getRevId();
+ psDrafts.remove(psId, c.getKey().get());
+ }
+ }
+
+ for (PatchLineComment c : upsertComments) {
+ if (c.getSide() == (short) 0) {
+ baseSideChanged = true;
+ baseDrafts.put(psId, c.getKey().get(), c);
+ } else {
+ revisionSideChanged = true;
+ psDrafts.put(psId, c.getKey().get(), c);
+ }
+ }
+
+ List<PatchLineComment> newBaseDrafts =
+ Lists.newArrayList(baseDrafts.row(psId).values());
+ List<PatchLineComment> newPsDrafts =
+ Lists.newArrayList(psDrafts.row(psId).values());
+
+ updateNoteMap(baseSideChanged, noteMap, newBaseDrafts,
+ baseRevId);
+ updateNoteMap(revisionSideChanged, noteMap, newPsDrafts,
+ psRevId);
+
+ removedAllComments.set(
+ baseDrafts.isEmpty() && psDrafts.isEmpty() && !draftsEmpty);
+
+ return noteMap.writeTree(inserter);
+ }
+
+ private void updateNoteMap(boolean changed, NoteMap noteMap,
+ List<PatchLineComment> comments, RevId commitId)
+ throws IOException {
+ if (changed) {
+ if (comments.isEmpty()) {
+ commentsUtil.removeNote(noteMap, commitId);
+ } else {
+ commentsUtil.writeCommentsToNoteMap(noteMap, comments, inserter);
+ }
+ }
+ }
+
+ public RevCommit commit() throws IOException {
+ BatchMetaDataUpdate batch = openUpdate();
+ try {
+ writeCommit(batch);
+ return batch.commit();
+ } catch (OrmException e) {
+ throw new IOException(e);
+ } finally {
+ batch.close();
+ }
+ }
+
+ @Override
+ public void writeCommit(BatchMetaDataUpdate batch)
+ throws OrmException, IOException {
+ CommitBuilder builder = new CommitBuilder();
+ if (migration.writeChanges()) {
+ AtomicBoolean removedAllComments = new AtomicBoolean();
+ ObjectId treeId = storeCommentsInNotes(removedAllComments);
+ if (treeId != null) {
+ if (removedAllComments.get()) {
+ batch.removeRef(getRefName());
+ } else {
+ builder.setTreeId(treeId);
+ batch.write(builder);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected Project.NameKey getProjectName() {
+ return draftsProject;
+ }
+
+ @Override
+ protected String getRefName() {
+ return RefNames.refsDraftComments(accountId, getChange().getId());
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException {
+ if (isEmpty()) {
+ return false;
+ }
+ commit.setAuthor(newIdent(getUser().getAccount(), when));
+ commit.setCommitter(new PersonIdent(serverIdent, when));
+ commit.setMessage(String.format("Comment on patch set %d", psId.get()));
+ return true;
+ }
+
+ private boolean isEmpty() {
+ return deleteComments.isEmpty()
+ && upsertComments.isEmpty();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 720473530f..c561a0de6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -14,14 +14,20 @@
package com.google.gerrit.server.notedb;
+import com.google.gerrit.common.data.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.FooterKey;
+import java.util.Date;
+
public class ChangeNoteUtil {
static final String GERRIT_PLACEHOLDER_HOST = "gerrit";
+ static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
static final FooterKey FOOTER_LABEL = new FooterKey("Label");
static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
static final FooterKey FOOTER_STATUS = new FooterKey("Status");
@@ -39,10 +45,18 @@ public class ChangeNoteUtil {
r.append(m);
r.append('/');
r.append(n);
- r.append("/meta");
+ r.append(RefNames.META_SUFFIX);
return r.toString();
}
+ static PersonIdent newIdent(Account author, Date when,
+ PersonIdent serverIdent, String anonymousCowardName) {
+ return new PersonIdent(
+ new AccountInfo(author).getName(anonymousCowardName),
+ author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
+ when, serverIdent.getTimeZone());
+ }
+
private ChangeNoteUtil() {
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index a33209cf5d..8cb72e93ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -14,29 +14,19 @@
package com.google.gerrit.server.notedb;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
+import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Enums;
import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Table;
-import com.google.common.collect.Tables;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
@@ -44,42 +34,31 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet.Id;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.Project;
+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.util.LabelVote;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
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.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.FooterKey;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.RawParseUtils;
import java.io.IOException;
-import java.nio.charset.Charset;
import java.sql.Timestamp;
-import java.text.ParseException;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
/** View of a single {@link Change} based on the log of its notes branch. */
public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
- private static final Ordering<PatchSetApproval> PSA_BY_TIME =
+ static final Ordering<PatchSetApproval> PSA_BY_TIME =
Ordering.natural().onResultOf(
new Function<PatchSetApproval, Timestamp>() {
@Override
@@ -97,8 +76,9 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
}
});
- public static Comparator<PatchLineComment> PatchLineCommentComparator =
+ public static Comparator<PatchLineComment> PLC_ORDER =
new Comparator<PatchLineComment>() {
+ @Override
public int compare(PatchLineComment c1, PatchLineComment c2) {
String filename1 = c1.getKey().getParentKey().get();
String filename2 = c2.getKey().getParentKey().get();
@@ -134,367 +114,48 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
@Singleton
public static class Factory {
private final GitRepositoryManager repoManager;
+ private final NotesMigration migration;
+ private final AllUsersNameProvider allUsersProvider;
@VisibleForTesting
@Inject
- public Factory(GitRepositoryManager repoManager) {
+ public Factory(GitRepositoryManager repoManager,
+ NotesMigration migration,
+ AllUsersNameProvider allUsersProvider) {
this.repoManager = repoManager;
+ this.migration = migration;
+ this.allUsersProvider = allUsersProvider;
}
public ChangeNotes create(Change change) {
- return new ChangeNotes(repoManager, change);
- }
- }
-
- private static class Parser {
- private final Change.Id changeId;
- private final ObjectId tip;
- private final RevWalk walk;
- private final Repository repo;
- private final Map<PatchSet.Id,
- Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
- private final Map<Account.Id, ReviewerState> reviewers;
- private final List<SubmitRecord> submitRecords;
- private final Multimap<PatchSet.Id, ChangeMessage> changeMessages;
- private final Multimap<Id, PatchLineComment> commentsForPs;
- private final Multimap<PatchSet.Id, PatchLineComment> commentsForBase;
- private NoteMap commentNoteMap;
- private Change.Status status;
-
- private Parser(Change change, ObjectId tip, RevWalk walk,
- GitRepositoryManager repoManager) throws RepositoryNotFoundException,
- IOException {
- this.changeId = change.getId();
- this.tip = tip;
- this.walk = walk;
- this.repo = repoManager.openRepository(getProjectName(change));
- approvals = Maps.newHashMap();
- reviewers = Maps.newLinkedHashMap();
- submitRecords = Lists.newArrayListWithExpectedSize(1);
- changeMessages = LinkedListMultimap.create();
- commentsForPs = ArrayListMultimap.create();
- commentsForBase = ArrayListMultimap.create();
- }
-
- private void parseAll() throws ConfigInvalidException, IOException, ParseException {
- walk.markStart(walk.parseCommit(tip));
- for (RevCommit commit : walk) {
- parse(commit);
- }
- parseComments();
- pruneReviewers();
- }
-
- private ImmutableListMultimap<PatchSet.Id, PatchSetApproval>
- buildApprovals() {
- Multimap<PatchSet.Id, PatchSetApproval> result =
- ArrayListMultimap.create(approvals.keySet().size(), 3);
- for (Table<?, ?, Optional<PatchSetApproval>> curr
- : approvals.values()) {
- for (PatchSetApproval psa : Optional.presentInstances(curr.values())) {
- result.put(psa.getPatchSetId(), psa);
- }
- }
- for (Collection<PatchSetApproval> v : result.asMap().values()) {
- Collections.sort((List<PatchSetApproval>) v, PSA_BY_TIME);
- }
- return ImmutableListMultimap.copyOf(result);
- }
-
- private ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessages() {
- for (Collection<ChangeMessage> v : changeMessages.asMap().values()) {
- Collections.sort((List<ChangeMessage>) v, MESSAGE_BY_TIME);
- }
- return ImmutableListMultimap.copyOf(changeMessages);
- }
-
- private void parse(RevCommit commit) throws ConfigInvalidException, IOException {
- if (status == null) {
- status = parseStatus(commit);
- }
- PatchSet.Id psId = parsePatchSetId(commit);
- Account.Id accountId = parseIdent(commit);
- parseChangeMessage(psId, accountId, commit);
-
-
- if (submitRecords.isEmpty()) {
- // Only parse the most recent set of submit records; any older ones are
- // still there, but not currently used.
- parseSubmitRecords(commit.getFooterLines(FOOTER_SUBMITTED_WITH));
- }
-
- for (String line : commit.getFooterLines(FOOTER_LABEL)) {
- parseApproval(psId, accountId, commit, line);
- }
-
- for (ReviewerState state : ReviewerState.values()) {
- for (String line : commit.getFooterLines(state.getFooterKey())) {
- parseReviewer(state, line);
- }
- }
- }
-
- private Change.Status parseStatus(RevCommit commit)
- throws ConfigInvalidException {
- List<String> statusLines = commit.getFooterLines(FOOTER_STATUS);
- if (statusLines.isEmpty()) {
- return null;
- } else if (statusLines.size() > 1) {
- throw expectedOneFooter(FOOTER_STATUS, statusLines);
- }
- Optional<Change.Status> status = Enums.getIfPresent(
- Change.Status.class, statusLines.get(0).toUpperCase());
- if (!status.isPresent()) {
- throw invalidFooter(FOOTER_STATUS, statusLines.get(0));
- }
- return status.get();
- }
-
- private PatchSet.Id parsePatchSetId(RevCommit commit)
- throws ConfigInvalidException {
- List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET);
- if (psIdLines.size() != 1) {
- throw expectedOneFooter(FOOTER_PATCH_SET, psIdLines);
- }
- Integer psId = Ints.tryParse(psIdLines.get(0));
- if (psId == null) {
- throw invalidFooter(FOOTER_PATCH_SET, psIdLines.get(0));
- }
- return new PatchSet.Id(changeId, psId);
- }
-
- private void parseChangeMessage(PatchSet.Id psId, Account.Id accountId,
- RevCommit commit) {
- byte[] raw = commit.getRawBuffer();
- int size = raw.length;
- Charset enc = RawParseUtils.parseEncoding(raw);
-
- int subjectStart = RawParseUtils.commitMessage(raw, 0);
- if (subjectStart < 0 || subjectStart >= size) {
- return;
- }
-
- int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
- if (subjectEnd == size) {
- return;
- }
-
- int changeMessageStart;
-
- if (raw[subjectEnd] == '\n') {
- changeMessageStart = subjectEnd + 2; //\n\n ends paragraph
- } else if (raw[subjectEnd] == '\r') {
- changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
- } else {
- return;
- }
-
- int ptr = size - 1;
- int changeMessageEnd = -1;
- while(ptr > changeMessageStart) {
- ptr = RawParseUtils.prevLF(raw, ptr, '\r');
- if (ptr == -1) {
- break;
- }
- if (raw[ptr] == '\n') {
- changeMessageEnd = ptr - 1;
- break;
- } else if (raw[ptr] == '\r') {
- changeMessageEnd = ptr - 3;
- break;
- }
- }
-
- if (ptr <= changeMessageStart) {
- return;
- }
-
- String changeMsgString = RawParseUtils.decode(enc, raw,
- changeMessageStart, changeMessageEnd + 1);
- ChangeMessage changeMessage = new ChangeMessage(
- new ChangeMessage.Key(psId.getParentKey(), commit.name()),
- accountId,
- new Timestamp(commit.getCommitterIdent().getWhen().getTime()),
- psId);
- changeMessage.setMessage(changeMsgString);
- changeMessages.put(psId, changeMessage);
- }
-
- private void parseComments()
- throws IOException, ConfigInvalidException, ParseException {
- commentNoteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
- ChangeNoteUtil.changeRefName(changeId), walk, changeId,
- commentsForBase, commentsForPs, Status.PUBLISHED);
- }
-
- private void parseApproval(PatchSet.Id psId, Account.Id accountId,
- RevCommit commit, String line) throws ConfigInvalidException {
- Table<Account.Id, String, Optional<PatchSetApproval>> curr =
- approvals.get(psId);
- if (curr == null) {
- curr = Tables.newCustomTable(
- Maps.<Account.Id, Map<String, Optional<PatchSetApproval>>>
- newHashMapWithExpectedSize(2),
- new Supplier<Map<String, Optional<PatchSetApproval>>>() {
- @Override
- public Map<String, Optional<PatchSetApproval>> get() {
- return Maps.newLinkedHashMap();
- }
- });
- approvals.put(psId, curr);
- }
-
- if (line.startsWith("-")) {
- String label = line.substring(1);
- if (!curr.contains(accountId, label)) {
- curr.put(accountId, label, Optional.<PatchSetApproval> absent());
- }
- } else {
- LabelVote l;
- try {
- l = LabelVote.parseWithEquals(line);
- } catch (IllegalArgumentException e) {
- ConfigInvalidException pe =
- parseException("invalid %s: %s", FOOTER_LABEL, line);
- pe.initCause(e);
- throw pe;
- }
- if (!curr.contains(accountId, l.getLabel())) {
- curr.put(accountId, l.getLabel(), Optional.of(new PatchSetApproval(
- new PatchSetApproval.Key(
- psId,
- accountId,
- new LabelId(l.getLabel())),
- l.getValue(),
- new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
- }
- }
- }
-
- private void parseSubmitRecords(List<String> lines)
- throws ConfigInvalidException {
- SubmitRecord rec = null;
-
- for (String line : lines) {
- int c = line.indexOf(": ");
- if (c < 0) {
- rec = new SubmitRecord();
- submitRecords.add(rec);
- int s = line.indexOf(' ');
- String statusStr = s >= 0 ? line.substring(0, s) : line;
- Optional<SubmitRecord.Status> status =
- Enums.getIfPresent(SubmitRecord.Status.class, statusStr);
- checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
- rec.status = status.get();
- if (s >= 0) {
- rec.errorMessage = line.substring(s);
- }
- } else {
- checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
- SubmitRecord.Label label = new SubmitRecord.Label();
- if (rec.labels == null) {
- rec.labels = Lists.newArrayList();
- }
- rec.labels.add(label);
-
- Optional<SubmitRecord.Label.Status> status = Enums.getIfPresent(
- SubmitRecord.Label.Status.class, line.substring(0, c));
- checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
- label.status = status.get();
- int c2 = line.indexOf(": ", c + 2);
- if (c2 >= 0) {
- label.label = line.substring(c + 2, c2);
- PersonIdent ident =
- RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
- checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
- label.appliedBy = parseIdent(ident);
- } else {
- label.label = line.substring(c + 2);
- }
- }
- }
- }
-
- private Account.Id parseIdent(RevCommit commit)
- throws ConfigInvalidException {
- return parseIdent(commit.getAuthorIdent());
- }
-
- private Account.Id parseIdent(PersonIdent ident)
- throws ConfigInvalidException {
- String email = ident.getEmailAddress();
- int at = email.indexOf('@');
- if (at >= 0) {
- String host = email.substring(at + 1, email.length());
- Integer id = Ints.tryParse(email.substring(0, at));
- if (id != null && host.equals(GERRIT_PLACEHOLDER_HOST)) {
- return new Account.Id(id);
- }
- }
- throw parseException("invalid identity, expected <id>@%s: %s",
- GERRIT_PLACEHOLDER_HOST, email);
- }
-
- private void parseReviewer(ReviewerState state, String line)
- throws ConfigInvalidException {
- PersonIdent ident = RawParseUtils.parsePersonIdent(line);
- if (ident == null) {
- throw invalidFooter(state.getFooterKey(), line);
- }
- Account.Id accountId = parseIdent(ident);
- if (!reviewers.containsKey(accountId)) {
- reviewers.put(accountId, state);
- }
- }
-
- private void pruneReviewers() {
- Iterator<Map.Entry<Account.Id, ReviewerState>> rit =
- reviewers.entrySet().iterator();
- while (rit.hasNext()) {
- Map.Entry<Account.Id, ReviewerState> e = rit.next();
- if (e.getValue() == ReviewerState.REMOVED) {
- rit.remove();
- for (Table<Account.Id, ?, ?> curr : approvals.values()) {
- curr.rowKeySet().remove(e.getKey());
- }
- }
- }
- }
-
- private ConfigInvalidException expectedOneFooter(FooterKey footer,
- List<String> actual) {
- return parseException("missing or multiple %s: %s",
- footer.getName(), actual);
- }
-
- private ConfigInvalidException invalidFooter(FooterKey footer,
- String actual) {
- return parseException("invalid %s: %s", footer.getName(), actual);
- }
-
- private void checkFooter(boolean expr, FooterKey footer, String actual)
- throws ConfigInvalidException {
- if (!expr) {
- throw invalidFooter(footer, actual);
- }
- }
-
- private ConfigInvalidException parseException(String fmt, Object... args) {
- return ChangeNotes.parseException(changeId, fmt, args);
+ return new ChangeNotes(repoManager, migration, allUsersProvider, change);
}
}
+ private final Change change;
private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
private ImmutableSetMultimap<ReviewerState, Account.Id> reviewers;
+ private ImmutableList<Account.Id> allPastReviewers;
private ImmutableList<SubmitRecord> submitRecords;
private ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessages;
private ImmutableListMultimap<PatchSet.Id, PatchLineComment> commentsForBase;
private ImmutableListMultimap<PatchSet.Id, PatchLineComment> commentsForPS;
+ private ImmutableSet<String> hashtags;
NoteMap noteMap;
+ private final AllUsersName allUsers;
+ private DraftCommentNotes draftCommentNotes;
+
@VisibleForTesting
- public ChangeNotes(GitRepositoryManager repoManager, Change change) {
- super(repoManager, change);
+ public ChangeNotes(GitRepositoryManager repoManager, NotesMigration migration,
+ AllUsersNameProvider allUsersProvider, Change change) {
+ super(repoManager, migration, change.getId());
+ this.allUsers = allUsersProvider.get();
+ this.change = new Change(change);
+ }
+
+ public Change getChange() {
+ return change;
}
public ImmutableListMultimap<PatchSet.Id, PatchSetApproval> getApprovals() {
@@ -506,6 +167,21 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
}
/**
+ *
+ * @return a ImmutableSet of all hashtags for this change sorted in alphabetical order.
+ */
+ public ImmutableSet<String> getHashtags() {
+ return ImmutableSortedSet.copyOf(hashtags);
+ }
+
+ /**
+ * @return a list of all users who have ever been a reviewer on this change.
+ */
+ public ImmutableList<Account.Id> getAllPastReviewers() {
+ return allPastReviewers;
+ }
+
+ /**
* @return submit records stored during the most recent submit; only for
* changes that were actually submitted.
*/
@@ -530,6 +206,55 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return commentsForPS;
}
+ public Table<PatchSet.Id, String, PatchLineComment> getDraftBaseComments(
+ Account.Id author) throws OrmException {
+ loadDraftComments(author);
+ return draftCommentNotes.getDraftBaseComments();
+ }
+
+ public Table<PatchSet.Id, String, PatchLineComment> getDraftPsComments(
+ Account.Id author) throws OrmException {
+ loadDraftComments(author);
+ return draftCommentNotes.getDraftPsComments();
+ }
+
+ /**
+ * If draft comments have already been loaded for this author, then they will
+ * not be reloaded. However, this method will load the comments if no draft
+ * comments have been loaded or if the caller would like the drafts for
+ * another author.
+ */
+ private void loadDraftComments(Account.Id author)
+ throws OrmException {
+ if (draftCommentNotes == null ||
+ !author.equals(draftCommentNotes.getAuthor())) {
+ draftCommentNotes = new DraftCommentNotes(repoManager, migration,
+ allUsers, getChangeId(), author);
+ draftCommentNotes.load();
+ }
+ }
+
+ public boolean containsComment(PatchLineComment c) throws OrmException {
+ if (containsCommentPublished(c)) {
+ return true;
+ }
+ loadDraftComments(c.getAuthor());
+ return draftCommentNotes.containsComment(c);
+ }
+
+ public boolean containsCommentPublished(PatchLineComment c) {
+ PatchSet.Id psId = getCommentPsId(c);
+ List<PatchLineComment> list = (c.getSide() == (short) 0)
+ ? getBaseComments().get(psId)
+ : getPatchSetComments().get(psId);
+ for (PatchLineComment l : list) {
+ if (c.getKey().equals(l.getKey())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** @return the NoteMap */
NoteMap getNoteMap() {
return noteMap;
@@ -547,10 +272,9 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
loadDefaults();
return;
}
- RevWalk walk = new RevWalk(reader);
- try {
- Change change = getChange();
- Parser parser = new Parser(change, rev, walk, repoManager);
+ try (RevWalk walk = new RevWalk(reader);
+ ChangeNotesParser parser =
+ new ChangeNotesParser(change, rev, walk, repoManager)) {
parser.parseAll();
if (parser.status != null) {
@@ -562,6 +286,11 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
commentsForPS = ImmutableListMultimap.copyOf(parser.commentsForPs);
noteMap = parser.commentNoteMap;
+ if (parser.hashtags != null) {
+ hashtags = ImmutableSet.copyOf(parser.hashtags);
+ } else {
+ hashtags = ImmutableSet.of();
+ }
ImmutableSetMultimap.Builder<ReviewerState, Account.Id> reviewers =
ImmutableSetMultimap.builder();
for (Map.Entry<Account.Id, ReviewerState> e
@@ -569,23 +298,21 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
reviewers.put(e.getValue(), e.getKey());
}
this.reviewers = reviewers.build();
+ this.allPastReviewers = ImmutableList.copyOf(parser.allPastReviewers);
submitRecords = ImmutableList.copyOf(parser.submitRecords);
- } catch (ParseException e1) {
- // TODO(yyonas): figure out how to handle this exception
- throw new IOException(e1);
- } finally {
- walk.close();
}
}
- private void loadDefaults() {
+ @Override
+ protected void loadDefaults() {
approvals = ImmutableListMultimap.of();
reviewers = ImmutableSetMultimap.of();
submitRecords = ImmutableList.of();
changeMessages = ImmutableListMultimap.of();
commentsForBase = ImmutableListMultimap.of();
commentsForPS = ImmutableListMultimap.of();
+ hashtags = ImmutableSet.of();
}
@Override
@@ -594,7 +321,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
getClass().getSimpleName() + " is read-only");
}
- private static Project.NameKey getProjectName(Change change) {
+ static Project.NameKey getProjectName(Change change) {
return change.getProject();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
new file mode 100644
index 0000000000..b5b3c746a6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -0,0 +1,436 @@
+// Copyright (C) 2014 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.ChangeNoteUtil.FOOTER_HASHTAGS;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
+
+import com.google.common.base.Enums;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+import com.google.common.collect.Tables;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.common.data.SubmitRecord;
+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.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.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.LabelVote;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+class ChangeNotesParser implements AutoCloseable {
+ final Map<Account.Id, ReviewerState> reviewers;
+ final List<Account.Id> allPastReviewers;
+ final List<SubmitRecord> submitRecords;
+ final Multimap<PatchSet.Id, PatchLineComment> commentsForPs;
+ final Multimap<PatchSet.Id, PatchLineComment> commentsForBase;
+ NoteMap commentNoteMap;
+ Change.Status status;
+ Set<String> hashtags;
+
+ private final Change.Id changeId;
+ private final ObjectId tip;
+ private final RevWalk walk;
+ private final Repository repo;
+ private final Map<PatchSet.Id,
+ Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
+ private final Multimap<PatchSet.Id, ChangeMessage> changeMessages;
+
+ ChangeNotesParser(Change change, ObjectId tip, RevWalk walk,
+ GitRepositoryManager repoManager) throws RepositoryNotFoundException,
+ IOException {
+ this.changeId = change.getId();
+ this.tip = tip;
+ this.walk = walk;
+ this.repo =
+ repoManager.openMetadataRepository(ChangeNotes.getProjectName(change));
+ approvals = Maps.newHashMap();
+ reviewers = Maps.newLinkedHashMap();
+ allPastReviewers = Lists.newArrayList();
+ submitRecords = Lists.newArrayListWithExpectedSize(1);
+ changeMessages = LinkedListMultimap.create();
+ commentsForPs = ArrayListMultimap.create();
+ commentsForBase = ArrayListMultimap.create();
+ }
+
+ @Override
+ public void close() {
+ repo.close();
+ }
+
+ void parseAll() throws ConfigInvalidException, IOException {
+ walk.markStart(walk.parseCommit(tip));
+ for (RevCommit commit : walk) {
+ parse(commit);
+ }
+ parseComments();
+ allPastReviewers.addAll(reviewers.keySet());
+ pruneReviewers();
+ }
+
+ ImmutableListMultimap<PatchSet.Id, PatchSetApproval>
+ buildApprovals() {
+ Multimap<PatchSet.Id, PatchSetApproval> result =
+ ArrayListMultimap.create(approvals.keySet().size(), 3);
+ for (Table<?, ?, Optional<PatchSetApproval>> curr
+ : approvals.values()) {
+ for (PatchSetApproval psa : Optional.presentInstances(curr.values())) {
+ result.put(psa.getPatchSetId(), psa);
+ }
+ }
+ for (Collection<PatchSetApproval> v : result.asMap().values()) {
+ Collections.sort((List<PatchSetApproval>) v, ChangeNotes.PSA_BY_TIME);
+ }
+ return ImmutableListMultimap.copyOf(result);
+ }
+
+ ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessages() {
+ for (Collection<ChangeMessage> v : changeMessages.asMap().values()) {
+ Collections.sort((List<ChangeMessage>) v, ChangeNotes.MESSAGE_BY_TIME);
+ }
+ return ImmutableListMultimap.copyOf(changeMessages);
+ }
+
+ private void parse(RevCommit commit) throws ConfigInvalidException {
+ if (status == null) {
+ status = parseStatus(commit);
+ }
+ PatchSet.Id psId = parsePatchSetId(commit);
+ Account.Id accountId = parseIdent(commit);
+ parseChangeMessage(psId, accountId, commit);
+ parseHashtags(commit);
+
+
+ if (submitRecords.isEmpty()) {
+ // Only parse the most recent set of submit records; any older ones are
+ // still there, but not currently used.
+ parseSubmitRecords(commit.getFooterLines(FOOTER_SUBMITTED_WITH));
+ }
+
+ for (String line : commit.getFooterLines(FOOTER_LABEL)) {
+ parseApproval(psId, accountId, commit, line);
+ }
+
+ for (ReviewerState state : ReviewerState.values()) {
+ for (String line : commit.getFooterLines(state.getFooterKey())) {
+ parseReviewer(state, line);
+ }
+ }
+ }
+
+ private void parseHashtags(RevCommit commit) throws ConfigInvalidException {
+ // Commits are parsed in reverse order and only the last set of hashtags should be used.
+ if (hashtags != null) {
+ return;
+ }
+ List<String> hashtagsLines = commit.getFooterLines(FOOTER_HASHTAGS);
+ if (hashtagsLines.isEmpty()) {
+ return;
+ } else if (hashtagsLines.size() > 1) {
+ throw expectedOneFooter(FOOTER_HASHTAGS, hashtagsLines);
+ } else if (hashtagsLines.get(0).isEmpty()) {
+ hashtags = ImmutableSet.of();
+ } else {
+ hashtags = Sets.newHashSet(Splitter.on(',').split(hashtagsLines.get(0)));
+ }
+ }
+
+ private Change.Status parseStatus(RevCommit commit)
+ throws ConfigInvalidException {
+ List<String> statusLines = commit.getFooterLines(FOOTER_STATUS);
+ if (statusLines.isEmpty()) {
+ return null;
+ } else if (statusLines.size() > 1) {
+ throw expectedOneFooter(FOOTER_STATUS, statusLines);
+ }
+ Optional<Change.Status> status = Enums.getIfPresent(
+ Change.Status.class, statusLines.get(0).toUpperCase());
+ if (!status.isPresent()) {
+ throw invalidFooter(FOOTER_STATUS, statusLines.get(0));
+ }
+ return status.get();
+ }
+
+ private PatchSet.Id parsePatchSetId(RevCommit commit)
+ throws ConfigInvalidException {
+ List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET);
+ if (psIdLines.size() != 1) {
+ throw expectedOneFooter(FOOTER_PATCH_SET, psIdLines);
+ }
+ Integer psId = Ints.tryParse(psIdLines.get(0));
+ if (psId == null) {
+ throw invalidFooter(FOOTER_PATCH_SET, psIdLines.get(0));
+ }
+ return new PatchSet.Id(changeId, psId);
+ }
+
+ private void parseChangeMessage(PatchSet.Id psId, Account.Id accountId,
+ RevCommit commit) {
+ byte[] raw = commit.getRawBuffer();
+ int size = raw.length;
+ Charset enc = RawParseUtils.parseEncoding(raw);
+
+ int subjectStart = RawParseUtils.commitMessage(raw, 0);
+ if (subjectStart < 0 || subjectStart >= size) {
+ return;
+ }
+
+ int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
+ if (subjectEnd == size) {
+ return;
+ }
+
+ int changeMessageStart;
+
+ if (raw[subjectEnd] == '\n') {
+ changeMessageStart = subjectEnd + 2; //\n\n ends paragraph
+ } else if (raw[subjectEnd] == '\r') {
+ changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
+ } else {
+ return;
+ }
+
+ int ptr = size - 1;
+ int changeMessageEnd = -1;
+ while(ptr > changeMessageStart) {
+ ptr = RawParseUtils.prevLF(raw, ptr, '\r');
+ if (ptr == -1) {
+ break;
+ }
+ if (raw[ptr] == '\n') {
+ changeMessageEnd = ptr - 1;
+ break;
+ } else if (raw[ptr] == '\r') {
+ changeMessageEnd = ptr - 3;
+ break;
+ }
+ }
+
+ if (ptr <= changeMessageStart) {
+ return;
+ }
+
+ String changeMsgString = RawParseUtils.decode(enc, raw,
+ changeMessageStart, changeMessageEnd + 1);
+ ChangeMessage changeMessage = new ChangeMessage(
+ new ChangeMessage.Key(psId.getParentKey(), commit.name()),
+ accountId,
+ new Timestamp(commit.getCommitterIdent().getWhen().getTime()),
+ psId);
+ changeMessage.setMessage(changeMsgString);
+ changeMessages.put(psId, changeMessage);
+ }
+
+ private void parseComments()
+ throws IOException, ConfigInvalidException {
+ commentNoteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
+ ChangeNoteUtil.changeRefName(changeId), walk, changeId,
+ commentsForBase, commentsForPs, PatchLineComment.Status.PUBLISHED);
+ }
+
+ private void parseApproval(PatchSet.Id psId, Account.Id accountId,
+ RevCommit commit, String line) throws ConfigInvalidException {
+ Table<Account.Id, String, Optional<PatchSetApproval>> curr =
+ approvals.get(psId);
+ if (curr == null) {
+ curr = Tables.newCustomTable(
+ Maps.<Account.Id, Map<String, Optional<PatchSetApproval>>>
+ newHashMapWithExpectedSize(2),
+ new Supplier<Map<String, Optional<PatchSetApproval>>>() {
+ @Override
+ public Map<String, Optional<PatchSetApproval>> get() {
+ return Maps.newLinkedHashMap();
+ }
+ });
+ approvals.put(psId, curr);
+ }
+
+ if (line.startsWith("-")) {
+ String label = line.substring(1);
+ if (!curr.contains(accountId, label)) {
+ curr.put(accountId, label, Optional.<PatchSetApproval> absent());
+ }
+ } else {
+ LabelVote l;
+ try {
+ l = LabelVote.parseWithEquals(line);
+ } catch (IllegalArgumentException e) {
+ ConfigInvalidException pe =
+ parseException("invalid %s: %s", FOOTER_LABEL, line);
+ pe.initCause(e);
+ throw pe;
+ }
+ if (!curr.contains(accountId, l.label())) {
+ curr.put(accountId, l.label(), Optional.of(new PatchSetApproval(
+ new PatchSetApproval.Key(
+ psId,
+ accountId,
+ new LabelId(l.label())),
+ l.value(),
+ new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
+ }
+ }
+ }
+
+ private void parseSubmitRecords(List<String> lines)
+ throws ConfigInvalidException {
+ SubmitRecord rec = null;
+
+ for (String line : lines) {
+ int c = line.indexOf(": ");
+ if (c < 0) {
+ rec = new SubmitRecord();
+ submitRecords.add(rec);
+ int s = line.indexOf(' ');
+ String statusStr = s >= 0 ? line.substring(0, s) : line;
+ Optional<SubmitRecord.Status> status =
+ Enums.getIfPresent(SubmitRecord.Status.class, statusStr);
+ checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
+ rec.status = status.get();
+ if (s >= 0) {
+ rec.errorMessage = line.substring(s);
+ }
+ } else {
+ checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
+ SubmitRecord.Label label = new SubmitRecord.Label();
+ if (rec.labels == null) {
+ rec.labels = Lists.newArrayList();
+ }
+ rec.labels.add(label);
+
+ Optional<SubmitRecord.Label.Status> status = Enums.getIfPresent(
+ SubmitRecord.Label.Status.class, line.substring(0, c));
+ checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
+ label.status = status.get();
+ int c2 = line.indexOf(": ", c + 2);
+ if (c2 >= 0) {
+ label.label = line.substring(c + 2, c2);
+ PersonIdent ident =
+ RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
+ checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
+ label.appliedBy = parseIdent(ident);
+ } else {
+ label.label = line.substring(c + 2);
+ }
+ }
+ }
+ }
+
+ private Account.Id parseIdent(RevCommit commit)
+ throws ConfigInvalidException {
+ return parseIdent(commit.getAuthorIdent());
+ }
+
+ private Account.Id parseIdent(PersonIdent ident)
+ throws ConfigInvalidException {
+ String email = ident.getEmailAddress();
+ int at = email.indexOf('@');
+ if (at >= 0) {
+ String host = email.substring(at + 1, email.length());
+ Integer id = Ints.tryParse(email.substring(0, at));
+ if (id != null && host.equals(GERRIT_PLACEHOLDER_HOST)) {
+ return new Account.Id(id);
+ }
+ }
+ throw parseException("invalid identity, expected <id>@%s: %s",
+ GERRIT_PLACEHOLDER_HOST, email);
+ }
+
+ private void parseReviewer(ReviewerState state, String line)
+ throws ConfigInvalidException {
+ PersonIdent ident = RawParseUtils.parsePersonIdent(line);
+ if (ident == null) {
+ throw invalidFooter(state.getFooterKey(), line);
+ }
+ Account.Id accountId = parseIdent(ident);
+ if (!reviewers.containsKey(accountId)) {
+ reviewers.put(accountId, state);
+ }
+ }
+
+ private void pruneReviewers() {
+ Iterator<Map.Entry<Account.Id, ReviewerState>> rit =
+ reviewers.entrySet().iterator();
+ while (rit.hasNext()) {
+ Map.Entry<Account.Id, ReviewerState> e = rit.next();
+ if (e.getValue() == ReviewerState.REMOVED) {
+ rit.remove();
+ for (Table<Account.Id, ?, ?> curr : approvals.values()) {
+ curr.rowKeySet().remove(e.getKey());
+ }
+ }
+ }
+ }
+
+ private ConfigInvalidException expectedOneFooter(FooterKey footer,
+ List<String> actual) {
+ return parseException("missing or multiple %s: %s",
+ footer.getName(), actual);
+ }
+
+ private ConfigInvalidException invalidFooter(FooterKey footer,
+ String actual) {
+ return parseException("invalid %s: %s", footer.getName(), actual);
+ }
+
+ private void checkFooter(boolean expr, FooterKey footer, String actual)
+ throws ConfigInvalidException {
+ if (!expr) {
+ throw invalidFooter(footer, actual);
+ }
+ }
+
+ private ConfigInvalidException parseException(String fmt, Object... args) {
+ return ChangeNotes.parseException(changeId, fmt, args);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
new file mode 100644
index 0000000000..76dfdc8a13
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
@@ -0,0 +1,332 @@
+// Copyright (C) 2014 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.checkState;
+import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
+import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+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.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+public class ChangeRebuilder {
+ private static final long TS_WINDOW_MS =
+ TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
+
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeControl.GenericFactory controlFactory;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final PatchListCache patchListCache;
+ private final ChangeUpdate.Factory updateFactory;
+ private final ChangeDraftUpdate.Factory draftUpdateFactory;
+
+ @Inject
+ ChangeRebuilder(Provider<ReviewDb> dbProvider,
+ ChangeControl.GenericFactory controlFactory,
+ IdentifiedUser.GenericFactory userFactory,
+ PatchListCache patchListCache,
+ ChangeUpdate.Factory updateFactory,
+ ChangeDraftUpdate.Factory draftUpdateFactory) {
+ this.dbProvider = dbProvider;
+ this.controlFactory = controlFactory;
+ this.userFactory = userFactory;
+ this.patchListCache = patchListCache;
+ this.updateFactory = updateFactory;
+ this.draftUpdateFactory = draftUpdateFactory;
+ }
+
+ public ListenableFuture<?> rebuildAsync(final Change change,
+ ListeningExecutorService executor, final BatchRefUpdate bru,
+ final BatchRefUpdate bruForDrafts, final Repository changeRepo,
+ final Repository allUsersRepo) {
+ return executor.submit(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ rebuild(change, bru, bruForDrafts, changeRepo, allUsersRepo);
+ return null;
+ }
+ });
+ }
+
+ public void rebuild(Change change, BatchRefUpdate bru,
+ BatchRefUpdate bruForDrafts, Repository changeRepo,
+ Repository allUsersRepo) throws NoSuchChangeException, IOException,
+ OrmException {
+ deleteRef(change, changeRepo);
+ ReviewDb db = dbProvider.get();
+ Change.Id changeId = change.getId();
+
+ // We will rebuild all events, except for draft comments, in buckets based
+ // on author and timestamp. However, all draft comments for a given change
+ // and author will be written as one commit in the notedb.
+ List<Event> events = Lists.newArrayList();
+ Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents =
+ ArrayListMultimap.create();
+
+ for (PatchSet ps : db.patchSets().byChange(changeId)) {
+ events.add(new PatchSetEvent(ps));
+ for (PatchLineComment c : db.patchComments().byPatchSet(ps.getId())) {
+ PatchLineCommentEvent e =
+ new PatchLineCommentEvent(c, change, ps, patchListCache);
+ if (c.getStatus() == Status.PUBLISHED) {
+ events.add(e);
+ } else {
+ draftCommentEvents.put(c.getAuthor(), e);
+ }
+ }
+ }
+
+ for (PatchSetApproval psa : db.patchSetApprovals().byChange(changeId)) {
+ events.add(new ApprovalEvent(psa));
+ }
+
+
+ Collections.sort(events);
+ BatchMetaDataUpdate batch = null;
+ ChangeUpdate update = null;
+ for (Event e : events) {
+ if (!sameUpdate(e, update)) {
+ if (update != null) {
+ writeToBatch(batch, update, changeRepo);
+ }
+ IdentifiedUser user = userFactory.create(dbProvider, e.who);
+ update = updateFactory.create(
+ controlFactory.controlFor(change, user), e.when);
+ update.setPatchSetId(e.psId);
+ if (batch == null) {
+ batch = update.openUpdateInBatch(bru);
+ }
+ }
+ e.apply(update);
+ }
+ if (batch != null) {
+ if (update != null) {
+ writeToBatch(batch, update, changeRepo);
+ }
+
+ // Since the BatchMetaDataUpdates generated by all ChangeRebuilders on a
+ // given project are backed by the same BatchRefUpdate, we need to
+ // synchronize on the BatchRefUpdate. Therefore, since commit on a
+ // BatchMetaDataUpdate is the only method that modifies a BatchRefUpdate,
+ // we can just synchronize this call.
+ synchronized (bru) {
+ batch.commit();
+ }
+ }
+
+ for (Account.Id author : draftCommentEvents.keys()) {
+ IdentifiedUser user = userFactory.create(dbProvider, author);
+ ChangeDraftUpdate draftUpdate = null;
+ BatchMetaDataUpdate batchForDrafts = null;
+ for (PatchLineCommentEvent e : draftCommentEvents.get(author)) {
+ if (draftUpdate == null) {
+ draftUpdate = draftUpdateFactory.create(
+ controlFactory.controlFor(change, user), e.when);
+ draftUpdate.setPatchSetId(e.psId);
+ batchForDrafts = draftUpdate.openUpdateInBatch(bruForDrafts);
+ }
+ e.applyDraft(draftUpdate);
+ }
+ writeToBatch(batchForDrafts, draftUpdate, allUsersRepo);
+ synchronized(bruForDrafts) {
+ batchForDrafts.commit();
+ }
+ }
+ }
+
+ private void deleteRef(Change change, Repository changeRepo)
+ throws IOException {
+ String refName = ChangeNoteUtil.changeRefName(change.getId());
+ RefUpdate ru = changeRepo.updateRef(refName, true);
+ ru.setForceUpdate(true);
+ RefUpdate.Result result = ru.delete();
+ switch (result) {
+ case FORCED:
+ case NEW:
+ case NO_CHANGE:
+ break;
+ default:
+ throw new IOException(
+ String.format("Failed to delete ref %s: %s", refName, result));
+ }
+ }
+
+ private void writeToBatch(BatchMetaDataUpdate batch,
+ AbstractChangeUpdate update, Repository repo) throws IOException,
+ OrmException {
+ try (ObjectInserter inserter = repo.newObjectInserter()) {
+ update.setInserter(inserter);
+ update.writeCommit(batch);
+ }
+ }
+
+ private static long round(Date when) {
+ return when.getTime() / TS_WINDOW_MS;
+ }
+
+ private static boolean sameUpdate(Event event, ChangeUpdate update) {
+ return update != null
+ && round(event.when) == round(update.getWhen())
+ && event.who.equals(update.getUser().getAccountId())
+ && event.psId.equals(update.getPatchSetId());
+ }
+
+ private abstract static class Event implements Comparable<Event> {
+ final PatchSet.Id psId;
+ final Account.Id who;
+ final Timestamp when;
+
+ protected Event(PatchSet.Id psId, Account.Id who, Timestamp when) {
+ this.psId = psId;
+ this.who = who;
+ this.when = when;
+ }
+
+ protected void checkUpdate(AbstractChangeUpdate update) {
+ checkState(Objects.equals(update.getPatchSetId(), psId),
+ "cannot apply event for %s to update for %s",
+ update.getPatchSetId(), psId);
+ checkState(when.getTime() - update.getWhen().getTime() <= TS_WINDOW_MS,
+ "event at %s outside update window starting at %s",
+ when, update.getWhen());
+ checkState(Objects.equals(update.getUser().getAccountId(), who),
+ "cannot apply event by %s to update by %s",
+ who, update.getUser().getAccountId());
+ }
+
+ abstract void apply(ChangeUpdate update) throws OrmException;
+
+ @Override
+ public int compareTo(Event other) {
+ return ComparisonChain.start()
+ // TODO(dborowitz): Smarter bucketing: pick a bucket start time T and
+ // include all events up to T + TS_WINDOW_MS but no further.
+ // Interleaving different authors complicates things.
+ .compare(round(when), round(other.when))
+ .compare(who.get(), other.who.get())
+ .compare(psId.get(), other.psId.get())
+ .result();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("psId", psId)
+ .add("who", who)
+ .add("when", when)
+ .toString();
+ }
+ }
+
+ private static class ApprovalEvent extends Event {
+ private PatchSetApproval psa;
+
+ ApprovalEvent(PatchSetApproval psa) {
+ super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted());
+ this.psa = psa;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) {
+ checkUpdate(update);
+ update.putApproval(psa.getLabel(), psa.getValue());
+ }
+ }
+
+ private static class PatchSetEvent extends Event {
+ private final PatchSet ps;
+
+ PatchSetEvent(PatchSet ps) {
+ super(ps.getId(), ps.getUploader(), ps.getCreatedOn());
+ this.ps = ps;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) {
+ checkUpdate(update);
+ if (ps.getPatchSetId() == 1) {
+ update.setSubject("Create change");
+ } else {
+ update.setSubject("Create patch set " + ps.getPatchSetId());
+ }
+ }
+ }
+
+ private static class PatchLineCommentEvent extends Event {
+ public final PatchLineComment c;
+ private final Change change;
+ private final PatchSet ps;
+ private final PatchListCache cache;
+
+ PatchLineCommentEvent(PatchLineComment c, Change change, PatchSet ps,
+ PatchListCache cache) {
+ super(getCommentPsId(c), c.getAuthor(), c.getWrittenOn());
+ this.c = c;
+ this.change = change;
+ this.ps = ps;
+ this.cache = cache;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) throws OrmException {
+ checkUpdate(update);
+ if (c.getRevId() == null) {
+ setCommentRevId(c, cache, change, ps);
+ }
+ update.insertComment(c);
+ }
+
+ void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException {
+ if (c.getRevId() == null) {
+ setCommentRevId(c, cache, change, ps);
+ }
+ draftUpdate.insertComment(c);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index be3a44d77a..7302425bc6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
@@ -22,6 +23,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WI
import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
@@ -31,11 +33,12 @@ import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
+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.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.project.ChangeControl;
@@ -58,14 +61,17 @@ import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
- * A single delta to apply atomically to a change.
+ * A delta to apply to a change.
* <p>
- * This delta becomes a single commit on the notes branch, so there are
- * limitations on the set of modifications that can be handled in a single
- * update. In particular, there is a single author and timestamp for each
- * update.
+ * This delta will become two unique commits: one in the AllUsers repo that will
+ * contain the draft comments on this change and one in the notes branch that
+ * will contain approvals, reviewers, change status, subject, submit records,
+ * the change message, and published comments. There are limitations on the set
+ * of modifications that can be handled in a single update. In particular, there
+ * is a single author and timestamp for each update.
* <p>
* This class is not thread-safe.
*/
@@ -87,35 +93,45 @@ public class ChangeUpdate extends AbstractChangeUpdate {
private final CommentsInNotesUtil commentsUtil;
private List<PatchLineComment> commentsForBase;
private List<PatchLineComment> commentsForPs;
+ private Set<String> hashtags;
private String changeMessage;
+ private ChangeNotes notes;
+
+ private final ChangeDraftUpdate.Factory draftUpdateFactory;
+ private ChangeDraftUpdate draftUpdate;
@AssistedInject
private ChangeUpdate(
@GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
GitRepositoryManager repoManager,
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
+ ChangeDraftUpdate.Factory draftUpdateFactory,
ProjectCache projectCache,
- IdentifiedUser user,
@Assisted ChangeControl ctl,
CommentsInNotesUtil commentsUtil) {
- this(serverIdent, repoManager, migration, accountCache, updateFactory,
+ this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
+ updateFactory, draftUpdateFactory,
projectCache, ctl, serverIdent.getWhen(), commentsUtil);
}
@AssistedInject
private ChangeUpdate(
@GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
GitRepositoryManager repoManager,
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
+ ChangeDraftUpdate.Factory draftUpdateFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
@Assisted Date when,
CommentsInNotesUtil commentsUtil) {
- this(serverIdent, repoManager, migration, accountCache, updateFactory, ctl,
+ this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
+ updateFactory, draftUpdateFactory, ctl,
when,
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
commentsUtil);
@@ -128,15 +144,19 @@ public class ChangeUpdate extends AbstractChangeUpdate {
@AssistedInject
private ChangeUpdate(
@GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName,
GitRepositoryManager repoManager,
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
+ ChangeDraftUpdate.Factory draftUpdateFactory,
@Assisted ChangeControl ctl,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator,
CommentsInNotesUtil commentsUtil) {
- super(migration, repoManager, updateFactory, ctl, serverIdent, when);
+ super(migration, repoManager, updateFactory, ctl, serverIdent,
+ anonymousCowardName, when);
+ this.draftUpdateFactory = draftUpdateFactory;
this.accountCache = accountCache;
this.commentsUtil = commentsUtil;
this.approvals = Maps.newTreeMap(labelNameComparator);
@@ -174,20 +194,152 @@ public class ChangeUpdate extends AbstractChangeUpdate {
this.changeMessage = changeMessage;
}
- public void putComment(PatchLineComment comment) {
- checkArgument(psId != null,
- "setPatchSetId must be called before putComment");
- checkArgument(getCommentPsId(comment).equals(psId),
- "Comment on %s doesn't match previous patch set %s",
- getCommentPsId(comment), psId);
- checkArgument(comment.getRevId() != null);
- if (comment.getSide() == 0) {
- commentsForBase.add(comment);
+ public void insertComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ insertDraftComment(comment);
} else {
- commentsForPs.add(comment);
+ insertPublishedComment(comment);
}
}
+ public void upsertComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ upsertDraftComment(comment);
+ } else {
+ deleteDraftCommentIfPresent(comment);
+ upsertPublishedComment(comment);
+ }
+ }
+
+ public void updateComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ updateDraftComment(comment);
+ } else {
+ deleteDraftCommentIfPresent(comment);
+ updatePublishedComment(comment);
+ }
+ }
+
+ public void deleteComment(PatchLineComment comment) throws OrmException {
+ if (comment.getStatus() == Status.DRAFT) {
+ deleteDraftComment(comment);
+ } else {
+ throw new IllegalArgumentException("Cannot delete a published comment.");
+ }
+ }
+
+ private void insertPublishedComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ if (notes == null) {
+ notes = getChangeNotes().load();
+ }
+ if (migration.readChanges()) {
+ checkArgument(!notes.containsComment(c),
+ "A comment already exists with the same key as the following comment,"
+ + " so we cannot insert this comment: %s", c);
+ }
+ if (c.getSide() == 0) {
+ commentsForBase.add(c);
+ } else {
+ commentsForPs.add(c);
+ }
+ }
+
+ private void insertDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.insertComment(c);
+ }
+
+ private void upsertPublishedComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ if (notes == null) {
+ notes = getChangeNotes().load();
+ }
+ // This could allow callers to update a published comment if migration.write
+ // is on and migration.readComments is off because we will not be able to
+ // verify that the comment didn't already exist as a published comment
+ // since we don't have a ReviewDb.
+ if (migration.readChanges()) {
+ checkArgument(!notes.containsCommentPublished(c),
+ "Cannot update a comment that has already been published and saved");
+ }
+ if (c.getSide() == 0) {
+ commentsForBase.add(c);
+ } else {
+ commentsForPs.add(c);
+ }
+ }
+
+ private void upsertDraftComment(PatchLineComment c) {
+ createDraftUpdateIfNull(c);
+ draftUpdate.upsertComment(c);
+ }
+
+ private void updatePublishedComment(PatchLineComment c) throws OrmException {
+ verifyComment(c);
+ if (notes == null) {
+ notes = getChangeNotes().load();
+ }
+ // See comment above in upsertPublishedComment() about potential risk with
+ // this check.
+ if (migration.readChanges()) {
+ checkArgument(!notes.containsCommentPublished(c),
+ "Cannot update a comment that has already been published and saved");
+ }
+ if (c.getSide() == 0) {
+ commentsForBase.add(c);
+ } else {
+ commentsForPs.add(c);
+ }
+ }
+
+ private void updateDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.updateComment(c);
+ }
+
+ private void deleteDraftComment(PatchLineComment c) throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.deleteComment(c);
+ }
+
+ private void deleteDraftCommentIfPresent(PatchLineComment c)
+ throws OrmException {
+ createDraftUpdateIfNull(c);
+ draftUpdate.deleteCommentIfPresent(c);
+ }
+
+ private void createDraftUpdateIfNull(PatchLineComment c) {
+ if (draftUpdate == null) {
+ draftUpdate = draftUpdateFactory.create(ctl, when);
+ if (psId != null) {
+ draftUpdate.setPatchSetId(psId);
+ } else {
+ draftUpdate.setPatchSetId(getCommentPsId(c));
+ }
+ }
+ }
+
+ private void verifyComment(PatchLineComment c) {
+ checkArgument(psId != null,
+ "setPatchSetId must be called first");
+ checkArgument(getCommentPsId(c).equals(psId),
+ "Comment on %s doesn't match previous patch set %s",
+ getCommentPsId(c), psId);
+ checkArgument(c.getRevId() != null);
+ checkArgument(c.getStatus() == Status.PUBLISHED,
+ "Cannot add a draft comment to a ChangeUpdate. Use a ChangeDraftUpdate"
+ + " for draft comments");
+ checkArgument(c.getAuthor().equals(getUser().getAccountId()),
+ "The author for the following comment does not match the author of"
+ + " this ChangeDraftUpdate (%s): %s", getUser().getAccountId(), c);
+
+ }
+
+ public void setHashtags(Set<String> hashtags) {
+ this.hashtags = hashtags;
+ }
+
public void putReviewer(Account.Id reviewer, ReviewerState type) {
checkArgument(type != ReviewerState.REMOVED, "invalid ReviewerType");
reviewers.put(reviewer, type);
@@ -235,14 +387,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
public RevCommit commit() throws IOException {
BatchMetaDataUpdate batch = openUpdate();
try {
- CommitBuilder builder = new CommitBuilder();
- if (migration.write()) {
- ObjectId treeId = storeCommentsInNotes();
- if (treeId != null) {
- builder.setTreeId(treeId);
- }
+ writeCommit(batch);
+ if (draftUpdate != null) {
+ draftUpdate.commit();
}
- batch.write(builder);
RevCommit c = batch.commit();
return c;
} catch (OrmException e) {
@@ -253,6 +401,19 @@ public class ChangeUpdate extends AbstractChangeUpdate {
}
@Override
+ public void writeCommit(BatchMetaDataUpdate batch) throws OrmException,
+ IOException {
+ CommitBuilder builder = new CommitBuilder();
+ if (migration.writeChanges()) {
+ ObjectId treeId = storeCommentsInNotes();
+ if (treeId != null) {
+ builder.setTreeId(treeId);
+ }
+ }
+ batch.write(this, builder);
+ }
+
+ @Override
protected String getRefName() {
return ChangeNoteUtil.changeRefName(getChange().getId());
}
@@ -285,6 +446,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
addFooter(msg, FOOTER_STATUS, status.name().toLowerCase());
}
+ if (hashtags != null) {
+ addFooter(msg, FOOTER_HASHTAGS, Joiner.on(",").join(hashtags));
+ }
+
for (Map.Entry<Account.Id, ReviewerState> e : reviewers.entrySet()) {
Account account = accountCache.get(e.getKey()).getAccount();
PersonIdent ident = newIdent(account, when);
@@ -297,8 +462,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
if (!e.getValue().isPresent()) {
addFooter(msg, FOOTER_LABEL, '-', e.getKey());
} else {
- addFooter(msg, FOOTER_LABEL,
- new LabelVote(e.getKey(), e.getValue().get()).formatWithEquals());
+ addFooter(msg, FOOTER_LABEL, LabelVote.create(
+ e.getKey(), e.getValue().get()).formatWithEquals());
}
}
@@ -338,12 +503,14 @@ public class ChangeUpdate extends AbstractChangeUpdate {
private boolean isEmpty() {
return approvals.isEmpty()
- && reviewers.isEmpty()
+ && changeMessage == null
&& commentsForBase.isEmpty()
&& commentsForPs.isEmpty()
+ && reviewers.isEmpty()
&& status == null
+ && subject == null
&& submitRecords == null
- && changeMessage == null;
+ && hashtags == null;
}
private static StringBuilder addFooter(StringBuilder sb, FooterKey footer) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
index ede979feed..f3e03c0110 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
@@ -18,11 +18,12 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.CommentRange;
@@ -33,13 +34,14 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Constants;
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;
@@ -48,11 +50,11 @@ import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.GitDateFormatter;
+import org.eclipse.jgit.util.GitDateFormatter.Format;
import org.eclipse.jgit.util.GitDateParser;
import org.eclipse.jgit.util.MutableInteger;
import org.eclipse.jgit.util.QuotedString;
import org.eclipse.jgit.util.RawParseUtils;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -69,6 +71,7 @@ import java.util.List;
* Utility functions to parse PatchLineComments out of a note byte array and
* store a list of PatchLineComments in the form of a note (in a byte array).
**/
+@Singleton
public class CommentsInNotesUtil {
private static final String AUTHOR = "Author";
private static final String BASE_PATCH_SET = "Base-for-patch-set";
@@ -79,6 +82,7 @@ public class CommentsInNotesUtil {
private static final String PATCH_SET = "Patch-set";
private static final String REVISION = "Revision";
private static final String UUID = "UUID";
+ private static final int MAX_NOTE_SZ = 25 << 20;
public static NoteMap parseCommentsFromNotes(Repository repo, String refName,
RevWalk walk, Change.Id changeId,
@@ -90,14 +94,16 @@ public class CommentsInNotesUtil {
if (ref == null) {
return null;
}
+
+ ObjectReader reader = walk.getObjectReader();
RevCommit commit = walk.parseCommit(ref.getObjectId());
- NoteMap noteMap = NoteMap.read(walk.getObjectReader(), commit);
+ NoteMap noteMap = NoteMap.read(reader, commit);
for (Note note: noteMap) {
- byte[] bytes = walk.getObjectReader().open(
- note.getData(), Constants.OBJ_BLOB).getBytes();
+ byte[] bytes =
+ reader.open(note.getData(), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
List<PatchLineComment> result = parseNote(bytes, changeId, status);
- if ((result == null) || (result.isEmpty())) {
+ if (result == null || result.isEmpty()) {
continue;
}
PatchSet.Id psId = result.get(0).getKey().getParentKey().getParentKey();
@@ -166,7 +172,7 @@ public class CommentsInNotesUtil {
throw parseException(changeId, "could not parse %s", FILE);
}
- CommentRange range = parseCommentRange(note, curr, changeId);
+ CommentRange range = parseCommentRange(note, curr);
if (range == null) {
throw parseException(changeId, "could not parse %s", COMMENT_RANGE);
}
@@ -221,17 +227,19 @@ public class CommentsInNotesUtil {
* 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,
- Change.Id changeId) throws ConfigInvalidException {
+ private static CommentRange parseCommentRange(byte[] note, MutableInteger ptr) {
CommentRange range = new CommentRange(-1, -1, -1, -1);
int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
if (startLine == 0) {
- return null;
+ range.setEndLine(0);
+ ptr.value += 1;
+ return range;
}
if (note[ptr.value] == '\n') {
range.setEndLine(startLine);
+ ptr.value += 1;
return range;
} else if (note[ptr.value] == ':') {
range.setStartLine(startLine);
@@ -368,7 +376,7 @@ public class CommentsInNotesUtil {
private PersonIdent newIdent(Account author, Date when) {
return new PersonIdent(
- author.getFullName(),
+ new AccountInfo(author).getName(anonymousCowardName),
author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
when, serverIdent.getTimeZone());
}
@@ -389,7 +397,7 @@ public class CommentsInNotesUtil {
}
private void appendHeaderField(PrintWriter writer,
- String field, String value) throws IOException {
+ String field, String value) {
writer.print(field);
writer.print(": ");
writer.print(value);
@@ -410,13 +418,15 @@ public class CommentsInNotesUtil {
private final AccountCache accountCache;
private final PersonIdent serverIdent;
+ private final String anonymousCowardName;
- @VisibleForTesting
@Inject
public CommentsInNotesUtil(AccountCache accountCache,
- @GerritPersonIdent PersonIdent serverIdent) {
+ @GerritPersonIdent PersonIdent serverIdent,
+ @AnonymousCowardName String anonymousCowardName) {
this.accountCache = accountCache;
this.serverIdent = serverIdent;
+ this.anonymousCowardName = anonymousCowardName;
}
/**
@@ -432,8 +442,7 @@ public class CommentsInNotesUtil {
* for no comments.
* @return the note. Null if there are no comments in the list.
*/
- public byte[] buildNote(List<PatchLineComment> comments)
- throws OrmException, IOException {
+ public byte[] buildNote(List<PatchLineComment> comments) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
OutputStreamWriter streamWriter = new OutputStreamWriter(buf, UTF_8);
PrintWriter writer = new PrintWriter(streamWriter);
@@ -454,11 +463,11 @@ public class CommentsInNotesUtil {
checkArgument(psId.equals(currentPsId),
"All comments being added must all have the same PatchSet.Id. The"
+ "comment below does not have the same PatchSet.Id as the others "
- + "(%d).\n%s", psId.toString(), c.toString());
+ + "(%s).\n%s", psId.toString(), c.toString());
checkArgument(side == c.getSide(),
"All comments being added must all have the same side. The"
+ "comment below does not have the same side as the others "
- + "(%d).\n%s", side, c.toString());
+ + "(%s).\n%s", side, c.toString());
String commentFilename =
QuotedString.GIT_PATH.quote(c.getKey().getParentKey().getFileName());
@@ -517,15 +526,13 @@ public class CommentsInNotesUtil {
public void writeCommentsToNoteMap(NoteMap noteMap,
List<PatchLineComment> allComments, ObjectInserter inserter)
- throws OrmException, IOException {
+ throws IOException {
checkArgument(!allComments.isEmpty(),
"No comments to write; to delete, use removeNoteFromNoteMap().");
- ObjectId commitOID =
+ ObjectId commit =
ObjectId.fromString(allComments.get(0).getRevId().get());
- Collections.sort(allComments, ChangeNotes.PatchLineCommentComparator);
- byte[] note = buildNote(allComments);
- ObjectId noteId = inserter.insert(Constants.OBJ_BLOB, note, 0, note.length);
- noteMap.set(commitOID, noteId);
+ Collections.sort(allComments, ChangeNotes.PLC_ORDER);
+ noteMap.set(commit, inserter.insert(OBJ_BLOB, buildNote(allComments)));
}
public void removeNote(NoteMap noteMap, RevId commitId)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
new file mode 100644
index 0000000000..20d6c4aa3d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -0,0 +1,171 @@
+// Copyright (C) 2014 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.CommentsInNotesUtil.getCommentPsId;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Table;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
+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.config.AllUsersNameProvider;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+/**
+ * View of the draft comments for a single {@link Change} based on the log of
+ * its drafts branch.
+ */
+public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
+ @Singleton
+ public static class Factory {
+ private final GitRepositoryManager repoManager;
+ private final NotesMigration migration;
+ private final AllUsersName draftsProject;
+
+ @VisibleForTesting
+ @Inject
+ public Factory(GitRepositoryManager repoManager,
+ NotesMigration migration,
+ AllUsersNameProvider allUsers) {
+ this.repoManager = repoManager;
+ this.migration = migration;
+ this.draftsProject = allUsers.get();
+ }
+
+ public DraftCommentNotes create(Change.Id changeId, Account.Id accountId) {
+ return new DraftCommentNotes(repoManager, migration, draftsProject,
+ changeId, accountId);
+ }
+ }
+
+ private final AllUsersName draftsProject;
+ private final Account.Id author;
+
+ private final Table<PatchSet.Id, String, PatchLineComment> draftBaseComments;
+ private final Table<PatchSet.Id, String, PatchLineComment> draftPsComments;
+ private NoteMap noteMap;
+
+ DraftCommentNotes(GitRepositoryManager repoManager, NotesMigration migration,
+ AllUsersName draftsProject, Change.Id changeId, Account.Id author) {
+ super(repoManager, migration, changeId);
+ this.draftsProject = draftsProject;
+ this.author = author;
+
+ this.draftBaseComments = HashBasedTable.create();
+ this.draftPsComments = HashBasedTable.create();
+ }
+
+ public NoteMap getNoteMap() {
+ return noteMap;
+ }
+
+ public Account.Id getAuthor() {
+ return author;
+ }
+
+ /**
+ * @return a defensive copy of the table containing all draft comments
+ * on this change with side == 0. The row key is the comment's PatchSet.Id,
+ * the column key is the comment's UUID, and the value is the comment.
+ */
+ public Table<PatchSet.Id, String, PatchLineComment>
+ getDraftBaseComments() {
+ return HashBasedTable.create(draftBaseComments);
+ }
+
+ /**
+ * @return a defensive copy of the table containing all draft comments
+ * on this change with side == 1. The row key is the comment's PatchSet.Id,
+ * the column key is the comment's UUID, and the value is the comment.
+ */
+ public Table<PatchSet.Id, String, PatchLineComment>
+ getDraftPsComments() {
+ return HashBasedTable.create(draftPsComments);
+ }
+
+ public boolean containsComment(PatchLineComment c) {
+ Table<PatchSet.Id, String, PatchLineComment> t =
+ c.getSide() == (short) 0
+ ? getDraftBaseComments()
+ : getDraftPsComments();
+ return t.contains(getCommentPsId(c), c.getKey().get());
+ }
+
+ @Override
+ protected String getRefName() {
+ return RefNames.refsDraftComments(author, getChangeId());
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ ObjectId rev = getRevision();
+ if (rev == null) {
+ return;
+ }
+
+ try (RevWalk walk = new RevWalk(reader);
+ DraftCommentNotesParser parser = new DraftCommentNotesParser(
+ getChangeId(), walk, rev, repoManager, draftsProject, author)) {
+ parser.parseDraftComments();
+
+ buildCommentTable(draftBaseComments, parser.draftBaseComments);
+ buildCommentTable(draftPsComments, parser.draftPsComments);
+ noteMap = parser.noteMap;
+ }
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException {
+ throw new UnsupportedOperationException(
+ getClass().getSimpleName() + " is read-only");
+ }
+
+ @Override
+ protected void loadDefaults() {
+ // Do nothing; tables are final and initialized in constructor.
+ }
+
+ @Override
+ protected Project.NameKey getProjectName() {
+ return draftsProject;
+ }
+
+ private void buildCommentTable(
+ Table<PatchSet.Id, String, PatchLineComment> commentTable,
+ Multimap<PatchSet.Id, PatchLineComment> allComments) {
+ for (PatchLineComment c : allComments.values()) {
+ commentTable.put(getCommentPsId(c), c.getKey().get(), c);
+ }
+ }
+
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java
new file mode 100644
index 0000000000..4b3fbdfd31
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotesParser.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2014 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.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+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.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+class DraftCommentNotesParser implements AutoCloseable {
+ final Multimap<PatchSet.Id, PatchLineComment> draftBaseComments;
+ final Multimap<PatchSet.Id, PatchLineComment> draftPsComments;
+ NoteMap noteMap;
+
+ private final Change.Id changeId;
+ private final ObjectId tip;
+ private final RevWalk walk;
+ private final Repository repo;
+ private final Account.Id author;
+
+ DraftCommentNotesParser(Change.Id changeId, RevWalk walk, ObjectId tip,
+ GitRepositoryManager repoManager, AllUsersName draftsProject,
+ Account.Id author) throws RepositoryNotFoundException, IOException {
+ this.changeId = changeId;
+ this.walk = walk;
+ this.tip = tip;
+ this.repo = repoManager.openMetadataRepository(draftsProject);
+ this.author = author;
+
+ draftBaseComments = ArrayListMultimap.create();
+ draftPsComments = ArrayListMultimap.create();
+ }
+
+ @Override
+ public void close() {
+ repo.close();
+ }
+
+ void parseDraftComments() throws IOException, ConfigInvalidException {
+ walk.markStart(walk.parseCommit(tip));
+ noteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
+ RefNames.refsDraftComments(author, changeId),
+ walk, changeId, draftBaseComments,
+ draftPsComments, PatchLineComment.Status.DRAFT);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
index 174997cd73..4193fe4dd3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
@@ -20,5 +20,6 @@ public class NoteDbModule extends FactoryModule {
@Override
public void configure() {
factory(ChangeUpdate.Factory.class);
+ factory(ChangeDraftUpdate.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
index 36f685d70f..e6d9ff89bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -14,61 +14,103 @@
package com.google.gerrit.server.notedb;
-import com.google.common.annotations.VisibleForTesting;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
+import java.util.HashSet;
+import java.util.Set;
+
/**
- * Holds the current state of the notes DB migration.
+ * Holds the current state of the NoteDb migration.
+ * <p>
+ * The migration will proceed one root entity type at a time. A <em>root
+ * entity</em> is an entity stored in ReviewDb whose key's
+ * {@code getParentKey()} method returns null. For an example of the entity
+ * hierarchy rooted at Change, see the diagram in
+ * {@code com.google.gerrit.reviewdb.client.Change}.
+ * <p>
+ * During a transitional period, each root entity group from ReviewDb may be
+ * either <em>written to</em> or <em>both written to and read from</em> NoteDb.
* <p>
- * During a transitional period, different subsets of the former gwtorm DB
- * functionality may be enabled on the site, possibly only for reading or
- * writing.
+ * This class controls the state of the migration according to options in
+ * {@code gerrit.config}. In general, any changes to these options should only
+ * be made by adventurous administrators, who know what they're doing, on
+ * non-production data, for the purposes of testing the NoteDb implementation.
+ * Changing options quite likely requires re-running {@code RebuildNoteDb}. For
+ * these reasons, the options remain undocumented.
*/
@Singleton
public class NotesMigration {
- @VisibleForTesting
- static NotesMigration allEnabled() {
+ private static enum Table {
+ CHANGES;
+
+ private String key() {
+ return name().toLowerCase();
+ }
+ }
+
+ private static final String NOTEDB = "notedb";
+ private static final String READ = "read";
+ private static final String WRITE = "write";
+
+ private static void checkConfig(Config cfg) {
+ Set<String> keys = new HashSet<>();
+ for (Table t : Table.values()) {
+ keys.add(t.key());
+ }
+ for (String t : cfg.getSubsections(NOTEDB)) {
+ checkArgument(keys.contains(t.toLowerCase()),
+ "invalid notedb table: %s", t);
+ for (String key : cfg.getNames(NOTEDB, t)) {
+ String lk = key.toLowerCase();
+ checkArgument(lk.equals(WRITE) || lk.equals(READ),
+ "invalid notedb key: %s.%s", t, key);
+ }
+ boolean write = cfg.getBoolean(NOTEDB, t, WRITE, false);
+ boolean read = cfg.getBoolean(NOTEDB, t, READ, false);
+ checkArgument(!(read && !write),
+ "must have write enabled when read enabled: %s", t);
+ }
+ }
+
+ public static NotesMigration allEnabled() {
+ return new NotesMigration(allEnabledConfig());
+ }
+
+ public static Config allEnabledConfig() {
Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
- cfg.setBoolean("notedb", "changeMessages", "read", true);
- cfg.setBoolean("notedb", "publishedComments", "read", true);
- return new NotesMigration(cfg);
+ for (Table t : Table.values()) {
+ cfg.setBoolean(NOTEDB, t.key(), WRITE, true);
+ cfg.setBoolean(NOTEDB, t.key(), READ, true);
+ }
+ return cfg;
}
- private final boolean write;
- private final boolean readPatchSetApprovals;
- private final boolean readChangeMessages;
- private final boolean readPublishedComments;
+ private final boolean writeChanges;
+ private final boolean readChanges;
@Inject
NotesMigration(@GerritServerConfig Config cfg) {
- write = cfg.getBoolean("notedb", null, "write", false);
- readPatchSetApprovals =
- cfg.getBoolean("notedb", "patchSetApprovals", "read", false);
- readChangeMessages =
- cfg.getBoolean("notedb", "changeMessages", "read", false);
- readPublishedComments =
- cfg.getBoolean("notedb", "publishedComments", "read", false);
- }
-
- public boolean write() {
- return write;
+ checkConfig(cfg);
+ writeChanges = cfg.getBoolean(NOTEDB, Table.CHANGES.key(), WRITE, false);
+ readChanges = cfg.getBoolean(NOTEDB, Table.CHANGES.key(), READ, false);
}
- public boolean readPatchSetApprovals() {
- return readPatchSetApprovals;
+ public boolean enabled() {
+ return writeChanges()
+ || readChanges();
}
- public boolean readChangeMessages() {
- return readChangeMessages;
+ public boolean writeChanges() {
+ return writeChanges;
}
- public boolean readPublishedComments() {
- return readPublishedComments;
+ public boolean readChanges() {
+ return readChanges;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
index 009b4920a6..ffcce14654 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -188,10 +188,11 @@ class IntraLineLoader extends CacheLoader<IntraLineDiffKey, IntraLineDiff> {
int nb = lf + 1;
int p = 0;
while (p < ae - ab) {
- if (cmp.equals(a, ab + p, a, ab + p))
+ if (cmp.equals(a, ab + p, a, ab + p)) {
p++;
- else
+ } else {
break;
+ }
}
if (p == ae - ab) {
ab = nb;
@@ -224,10 +225,11 @@ class IntraLineLoader extends CacheLoader<IntraLineDiffKey, IntraLineDiff> {
int nb = lf + 1;
int p = 0;
while (p < be - bb) {
- if (cmp.equals(b, bb + p, b, bb + p))
+ if (cmp.equals(b, bb + p, b, bb + p)) {
p++;
- else
+ } else {
break;
+ }
}
if (p == be - bb) {
bb = nb;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
index c65262f6f3..8740a6b75f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
@@ -48,18 +48,17 @@ public class PatchFile {
this.repo = repo;
this.entry = patchList.get(fileName);
- final ObjectReader reader = repo.newObjectReader();
- try {
- final RevWalk rw = new RevWalk(reader);
+ try (ObjectReader reader = repo.newObjectReader();
+ RevWalk rw = new RevWalk(reader)) {
final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
if (Patch.COMMIT_MSG.equals(fileName)) {
if (patchList.isAgainstParent()) {
a = Text.EMPTY;
} else {
- a = Text.forCommit(repo, reader, patchList.getOldId());
+ a = Text.forCommit(reader, patchList.getOldId());
}
- b = Text.forCommit(repo, reader, bCommit);
+ b = Text.forCommit(reader, bCommit);
aTree = null;
bTree = null;
@@ -74,8 +73,6 @@ public class PatchFile {
}
bTree = bCommit.getTree();
}
- } finally {
- reader.close();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
index 7d449124ed..4fff619632 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -150,12 +150,13 @@ public class PatchList implements Serializable {
while (low < high) {
final int mid = (low + high) >>> 1;
final int cmp = patches[mid].getNewName().compareTo(fileName);
- if (cmp < 0)
+ if (cmp < 0) {
low = mid + 1;
- else if (cmp == 0)
+ } else if (cmp == 0) {
return mid;
- else
+ } else {
high = mid;
+ }
}
return -(low + 1);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 6c769f742a..52856c6e6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -27,6 +27,7 @@ import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
+import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@@ -79,7 +80,7 @@ public class PatchListCacheImpl implements PatchListCache {
public PatchList get(PatchListKey key) throws PatchListNotAvailableException {
try {
return fileCache.get(key);
- } catch (ExecutionException e) {
+ } catch (ExecutionException | LargeObjectException e) {
PatchListLoader.log.warn("Error computing " + key, e);
throw new PatchListNotAvailableException(e.getCause());
}
@@ -104,7 +105,7 @@ public class PatchListCacheImpl implements PatchListCache {
if (computeIntraline) {
try {
return intraCache.get(key);
- } catch (ExecutionException e) {
+ } catch (ExecutionException | LargeObjectException e) {
IntraLineLoader.log.warn("Error computing " + key, e);
return new IntraLineDiff(IntraLineDiff.Status.ERROR);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index 31f5e96b4d..e7c56be7d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -24,9 +24,9 @@ import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.Patch.PatchType;
+import com.google.gerrit.reviewdb.client.PatchSet;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.lib.Constants;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
index ff038403cc..9867b118e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -22,8 +22,8 @@ import static org.eclipse.jgit.lib.ObjectIdSerialization.writeCanBeNull;
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 5444e4a318..0b6688c737 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -87,7 +87,6 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
private final ExecutorService diffExecutor;
private final long timeoutMillis;
-
@Inject
PatchListLoader(GitRepositoryManager mgr,
PatchListCache plc,
@@ -134,9 +133,9 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
private PatchList readPatchList(final PatchListKey key, final Repository repo)
throws IOException, PatchListNotAvailableException {
final RawTextComparator cmp = comparatorFor(key.getWhitespace());
- final ObjectReader reader = repo.newObjectReader();
- try {
- final RevWalk rw = new RevWalk(reader);
+ try (ObjectReader reader = repo.newObjectReader();
+ RevWalk rw = new RevWalk(reader);
+ DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
final RevCommit b = rw.parseCommit(key.getNewId());
final RevObject a = aFor(key, repo, rw, b);
@@ -145,7 +144,7 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
// This is a merge commit, compared to its ancestor.
//
final PatchListEntry[] entries = new PatchListEntry[1];
- entries[0] = newCommitMessage(cmp, repo, reader, null, b);
+ entries[0] = newCommitMessage(cmp, reader, null, b);
return new PatchList(a, b, true, entries);
}
@@ -156,7 +155,6 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
RevTree aTree = rw.parseTree(a);
RevTree bTree = b.getTree();
- DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
df.setRepository(repo);
df.setDiffComparator(cmp);
df.setDetectRenames(true);
@@ -176,7 +174,7 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
: null;
int cnt = diffEntries.size();
List<PatchListEntry> entries = new ArrayList<>();
- entries.add(newCommitMessage(cmp, repo, reader, //
+ entries.add(newCommitMessage(cmp, reader,
againstParent ? null : aCommit, b));
for (int i = 0; i < cnt; i++) {
DiffEntry diffEntry = diffEntries.get(i);
@@ -188,8 +186,6 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
}
return new PatchList(a, b, againstParent,
entries.toArray(new PatchListEntry[entries.size()]));
- } finally {
- reader.close();
}
}
@@ -232,7 +228,7 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
}
private PatchListEntry newCommitMessage(final RawTextComparator cmp,
- final Repository db, final ObjectReader reader,
+ final ObjectReader reader,
final RevCommit aCommit, final RevCommit bCommit) throws IOException {
StringBuilder hdr = new StringBuilder();
@@ -253,8 +249,8 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
hdr.append("+++ b/").append(Patch.COMMIT_MSG).append("\n");
Text aText =
- aCommit != null ? Text.forCommit(db, reader, aCommit) : Text.EMPTY;
- Text bText = Text.forCommit(db, reader, bCommit);
+ aCommit != null ? Text.forCommit(reader, aCommit) : Text.EMPTY;
+ Text bText = Text.forCommit(reader, bCommit);
byte[] rawHdr = hdr.toString().getBytes("UTF-8");
RawText aRawText = new RawText(aText.getContent());
@@ -327,8 +323,7 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
}
ResolveMerger m = (ResolveMerger) mergeStrategy.newMerger(repo, true);
- final ObjectInserter ins = repo.newObjectInserter();
- try {
+ try (ObjectInserter ins = repo.newObjectInserter()) {
DirCache dc = DirCache.newInCore();
m.setDirCache(dc);
m.setObjectInserter(new ObjectInserter.Filter() {
@@ -453,19 +448,14 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
}
return rw.lookupTree(treeId);
- } finally {
- ins.close();
}
}
private static ObjectId emptyTree(final Repository repo) throws IOException {
- ObjectInserter oi = repo.newObjectInserter();
- try {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
oi.flush();
return id;
- } finally {
- oi.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 3f87aefb92..ea427eb4f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -20,12 +20,12 @@ import com.google.gerrit.common.data.PatchScript.DisplayMethod;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
-import com.google.gerrit.server.FileTypeRegistry;
+import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.inject.Inject;
import eu.medsea.mimeutil.MimeType;
@@ -213,7 +213,8 @@ class PatchScriptBuilder {
content.getHeaderLines(), diffPrefs, a.dst, b.dst, edits,
a.displayMethod, b.displayMethod, a.mimeType.toString(),
b.mimeType.toString(), comments, history, hugeFile,
- intralineDifferenceIsPossible, intralineFailure, intralineTimeout);
+ intralineDifferenceIsPossible, intralineFailure, intralineTimeout,
+ content.getPatchType() == Patch.PatchType.BINARY);
}
private static boolean isModify(PatchListEntry content) {
@@ -436,7 +437,7 @@ class PatchScriptBuilder {
displayMethod = DisplayMethod.NONE;
} else {
id = within;
- src = Text.forCommit(db, reader, within);
+ src = Text.forCommit(reader, within);
srcContent = src.getContent();
if (src == Text.EMPTY) {
mode = FileMode.MISSING;
@@ -513,9 +514,10 @@ class PatchScriptBuilder {
if (path == null || within == null) {
return null;
}
- final RevWalk rw = new RevWalk(reader);
- final RevTree tree = rw.parseTree(within);
- return TreeWalk.forPath(reader, path, tree);
+ try (RevWalk rw = new RevWalk(reader)) {
+ final RevTree tree = rw.parseTree(within);
+ return TreeWalk.forPath(reader, path, tree);
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index b4337cf45c..3bdb6b2d83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -14,27 +14,32 @@
package com.google.gerrit.server.patch;
+import com.google.common.base.Optional;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
+import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -80,6 +85,8 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private final PatchSet.Id psa;
private final PatchSet.Id psb;
private final AccountDiffPreference diffPrefs;
+ private final ChangeEditUtil editReader;
+ private Optional<ChangeEdit> edit;
private final Change.Id changeId;
private boolean loadHistory = true;
@@ -99,6 +106,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
final PatchListCache patchListCache, final ReviewDb db,
final AccountInfoCacheFactory.Factory aicFactory,
PatchLineCommentsUtil plcUtil,
+ ChangeEditUtil editReader,
@Assisted ChangeControl control,
@Assisted final String fileName,
@Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
@@ -111,6 +119,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
this.control = control;
this.aicFactory = aicFactory;
this.plcUtil = plcUtil;
+ this.editReader = editReader;
this.fileName = fileName;
this.psa = patchSetA;
@@ -130,7 +139,8 @@ public class PatchScriptFactory implements Callable<PatchScript> {
@Override
public PatchScript call() throws OrmException, NoSuchChangeException,
- LargeObjectException {
+ LargeObjectException, AuthException,
+ InvalidChangeOperationException, IOException {
validatePatchSetId(psa);
validatePatchSetId(psb);
@@ -197,12 +207,16 @@ public class PatchScriptFactory implements Callable<PatchScript> {
}
private ObjectId toObjectId(final ReviewDb db, final PatchSet.Id psId)
- throws OrmException, NoSuchChangeException {
+ throws OrmException, NoSuchChangeException, AuthException,
+ NoSuchChangeException, IOException {
if (!changeId.equals(psId.getParentKey())) {
throw new NoSuchChangeException(changeId);
}
- final PatchSet ps = db.patchSets().get(psId);
+ if (psId.get() == 0) {
+ return getEditRev();
+ }
+ PatchSet ps = db.patchSets().get(psId);
if (ps == null || ps.getRevision() == null
|| ps.getRevision().get() == null) {
throw new NoSuchChangeException(changeId);
@@ -216,6 +230,15 @@ public class PatchScriptFactory implements Callable<PatchScript> {
}
}
+ private ObjectId getEditRev() throws AuthException,
+ NoSuchChangeException, IOException {
+ edit = editReader.byChange(change);
+ if (edit.isPresent()) {
+ return edit.get().getRef().getObjectId();
+ }
+ throw new NoSuchChangeException(change.getId());
+ }
+
private void validatePatchSetId(final PatchSet.Id psId)
throws NoSuchChangeException {
if (psId == null) { // OK, means use base;
@@ -262,9 +285,15 @@ public class PatchScriptFactory implements Callable<PatchScript> {
history.add(p);
byKey.put(p.getKey(), p);
}
+ if (edit != null && edit.isPresent()) {
+ final Patch p = new Patch(new Patch.Key(
+ new PatchSet.Id(psb.getParentKey(), 0), fileName));
+ history.add(p);
+ byKey.put(p.getKey(), p);
+ }
}
- if (loadComments) {
+ if (loadComments && edit == null) {
final AccountInfoCacheFactory aic = aicFactory.create();
comments = new CommentDetail(psa, psb);
switch (changeType) {
@@ -338,7 +367,8 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private void loadDrafts(final Map<Patch.Key, Patch> byKey,
final AccountInfoCacheFactory aic, final Account.Id me, final String file)
throws OrmException {
- for (PatchLineComment c : db.patchComments().draftByChangeFileAuthor(changeId, file, me)) {
+ for (PatchLineComment c :
+ plcUtil.draftByChangeFileAuthor(db, control.getNotes(), file, me)) {
if (comments.include(c)) {
aic.want(me);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 0571b58b81..1e4ffce1a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -85,17 +85,12 @@ public class PatchSetInfoFactory {
} catch (IOException e) {
throw new PatchSetInfoNotAvailableException(e);
}
- try {
- final RevWalk rw = new RevWalk(repo);
- try {
- final RevCommit src =
- rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- PatchSetInfo info = get(src, patchSet.getId());
- info.setParents(toParentInfos(src.getParents(), rw));
- return info;
- } finally {
- rw.close();
- }
+ try (RevWalk rw = new RevWalk(repo)) {
+ final RevCommit src =
+ rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
+ PatchSetInfo info = get(src, patchSet.getId());
+ info.setParents(toParentInfos(src.getParents(), rw));
+ return info;
} catch (IOException e) {
throw new PatchSetInfoNotAvailableException(e);
} finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
index f12b02beb7..882e25f6f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
@@ -21,7 +21,6 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
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.storage.pack.PackConfig;
@@ -44,46 +43,46 @@ public class Text extends RawText {
public static final byte[] NO_BYTES = {};
public static final Text EMPTY = new Text(NO_BYTES);
- public static Text forCommit(Repository db, ObjectReader reader,
- AnyObjectId commitId) throws IOException {
- RevWalk rw = new RevWalk(reader);
- RevCommit c;
- if (commitId instanceof RevCommit) {
- c = (RevCommit) commitId;
- } else {
- c = rw.parseCommit(commitId);
- }
-
- StringBuilder b = new StringBuilder();
- switch (c.getParentCount()) {
- case 0:
- break;
- case 1: {
- RevCommit p = c.getParent(0);
- rw.parseBody(p);
- b.append("Parent: ");
- b.append(reader.abbreviate(p, 8).name());
- b.append(" (");
- b.append(p.getShortMessage());
- b.append(")\n");
- break;
+ public static Text forCommit(ObjectReader reader, AnyObjectId commitId) throws IOException {
+ try (RevWalk rw = new RevWalk(reader)) {
+ RevCommit c;
+ if (commitId instanceof RevCommit) {
+ c = (RevCommit) commitId;
+ } else {
+ c = rw.parseCommit(commitId);
}
- default:
- for (int i = 0; i < c.getParentCount(); i++) {
- RevCommit p = c.getParent(i);
+
+ StringBuilder b = new StringBuilder();
+ switch (c.getParentCount()) {
+ case 0:
+ break;
+ case 1: {
+ RevCommit p = c.getParent(0);
rw.parseBody(p);
- b.append(i == 0 ? "Merge Of: " : " ");
+ b.append("Parent: ");
b.append(reader.abbreviate(p, 8).name());
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
+ break;
}
+ default:
+ for (int i = 0; i < c.getParentCount(); i++) {
+ RevCommit p = c.getParent(i);
+ rw.parseBody(p);
+ b.append(i == 0 ? "Merge Of: " : " ");
+ b.append(reader.abbreviate(p, 8).name());
+ b.append(" (");
+ b.append(p.getShortMessage());
+ b.append(")\n");
+ }
+ }
+ appendPersonIdent(b, "Author", c.getAuthorIdent());
+ appendPersonIdent(b, "Commit", c.getCommitterIdent());
+ b.append("\n");
+ b.append(c.getFullMessage());
+ return new Text(b.toString().getBytes("UTF-8"));
}
- appendPersonIdent(b, "Author", c.getAuthorIdent());
- appendPersonIdent(b, "Commit", c.getCommitterIdent());
- b.append("\n");
- b.append(c.getFullMessage());
- return new Text(b.toString().getBytes("UTF-8"));
}
private static void appendPersonIdent(StringBuilder b, String field,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
index 0b128dd8f6..e23965426d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.plugins;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.Export;
@@ -28,8 +30,6 @@ import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
-import static com.google.common.base.Preconditions.checkState;
-
/**
* Base plugin scanner for a set of pre-loaded classes.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index 6f1204be86..0eaddb3fea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -23,12 +23,17 @@ import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
@@ -36,12 +41,14 @@ import java.util.Map;
import java.util.Set;
class AutoRegisterModules {
+ private static final Logger log = LoggerFactory.getLogger(AutoRegisterModules.class);
+
private final String pluginName;
private final PluginGuiceEnvironment env;
private final PluginContentScanner scanner;
private final ClassLoader classLoader;
private final ModuleGenerator sshGen;
- private final ModuleGenerator httpGen;
+ private final HttpModuleGenerator httpGen;
private Set<Class<?>> sysSingletons;
private Multimap<TypeLiteral<?>, Class<?>> sysListen;
@@ -58,32 +65,28 @@ class AutoRegisterModules {
this.env = env;
this.scanner = scanner;
this.classLoader = classLoader;
- this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
- this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null;
+ this.sshGen = env.hasSshModule()
+ ? env.newSshModuleGenerator()
+ : new ModuleGenerator.NOP();
+ this.httpGen = env.hasHttpModule()
+ ? env.newHttpModuleGenerator()
+ : new HttpModuleGenerator.NOP();
}
AutoRegisterModules discover() throws InvalidPluginException {
sysSingletons = Sets.newHashSet();
sysListen = LinkedListMultimap.create();
- if (sshGen != null) {
- sshGen.setPluginName(pluginName);
- }
- if (httpGen != null) {
- httpGen.setPluginName(pluginName);
- }
+ sshGen.setPluginName(pluginName);
+ httpGen.setPluginName(pluginName);
scan();
if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) {
sysModule = makeSystemModule();
}
- if (sshGen != null) {
- sshModule = sshGen.create();
- }
- if (httpGen != null) {
- httpModule = httpGen.create();
- }
+ sshModule = sshGen.create();
+ httpModule = httpGen.create();
return this;
}
@@ -117,6 +120,19 @@ class AutoRegisterModules {
for (ExtensionMetaData listener : extensions.get(Listen.class)) {
listen(listener);
}
+ exportInitJs();
+ }
+
+ private void exportInitJs() {
+ try {
+ if (scanner.getEntry(JavaScriptPlugin.STATIC_INIT_JS).isPresent()) {
+ httpGen.export(JavaScriptPlugin.INIT_JS);
+ }
+ } catch (IOException e) {
+ log.warn(String.format("Cannot access %s from plugin %s: "
+ + "JavaScript auto-discovered plugin will not be registered",
+ JavaScriptPlugin.STATIC_INIT_JS, pluginName), e);
+ }
}
private void export(ExtensionMetaData def) throws InvalidPluginException {
@@ -138,14 +154,10 @@ class AutoRegisterModules {
}
if (is("org.apache.sshd.server.Command", clazz)) {
- if (sshGen != null) {
- sshGen.export(export, clazz);
- }
+ sshGen.export(export, clazz);
} else if (is("javax.servlet.http.HttpServlet", clazz)) {
- if (httpGen != null) {
- httpGen.export(export, clazz);
- listen(clazz, clazz);
- }
+ httpGen.export(export, clazz);
+ listen(clazz, clazz);
} else {
int cnt = sysListen.size();
listen(clazz, clazz);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java
index 67ee7b4e98..7252617086 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java
@@ -23,6 +23,7 @@ import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.securestore.SecureStore;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
@@ -116,6 +117,14 @@ class CopyConfigModule extends AbstractModule {
}
@Inject
+ private SecureStore secureStore;
+
+ @Provides
+ SecureStore getSecureStore() {
+ return secureStore;
+ }
+
+ @Inject
CopyConfigModule() {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
new file mode 100644
index 0000000000..dd0ce67062
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/HttpModuleGenerator.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 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;
+
+
+public interface HttpModuleGenerator extends ModuleGenerator {
+ void export(String javascript);
+
+ static class NOP extends ModuleGenerator.NOP
+ implements HttpModuleGenerator {
+ @Override
+ public void export(String javascript) {
+ // do nothing
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
index df9ccf4dcd..34060805a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.plugins;
import static com.google.gerrit.server.plugins.PluginLoader.asTemp;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
@@ -60,7 +60,7 @@ public class JarPluginProvider implements ServerPluginProvider {
@Override
public String getPluginName(File srcFile) {
try {
- return Objects.firstNonNull(getJarPluginName(srcFile),
+ return MoreObjects.firstNonNull(getJarPluginName(srcFile),
PluginLoader.nameOf(srcFile));
} catch (IOException e) {
throw new IllegalArgumentException("Invalid plugin file " + srcFile
@@ -85,7 +85,7 @@ public class JarPluginProvider implements ServerPluginProvider {
File tmp = asTemp(in, tempNameFor(name), extension, tmpDir);
return loadJarPlugin(name, srcFile, snapshot, tmp, description);
}
- } catch (IOException | ClassNotFoundException e) {
+ } catch (IOException e) {
throw new InvalidPluginException("Cannot load Jar plugin " + srcFile, e);
}
}
@@ -119,8 +119,7 @@ public class JarPluginProvider implements ServerPluginProvider {
private ServerPlugin loadJarPlugin(String name, File srcJar,
FileSnapshot snapshot, File tmp, PluginDescription description)
- throws IOException, InvalidPluginException, MalformedURLException,
- ClassNotFoundException {
+ throws IOException, InvalidPluginException, MalformedURLException {
JarFile jarFile = new JarFile(tmp);
boolean keep = false;
try {
@@ -143,9 +142,10 @@ public class JarPluginProvider implements ServerPluginProvider {
new URLClassLoader(urls.toArray(new URL[urls.size()]),
PluginLoader.parentFor(type));
+ JarScanner jarScanner = createJarScanner(tmp);
ServerPlugin plugin =
new ServerPlugin(name, description.canonicalUrl, description.user,
- srcJar, snapshot, new JarScanner(srcJar), description.dataDir,
+ srcJar, snapshot, jarScanner, description.dataDir,
pluginLoader);
plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile));
keep = true;
@@ -156,4 +156,13 @@ public class JarPluginProvider implements ServerPluginProvider {
}
}
}
+
+ private JarScanner createJarScanner(File srcJar)
+ throws InvalidPluginException {
+ try {
+ return new JarScanner(srcJar);
+ } catch (IOException e) {
+ throw new InvalidPluginException("Cannot scan plugin file " + srcJar, e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index 31a7c985cd..a8600fe683 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.plugins;
-import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.Iterables.transform;
import com.google.common.base.Function;
@@ -43,9 +43,11 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
@@ -67,12 +69,8 @@ public class JarScanner implements PluginContentScanner {
private final JarFile jarFile;
- public JarScanner(File srcFile) throws InvalidPluginException {
- try {
- this.jarFile = new JarFile(srcFile);
- } catch (IOException e) {
- throw new InvalidPluginException("Cannot scan plugin file " + srcFile, e);
- }
+ public JarScanner(File srcFile) throws IOException {
+ this.jarFile = new JarFile(srcFile);
}
@Override
@@ -136,6 +134,41 @@ public class JarScanner implements PluginContentScanner {
return result.build();
}
+ public List<String> findSubClassesOf(Class<?> superClass) throws IOException {
+ return findSubClassesOf(superClass.getName());
+ }
+
+ private List<String> findSubClassesOf(String superClass) throws IOException {
+ String name = superClass.replace('.', '/');
+
+ List<String> classes = new ArrayList<>();
+ Enumeration<JarEntry> e = jarFile.entries();
+ while (e.hasMoreElements()) {
+ JarEntry entry = e.nextElement();
+ if (skip(entry)) {
+ continue;
+ }
+
+ ClassData def = new ClassData(Collections.<String>emptySet());
+ try {
+ new ClassReader(read(jarFile, entry)).accept(def, SKIP_ALL);
+ } catch (RuntimeException err) {
+ PluginLoader.log.warn(String.format("Jar %s has invalid class file %s",
+ jarFile.getName(), entry.getName()), err);
+ continue;
+ }
+
+ if (name.equals(def.superName)) {
+ classes.addAll(findSubClassesOf(def.className));
+ if (def.isConcrete()) {
+ classes.add(def.className);
+ }
+ }
+ }
+
+ return classes;
+ }
+
private static boolean skip(JarEntry entry) {
if (!entry.getName().endsWith(".class")) {
return true; // Avoid non-class resources.
@@ -164,8 +197,10 @@ public class JarScanner implements PluginContentScanner {
public static class ClassData extends ClassVisitor {
int access;
String className;
+ String superName;
String annotationName;
String annotationValue;
+ String[] interfaces;
Iterable<String> exports;
private ClassData(Iterable<String> exports) {
@@ -183,6 +218,7 @@ public class JarScanner implements PluginContentScanner {
String superName, String[] interfaces) {
this.className = Type.getObjectType(name).getClassName();
this.access = access;
+ this.superName = superName;
}
@Override
@@ -234,7 +270,7 @@ public class JarScanner implements PluginContentScanner {
}
}
- private static abstract class AbstractAnnotationVisitor extends
+ private abstract static class AbstractAnnotationVisitor extends
AnnotationVisitor {
AbstractAnnotationVisitor() {
super(Opcodes.ASM4);
@@ -274,6 +310,7 @@ public class JarScanner implements PluginContentScanner {
return Collections.enumeration(Lists.transform(
Collections.list(jarFile.entries()),
new Function<JarEntry, PluginEntry>() {
+ @Override
public PluginEntry apply(JarEntry jarEntry) {
try {
return resourceOf(jarEntry);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index c5611dd6f9..54f05f05f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -29,11 +29,7 @@ import com.google.inject.Inject;
import org.kohsuke.args4j.Option;
-import java.io.BufferedWriter;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -66,27 +62,12 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
}
@Override
- public Object apply(TopLevelResource resource)
- throws UnsupportedEncodingException {
+ public Object apply(TopLevelResource resource) {
format = OutputFormat.JSON;
return display(null);
}
- public JsonElement display(OutputStream displayOutputStream)
- throws UnsupportedEncodingException {
- PrintWriter stdout = null;
- if (displayOutputStream != null) {
- try {
- stdout = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(displayOutputStream, "UTF-8")));
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("JVM lacks UTF-8 encoding", e);
- }
- } else if (!format.isJson()) {
- throw new IllegalStateException(
- "Text output requires that a display OutputStream is provided.");
- }
-
+ public JsonElement display(PrintWriter stdout) {
Map<String, PluginInfo> output = Maps.newTreeMap();
List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins(all));
Collections.sort(plugins, new Comparator<Plugin>() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java
index 2011e9d198..ae8bb0ce0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java
@@ -26,4 +26,27 @@ public interface ModuleGenerator {
void listen(TypeLiteral<?> tl, Class<?> clazz);
Module create() throws InvalidPluginException;
+
+ static class NOP implements ModuleGenerator {
+
+ @Override
+ public void setPluginName(String name) {
+ // do nothing
+ }
+
+ @Override
+ public void listen(TypeLiteral<?> tl, Class<?> clazz) {
+ // do nothing
+ }
+
+ @Override
+ public void export(Export export, Class<?> type) {
+ // do nothing
+ }
+
+ @Override
+ public Module create() throws InvalidPluginException {
+ return null;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginCleanerTask.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginCleanerTask.java
index 606d5daa7e..c13c5337ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginCleanerTask.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginCleanerTask.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.plugins;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index 5b63a21561..3fbbfa9b0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -40,6 +40,7 @@ import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
+import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
@@ -56,7 +57,6 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
-import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -83,7 +83,7 @@ public class PluginGuiceEnvironment {
private Module httpModule;
private Provider<ModuleGenerator> sshGen;
- private Provider<ModuleGenerator> httpGen;
+ private Provider<HttpModuleGenerator> httpGen;
private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
@@ -149,13 +149,15 @@ public class PluginGuiceEnvironment {
return sysModule;
}
- public void setCfgInjector(Injector cfgInjector) {
+ public void setDbCfgInjector(Injector dbInjector, Injector cfgInjector) {
+ final Module db = copy(dbInjector);
final Module cm = copy(cfgInjector);
final Module sm = copy(sysInjector);
sysModule = new AbstractModule() {
@Override
protected void configure() {
install(copyConfigModule);
+ install(db);
install(cm);
install(sm);
}
@@ -187,7 +189,7 @@ public class PluginGuiceEnvironment {
public void setHttpInjector(Injector injector) {
httpModule = copy(injector);
- httpGen = injector.getProvider(ModuleGenerator.class);
+ httpGen = injector.getProvider(HttpModuleGenerator.class);
httpItems = dynamicItemsOf(injector);
httpSets = dynamicSetsOf(injector);
httpMaps = dynamicMapsOf(injector);
@@ -204,7 +206,7 @@ public class PluginGuiceEnvironment {
return httpModule;
}
- ModuleGenerator newHttpModuleGenerator() {
+ HttpModuleGenerator newHttpModuleGenerator() {
return httpGen.get();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 003da6c422..b51359df8c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.plugins;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
@@ -71,8 +71,9 @@ public class PluginLoader implements LifecycleListener {
static final String PLUGIN_TMP_PREFIX = "plugin_";
static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
- public String getPluginName(File srcFile) throws IOException {
- return Objects.firstNonNull(getGerritPluginName(srcFile), nameOf(srcFile));
+ public String getPluginName(File srcFile) {
+ return MoreObjects.firstNonNull(getGerritPluginName(srcFile),
+ nameOf(srcFile));
}
private final File pluginsDir;
@@ -158,7 +159,7 @@ public class PluginLoader implements LifecycleListener {
String fileName = originalName;
File tmp = asTemp(in, ".next_" + fileName + "_", ".tmp", pluginsDir);
- String name = Objects.firstNonNull(getGerritPluginName(tmp),
+ String name = MoreObjects.firstNonNull(getGerritPluginName(tmp),
nameOf(fileName));
if (!originalName.equals(name)) {
log.warn(String.format("Plugin provides its own name: <%s>,"
@@ -213,7 +214,7 @@ public class PluginLoader implements LifecycleListener {
}
}
- synchronized private void unloadPlugin(Plugin plugin) {
+ private synchronized void unloadPlugin(Plugin plugin) {
persistentCacheFactory.onStop(plugin);
String name = plugin.getName();
log.info(String.format("Unloading plugin %s", name));
@@ -404,7 +405,7 @@ public class PluginLoader implements LifecycleListener {
Iterator<Entry<String, File>> it = from.entrySet().iterator();
while (it.hasNext()) {
Entry<String,File> entry = it.next();
- to.add(new AbstractMap.SimpleImmutableEntry<String, File>(
+ to.add(new AbstractMap.SimpleImmutableEntry<>(
entry.getKey(), entry.getValue()));
}
}
@@ -545,7 +546,7 @@ public class PluginLoader implements LifecycleListener {
}
private Plugin loadPlugin(String name, File srcPlugin, FileSnapshot snapshot)
- throws IOException, ClassNotFoundException, InvalidPluginException {
+ throws InvalidPluginException {
String pluginName = srcPlugin.getName();
if (isJsPlugin(pluginName)) {
return loadJsPlugin(name, srcPlugin, snapshot);
@@ -623,43 +624,37 @@ public class PluginLoader implements LifecycleListener {
public Multimap<String, File> prunePlugins(File pluginsDir) {
List<File> pluginFiles = scanFilesInPluginsDirectory(pluginsDir);
Multimap<String, File> map;
- try {
- map = asMultimap(pluginFiles);
- for (String plugin : map.keySet()) {
- Collection<File> files = map.asMap().get(plugin);
- if (files.size() == 1) {
- continue;
- }
- // retrieve enabled plugins
- Iterable<File> enabled = filterDisabledPlugins(
- files);
- // If we have only one (the winner) plugin, nothing to do
- if (!Iterables.skip(enabled, 1).iterator().hasNext()) {
- continue;
- }
- File winner = Iterables.getFirst(enabled, null);
- assert(winner != null);
- // Disable all loser plugins by renaming their file names to
- // "file.disabled" and replace the disabled files in the multimap.
- Collection<File> elementsToRemove = Lists.newArrayList();
- Collection<File> elementsToAdd = Lists.newArrayList();
- for (File loser : Iterables.skip(enabled, 1)) {
- log.warn(String.format("Plugin <%s> was disabled, because"
- + " another plugin <%s>"
- + " with the same name <%s> already exists",
- loser, winner, plugin));
- File disabledPlugin = new File(loser + ".disabled");
- elementsToAdd.add(disabledPlugin);
- elementsToRemove.add(loser);
- loser.renameTo(disabledPlugin);
- }
- Iterables.removeAll(files, elementsToRemove);
- Iterables.addAll(files, elementsToAdd);
+ map = asMultimap(pluginFiles);
+ for (String plugin : map.keySet()) {
+ Collection<File> files = map.asMap().get(plugin);
+ if (files.size() == 1) {
+ continue;
+ }
+ // retrieve enabled plugins
+ Iterable<File> enabled = filterDisabledPlugins(
+ files);
+ // If we have only one (the winner) plugin, nothing to do
+ if (!Iterables.skip(enabled, 1).iterator().hasNext()) {
+ continue;
+ }
+ File winner = Iterables.getFirst(enabled, null);
+ assert(winner != null);
+ // Disable all loser plugins by renaming their file names to
+ // "file.disabled" and replace the disabled files in the multimap.
+ Collection<File> elementsToRemove = Lists.newArrayList();
+ Collection<File> elementsToAdd = Lists.newArrayList();
+ for (File loser : Iterables.skip(enabled, 1)) {
+ log.warn(String.format("Plugin <%s> was disabled, because"
+ + " another plugin <%s>"
+ + " with the same name <%s> already exists",
+ loser, winner, plugin));
+ File disabledPlugin = new File(loser + ".disabled");
+ elementsToAdd.add(disabledPlugin);
+ elementsToRemove.add(loser);
+ loser.renameTo(disabledPlugin);
}
- } catch (IOException e) {
- log.warn("Cannot prune plugin list",
- e.getCause());
- return LinkedHashMultimap.create();
+ Iterables.removeAll(files, elementsToRemove);
+ Iterables.addAll(files, elementsToAdd);
}
return map;
}
@@ -693,7 +688,7 @@ public class PluginLoader implements LifecycleListener {
});
}
- public String getGerritPluginName(File srcFile) throws IOException {
+ public String getGerritPluginName(File srcFile) {
String fileName = srcFile.getName();
if (isJsPlugin(fileName)) {
return fileName.substring(0, fileName.length() - 3);
@@ -704,8 +699,7 @@ public class PluginLoader implements LifecycleListener {
return null;
}
- private Multimap<String, File> asMultimap(List<File> plugins)
- throws IOException {
+ private Multimap<String, File> asMultimap(List<File> plugins) {
Multimap<String, File> map = LinkedHashMultimap.create();
for (File srcFile : plugins) {
map.put(getPluginName(srcFile), srcFile);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginResource.java
index 9572271b0b..e7ebd5654e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginResource.java
@@ -25,12 +25,12 @@ public class PluginResource implements RestResource {
private final Plugin plugin;
private final String name;
- PluginResource(Plugin plugin) {
+ public PluginResource(Plugin plugin) {
this.plugin = plugin;
this.name = plugin.getName();
}
- PluginResource(String name) {
+ public PluginResource(String name) {
this.plugin = null;
this.name = name;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index ee5e90a6e0..0b037fb678 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -69,7 +69,7 @@ public class ServerPlugin extends Plugin {
private Injector sysInjector;
private Injector sshInjector;
private Injector httpInjector;
- private LifecycleManager manager;
+ private LifecycleManager serverManager;
private List<ReloadableRegistrationHandle<?>> reloadableHandles;
public ServerPlugin(String name,
@@ -140,12 +140,14 @@ public class ServerPlugin extends Plugin {
}
}
+ @Override
@Nullable
public String getVersion() {
Attributes main = manifest.getMainAttributes();
return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}
+ @Override
boolean canReload() {
Attributes main = manifest.getMainAttributes();
String v = main.getValue("Gerrit-ReloadMode");
@@ -161,6 +163,7 @@ public class ServerPlugin extends Plugin {
}
}
+ @Override
void start(PluginGuiceEnvironment env) throws Exception {
RequestContext oldContext = env.enter(this);
try {
@@ -172,7 +175,7 @@ public class ServerPlugin extends Plugin {
private void startPlugin(PluginGuiceEnvironment env) throws Exception {
Injector root = newRootInjector(env);
- manager = new LifecycleManager();
+ serverManager = new LifecycleManager();
AutoRegisterModules auto = null;
if (sysModule == null && sshModule == null && httpModule == null) {
@@ -182,10 +185,10 @@ public class ServerPlugin extends Plugin {
if (sysModule != null) {
sysInjector = root.createChildInjector(root.getInstance(sysModule));
- manager.add(sysInjector);
+ serverManager.add(sysInjector);
} else if (auto != null && auto.sysModule != null) {
sysInjector = root.createChildInjector(auto.sysModule);
- manager.add(sysInjector);
+ serverManager.add(sysInjector);
} else {
sysInjector = root;
}
@@ -198,11 +201,11 @@ public class ServerPlugin extends Plugin {
if (sshModule != null) {
modules.add(sysInjector.getInstance(sshModule));
sshInjector = sysInjector.createChildInjector(modules);
- manager.add(sshInjector);
+ serverManager.add(sshInjector);
} else if (auto != null && auto.sshModule != null) {
modules.add(auto.sshModule);
sshInjector = sysInjector.createChildInjector(modules);
- manager.add(sshInjector);
+ serverManager.add(sshInjector);
}
}
@@ -214,15 +217,15 @@ public class ServerPlugin extends Plugin {
if (httpModule != null) {
modules.add(sysInjector.getInstance(httpModule));
httpInjector = sysInjector.createChildInjector(modules);
- manager.add(httpInjector);
+ serverManager.add(httpInjector);
} else if (auto != null && auto.httpModule != null) {
modules.add(auto.httpModule);
httpInjector = sysInjector.createChildInjector(modules);
- manager.add(httpInjector);
+ serverManager.add(httpInjector);
}
}
- manager.start();
+ serverManager.start();
}
private Injector newRootInjector(final PluginGuiceEnvironment env) {
@@ -250,12 +253,14 @@ public class ServerPlugin extends Plugin {
public File get() {
if (!ready) {
synchronized (dataDir) {
- if (!dataDir.exists() && !dataDir.mkdirs()) {
- throw new ProvisionException(String.format(
- "Cannot create %s for plugin %s",
- dataDir.getAbsolutePath(), getName()));
+ if (!ready) {
+ if (!dataDir.exists() && !dataDir.mkdirs()) {
+ throw new ProvisionException(String.format(
+ "Cannot create %s for plugin %s",
+ dataDir.getAbsolutePath(), getName()));
+ }
+ ready = true;
}
- ready = true;
}
}
return dataDir;
@@ -266,44 +271,49 @@ public class ServerPlugin extends Plugin {
return Guice.createInjector(modules);
}
+ @Override
void stop(PluginGuiceEnvironment env) {
- if (manager != null) {
+ if (serverManager != null) {
RequestContext oldContext = env.enter(this);
try {
- manager.stop();
+ serverManager.stop();
} finally {
env.exit(oldContext);
}
- manager = null;
+ serverManager = null;
sysInjector = null;
sshInjector = null;
httpInjector = null;
}
}
+ @Override
public Injector getSysInjector() {
return sysInjector;
}
+ @Override
@Nullable
public Injector getSshInjector() {
return sshInjector;
}
+ @Override
@Nullable
public Injector getHttpInjector() {
return httpInjector;
}
+ @Override
public void add(RegistrationHandle handle) {
- if (manager != null) {
+ if (serverManager != null) {
if (handle instanceof ReloadableRegistrationHandle) {
if (reloadableHandles == null) {
reloadableHandles = Lists.newArrayList();
}
reloadableHandles.add((ReloadableRegistrationHandle<?>) handle);
}
- manager.add(handle);
+ serverManager.add(handle);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
index fab1ed3090..ef153ced1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
@@ -22,7 +22,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.git.BanCommitResult;
-import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.project.BanCommit.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -51,8 +50,12 @@ public class BanCommit implements RestModifyView<ProjectResource, Input> {
}
}
+ private final com.google.gerrit.server.git.BanCommit banCommit;
+
@Inject
- private com.google.gerrit.server.git.BanCommit banCommit;
+ BanCommit(com.google.gerrit.server.git.BanCommit banCommit) {
+ this.banCommit = banCommit;
+ }
@Override
public BanResultInfo apply(ProjectResource rsrc, Input input)
@@ -77,7 +80,7 @@ public class BanCommit implements RestModifyView<ProjectResource, Input> {
r.ignored = transformCommits(result.getIgnoredObjectIds());
} catch (PermissionDeniedException e) {
throw new AuthException(e.getMessage());
- } catch (MergeException | ConcurrentRefUpdateException e) {
+ } catch (ConcurrentRefUpdateException e) {
throw new ResourceConflictException(e.getMessage(), e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
index 6681d946f1..98531cec9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
@@ -41,4 +41,8 @@ public class BranchResource extends ProjectResource {
public String getRef() {
return branchInfo.ref;
}
+
+ public String getRevision() {
+ return branchInfo.revision;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
index 3bf3522d37..e96d3a53d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
@@ -16,12 +16,14 @@ package com.google.gerrit.server.project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
+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.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.project.ListBranches.BranchInfo;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Constants;
@@ -34,12 +36,12 @@ public class BranchesCollection implements
ChildCollection<ProjectResource, BranchResource>,
AcceptsCreate<ProjectResource> {
private final DynamicMap<RestView<BranchResource>> views;
- private final ListBranches list;
+ private final Provider<ListBranches> list;
private final CreateBranch.Factory createBranchFactory;
@Inject
BranchesCollection(DynamicMap<RestView<BranchResource>> views,
- ListBranches list, CreateBranch.Factory createBranchFactory) {
+ Provider<ListBranches> list, CreateBranch.Factory createBranchFactory) {
this.views = views;
this.list = list;
this.createBranchFactory = createBranchFactory;
@@ -47,18 +49,18 @@ public class BranchesCollection implements
@Override
public RestView<ProjectResource> list() {
- return list;
+ return list.get();
}
@Override
public BranchResource parse(ProjectResource parent, IdString id)
- throws ResourceNotFoundException, IOException {
+ throws ResourceNotFoundException, IOException, BadRequestException {
String branchName = id.get();
if (!branchName.startsWith(Constants.R_REFS)
&& !branchName.equals(Constants.HEAD)) {
branchName = Constants.R_HEADS + branchName;
}
- List<BranchInfo> branches = list.apply(parent);
+ List<BranchInfo> branches = list.get().apply(parent);
for (BranchInfo b : branches) {
if (branchName.equals(b.ref)) {
return new BranchResource(parent.getControl(), b);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 1878c020bd..83db7263d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -20,9 +20,6 @@ import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.RefConfigSection;
-import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.extensions.common.SubmitType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -39,28 +36,13 @@ import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
-import com.googlecode.prolog_cafe.lang.IntegerTerm;
-import com.googlecode.prolog_cafe.lang.ListTerm;
-import com.googlecode.prolog_cafe.lang.Prolog;
-import com.googlecode.prolog_cafe.lang.StructureTerm;
-import com.googlecode.prolog_cafe.lang.SymbolTerm;
-import com.googlecode.prolog_cafe.lang.Term;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
/** Access control management for a user accessing a single change. */
public class ChangeControl {
- private static final Logger log = LoggerFactory
- .getLogger(ChangeControl.class);
-
public static class GenericFactory {
private final ProjectControl.GenericFactory projectControl;
private final Provider<ReviewDb> db;
@@ -71,6 +53,15 @@ public class ChangeControl {
db = d;
}
+ public ChangeControl controlFor(Change.Id changeId, CurrentUser user)
+ throws NoSuchChangeException, OrmException {
+ Change change = db.get().changes().get(changeId);
+ if (change == null) {
+ throw new NoSuchChangeException(changeId);
+ }
+ return controlFor(change, user);
+ }
+
public ChangeControl controlFor(Change change, CurrentUser user)
throws NoSuchChangeException {
final Project.NameKey projectKey = change.getProject();
@@ -84,63 +75,19 @@ public class ChangeControl {
}
}
- public ChangeControl validateFor(Change change, CurrentUser user)
+ public ChangeControl validateFor(Change.Id changeId, CurrentUser user)
throws NoSuchChangeException, OrmException {
- ChangeControl c = controlFor(change, user);
- if (!c.isVisible(db.get())) {
- throw new NoSuchChangeException(c.getChange().getId());
+ Change change = db.get().changes().get(changeId);
+ if (change == null) {
+ throw new NoSuchChangeException(changeId);
}
- return c;
+ return validateFor(change, user);
}
- }
- public static class Factory {
- private final ProjectControl.Factory projectControl;
- private final Provider<ReviewDb> db;
-
- @Inject
- Factory(final ProjectControl.Factory p, final Provider<ReviewDb> d) {
- projectControl = p;
- db = d;
- }
-
- public ChangeControl controlFor(final Change.Id id)
- throws NoSuchChangeException {
- final Change change;
- try {
- change = db.get().changes().get(id);
- if (change == null) {
- throw new NoSuchChangeException(id);
- }
- } catch (OrmException e) {
- throw new NoSuchChangeException(id, e);
- }
- return controlFor(change);
- }
-
- public ChangeControl controlFor(final Change change)
- throws NoSuchChangeException {
- try {
- final Project.NameKey projectKey = change.getProject();
- return projectControl.validateFor(projectKey).controlFor(change);
- } catch (NoSuchProjectException e) {
- throw new NoSuchChangeException(change.getId(), e);
- }
- }
-
- public ChangeControl validateFor(final Change.Id id)
- throws NoSuchChangeException, OrmException {
- return validate(controlFor(id), db.get());
- }
-
- public ChangeControl validateFor(final Change change)
+ public ChangeControl validateFor(Change change, CurrentUser user)
throws NoSuchChangeException, OrmException {
- return validate(controlFor(change), db.get());
- }
-
- private static ChangeControl validate(final ChangeControl c, final ReviewDb db)
- throws NoSuchChangeException, OrmException{
- if (!c.isVisible(db)) {
+ ChangeControl c = controlFor(change, user);
+ if (!c.isVisible(db.get())) {
throw new NoSuchChangeException(c.getChange().getId());
}
return c;
@@ -152,19 +99,6 @@ public class ChangeControl {
ChangeControl create(RefControl refControl, ChangeNotes notes);
}
- /**
- * Exception thrown when the label term of a submit record
- * unexpectedly didn't contain a user term.
- */
- private static class UserTermExpected extends Exception {
- private static final long serialVersionUID = 1L;
-
- public UserTermExpected(SubmitRecord.Label label) {
- super(String.format("A label with the status %s must contain a user.",
- label.toString()));
- }
- }
-
private final ChangeData.Factory changeDataFactory;
private final RefControl refControl;
private final ChangeNotes notes;
@@ -390,8 +324,13 @@ public class ChangeControl {
}
}
- public List<SubmitRecord> getSubmitRecords(ReviewDb db, PatchSet patchSet) {
- return canSubmit(db, patchSet, null, false, true, false);
+ /** Can this user edit the hashtag name? */
+ public boolean canEditHashtags() {
+ return isOwner() // owner (aka creator) of the change can edit hashtags
+ || getRefControl().isOwner() // branch owner can edit hashtags
+ || getProjectControl().isOwner() // project owner can edit hashtags
+ || getCurrentUser().getCapabilities().canAdministrateServer() // site administers are god
+ || getRefControl().canEditHashtags(); // user can edit hashtag on a specific ref
}
public boolean canSubmit() {
@@ -402,306 +341,17 @@ public class ChangeControl {
return getRefControl().canSubmitAs();
}
- public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet) {
- return canSubmit(db, patchSet, null, false, false, false);
- }
-
- public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
- @Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed,
- boolean allowDraft) {
- if (!allowClosed && getChange().getStatus().isClosed()) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.CLOSED;
- return Collections.singletonList(rec);
- }
-
- if (!patchSet.getId().equals(getChange().currentPatchSetId())) {
- return ruleError("Patch set " + patchSet.getPatchSetId() + " is not current");
- }
-
- cd = changeData(db, cd);
- if ((getChange().getStatus() == Change.Status.DRAFT || patchSet.isDraft())
- && !allowDraft) {
- return cannotSubmitDraft(db, patchSet, cd);
- }
-
- List<Term> results;
- SubmitRuleEvaluator evaluator;
- try {
- evaluator = new SubmitRuleEvaluator(db, patchSet,
- getProjectControl(),
- this, getChange(), cd,
- fastEvalLabels,
- "locate_submit_rule", "can_submit",
- "locate_submit_filter", "filter_submit_results");
- results = evaluator.evaluate();
- } catch (RuleEvalException e) {
- return logRuleError(e.getMessage(), e);
- }
-
- if (results.isEmpty()) {
- // This should never occur. A well written submit rule will always produce
- // at least one result informing the caller of the labels that are
- // required for this change to be submittable. Each label will indicate
- // whether or not that is actually possible given the permissions.
- log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
- + getChange().getId() + " of " + getProject().getName()
- + " has no solution.");
- return ruleError("Project submit rule has no solution");
- }
-
- return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
- }
-
private boolean match(String destBranch, String refPattern) {
return RefPatternMatcher.getMatcher(refPattern).match(destBranch,
this.getRefControl().getCurrentUser().getUserName());
}
- private List<SubmitRecord> cannotSubmitDraft(ReviewDb db, PatchSet patchSet,
- @Nullable ChangeData cd) {
- try {
- if (!isDraftVisible(db, cd)) {
- return ruleError("Patch set " + patchSet.getPatchSetId() + " not found");
- } else if (patchSet.isDraft()) {
- return ruleError("Cannot submit draft patch sets");
- } else {
- return ruleError("Cannot submit draft changes");
- }
- } catch (OrmException err) {
- return logRuleError("Cannot read patch set " + patchSet.getId(), err);
- }
- }
-
- /**
- * Convert the results from Prolog Cafe's format to Gerrit's common format.
- *
- * can_submit/1 terminates when an ok(P) record is found. Therefore walk
- * the results backwards, using only that ok(P) record if it exists. This
- * skips partial results that occur early in the 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) {
- List<SubmitRecord> out = new ArrayList<>(results.size());
- for (int resultIdx = results.size() - 1; 0 <= resultIdx; resultIdx--) {
- Term submitRecord = results.get(resultIdx);
- SubmitRecord rec = new SubmitRecord();
- out.add(rec);
-
- if (!submitRecord.isStructure() || 1 != submitRecord.arity()) {
- return logInvalidResult(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 {
- return logInvalidResult(submitRule, submitRecord);
- }
-
- // Unpack the one argument. This should also be a structure with one
- // argument per label that needs to be reported on to the caller.
- //
- submitRecord = submitRecord.arg(0);
-
- if (!submitRecord.isStructure()) {
- return logInvalidResult(submitRule, submitRecord);
- }
-
- rec.labels = new ArrayList<>(submitRecord.arity());
-
- for (Term state : ((StructureTerm) submitRecord).args()) {
- if (!state.isStructure() || 2 != state.arity() || !"label".equals(state.name())) {
- return logInvalidResult(submitRule, submitRecord);
- }
-
- SubmitRecord.Label lbl = new SubmitRecord.Label();
- rec.labels.add(lbl);
-
- lbl.label = state.arg(0).name();
- Term status = state.arg(1);
-
- try {
- if ("ok".equals(status.name())) {
- lbl.status = SubmitRecord.Label.Status.OK;
- appliedBy(lbl, status);
-
- } else if ("reject".equals(status.name())) {
- lbl.status = SubmitRecord.Label.Status.REJECT;
- appliedBy(lbl, status);
-
- } else if ("need".equals(status.name())) {
- lbl.status = SubmitRecord.Label.Status.NEED;
-
- } else if ("may".equals(status.name())) {
- lbl.status = SubmitRecord.Label.Status.MAY;
-
- } else if ("impossible".equals(status.name())) {
- lbl.status = SubmitRecord.Label.Status.IMPOSSIBLE;
-
- } else {
- return logInvalidResult(submitRule, submitRecord);
- }
- } catch (UserTermExpected e) {
- return logInvalidResult(submitRule, submitRecord, e.getMessage());
- }
- }
-
- if (rec.status == SubmitRecord.Status.OK) {
- break;
- }
- }
- Collections.reverse(out);
-
- return out;
- }
-
- public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet) {
- return getSubmitTypeRecord(db, patchSet, null);
- }
-
- public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet,
- @Nullable ChangeData cd) {
- cd = changeData(db, cd);
- try {
- if (getChange().getStatus() == Change.Status.DRAFT
- && !isDraftVisible(db, cd)) {
- return typeRuleError("Patch set " + patchSet.getPatchSetId()
- + " not found");
- }
- if (patchSet.isDraft() && !isDraftVisible(db, cd)) {
- return typeRuleError("Patch set " + patchSet.getPatchSetId()
- + " not found");
- }
- } catch (OrmException err) {
- return logTypeRuleError("Cannot read patch set " + patchSet.getId(),
- err);
- }
-
- List<Term> results;
- SubmitRuleEvaluator evaluator;
- try {
- evaluator = new SubmitRuleEvaluator(db, patchSet,
- getProjectControl(), this, getChange(), cd,
- false,
- "locate_submit_type", "get_submit_type",
- "locate_submit_type_filter", "filter_submit_type_results");
- results = evaluator.evaluate();
- } catch (RuleEvalException e) {
- return logTypeRuleError(e.getMessage(), e);
- }
-
- if (results.isEmpty()) {
- // Should never occur for a well written rule
- log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
- + getChange().getId() + " of " + getProject().getName()
- + " has no solution.");
- return typeRuleError("Project submit rule has no solution");
- }
-
- Term typeTerm = results.get(0);
- if (!typeTerm.isSymbol()) {
- log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
- + getChange().getId() + " of " + getProject().getName()
- + " did not return a symbol.");
- return typeRuleError("Project submit rule has invalid solution");
- }
-
- String typeName = ((SymbolTerm)typeTerm).name();
- try {
- return SubmitTypeRecord.OK(
- SubmitType.valueOf(typeName.toUpperCase()));
- } catch (IllegalArgumentException e) {
- return logInvalidType(evaluator.getSubmitRule(), typeName);
- }
- }
-
- private List<SubmitRecord> logInvalidResult(Term rule, Term record, String reason) {
- return logRuleError("Submit rule " + rule + " for change " + getChange().getId()
- + " of " + getProject().getName() + " output invalid result: " + record
- + (reason == null ? "" : ". Reason: " + reason));
- }
-
- private List<SubmitRecord> logInvalidResult(Term rule, Term record) {
- return logInvalidResult(rule, record, null);
- }
-
- private List<SubmitRecord> logRuleError(String err, Exception e) {
- log.error(err, e);
- return ruleError("Error evaluating project rules, check server log");
- }
-
- private List<SubmitRecord> logRuleError(String err) {
- log.error(err);
- return ruleError("Error evaluating project rules, check server log");
- }
-
- private List<SubmitRecord> ruleError(String err) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.RULE_ERROR;
- rec.errorMessage = err;
- return Collections.singletonList(rec);
- }
-
- private SubmitTypeRecord logInvalidType(Term rule, String record) {
- return logTypeRuleError("Submit type rule " + rule + " for change "
- + getChange().getId() + " of " + getProject().getName()
- + " output invalid result: " + record);
- }
-
- private SubmitTypeRecord logTypeRuleError(String err, Exception e) {
- log.error(err, e);
- return typeRuleError("Error evaluating project type rules, check server log");
- }
-
- private SubmitTypeRecord logTypeRuleError(String err) {
- log.error(err);
- return typeRuleError("Error evaluating project type rules, check server log");
- }
-
- private SubmitTypeRecord typeRuleError(String err) {
- SubmitTypeRecord rec = new SubmitTypeRecord();
- rec.status = SubmitTypeRecord.Status.RULE_ERROR;
- rec.errorMessage = err;
- return rec;
- }
-
private ChangeData changeData(ReviewDb db, @Nullable ChangeData cd) {
return cd != null ? cd : changeDataFactory.create(db, this);
}
- private void appliedBy(SubmitRecord.Label label, Term status)
- throws UserTermExpected {
- if (status.isStructure() && status.arity() == 1) {
- Term who = status.arg(0);
- if (isUser(who)) {
- label.appliedBy = new Account.Id(((IntegerTerm) who.arg(0)).intValue());
- } else {
- throw new UserTermExpected(label);
- }
- }
- }
-
- private boolean isDraftVisible(ReviewDb db, ChangeData cd)
+ public boolean isDraftVisible(ReviewDb db, ChangeData cd)
throws OrmException {
return isOwner() || isReviewer(db, cd) || getRefControl().canViewDrafts();
}
-
- private static boolean isUser(Term who) {
- return who.isStructure()
- && who.arity() == 1
- && who.name().equals("user")
- && who.arg(0).isInteger();
- }
-
- public static Term toListTerm(List<Term> terms) {
- Term list = Prolog.Nil;
- for (int i = terms.size() - 1; i >= 0; i--) {
- list = new ListTerm(terms.get(i), list);
- }
- return list;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PathConflictException.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java
index 7e2f2d7b20..f3ca8b6738 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PathConflictException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.changedetail;
+package com.google.gerrit.server.project;
-/** Indicates a path conflict during rebase or merge */
-public class PathConflictException extends Exception {
+public class ChangeModifiedException extends InvalidChangeOperationException {
private static final long serialVersionUID = 1L;
- public PathConflictException(String msg) {
+ public ChangeModifiedException(String msg) {
super(msg);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java
index 2a386a4625..f68acddc14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java
@@ -24,7 +24,7 @@ public class ChildProjectResource extends ProjectResource {
private final ProjectControl child;
- ChildProjectResource(ProjectResource project, ProjectControl child) {
+ public ChildProjectResource(ProjectResource project, ProjectControl child) {
super(project);
this.child = child;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommentLinkInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommentLinkInfo.java
index 4035c7e47b..a91e74515a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommentLinkInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommentLinkInfo.java
@@ -47,7 +47,7 @@ public class CommentLinkInfo {
public final String html;
public final Boolean enabled; // null means true
- public transient final String name;
+ public final transient String name;
public CommentLinkInfo(String name, String match, String link, String html,
Boolean enabled) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
index fc9c8078d6..36186a436d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
@@ -14,22 +14,28 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.revwalk.RevCommit;
-public class CommitResource extends ProjectResource {
+public class CommitResource implements RestResource {
public static final TypeLiteral<RestView<CommitResource>> COMMIT_KIND =
new TypeLiteral<RestView<CommitResource>>() {};
+ private final ProjectResource project;
private final RevCommit commit;
- public CommitResource(ProjectControl control, RevCommit commit) {
- super(control);
+ public CommitResource(ProjectResource project, RevCommit commit) {
+ this.project = project;
this.commit = commit;
}
+ public ProjectControl getProject() {
+ return project.getControl();
+ }
+
public RevCommit getCommit() {
return commit;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java
index f82142af7b..4879bb7b88 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitsCollection.java
@@ -19,8 +19,10 @@ 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.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -37,12 +39,15 @@ public class CommitsCollection implements
ChildCollection<ProjectResource, CommitResource> {
private final DynamicMap<RestView<CommitResource>> views;
private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> db;
@Inject
public CommitsCollection(DynamicMap<RestView<CommitResource>> views,
- GitRepositoryManager repoManager) {
+ GitRepositoryManager repoManager,
+ Provider<ReviewDb> db) {
this.views = views;
this.repoManager = repoManager;
+ this.db = db;
}
@Override
@@ -60,25 +65,19 @@ public class CommitsCollection implements
throw new ResourceNotFoundException(id);
}
- Repository repo = repoManager.openRepository(parent.getNameKey());
- try {
- RevWalk rw = new RevWalk(repo);
- try {
- RevCommit commit = rw.parseCommit(objectId);
- if (!parent.getControl().canReadCommit(rw, commit)) {
- throw new ResourceNotFoundException(id);
- }
- for (int i = 0; i < commit.getParentCount(); i++) {
- rw.parseCommit(commit.getParent(i));
- }
- return new CommitResource(parent.getControl(), commit);
- } catch (MissingObjectException | IncorrectObjectTypeException e) {
+ try (Repository repo = repoManager.openRepository(parent.getNameKey());
+ RevWalk rw = new RevWalk(repo)) {
+ RevCommit commit = rw.parseCommit(objectId);
+ rw.parseBody(commit);
+ if (!parent.getControl().canReadCommit(db.get(), rw, commit)) {
throw new ResourceNotFoundException(id);
- } finally {
- rw.close();
}
- } finally {
- repo.close();
+ for (int i = 0; i < commit.getParentCount(); i++) {
+ rw.parseBody(rw.parseCommit(commit.getParent(i)));
+ }
+ return new CommitResource(parent, commit);
+ } catch (MissingObjectException | IncorrectObjectTypeException e) {
+ throw new ResourceNotFoundException(id);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index 358e0236c6..1c6782c09d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.project;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
import com.google.gerrit.extensions.restapi.RestView;
@@ -43,10 +43,11 @@ public class ConfigInfo {
public InheritedBooleanInfo useContributorAgreements;
public InheritedBooleanInfo useContentMerge;
public InheritedBooleanInfo useSignedOffBy;
+ public InheritedBooleanInfo createNewChangeForAllNotInTarget;
public InheritedBooleanInfo requireChangeId;
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
public SubmitType submitType;
- public com.google.gerrit.extensions.api.projects.ProjectState state;
+ public com.google.gerrit.extensions.client.ProjectState state;
public Map<String, Map<String, ConfigParameterInfo>> pluginConfig;
public Map<String, ActionInfo> actions;
@@ -68,17 +69,23 @@ public class ConfigInfo {
InheritedBooleanInfo useSignedOffBy = new InheritedBooleanInfo();
InheritedBooleanInfo useContentMerge = new InheritedBooleanInfo();
InheritedBooleanInfo requireChangeId = new InheritedBooleanInfo();
+ InheritedBooleanInfo createNewChangeForAllNotInTarget =
+ new InheritedBooleanInfo();
useContributorAgreements.value = projectState.isUseContributorAgreements();
useSignedOffBy.value = projectState.isUseSignedOffBy();
useContentMerge.value = projectState.isUseContentMerge();
requireChangeId.value = projectState.isRequireChangeID();
+ createNewChangeForAllNotInTarget.value =
+ projectState.isCreateNewChangeForAllNotInTarget();
useContributorAgreements.configuredValue =
p.getUseContributorAgreements();
useSignedOffBy.configuredValue = p.getUseSignedOffBy();
useContentMerge.configuredValue = p.getUseContentMerge();
requireChangeId.configuredValue = p.getRequireChangeID();
+ createNewChangeForAllNotInTarget.configuredValue =
+ p.getCreateNewChangeForAllNotInTarget();
ProjectState parentState = Iterables.getFirst(projectState
.parents(), null);
@@ -88,12 +95,15 @@ public class ConfigInfo {
useSignedOffBy.inheritedValue = parentState.isUseSignedOffBy();
useContentMerge.inheritedValue = parentState.isUseContentMerge();
requireChangeId.inheritedValue = parentState.isRequireChangeID();
+ createNewChangeForAllNotInTarget.inheritedValue =
+ parentState.isCreateNewChangeForAllNotInTarget();
}
this.useContributorAgreements = useContributorAgreements;
this.useSignedOffBy = useSignedOffBy;
this.useContentMerge = useContentMerge;
this.requireChangeId = requireChangeId;
+ this.createNewChangeForAllNotInTarget = createNewChangeForAllNotInTarget;
MaxObjectSizeLimitInfo maxObjectSizeLimit = new MaxObjectSizeLimitInfo();
maxObjectSizeLimit.value =
@@ -106,7 +116,7 @@ public class ConfigInfo {
this.maxObjectSizeLimit = maxObjectSizeLimit;
this.submitType = p.getSubmitType();
- this.state = p.getState() != com.google.gerrit.extensions.api.projects.ProjectState.ACTIVE ? p.getState() : null;
+ this.state = p.getState() != com.google.gerrit.extensions.client.ProjectState.ACTIVE ? p.getState() : null;
this.commentlinks = Maps.newLinkedHashMap();
for (CommentLinkInfo cl : projectState.getCommentLinks()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 4a3b415417..ac40df0a14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -25,6 +25,7 @@ import com.google.gerrit.extensions.restapi.RestModifyView;
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.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -69,6 +70,7 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
private final Provider<IdentifiedUser> identifiedUser;
private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> db;
private final GitReferenceUpdated referenceUpdated;
private final ChangeHooks hooks;
private String ref;
@@ -76,10 +78,12 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
@Inject
CreateBranch(Provider<IdentifiedUser> identifiedUser,
GitRepositoryManager repoManager,
+ Provider<ReviewDb> db,
GitReferenceUpdated referenceUpdated, ChangeHooks hooks,
@Assisted String ref) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
+ this.db = db;
this.referenceUpdated = referenceUpdated;
this.hooks = hooks;
this.ref = ref;
@@ -131,7 +135,8 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
}
}
- if (!refControl.canCreate(rw, object, true)) {
+ rw.reset();
+ if (!refControl.canCreate(db.get(), rw, object)) {
throw new AuthException("Cannot create \"" + ref + "\"");
}
@@ -163,6 +168,7 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
}
refPrefix = getRefPrefix(refPrefix);
}
+ //$FALL-THROUGH$
default: {
throw new IOException(result.name());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index e69b62cf9d..b0ac201b98 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -14,16 +14,16 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Objects;
+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.common.errors.ProjectCreationFailedException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.projects.ProjectInput;
-import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ProjectInfo;
-import com.google.gerrit.extensions.common.SubmitType;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -114,16 +114,21 @@ public class CreateProject implements RestModifyView<TopLevelResource, ProjectIn
args.ownerIds = ownerIds;
}
args.contributorAgreements =
- Objects.firstNonNull(input.useContributorAgreements,
+ MoreObjects.firstNonNull(input.useContributorAgreements,
InheritableBoolean.INHERIT);
args.signedOffBy =
- Objects.firstNonNull(input.useSignedOffBy, InheritableBoolean.INHERIT);
+ MoreObjects.firstNonNull(input.useSignedOffBy,
+ InheritableBoolean.INHERIT);
args.contentMerge =
input.submitType == SubmitType.FAST_FORWARD_ONLY
- ? InheritableBoolean.FALSE : Objects.firstNonNull(
- input.useContentMerge, InheritableBoolean.INHERIT);
+ ? InheritableBoolean.FALSE : MoreObjects.firstNonNull(
+ input.useContentMerge,
+ InheritableBoolean.INHERIT);
+ args.newChangeForAllNotInTarget =
+ MoreObjects.firstNonNull(input.createNewChangeForAllNotInTarget,
+ InheritableBoolean.INHERIT);
args.changeIdRequired =
- Objects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
+ MoreObjects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
try {
args.maxObjectSizeLimit =
ProjectConfig.validMaxObjectSizeLimit(input.maxObjectSizeLimit);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
index e937e0f106..bbecb33a26 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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;
@@ -33,6 +33,7 @@ public class CreateProjectArgs {
public boolean permissionsOnly;
public List<String> branch;
public InheritableBoolean contentMerge;
+ public InheritableBoolean newChangeForAllNotInTarget;
public InheritableBoolean changeIdRequired;
public boolean createEmptyCommit;
public String maxObjectSizeLimit;
@@ -42,6 +43,7 @@ public class CreateProjectArgs {
signedOffBy = InheritableBoolean.INHERIT;
contentMerge = InheritableBoolean.INHERIT;
changeIdRequired = InheritableBoolean.INHERIT;
+ newChangeForAllNotInTarget = InheritableBoolean.INHERIT;
submitType = SubmitType.MERGE_IF_NECESSARY;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java
index 0e2db483c0..099350d439 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java
@@ -24,7 +24,7 @@ public class DashboardResource implements RestResource {
public static final TypeLiteral<RestView<DashboardResource>> DASHBOARD_KIND =
new TypeLiteral<RestView<DashboardResource>>() {};
- static DashboardResource projectDefault(ProjectControl ctl) {
+ public static DashboardResource projectDefault(ProjectControl ctl) {
return new DashboardResource(ctl, null, null, null, true);
}
@@ -34,7 +34,7 @@ public class DashboardResource implements RestResource {
private final Config config;
private final boolean projectDefault;
- DashboardResource(ProjectControl control,
+ public DashboardResource(ProjectControl control,
String refName,
String pathName,
Config config,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index 061e992d55..7822fa6940 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.project;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
@@ -49,7 +49,6 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.util.List;
@Singleton
@@ -157,8 +156,7 @@ class DashboardsCollection implements
}
static DashboardInfo parse(Project definingProject, String refName,
- String path, Config config, String project, boolean setDefault)
- throws UnsupportedEncodingException {
+ String path, Config config, String project, boolean setDefault) {
DashboardInfo info = new DashboardInfo(refName, path);
info.project = project;
info.definingProject = definingProject.getName();
@@ -173,7 +171,7 @@ class DashboardsCollection implements
}
UrlEncoded u = new UrlEncoded("/dashboard/");
- u.put("title", Objects.firstNonNull(info.title, info.path));
+ u.put("title", MoreObjects.firstNonNull(info.title, info.path));
if (info.foreach != null) {
u.put("foreach", replace(project, info.foreach));
}
@@ -194,7 +192,7 @@ class DashboardsCollection implements
}
private static String defaultOf(Project proj) {
- final String defaultId = Objects.firstNonNull(
+ final String defaultId = MoreObjects.firstNonNull(
proj.getLocalDefaultDashboard(),
Strings.nullToEmpty(proj.getDefaultDashboard()));
if (defaultId.startsWith(REFS_DASHBOARDS)) {
@@ -220,8 +218,7 @@ class DashboardsCollection implements
String title;
List<Section> sections = Lists.newArrayList();
- DashboardInfo(String ref, String name)
- throws UnsupportedEncodingException {
+ DashboardInfo(String ref, String name) {
this.ref = ref;
this.path = name;
this.id = Joiner.on(':').join(Url.encode(ref), Url.encode(path));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 162a97dea5..4aba333c74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -25,12 +25,14 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.DeleteBranch.Input;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
@@ -41,6 +43,8 @@ import java.io.IOException;
@Singleton
public class DeleteBranch implements RestModifyView<BranchResource, Input>{
private static final Logger log = LoggerFactory.getLogger(DeleteBranch.class);
+ private static final int MAX_LOCK_FAILURE_CALLS = 10;
+ private static final long SLEEP_ON_LOCK_FAILURE_MS = 15;
static class Input {
}
@@ -48,16 +52,19 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input>{
private final Provider<IdentifiedUser> identifiedUser;
private final GitRepositoryManager repoManager;
private final Provider<ReviewDb> dbProvider;
+ private final Provider<InternalChangeQuery> queryProvider;
private final GitReferenceUpdated referenceUpdated;
private final ChangeHooks hooks;
@Inject
DeleteBranch(Provider<IdentifiedUser> identifiedUser,
GitRepositoryManager repoManager, Provider<ReviewDb> dbProvider,
+ Provider<InternalChangeQuery> queryProvider,
GitReferenceUpdated referenceUpdated, ChangeHooks hooks) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.dbProvider = dbProvider;
+ this.queryProvider = queryProvider;
this.referenceUpdated = referenceUpdated;
this.hooks = hooks;
}
@@ -68,8 +75,8 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input>{
if (!rsrc.getControl().controlForRef(rsrc.getBranchKey()).canDelete()) {
throw new AuthException("Cannot delete branch");
}
- if (dbProvider.get().changes().byBranchOpenAll(rsrc.getBranchKey())
- .iterator().hasNext()) {
+ if (!queryProvider.get().setLimit(1)
+ .byBranchOpen(rsrc.getBranchKey()).isEmpty()) {
throw new ResourceConflictException("branch " + rsrc.getBranchKey()
+ " has open changes");
}
@@ -77,14 +84,28 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input>{
Repository r = repoManager.openRepository(rsrc.getNameKey());
try {
RefUpdate.Result result;
- RefUpdate u;
- try {
- u = r.updateRef(rsrc.getRef());
- u.setForceUpdate(true);
- result = u.delete();
- } catch (IOException e) {
- log.error("Cannot delete " + rsrc.getBranchKey(), e);
- throw e;
+ RefUpdate u = r.updateRef(rsrc.getRef());
+ u.setForceUpdate(true);
+ int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+ for (;;) {
+ try {
+ result = u.delete();
+ } catch (LockFailedException e) {
+ result = RefUpdate.Result.LOCK_FAILURE;
+ } catch (IOException e) {
+ log.error("Cannot delete " + rsrc.getBranchKey(), e);
+ throw e;
+ }
+ if (result == RefUpdate.Result.LOCK_FAILURE
+ && --remainingLockFailureCalls > 0) {
+ try {
+ Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+ } catch (InterruptedException ie) {
+ // ignore
+ }
+ } else {
+ break;
+ }
}
switch (result) {
@@ -100,7 +121,7 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input>{
break;
case REJECTED_CURRENT_BRANCH:
- log.warn("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
+ log.error("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
throw new ResourceConflictException("cannot delete current branch");
default:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
new file mode 100644
index 0000000000..d6e93f08c5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -0,0 +1,178 @@
+// Copyright (C) 2015 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;
+
+import static java.lang.String.format;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.ChangeHooks;
+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.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.DeleteBranches.Input;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+@Singleton
+class DeleteBranches implements RestModifyView<ProjectResource, Input> {
+ private static final Logger log = LoggerFactory.getLogger(DeleteBranches.class);
+
+ static class Input {
+ List<String> branches;
+
+ static Input init(Input in) {
+ if (in == null) {
+ in = new Input();
+ }
+ if (in.branches == null) {
+ in.branches = Lists.newArrayListWithCapacity(1);
+ }
+ return in;
+ }
+ }
+
+ private final Provider<IdentifiedUser> identifiedUser;
+ private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> dbProvider;
+ private final Provider<InternalChangeQuery> queryProvider;
+ private final GitReferenceUpdated referenceUpdated;
+ private final ChangeHooks hooks;
+
+ @Inject
+ DeleteBranches(Provider<IdentifiedUser> identifiedUser,
+ GitRepositoryManager repoManager,
+ Provider<ReviewDb> dbProvider,
+ Provider<InternalChangeQuery> queryProvider,
+ GitReferenceUpdated referenceUpdated,
+ ChangeHooks hooks) {
+ this.identifiedUser = identifiedUser;
+ this.repoManager = repoManager;
+ this.dbProvider = dbProvider;
+ this.queryProvider = queryProvider;
+ this.referenceUpdated = referenceUpdated;
+ this.hooks = hooks;
+ }
+
+ @Override
+ public Response<?> apply(ProjectResource project, Input input)
+ throws OrmException, IOException, ResourceConflictException {
+ input = Input.init(input);
+ Repository r = repoManager.openRepository(project.getNameKey());
+ try {
+ BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
+ for (String branch : input.branches) {
+ batchUpdate.addCommand(createDeleteCommand(project, r, branch));
+ }
+ try (RevWalk rw = new RevWalk(r)) {
+ batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+ StringBuilder errorMessages = new StringBuilder();
+ for (ReceiveCommand command : batchUpdate.getCommands()) {
+ if (command.getResult() == Result.OK) {
+ postDeletion(project, command);
+ } else {
+ appendAndLogErrorMessage(errorMessages, command);
+ }
+ }
+ if (errorMessages.length() > 0) {
+ throw new ResourceConflictException(errorMessages.toString());
+ }
+ } finally {
+ r.close();
+ }
+ return Response.none();
+ }
+
+ private ReceiveCommand createDeleteCommand(ProjectResource project,
+ Repository r, String branch) throws OrmException, IOException {
+ Ref ref = r.getRefDatabase().getRef(branch);
+ ReceiveCommand command;
+ if (ref == null) {
+ command = new ReceiveCommand(ObjectId.zeroId(), ObjectId.zeroId(), branch);
+ command.setResult(Result.REJECTED_OTHER_REASON,
+ "it doesn't exist or you do not have permission to delete it");
+ return command;
+ }
+ command =
+ new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName());
+ Branch.NameKey branchKey =
+ new Branch.NameKey(project.getNameKey(), ref.getName());
+ if (!project.getControl().controlForRef(branchKey).canDelete()) {
+ command.setResult(Result.REJECTED_OTHER_REASON,
+ "it doesn't exist or you do not have permission to delete it");
+ }
+ if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
+ command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
+ }
+ return command;
+ }
+
+ private void appendAndLogErrorMessage(StringBuilder errorMessages,
+ ReceiveCommand cmd) {
+ String msg = null;
+ switch (cmd.getResult()) {
+ case REJECTED_CURRENT_BRANCH:
+ msg = format("Cannot delete %s: it is the current branch",
+ cmd.getRefName());
+ break;
+ case REJECTED_OTHER_REASON:
+ msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getMessage());
+ break;
+ default:
+ msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getResult());
+ break;
+ }
+ log.error(msg);
+ errorMessages.append(msg);
+ errorMessages.append("\n");
+ }
+
+ private void postDeletion(ProjectResource project, ReceiveCommand cmd)
+ throws OrmException {
+ referenceUpdated.fire(project.getNameKey(), cmd.getRefName(),
+ cmd.getOldId(), cmd.getNewId());
+ Branch.NameKey branchKey =
+ new Branch.NameKey(project.getNameKey(), cmd.getRefName());
+ hooks.doRefUpdatedHook(branchKey, cmd.getOldId(), cmd.getNewId(),
+ identifiedUser.get().getAccount());
+ ResultSet<SubmoduleSubscription> submoduleSubscriptions =
+ dbProvider.get().submoduleSubscriptions().bySuperProject(branchKey);
+ dbProvider.get().submoduleSubscriptions().delete(submoduleSubscriptions);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
index c7c66759a7..47942be9f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
@@ -16,28 +16,29 @@ package com.google.gerrit.server.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.inject.TypeLiteral;
+import org.eclipse.jgit.lib.ObjectId;
+
public class FileResource implements RestResource {
public static final TypeLiteral<RestView<FileResource>> FILE_KIND =
new TypeLiteral<RestView<FileResource>>() {};
- private final Project.NameKey project;
- private final String rev;
+ private final ProjectControl project;
+ private final ObjectId rev;
private final String path;
- public FileResource(Project.NameKey project, String rev, String path) {
+ public FileResource(ProjectControl project, ObjectId rev, String path) {
this.project = project;
this.rev = rev;
this.path = path;
}
- public Project.NameKey getProject() {
+ public ProjectControl getProject() {
return project;
}
- public String getRev() {
+ public ObjectId getRev() {
return rev;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
index 2d0af29ae7..d0460d5652 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
@@ -19,10 +19,11 @@ 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.server.project.BranchResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.ObjectId;
+
@Singleton
public class FilesCollection implements
ChildCollection<BranchResource, FileResource> {
@@ -40,7 +41,10 @@ public class FilesCollection implements
@Override
public FileResource parse(BranchResource parent, IdString id) {
- return new FileResource(parent.getNameKey(), parent.getRef(), id.get());
+ return new FileResource(
+ parent.getControl(),
+ ObjectId.fromString(parent.getRevision()),
+ id.get());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
index 4dc4338238..8e0aab8eb5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
@@ -40,8 +40,7 @@ public class FilesInCommitCollection implements
@Override
public FileResource parse(CommitResource parent, IdString id)
throws ResourceNotFoundException {
- return new FileResource(parent.getNameKey(), parent.getCommit().getName(),
- id.get());
+ return new FileResource(parent.getProject(), parent.getCommit(), id.get());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
index d43ea68b1f..63cee1f7dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
@@ -15,14 +15,12 @@
package com.google.gerrit.server.project;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CommonConverters;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
-import java.sql.Timestamp;
import java.util.ArrayList;
@Singleton
@@ -30,16 +28,14 @@ public class GetCommit implements RestReadView<CommitResource> {
@Override
public CommitInfo apply(CommitResource rsrc) {
- RevCommit c = rsrc.getCommit();
- CommitInfo info = toCommitInfo(c);
- return info;
+ return toCommitInfo(rsrc.getCommit());
}
private static CommitInfo toCommitInfo(RevCommit commit) {
CommitInfo info = new CommitInfo();
info.commit = commit.getName();
- info.author = toGitPerson(commit.getAuthorIdent());
- info.committer = toGitPerson(commit.getCommitterIdent());
+ info.author = CommonConverters.toGitPerson(commit.getAuthorIdent());
+ info.committer = CommonConverters.toGitPerson(commit.getCommitterIdent());
info.subject = commit.getShortMessage();
info.message = commit.getFullMessage();
info.parents = new ArrayList<>(commit.getParentCount());
@@ -52,13 +48,4 @@ public class GetCommit implements RestReadView<CommitResource> {
}
return info;
}
-
- private static GitPerson toGitPerson(PersonIdent ident) {
- GitPerson gp = new GitPerson();
- gp.name = ident.getName();
- gp.email = ident.getEmailAddress();
- gp.date = new Timestamp(ident.getWhen().getTime());
- gp.tz = ident.getTimeZoneOffset();
- return gp;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index e0bc7e6978..bb910970da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -17,13 +17,11 @@ package com.google.gerrit.server.project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.git.TransferConfig;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
@@ -40,8 +38,7 @@ public class GetConfig implements RestReadView<ProjectResource> {
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsNameProvider allProjects,
- DynamicMap<RestView<ProjectResource>> views,
- Provider<CurrentUser> currentUser) {
+ DynamicMap<RestView<ProjectResource>> views) {
this.config = config;
this.pluginConfigEntries = pluginConfigEntries;
this.allProjects = allProjects;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
index 5a9221bf33..23e9e30f21 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
@@ -17,25 +17,27 @@ package com.google.gerrit.server.project;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.FileContentUtil;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
public class GetContent implements RestReadView<FileResource> {
- private final Provider<com.google.gerrit.server.change.GetContent> getContent;
+ private final FileContentUtil fileContentUtil;
@Inject
- GetContent(Provider<com.google.gerrit.server.change.GetContent> getContent) {
- this.getContent = getContent;
+ GetContent(FileContentUtil fileContentUtil) {
+ this.fileContentUtil = fileContentUtil;
}
@Override
public BinaryResult apply(FileResource rsrc)
throws ResourceNotFoundException, IOException {
- return getContent.get().apply(rsrc.getProject(), rsrc.getRev(),
+ return fileContentUtil.getContent(
+ rsrc.getProject().getProjectState(),
+ rsrc.getRev(),
rsrc.getPath());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
index 29c44b5262..2efd257bcf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
@@ -17,8 +17,10 @@ package com.google.gerrit.server.project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -34,20 +36,20 @@ import java.io.IOException;
@Singleton
public class GetHead implements RestReadView<ProjectResource> {
-
private GitRepositoryManager repoManager;
+ private Provider<ReviewDb> db;
@Inject
- GetHead(GitRepositoryManager repoManager) {
+ GetHead(GitRepositoryManager repoManager,
+ Provider<ReviewDb> db) {
this.repoManager = repoManager;
+ this.db = db;
}
@Override
public String apply(ProjectResource rsrc) throws AuthException,
ResourceNotFoundException, IOException {
- Repository repo = null;
- try {
- repo = repoManager.openRepository(rsrc.getNameKey());
+ try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
Ref head = repo.getRef(Constants.HEAD);
if (head == null) {
throw new ResourceNotFoundException(Constants.HEAD);
@@ -58,10 +60,9 @@ public class GetHead implements RestReadView<ProjectResource> {
}
throw new AuthException("not allowed to see HEAD");
} else if (head.getObjectId() != null) {
- RevWalk rw = new RevWalk(repo);
- try {
+ try (RevWalk rw = new RevWalk(repo)) {
RevCommit commit = rw.parseCommit(head.getObjectId());
- if (rsrc.getControl().canReadCommit(rw, commit)) {
+ if (rsrc.getControl().canReadCommit(db.get(), rw, commit)) {
return head.getObjectId().name();
}
throw new AuthException("not allowed to see HEAD");
@@ -70,17 +71,11 @@ public class GetHead implements RestReadView<ProjectResource> {
return head.getObjectId().name();
}
throw new AuthException("not allowed to see HEAD");
- } finally {
- rw.close();
}
}
throw new ResourceNotFoundException(Constants.HEAD);
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(rsrc.getName());
- } finally {
- if (repo != null) {
- repo.close();
- }
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
index b6415b5eaa..7e52381e0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
@@ -20,12 +20,12 @@ import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.args4j.TimestampHandler;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
@@ -75,7 +75,7 @@ public class GetReflog implements RestReadView<BranchResource> {
public List<ReflogEntryInfo> apply(BranchResource rsrc) throws AuthException,
ResourceNotFoundException, RepositoryNotFoundException, IOException {
if (!rsrc.getControl().isOwner()) {
- throw new AuthException("no project owner");
+ throw new AuthException("not project owner");
}
Repository repo = repoManager.openRepository(rsrc.getNameKey());
@@ -122,14 +122,7 @@ public class GetReflog implements RestReadView<BranchResource> {
public ReflogEntryInfo(ReflogEntry e) {
oldId = e.getOldId().getName();
newId = e.getNewId().getName();
-
- PersonIdent ident = e.getWho();
- who = new GitPerson();
- who.name = ident.getName();
- who.email = ident.getEmailAddress();
- who.date = new Timestamp(ident.getWhen().getTime());
- who.tz = ident.getTimeZoneOffset();
-
+ who = CommonConverters.toGitPerson(e.getWho());
comment = e.getComment();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
new file mode 100644
index 0000000000..5b78e08e7b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 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;
+
+import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetTag implements RestReadView<TagResource> {
+
+ @Override
+ public TagInfo apply(TagResource resource) {
+ return resource.getTagInfo();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index b09e39fdbc..a8eda9782b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -14,50 +14,74 @@
package com.google.gerrit.server.project;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.WebLinks;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
-import com.google.inject.Singleton;
import com.google.inject.util.Providers;
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-@Singleton
public class ListBranches implements RestReadView<ProjectResource> {
private final GitRepositoryManager repoManager;
private final DynamicMap<RestView<BranchResource>> branchViews;
+ private final WebLinks webLinks;
+
+ @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of branches to list")
+ private int limit;
+
+ @Option(name = "--start", aliases = {"-s"}, metaVar = "CNT", usage = "number of branches to skip")
+ private int start;
+
+ @Option(name = "--match", aliases = {"-m"}, metaVar = "MATCH", usage = "match branches substring")
+ private String matchSubstring;
+
+ @Option(name = "--regex", aliases = {"-r"}, metaVar = "REGEX", usage = "match branches regex")
+ private String matchRegex;
@Inject
public ListBranches(GitRepositoryManager repoManager,
- DynamicMap<RestView<BranchResource>> branchViews) {
+ DynamicMap<RestView<BranchResource>> branchViews,
+ WebLinks webLinks) {
this.repoManager = repoManager;
this.branchViews = branchViews;
+ this.webLinks = webLinks;
}
@Override
public List<BranchInfo> apply(ProjectResource rsrc)
- throws ResourceNotFoundException, IOException {
+ throws ResourceNotFoundException, IOException, BadRequestException {
List<BranchInfo> branches = Lists.newArrayList();
BranchInfo headBranch = null;
@@ -148,6 +172,73 @@ public class ListBranches implements RestReadView<ProjectResource> {
if (headBranch != null) {
branches.add(0, headBranch);
}
+
+ List<BranchInfo> filteredBranches;
+ if ((matchSubstring != null && !matchSubstring.isEmpty())
+ || (matchRegex != null && !matchRegex.isEmpty())) {
+ filteredBranches = filterBranches(branches);
+ } else {
+ filteredBranches = branches;
+ }
+ if (!filteredBranches.isEmpty()) {
+ int end = filteredBranches.size();
+ if (limit > 0 && start + limit < end) {
+ end = start + limit;
+ }
+ if (start <= end) {
+ filteredBranches = filteredBranches.subList(start, end);
+ } else {
+ filteredBranches = Collections.emptyList();
+ }
+ }
+ return filteredBranches;
+ }
+
+ private List<BranchInfo> filterBranches(List<BranchInfo> branches)
+ throws BadRequestException {
+ if (matchSubstring != null) {
+ return Lists.newArrayList(Iterables.filter(branches,
+ new Predicate<BranchInfo>() {
+ @Override
+ public boolean apply(BranchInfo in) {
+ if (!in.ref.startsWith(Constants.R_HEADS)){
+ return in.ref.toLowerCase(Locale.US).contains(
+ matchSubstring.toLowerCase(Locale.US));
+ } else {
+ return in.ref.substring(Constants.R_HEADS.length())
+ .toLowerCase(Locale.US)
+ .contains(matchSubstring.toLowerCase(Locale.US));
+ }
+ }
+ }));
+ } else if (matchRegex != null) {
+ if (matchRegex.startsWith("^")) {
+ matchRegex = matchRegex.substring(1);
+ if (matchRegex.endsWith("$") && !matchRegex.endsWith("\\$")) {
+ matchRegex = matchRegex.substring(0, matchRegex.length() - 1);
+ }
+ }
+ if (matchRegex.equals(".*")) {
+ return branches;
+ }
+ try {
+ final RunAutomaton a =
+ new RunAutomaton(new RegExp(matchRegex).toAutomaton());
+ return Lists.newArrayList(Iterables.filter(
+ branches, new Predicate<BranchInfo>() {
+ @Override
+ public boolean apply(BranchInfo in) {
+ if (!in.ref.startsWith(Constants.R_HEADS)){
+ return a.run(in.ref);
+ } else {
+ return a.run(in.ref.substring(Constants.R_HEADS.length()));
+ }
+ }
+ }));
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ }
return branches;
}
@@ -165,6 +256,10 @@ public class ListBranches implements RestReadView<ProjectResource> {
}
info.actions.put(d.getId(), new ActionInfo(d));
}
+ FluentIterable<WebLinkInfo> links =
+ webLinks.getBranchLinks(
+ refControl.getProjectControl().getProject().getName(), ref.getName());
+ info.webLinks = links.isEmpty() ? null : links.toList();
return info;
}
@@ -173,6 +268,7 @@ public class ListBranches implements RestReadView<ProjectResource> {
public String revision;
public Boolean canDelete;
public Map<String, ActionInfo> actions;
+ public List<WebLinkInfo> webLinks;
public BranchInfo(String ref, String revision, boolean canDelete) {
this.ref = ref;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
index 658cf13f1a..456abfcdf5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
@@ -82,28 +82,19 @@ class ListDashboards implements RestReadView<ProjectResource> {
private List<DashboardInfo> scan(ProjectControl ctl, String project,
boolean setDefault) throws ResourceNotFoundException, IOException {
- Repository git;
- try {
- git = gitManager.openRepository(ctl.getProject().getNameKey());
- } catch (RepositoryNotFoundException e) {
- throw new ResourceNotFoundException();
- }
- try {
- RevWalk rw = new RevWalk(git);
- try {
- List<DashboardInfo> all = Lists.newArrayList();
- for (Ref ref : git.getRefDatabase().getRefs(REFS_DASHBOARDS).values()) {
- if (ctl.controlForRef(ref.getName()).canRead()) {
- all.addAll(scanDashboards(ctl.getProject(), git, rw, ref,
- project, setDefault));
- }
+ Project.NameKey projectName = ctl.getProject().getNameKey();
+ try (Repository git = gitManager.openRepository(projectName);
+ RevWalk rw = new RevWalk(git)) {
+ List<DashboardInfo> all = Lists.newArrayList();
+ for (Ref ref : git.getRefDatabase().getRefs(REFS_DASHBOARDS).values()) {
+ if (ctl.controlForRef(ref.getName()).canRead()) {
+ all.addAll(scanDashboards(ctl.getProject(), git, rw, ref,
+ project, setDefault));
}
- return all;
- } finally {
- rw.close();
}
- } finally {
- git.close();
+ return all;
+ } catch (RepositoryNotFoundException e) {
+ throw new ResourceNotFoundException();
}
}
@@ -111,8 +102,7 @@ class ListDashboards implements RestReadView<ProjectResource> {
Repository git, RevWalk rw, Ref ref, String project, boolean setDefault)
throws IOException {
List<DashboardInfo> list = Lists.newArrayList();
- TreeWalk tw = new TreeWalk(rw.getObjectReader());
- try {
+ try (TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
tw.addTree(rw.parseTree(ref.getObjectId()));
tw.setRecursive(true);
while (tw.next()) {
@@ -133,8 +123,6 @@ class ListDashboards implements RestReadView<ProjectResource> {
}
}
}
- } finally {
- tw.close();
}
return list;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index 3f829c9098..a9851e4f6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -16,6 +16,8 @@ package com.google.gerrit.server.project;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -38,13 +40,10 @@ import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
@@ -113,7 +112,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private final GroupControl.Factory groupControlFactory;
private final GitRepositoryManager repoManager;
private final ProjectNode.Factory projectNodeFactory;
- private final Provider<WebLinks> webLinks;
+ private final WebLinks webLinks;
@Deprecated
@Option(name = "--format", usage = "(deprecated) output format")
@@ -194,7 +193,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
protected ListProjects(CurrentUser currentUser, ProjectCache projectCache,
GroupCache groupCache, GroupControl.Factory groupControlFactory,
GitRepositoryManager repoManager, ProjectNode.Factory projectNodeFactory,
- Provider<WebLinks> webLinks) {
+ WebLinks webLinks) {
this.currentUser = currentUser;
this.projectCache = projectCache;
this.groupCache = groupCache;
@@ -384,13 +383,9 @@ public class ListProjects implements RestReadView<TopLevelResource> {
log.warn("Unexpected error reading " + projectName, err);
continue;
}
-
- info.webLinks = Lists.newArrayList();
- for (WebLinkInfo link : webLinks.get().getProjectLinks(projectName.get())) {
- if (!Strings.isNullOrEmpty(link.name) && !Strings.isNullOrEmpty(link.url)) {
- info.webLinks.add(link);
- }
- }
+ FluentIterable<WebLinkInfo> links =
+ webLinks.getProjectLinks(projectName.get());
+ info.webLinks = links.isEmpty() ? null : links.toList();
}
if (foundIndex++ < start) {
@@ -449,42 +444,44 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private Iterable<Project.NameKey> scan() throws BadRequestException {
if (matchPrefix != null) {
+ checkMatchOptions(matchSubstring == null && matchRegex == null);
return projectCache.byName(matchPrefix);
} else if (matchSubstring != null) {
+ checkMatchOptions(matchPrefix == null && matchRegex == null);
return Iterables.filter(projectCache.all(),
new Predicate<Project.NameKey>() {
+ @Override
public boolean apply(Project.NameKey in) {
return in.get().toLowerCase(Locale.US)
.contains(matchSubstring.toLowerCase(Locale.US));
}
});
} else if (matchRegex != null) {
- if (matchRegex.startsWith("^")) {
- matchRegex = matchRegex.substring(1);
- if (matchRegex.endsWith("$") && !matchRegex.endsWith("\\$")) {
- matchRegex = matchRegex.substring(0, matchRegex.length() - 1);
- }
- }
- if (matchRegex.equals(".*")) {
- return projectCache.all();
- }
+ checkMatchOptions(matchPrefix == null && matchSubstring == null);
+ RegexListSearcher<Project.NameKey> searcher;
try {
- final RunAutomaton a =
- new RunAutomaton(new RegExp(matchRegex).toAutomaton());
- return Iterables.filter(projectCache.all(),
- new Predicate<Project.NameKey>() {
- public boolean apply(Project.NameKey in) {
- return a.run(in.get());
- }
- });
+ searcher = new RegexListSearcher<Project.NameKey>(matchRegex) {
+ @Override
+ public String apply(Project.NameKey in) {
+ return in.get();
+ }
+ };
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
+ return searcher.search(ImmutableList.copyOf(projectCache.all()));
} else {
return projectCache.all();
}
}
+ private static void checkMatchOptions(boolean cond)
+ throws BadRequestException {
+ if (!cond) {
+ throw new BadRequestException("specify exactly one of p/m/r");
+ }
+ }
+
private void printProjectTree(final PrintWriter stdout,
final TreeMap<Project.NameKey, ProjectNode> treeMap) {
final SortedSet<ProjectNode> sortedNodes = new TreeSet<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
new file mode 100644
index 0000000000..b01e563e1e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
@@ -0,0 +1,150 @@
+// Copyright (C) 2014 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;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CommonConverters;
+import com.google.gerrit.server.git.ChangeCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.git.VisibleRefFilter;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+@Singleton
+public class ListTags implements RestReadView<ProjectResource> {
+ private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> dbProvider;
+ private final TagCache tagCache;
+ private final ChangeCache changeCache;
+
+ @Inject
+ public ListTags(GitRepositoryManager repoManager,
+ Provider<ReviewDb> dbProvider,
+ TagCache tagCache,
+ ChangeCache changeCache) {
+ this.repoManager = repoManager;
+ this.dbProvider = dbProvider;
+ this.tagCache = tagCache;
+ this.changeCache = changeCache;
+ }
+
+ @Override
+ public List<TagInfo> apply(ProjectResource resource) throws IOException,
+ ResourceNotFoundException {
+ List<TagInfo> tags = Lists.newArrayList();
+
+ Repository repo = getRepository(resource.getNameKey());
+
+ try {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ Map<String, Ref> all = visibleTags(resource.getControl(), repo,
+ repo.getRefDatabase().getRefs(Constants.R_TAGS));
+ for (Ref ref : all.values()) {
+ tags.add(createTagInfo(ref, rw));
+ }
+ } finally {
+ rw.dispose();
+ }
+ } finally {
+ repo.close();
+ }
+
+ Collections.sort(tags, new Comparator<TagInfo>() {
+ @Override
+ public int compare(TagInfo a, TagInfo b) {
+ return a.ref.compareTo(b.ref);
+ }
+ });
+
+ return tags;
+ }
+
+ public TagInfo get(ProjectResource resource, IdString id)
+ throws ResourceNotFoundException, IOException {
+ try (Repository repo = getRepository(resource.getNameKey());
+ RevWalk rw = new RevWalk(repo)) {
+ String tagName = id.get();
+ if (!tagName.startsWith(Constants.R_TAGS)) {
+ tagName = Constants.R_TAGS + tagName;
+ }
+ Ref ref = repo.getRefDatabase().getRef(tagName);
+ if (ref != null && !visibleTags(resource.getControl(), repo,
+ ImmutableMap.of(ref.getName(), ref)).isEmpty()) {
+ return createTagInfo(ref, rw);
+ }
+ }
+ throw new ResourceNotFoundException(id);
+ }
+
+ private Repository getRepository(Project.NameKey project)
+ throws ResourceNotFoundException, IOException {
+ try {
+ return repoManager.openRepository(project);
+ } catch (RepositoryNotFoundException noGitRepository) {
+ throw new ResourceNotFoundException();
+ }
+ }
+
+ private Map<String, Ref> visibleTags(ProjectControl control, Repository repo,
+ Map<String, Ref> tags) {
+ return new VisibleRefFilter(tagCache, changeCache, repo,
+ control, dbProvider.get(), false).filter(tags, true);
+ }
+
+ private static TagInfo createTagInfo(Ref ref, RevWalk rw)
+ throws MissingObjectException, IOException {
+ RevObject object = rw.parseAny(ref.getObjectId());
+ if (object instanceof RevTag) {
+ RevTag tag = (RevTag)object;
+ // Annotated or signed tag
+ return new TagInfo(
+ Constants.R_TAGS + tag.getTagName(),
+ tag.getName(),
+ tag.getObject().getName(),
+ tag.getFullMessage().trim(),
+ CommonConverters.toGitPerson(tag.getTaggerIdent()));
+ } else {
+ // Lightweight tag
+ return new TagInfo(
+ ref.getName(),
+ ref.getObjectId().getName());
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 7b50b0f421..430d8f5a4d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -20,6 +20,7 @@ import static com.google.gerrit.server.project.CommitResource.COMMIT_KIND;
import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND;
import static com.google.gerrit.server.project.FileResource.FILE_KIND;
import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
+import static com.google.gerrit.server.project.TagResource.TAG_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -37,6 +38,7 @@ public class Module extends RestApiModule {
DynamicMap.mapOf(binder(), DASHBOARD_KIND);
DynamicMap.mapOf(binder(), FILE_KIND);
DynamicMap.mapOf(binder(), COMMIT_KIND);
+ DynamicMap.mapOf(binder(), TAG_KIND);
put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
@@ -62,6 +64,7 @@ public class Module extends RestApiModule {
put(BRANCH_KIND).to(PutBranch.class);
get(BRANCH_KIND).to(GetBranch.class);
delete(BRANCH_KIND).to(DeleteBranch.class);
+ post(PROJECT_KIND, "branches:delete").to(DeleteBranches.class);
install(new FactoryModuleBuilder().build(CreateBranch.Factory.class));
get(BRANCH_KIND, "reflog").to(GetReflog.class);
child(BRANCH_KIND, "files").to(FilesCollection.class);
@@ -71,6 +74,9 @@ public class Module extends RestApiModule {
get(COMMIT_KIND).to(GetCommit.class);
child(COMMIT_KIND, "files").to(FilesInCommitCollection.class);
+ child(PROJECT_KIND, "tags").to(TagsCollection.class);
+ get(TAG_KIND).to(GetTag.class);
+
child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
get(DASHBOARD_KIND).to(GetDashboard.class);
put(DASHBOARD_KIND).to(SetDashboard.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index 43bdd22526..689920b6d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.gerrit.common.ProjectUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupDescription;
@@ -22,7 +22,7 @@ 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.common.errors.ProjectCreationFailedException;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -187,12 +187,13 @@ public class PerformCreateProject {
Project newProject = config.getProject();
newProject.setDescription(createProjectArgs.projectDescription);
- newProject.setSubmitType(Objects.firstNonNull(createProjectArgs.submitType,
+ newProject.setSubmitType(MoreObjects.firstNonNull(createProjectArgs.submitType,
cfg.getEnum("repository", "*", "defaultSubmitType", SubmitType.MERGE_IF_NECESSARY)));
newProject
.setUseContributorAgreements(createProjectArgs.contributorAgreements);
newProject.setUseSignedOffBy(createProjectArgs.signedOffBy);
newProject.setUseContentMerge(createProjectArgs.contentMerge);
+ newProject.setCreateNewChangeForAllNotInTarget(createProjectArgs.newChangeForAllNotInTarget);
newProject.setRequireChangeID(createProjectArgs.changeIdRequired);
newProject.setMaxObjectSizeLimit(createProjectArgs.maxObjectSizeLimit);
if (createProjectArgs.newParent != null) {
@@ -269,8 +270,7 @@ public class PerformCreateProject {
private void createEmptyCommits(final Repository repo,
final Project.NameKey project, final List<String> refs)
throws IOException {
- ObjectInserter oi = repo.newObjectInserter();
- try {
+ try (ObjectInserter oi = repo.newObjectInserter()) {
CommitBuilder cb = new CommitBuilder();
cb.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {}));
cb.setAuthor(metaDataUpdateFactory.getUserPersonIdent());
@@ -299,8 +299,6 @@ public class PerformCreateProject {
"Cannot create empty commit for "
+ createProjectArgs.getProjectName(), e);
throw e;
- } finally {
- oi.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index 321c2ea264..a5cf5d6d2f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -14,17 +14,20 @@
package com.google.gerrit.server.project;
-import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.server.project.RefControl.isRE;
+import com.google.auto.value.AutoValue;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.Nullable;
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.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.ArrayList;
@@ -61,23 +64,22 @@ public class PermissionCollection {
* priority order (project specific definitions must appear before
* inherited ones).
* @param ref reference being accessed.
- * @param usernames if the reference is a per-user reference, access sections
- * using the parameter variable "${username}" will first have each of
- * {@code usernames} inserted into them before seeing if they apply to
- * the reference named by {@code ref}. If null or empty, per-user
- * references are ignored.
+ * @param usernameProvider if the reference is a per-user reference, access
+ * sections using the parameter variable "${username}" will first
+ * have each of {@code usernames} inserted into them before seeing if
+ * they apply to the reference named by {@code ref}.
* @return map of permissions that apply to this reference, keyed by
* permission name.
*/
PermissionCollection filter(Iterable<SectionMatcher> matcherList,
- String ref, Collection<String> usernames) {
+ String ref, Provider<? extends Collection<String>> usernameProvider) {
if (isRE(ref)) {
ref = RefControl.shortestExample(ref);
} else if (ref.endsWith("/*")) {
ref = ref.substring(0, ref.length() - 1);
}
- boolean hasUsernames = usernames != null && !usernames.isEmpty();
+ Collection<String> usernames = null;
boolean perUser = false;
Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
for (SectionMatcher sm : matcherList) {
@@ -93,9 +95,13 @@ public class PermissionCollection {
// that will never be shared with non-user references, and the per-user
// references are usually less frequent than the non-user references.
//
- if (hasUsernames) {
- if (!perUser && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
- perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
+ if (sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
+ if (!((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref)) {
+ continue;
+ }
+ perUser = true;
+ if (usernames == null) {
+ usernames = usernameProvider.get();
}
for (String username : usernames) {
if (sm.match(ref, username)) {
@@ -110,7 +116,7 @@ public class PermissionCollection {
List<AccessSection> sections = Lists.newArrayList(sectionToProject.keySet());
sorter.sort(ref, sections);
- Set<SeenRule> seen = new HashSet<SeenRule>();
+ Set<SeenRule> seen = new HashSet<>();
Set<String> exclusiveGroupPermissions = new HashSet<>();
HashMap<String, List<PermissionRule>> permissions = new HashMap<>();
@@ -123,7 +129,7 @@ public class PermissionCollection {
exclusiveGroupPermissions.contains(permission.getName());
for (PermissionRule rule : permission.getRules()) {
- SeenRule s = new SeenRule(section, permission, rule);
+ SeenRule s = SeenRule.create(section, permission, rule);
boolean addRule;
if (rule.isBlock()) {
addRule = true;
@@ -145,7 +151,7 @@ public class PermissionCollection {
p.put(permission.getName(), r);
}
r.add(rule);
- ruleProps.put(rule, new ProjectRef(project, section.getName()));
+ ruleProps.put(rule, ProjectRef.create(project, section.getName()));
}
}
@@ -220,41 +226,19 @@ public class PermissionCollection {
}
/** Tracks whether or not a permission has been overridden. */
- private static class SeenRule {
- final String refPattern;
- final String permissionName;
- final AccountGroup.UUID group;
-
- SeenRule(AccessSection section, Permission permission, PermissionRule rule) {
- refPattern = section.getName();
- permissionName = permission.getName();
- group = rule.getGroup().getUUID();
- }
-
- @Override
- public int hashCode() {
- int hc = refPattern.hashCode();
- hc = hc * 31 + permissionName.hashCode();
- if (group != null) {
- hc = hc * 31 + group.hashCode();
- }
- return hc;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof SeenRule) {
- SeenRule a = this;
- SeenRule b = (SeenRule) other;
- return a.refPattern.equals(b.refPattern) //
- && a.permissionName.equals(b.permissionName) //
- && eq(a.group, b.group);
- }
- return false;
- }
-
- private boolean eq(AccountGroup.UUID a, AccountGroup.UUID b) {
- return a != null && b != null && a.equals(b);
+ @AutoValue
+ abstract static class SeenRule {
+ public abstract String refPattern();
+ public abstract String permissionName();
+ @Nullable public abstract AccountGroup.UUID group();
+
+ static SeenRule create(AccessSection section, Permission permission,
+ @Nullable PermissionRule rule) {
+ AccountGroup.UUID group = rule != null && rule.getGroup() != null
+ ? rule.getGroup().getUUID()
+ : null;
+ return new AutoValue_PermissionCollection_SeenRule(
+ section.getName(), permission.getName(), group);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 1e7a221a18..3b2dfbcb91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -18,6 +18,7 @@ import com.google.common.base.Throwables;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheModule;
@@ -29,6 +30,7 @@ import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
+import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.name.Named;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -70,6 +72,9 @@ public class ProjectCacheImpl implements ProjectCache {
bind(ProjectCacheImpl.class);
bind(ProjectCache.class).to(ProjectCacheImpl.class);
+ bind(LifecycleListener.class)
+ .annotatedWith(UniqueAnnotations.create())
+ .to(ProjectCacheWarmer.class);
}
};
}
@@ -157,6 +162,7 @@ public class ProjectCacheImpl implements ProjectCache {
}
/** Invalidate the cached information about the given project. */
+ @Override
public void evict(final Project.NameKey p) {
if (p != null) {
byName.invalidate(p.get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheWarmer.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
new file mode 100644
index 0000000000..2cdb172735
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2014 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;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Singleton
+public class ProjectCacheWarmer implements LifecycleListener {
+ private static final Logger log =
+ LoggerFactory.getLogger(ProjectCacheWarmer.class);
+
+ private final Config config;
+ private final ProjectCache cache;
+
+ @Inject
+ ProjectCacheWarmer(@GerritServerConfig Config config, ProjectCache cache) {
+ this.config = config;
+ this.cache = cache;
+ }
+
+ @Override
+ public void start() {
+ int cpus = Runtime.getRuntime().availableProcessors();
+ if (config.getBoolean("cache", "projects", "loadOnStartup", false)) {
+ final ThreadPoolExecutor pool =
+ new ScheduledThreadPoolExecutor(config.getInt("cache", "projects",
+ "loadThreads", cpus), new ThreadFactoryBuilder().setNameFormat(
+ "ProjectCacheLoader-%d").build());
+
+ log.info("Loading project cache");
+ pool.execute(new Runnable() {
+ @Override
+ public void run() {
+ for (final Project.NameKey name : cache.all()) {
+ pool.execute(new Runnable() {
+ @Override
+ public void run() {
+ cache.get(name);
+ }
+ });
+ }
+ pool.shutdown();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void stop() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 7eda31f1e6..3bbcf718e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.project;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccessSection;
@@ -30,22 +30,25 @@ 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.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
+import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.change.IncludedInResolver;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
+import com.google.gerrit.server.git.ChangeCache;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -53,6 +56,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -148,6 +152,8 @@ public class ProjectControl {
private final ChangeControl.AssistedFactory changeControlFactory;
private final PermissionCollection.Factory permissionFilter;
private final Collection<ContributorAgreement> contributorAgreements;
+ private final TagCache tagCache;
+ private final ChangeCache changeCache;
private List<SectionMatcher> allSections;
private List<SectionMatcher> localSections;
@@ -162,11 +168,15 @@ public class ProjectControl {
PermissionCollection.Factory permissionFilter,
GitRepositoryManager repoManager,
ChangeControl.AssistedFactory changeControlFactory,
+ TagCache tagCache,
+ ChangeCache changeCache,
@CanonicalWebUrl @Nullable String canonicalWebUrl,
@Assisted CurrentUser who,
@Assisted ProjectState ps) {
this.repoManager = repoManager;
this.changeControlFactory = changeControlFactory;
+ this.tagCache = tagCache;
+ this.changeCache = changeCache;
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.permissionFilter = permissionFilter;
@@ -197,15 +207,25 @@ public class ProjectControl {
}
RefControl ctl = refControls.get(refName);
if (ctl == null) {
- ImmutableList.Builder<String> usernames = ImmutableList.<String> builder();
- if (user.getUserName() != null) {
- usernames.add(user.getUserName());
- }
- if (user instanceof IdentifiedUser) {
- usernames.addAll(((IdentifiedUser) user).getEmailAddresses());
- }
+ Provider<List<String>> usernames = new Provider<List<String>>() {
+ @Override
+ public List<String> get() {
+ List<String> r;
+ if (user.isIdentifiedUser()) {
+ Set<String> emails = ((IdentifiedUser) user).getEmailAddresses();
+ r = new ArrayList<>(emails.size() + 1);
+ r.addAll(emails);
+ } else {
+ r = new ArrayList<>(1);
+ }
+ if (user.getUserName() != null) {
+ r.add(user.getUserName());
+ }
+ return r;
+ }
+ };
PermissionCollection relevant =
- permissionFilter.filter(access(), refName, usernames.build());
+ permissionFilter.filter(access(), refName, usernames);
ctl = new RefControl(this, refName, relevant);
refControls.put(refName, ctl);
}
@@ -233,7 +253,7 @@ public class ProjectControl {
private boolean isHidden() {
return getProject().getState().equals(
- com.google.gerrit.extensions.api.projects.ProjectState.HIDDEN);
+ com.google.gerrit.extensions.client.ProjectState.HIDDEN);
}
/** Can this user see this project exists? */
@@ -263,12 +283,12 @@ public class ProjectControl {
/** Can this user see all the refs in this projects? */
public boolean allRefsAreVisible() {
- return allRefsAreVisibleExcept(Collections.<String> emptySet());
+ return allRefsAreVisible(Collections.<String> emptySet());
}
- public boolean allRefsAreVisibleExcept(Set<String> except) {
+ public boolean allRefsAreVisible(Set<String> ignore) {
return user instanceof InternalUser
- || canPerformOnAllRefs(Permission.READ, except);
+ || canPerformOnAllRefs(Permission.READ, ignore);
}
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
@@ -279,7 +299,8 @@ public class ProjectControl {
private boolean isDeclaredOwner() {
if (declaredOwner == null) {
- declaredOwner = state.isOwner(user.getEffectiveGroups());
+ GroupMembership effectiveGroups = user.getEffectiveGroups();
+ declaredOwner = effectiveGroups.containsAnyOf(state.getAllOwners());
}
return declaredOwner;
}
@@ -426,7 +447,7 @@ public class ProjectControl {
return false;
}
- private boolean canPerformOnAllRefs(String permission, Set<String> except) {
+ private boolean canPerformOnAllRefs(String permission, Set<String> ignore) {
boolean canPerform = false;
Set<String> patterns = allRefPatterns(permission);
if (patterns.contains(AccessSection.ALL)) {
@@ -437,7 +458,7 @@ public class ProjectControl {
for (final String pattern : patterns) {
if (controlForRef(pattern).canPerform(permission)) {
canPerform = true;
- } else if (except.contains(pattern)) {
+ } else if (ignore.contains(pattern)) {
continue;
} else {
return false;
@@ -513,40 +534,38 @@ public class ProjectControl {
return false;
}
- public boolean canReadCommit(RevWalk rw, RevCommit commit) {
- if (controlForRef("refs/*").canPerform(Permission.READ)) {
- return true;
- }
-
- Project.NameKey projName = state.getProject().getNameKey();
+ public boolean canReadCommit(ReviewDb db, RevWalk rw, RevCommit commit) {
try {
- Repository repo = repoManager.openRepository(projName);
+ Repository repo = openRepository();
try {
- RefDatabase refDb = repo.getRefDatabase();
- List<Ref> allRefs = Lists.newLinkedList();
- allRefs.addAll(refDb.getRefs(Constants.R_HEADS).values());
- allRefs.addAll(refDb.getRefs(Constants.R_TAGS).values());
- List<Ref> canReadRefs = Lists.newLinkedList();
- for (Ref r : allRefs) {
- if (controlForRef(r.getName()).canPerform(Permission.READ)) {
- canReadRefs.add(r);
- }
- }
-
- if (!canReadRefs.isEmpty() && IncludedInResolver.includedInOne(
- repo, rw, commit, canReadRefs)) {
- return true;
- }
+ return isMergedIntoVisibleRef(repo, db, rw, commit,
+ repo.getAllRefs().values());
} finally {
repo.close();
}
} catch (IOException e) {
- String msg =
- String.format(
- "Cannot verify permissions to commit object %s in repository %s",
- commit.name(), projName.get());
+ String msg = String.format(
+ "Cannot verify permissions to commit object %s in repository %s",
+ commit.name(), getProject().getNameKey());
log.error(msg, e);
+ return false;
}
- return false;
+ }
+
+ boolean isMergedIntoVisibleRef(Repository repo, ReviewDb db, RevWalk rw,
+ RevCommit commit, Collection<Ref> unfilteredRefs) throws IOException {
+ VisibleRefFilter filter =
+ new VisibleRefFilter(tagCache, changeCache, repo, this, db, true);
+ Map<String, Ref> m = Maps.newHashMapWithExpectedSize(unfilteredRefs.size());
+ for (Ref r : unfilteredRefs) {
+ m.put(r.getName(), r);
+ }
+ Map<String, Ref> refs = filter.filter(m, true);
+ return !refs.isEmpty()
+ && IncludedInResolver.includedInOne(repo, rw, commit, refs.values());
+ }
+
+ Repository openRepository() throws IOException {
+ return repoManager.openRepository(getProject().getNameKey());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
index 4cafc0a556..6415664afb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.project;
import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
+import com.google.common.collect.FluentIterable;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.Url;
@@ -24,18 +24,17 @@ import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
public class ProjectJson {
private final AllProjectsName allProjects;
- private final Provider<WebLinks> webLinks;
+ private final WebLinks webLinks;
@Inject
ProjectJson(AllProjectsNameProvider allProjectsNameProvider,
- Provider<WebLinks> webLinks) {
+ WebLinks webLinks) {
this.allProjects = allProjectsNameProvider.get();
this.webLinks = webLinks;
}
@@ -52,14 +51,9 @@ public class ProjectJson {
info.description = Strings.emptyToNull(p.getDescription());
info.state = p.getState();
info.id = Url.encode(info.name);
-
- info.webLinks = Lists.newArrayList();
- for (WebLinkInfo link : webLinks.get().getProjectLinks(p.getName())) {
- if (!Strings.isNullOrEmpty(link.name) && !Strings.isNullOrEmpty(link.url)) {
- info.webLinks.add(link);
- }
- }
-
+ FluentIterable<WebLinkInfo> links =
+ webLinks.getProjectLinks(p.getName());
+ info.webLinks = links.isEmpty() ? null : links.toList();
return info;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
index 0315fada16..8d3185d010 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
@@ -14,32 +14,15 @@
package com.google.gerrit.server.project;
+import com.google.auto.value.AutoValue;
import com.google.gerrit.reviewdb.client.Project;
-class ProjectRef {
+@AutoValue
+abstract class ProjectRef {
+ public abstract Project.NameKey project();
+ public abstract String ref();
- final Project.NameKey project;
- final String ref;
-
- ProjectRef(Project.NameKey project, String ref) {
- this.project = project;
- this.ref = ref;
- }
-
- @Override
- public boolean equals(Object other) {
- return other instanceof ProjectRef
- && project.equals(((ProjectRef) other).project)
- && ref.equals(((ProjectRef) other).ref);
- }
-
- @Override
- public int hashCode() {
- return project.hashCode() * 31 + ref.hashCode();
- }
-
- @Override
- public String toString() {
- return project + ", " + ref;
+ static ProjectRef create(Project.NameKey project, String ref) {
+ return new AutoValue_ProjectRef(project, ref);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
index d6a2e09d77..b8830a053c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Project;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index b9cead32ef..558b572f8e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -18,7 +18,6 @@ import static com.google.gerrit.common.data.PermissionRule.Action.ALLOW;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
-import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -30,7 +29,7 @@ import com.google.gerrit.common.data.LabelType;
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.extensions.common.InheritableBoolean;
+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;
@@ -38,7 +37,6 @@ import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityCollection;
-import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.BranchOrderSection;
@@ -314,16 +312,20 @@ public class ProjectState {
}
/**
- * @return true if any of the groups listed in {@code groups} was declared to
- * be an owner of this project, or one of its parent projects..
+ * @return all {@link AccountGroup}'s that are allowed to administrate the
+ * complete project. This includes all groups to which the owner
+ * privilege for 'refs/*' is assigned for this project (the local
+ * owners) and all groups to which the owner privilege for 'refs/*' is
+ * assigned for one of the parent projects (the inherited owners).
*/
- boolean isOwner(final GroupMembership groups) {
- return Iterables.any(tree(), new Predicate<ProjectState>() {
- @Override
- public boolean apply(ProjectState in) {
- return groups.containsAnyOf(in.localOwners);
- }
- });
+ public Set<AccountGroup.UUID> getAllOwners() {
+ Set<AccountGroup.UUID> result = new HashSet<>();
+
+ for (ProjectState p : tree()) {
+ result.addAll(p.localOwners);
+ }
+
+ return result;
}
public ProjectControl controlFor(final CurrentUser user) {
@@ -405,6 +407,15 @@ public class ProjectState {
});
}
+ public boolean isCreateNewChangeForAllNotInTarget() {
+ return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
+ @Override
+ public InheritableBoolean apply(Project input) {
+ return input.getCreateNewChangeForAllNotInTarget();
+ }
+ });
+ }
+
public LabelTypes getLabelTypes() {
Map<String, LabelType> types = Maps.newLinkedHashMap();
for (ProjectState s : treeInOrder()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
index 519f4f21a8..0ffbe3e60f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
@@ -29,6 +29,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Constants;
+
import java.io.IOException;
@Singleton
@@ -88,6 +90,9 @@ public class ProjectsCollection implements
}
private ProjectResource _parse(String id) throws IOException {
+ if (id.endsWith(Constants.DOT_GIT_EXT)) {
+ id = id.substring(0, id.length() - Constants.DOT_GIT_EXT.length());
+ }
ProjectControl ctl;
try {
ctl = controlFactory.controlFor(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 3aed95d07b..27e7b1b75f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -16,12 +16,11 @@ package com.google.gerrit.server.project;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.extensions.api.projects.ProjectInput.ConfigValue;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -32,11 +31,11 @@ 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.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
@@ -57,6 +56,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
@Singleton
public class PutConfig implements RestModifyView<ProjectResource, Input> {
@@ -66,10 +66,11 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
public InheritableBoolean useContributorAgreements;
public InheritableBoolean useContentMerge;
public InheritableBoolean useSignedOffBy;
+ public InheritableBoolean createNewChangeForAllNotInTarget;
public InheritableBoolean requireChangeId;
public String maxObjectSizeLimit;
public SubmitType submitType;
- public com.google.gerrit.extensions.api.projects.ProjectState state;
+ public com.google.gerrit.extensions.client.ProjectState state;
public Map<String, Map<String, ConfigValue>> pluginConfigValues;
}
@@ -151,6 +152,11 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
if (input.useSignedOffBy != null) {
p.setUseSignedOffBy(input.useSignedOffBy);
}
+
+ if (input.createNewChangeForAllNotInTarget != null) {
+ p.setCreateNewChangeForAllNotInTarget(input.createNewChangeForAllNotInTarget);
+ }
+
if (input.requireChangeId != null) {
p.setRequireChangeID(input.requireChangeId);
}
@@ -177,12 +183,12 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
ObjectId baseRev = projectConfig.getRevision();
ObjectId commitRev = projectConfig.commit(md);
// Only fire hook if project was actually changed.
- if (!Objects.equal(baseRev, commitRev)) {
+ if (!Objects.equals(baseRev, commitRev)) {
IdentifiedUser user = (IdentifiedUser) currentUser.get();
hooks.doRefUpdatedHook(
new Branch.NameKey(projectName, RefNames.REFS_CONFIG),
baseRev, commitRev, user.getAccount());
- };
+ }
projectCache.evict(projectConfig.getProject());
gitMgr.setProjectDescription(projectName, p.getDescription());
} catch (IOException e) {
@@ -190,6 +196,8 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
throw new ResourceConflictException("Cannot update " + projectName
+ ": " + e.getCause().getMessage());
} else {
+ log.warn(String.format("Failed to update config of project %s.",
+ projectName), e);
throw new ResourceConflictException("Cannot update " + projectName);
}
}
@@ -252,6 +260,7 @@ public class PutConfig implements RestModifyView<ProjectResource, Input> {
"The value '%s' is not permitted for parameter '%s' of plugin '"
+ pluginName + "'", value, v.getKey()));
}
+ //$FALL-THROUGH$
case STRING:
cfg.setString(v.getKey(), value);
break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
index b8a4c7c589..536bfa7169 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -39,6 +39,7 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException;
+import java.util.Objects;
@Singleton
class PutDescription implements RestModifyView<ProjectResource, Input> {
@@ -85,7 +86,7 @@ class PutDescription implements RestModifyView<ProjectResource, Input> {
Project project = config.getProject();
project.setDescription(Strings.emptyToNull(input.description));
- String msg = Objects.firstNonNull(
+ String msg = MoreObjects.firstNonNull(
Strings.emptyToNull(input.commitMessage),
"Updated description.\n");
if (!msg.endsWith("\n")) {
@@ -96,7 +97,7 @@ class PutDescription implements RestModifyView<ProjectResource, Input> {
ObjectId baseRev = config.getRevision();
ObjectId commitRev = config.commit(md);
// Only fire hook if project was actually changed.
- if (!Objects.equal(baseRev, commitRev)) {
+ if (!Objects.equals(baseRev, commitRev)) {
hooks.doRefUpdatedHook(
new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG),
baseRev, commitRev, user.getAccount());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 7dc7a2a4cb..71c3104bbf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -22,8 +22,9 @@ import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.common.errors.InvalidNameException;
-import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
@@ -31,12 +32,16 @@ import com.google.gerrit.server.group.SystemGroupBackend;
import dk.brics.automaton.RegExp;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
+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.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -49,6 +54,8 @@ import java.util.Set;
/** Manages access control for Git references (aka branches, tags). */
public class RefControl {
+ private static final Logger log = LoggerFactory.getLogger(RefControl.class);
+
private final ProjectControl projectControl;
private final String refName;
@@ -237,12 +244,13 @@ public class RefControl {
/**
* Determines whether the user can create a new Git ref.
*
- * @param rw revision pool {@code object} was parsed in.
+ * @param db db for checking change visibility.
+ * @param rw revision pool {@code object} was parsed in; must be reset before
+ * calling this method.
* @param object the object the user will start the reference with.
- * @param existsOnServer the object exists on server or not.
* @return {@code true} if the user specified can create a new Git ref
*/
- public boolean canCreate(RevWalk rw, RevObject object, boolean existsOnServer) {
+ public boolean canCreate(ReviewDb db, RevWalk rw, RevObject object) {
if (!canWrite()) {
return false;
}
@@ -261,10 +269,24 @@ public class RefControl {
}
if (object instanceof RevCommit) {
- return admin
- || (owner && !isBlocked(Permission.CREATE))
- || (canPerform(Permission.CREATE) && (!existsOnServer && canUpdate() || projectControl
- .canReadCommit(rw, (RevCommit) object)));
+ if (admin || (owner && !isBlocked(Permission.CREATE))) {
+ // Admin or project owner; bypass visibility check.
+ return true;
+ } else if (!canPerform(Permission.CREATE)) {
+ // No create permissions.
+ return false;
+ } else if (canUpdate()) {
+ // If the user has push permissions, they can create the ref regardless
+ // of whether they are pushing any new objects along with the create.
+ return true;
+ } else if (isMergedIntoBranchOrTag(db, rw, (RevCommit) object)) {
+ // If the user has no push permissions, check whether the object is
+ // merged into a branch or tag readable by this user. If so, they are
+ // not effectively "pushing" more objects, so they can create the ref
+ // even if they don't have push permission.
+ return true;
+ }
+ return false;
} else if (object instanceof RevTag) {
final RevTag tag = (RevTag) object;
try {
@@ -281,7 +303,7 @@ public class RefControl {
if (getCurrentUser().isIdentifiedUser()) {
final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
final String addr = tagger.getEmailAddress();
- valid = user.getEmailAddresses().contains(addr);
+ valid = user.hasEmailAddress(addr);
} else {
valid = false;
}
@@ -303,6 +325,28 @@ public class RefControl {
}
}
+ private boolean isMergedIntoBranchOrTag(ReviewDb db, RevWalk rw,
+ RevCommit commit) {
+ try {
+ Repository repo = projectControl.openRepository();
+ try {
+ List<Ref> refs = new ArrayList<>(
+ repo.getRefDatabase().getRefs(Constants.R_HEADS).values());
+ refs.addAll(repo.getRefDatabase().getRefs(Constants.R_TAGS).values());
+ return projectControl.isMergedIntoVisibleRef(
+ repo, db, rw, commit, refs);
+ } finally {
+ repo.close();
+ }
+ } catch (IOException e) {
+ String msg = String.format(
+ "Cannot verify permissions to commit object %s in repository %s",
+ commit.name(), projectControl.getProject().getNameKey());
+ log.error(msg, e);
+ }
+ return false;
+ }
+
/**
* Determines whether the user can delete the Git ref controlled by this
* object.
@@ -386,6 +430,11 @@ public class RefControl {
return canPerform(Permission.EDIT_TOPIC_NAME);
}
+ /** @return true if this user can edit hashtag names. */
+ public boolean canEditHashtags() {
+ return canPerform(Permission.EDIT_HASHTAGS);
+ }
+
/** @return true if this user can force edit topic names. */
public boolean canForceEditTopicName() {
return canForcePerform(Permission.EDIT_TOPIC_NAME);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
index d882e0dac1..d80323fd4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -15,8 +15,11 @@
package com.google.gerrit.server.project;
import static com.google.gerrit.server.project.RefControl.isRE;
+
import com.google.gerrit.common.data.ParameterizedString;
+
import dk.brics.automaton.Automaton;
+
import java.util.Collections;
import java.util.regex.Pattern;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
index c012bd53db..9009aadfc6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
@@ -14,7 +14,9 @@
package com.google.gerrit.server.project;
+import com.google.auto.value.AutoValue;
import com.google.common.cache.Cache;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.util.MostSpecificComparator;
@@ -26,7 +28,7 @@ import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -62,7 +64,7 @@ public class SectionSortCache {
return;
}
- EntryKey key = new EntryKey(ref, sections);
+ EntryKey key = EntryKey.create(ref, sections);
EntryVal val = cache.getIfPresent(key);
if (val != null) {
int[] srcIdx = val.order;
@@ -116,35 +118,27 @@ public class SectionSortCache {
return true;
}
- static final class EntryKey {
- private final String ref;
- private final String[] patterns;
- private final int hashCode;
+ @AutoValue
+ abstract static class EntryKey {
+ public abstract String ref();
+ public abstract List<String> patterns();
+ public abstract int cachedHashCode();
- EntryKey(String refName, List<AccessSection> sections) {
+ static EntryKey create(String refName, List<AccessSection> sections) {
int hc = refName.hashCode();
- ref = refName;
- patterns = new String[sections.size()];
- for (int i = 0; i < patterns.length; i++) {
- String n = sections.get(i).getName();
- patterns[i] = n;
+ List<String> patterns = new ArrayList<>(sections.size());
+ for (AccessSection s : sections) {
+ String n = s.getName();
+ patterns.add(n);
hc = hc * 31 + n.hashCode();
}
- hashCode = hc;
+ return new AutoValue_SectionSortCache_EntryKey(
+ refName, ImmutableList.copyOf(patterns), hc);
}
@Override
public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof EntryKey) {
- EntryKey b = (EntryKey) other;
- return ref.equals(b.ref) && Arrays.equals(patterns, b.patterns);
- }
- return false;
+ return cachedHashCode();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
index a323ec83b9..b18b8ec442 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -97,7 +97,7 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, Input> {
project.setLocalDefaultDashboard(input.id);
}
- String msg = Objects.firstNonNull(
+ String msg = MoreObjects.firstNonNull(
Strings.emptyToNull(input.commitMessage),
input.id == null
? "Removed default dashboard.\n"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
index 7efc1b79fd..77c221ca8e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
@@ -17,13 +17,13 @@ package com.google.gerrit.server.project;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.events.HeadUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.auth.AuthException;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.SetHead.Input;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 81ce5820fe..4ff2b0fa18 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
@@ -77,7 +77,8 @@ public class SetParent implements RestModifyView<ProjectResource, Input> {
if (msg == null) {
msg = String.format(
"Changed parent to %s.\n",
- Objects.firstNonNull(project.getParentName(), allProjects.get()));
+ MoreObjects.firstNonNull(project.getParentName(),
+ allProjects.get()));
} else if (!msg.endsWith("\n")) {
msg += "\n";
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index d79716ca76..4df9831434 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -14,24 +14,38 @@
package com.google.gerrit.server.project;
-import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+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.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.ReductionLimitException;
import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.ListTerm;
import com.googlecode.prolog_cafe.lang.Prolog;
-import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
import com.googlecode.prolog_cafe.lang.VariableTerm;
-import java.io.InputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -42,155 +56,480 @@ import java.util.List;
* all the way up to All-Projects.
*/
public class SubmitRuleEvaluator {
- private final ReviewDb db;
- private final PatchSet patchSet;
- private final ProjectControl projectControl;
- private final ChangeControl changeControl;
- private final Change change;
+ private static final Logger log = LoggerFactory
+ .getLogger(SubmitRuleEvaluator.class);
+
+ private static final String DEFAULT_MSG =
+ "Error evaluating project rules, check server log";
+
+ public static List<SubmitRecord> defaultRuleError() {
+ return createRuleError(DEFAULT_MSG);
+ }
+
+ public static List<SubmitRecord> createRuleError(String err) {
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.RULE_ERROR;
+ rec.errorMessage = err;
+ return Collections.singletonList(rec);
+ }
+
+ public static SubmitTypeRecord defaultTypeError() {
+ return createTypeError(DEFAULT_MSG);
+ }
+
+ public static SubmitTypeRecord createTypeError(String err) {
+ SubmitTypeRecord rec = new SubmitTypeRecord();
+ rec.status = SubmitTypeRecord.Status.RULE_ERROR;
+ rec.errorMessage = err;
+ return rec;
+ }
+
+ /**
+ * Exception thrown when the label term of a submit record
+ * unexpectedly didn't contain a user term.
+ */
+ private static class UserTermExpected extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public UserTermExpected(SubmitRecord.Label label) {
+ super(String.format("A label with the status %s must contain a user.",
+ label.toString()));
+ }
+ }
+
private final ChangeData cd;
- private final boolean fastEvalLabels;
- private final String userRuleLocatorName;
- private final String userRuleWrapperName;
- private final String filterRuleLocatorName;
- private final String filterRuleWrapperName;
- private final boolean skipFilters;
- private final InputStream rulesInputStream;
+ private final ChangeControl control;
+
+ private PatchSet patchSet;
+ private boolean fastEvalLabels;
+ private boolean allowDraft;
+ private boolean allowClosed;
+ private boolean skipFilters;
+ private String rule;
+ private boolean logErrors = true;
+ private int reductionsConsumed;
private Term submitRule;
- private String projectName;
+
+ public SubmitRuleEvaluator(ChangeData cd) throws OrmException {
+ this.cd = cd;
+ this.control = cd.changeControl();
+ }
+
+ /**
+ * @param ps patch set of the change to evaluate. If not set, the current
+ * patch set will be loaded from {@link #evaluate()} or {@link
+ * #getSubmitType}.
+ * @return this
+ */
+ public SubmitRuleEvaluator setPatchSet(PatchSet ps) {
+ checkArgument(ps.getId().getParentKey().equals(cd.getId()),
+ "Patch set %s does not match change %s", ps.getId(), cd.getId());
+ patchSet = ps;
+ return this;
+ }
+
+ /**
+ * @param fast if true, infer label information from rules rather than reading
+ * from project config.
+ * @return this
+ */
+ public SubmitRuleEvaluator setFastEvalLabels(boolean fast) {
+ fastEvalLabels = fast;
+ return this;
+ }
+
+ /**
+ * @param allow whether to allow {@link #evaluate()} on closed changes.
+ * @return this
+ */
+ public SubmitRuleEvaluator setAllowClosed(boolean allow) {
+ allowClosed = allow;
+ return this;
+ }
+
+ /**
+ * @param allow whether to allow {@link #evaluate()} on draft changes.
+ * @return this
+ */
+ public SubmitRuleEvaluator setAllowDraft(boolean allow) {
+ allowDraft = allow;
+ return this;
+ }
+
+ /**
+ * @param skip if true, submit filter will not be applied.
+ * @return this
+ */
+ public SubmitRuleEvaluator setSkipSubmitFilters(boolean skip) {
+ skipFilters = skip;
+ return this;
+ }
/**
- * @param userRuleLocatorName The name of the rule used to locate the
- * user-supplied rule.
- * @param userRuleWrapperName The name of the wrapper rule used to evaluate
- * the user-supplied rule.
- * @param filterRuleLocatorName The name of the rule used to locate the filter
- * rule.
- * @param filterRuleWrapperName The name of the rule used to evaluate the
- * filter rule.
+ * @param rule custom rule to use, or null to use refs/meta/config:rules.pl.
+ * @return this
*/
- public SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
- ProjectControl projectControl,
- ChangeControl changeControl, Change change, ChangeData cd,
- boolean fastEvalLabels,
- String userRuleLocatorName, String userRuleWrapperName,
- String filterRuleLocatorName, String filterRuleWrapperName) {
- this(db, patchSet, projectControl, changeControl, change, cd,
- fastEvalLabels, userRuleLocatorName, userRuleWrapperName,
- filterRuleLocatorName, filterRuleWrapperName, false, null);
+ public SubmitRuleEvaluator setRule(@Nullable String rule) {
+ this.rule = rule;
+ return this;
}
/**
- * @param userRuleLocatorName The name of the rule used to locate the
- * user-supplied rule.
- * @param userRuleWrapperName The name of the wrapper rule used to evaluate
- * the user-supplied rule.
- * @param filterRuleLocatorName The name of the rule used to locate the filter
- * rule.
- * @param filterRuleWrapperName The name of the rule used to evaluate the
- * filter rule.
- * @param skipSubmitFilters if {@code true} submit filter will not be
- * applied
- * @param rules when non-null the rules will be read from this input stream
- * instead of refs/meta/config:rules.pl file
+ * @param log whether to log error messages in addition to returning error
+ * records. If true, error record messages will be less descriptive.
*/
- public SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
- ProjectControl projectControl,
- ChangeControl changeControl, Change change, ChangeData cd,
- boolean fastEvalLabels,
- String userRuleLocatorName, String userRuleWrapperName,
- String filterRuleLocatorName, String filterRuleWrapperName,
- boolean skipSubmitFilters, InputStream rules) {
- this.db = db;
- this.patchSet = patchSet;
- this.projectControl = projectControl;
- this.changeControl = changeControl;
- this.change = change;
- this.cd = checkNotNull(cd, "ChangeData");
- this.fastEvalLabels = fastEvalLabels;
- this.userRuleLocatorName = userRuleLocatorName;
- this.userRuleWrapperName = userRuleWrapperName;
- this.filterRuleLocatorName = filterRuleLocatorName;
- this.filterRuleWrapperName = filterRuleWrapperName;
- this.skipFilters = skipSubmitFilters;
- this.rulesInputStream = rules;
+ public SubmitRuleEvaluator setLogErrors(boolean log) {
+ logErrors = log;
+ return this;
+ }
+
+ /** @return Prolog reductions consumed during evaluation. */
+ public int getReductionsConsumed() {
+ return reductionsConsumed;
}
/**
- * Evaluates the given rule and filters.
+ * Evaluate the submit rules.
*
- * Sets the {@link #submitRule} to the Term found by the
- * {@link #userRuleLocatorName}. This can be used when reporting error(s) on
- * unexpected return value of this method.
+ * @return List of {@link SubmitRecord} objects returned from the evaluated
+ * rules, including any errors.
+ */
+ public List<SubmitRecord> evaluate() {
+ Change c = control.getChange();
+ if (!allowClosed && c.getStatus().isClosed()) {
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.CLOSED;
+ return Collections.singletonList(rec);
+ }
+ if (!allowDraft) {
+ if (c.getStatus() == Change.Status.DRAFT) {
+ return cannotSubmitDraft();
+ }
+ try {
+ initPatchSet();
+ } catch (OrmException e) {
+ return ruleError("Error looking up patch set "
+ + control.getChange().currentPatchSetId());
+ }
+ if (patchSet.isDraft()) {
+ return cannotSubmitDraft();
+ }
+ }
+
+ List<Term> results;
+ try {
+ results = evaluateImpl("locate_submit_rule", "can_submit",
+ "locate_submit_filter", "filter_submit_results",
+ control.getCurrentUser());
+ } catch (RuleEvalException e) {
+ return ruleError(e.getMessage(), e);
+ }
+
+ if (results.isEmpty()) {
+ // This should never occur. A well written submit rule will always produce
+ // at least one result informing the caller of the labels that are
+ // required for this change to be submittable. Each label will indicate
+ // whether or not that is actually possible given the permissions.
+ return ruleError(String.format("Submit rule '%s' for change %s of %s has "
+ + "no solution.", getSubmitRule(), cd.getId(), getProjectName()));
+ }
+
+ return resultsToSubmitRecord(getSubmitRule(), results);
+ }
+
+ private List<SubmitRecord> cannotSubmitDraft() {
+ try {
+ if (!control.isDraftVisible(cd.db(), cd)) {
+ return createRuleError("Patch set " + patchSet.getId() + " not found");
+ } else if (patchSet.isDraft()) {
+ return createRuleError("Cannot submit draft patch sets");
+ } else {
+ return createRuleError("Cannot submit draft changes");
+ }
+ } catch (OrmException err) {
+ String msg = "Cannot check visibility of patch set " + patchSet.getId();
+ log.error(msg, err);
+ return createRuleError(msg);
+ }
+ }
+
+ /**
+ * Convert the results from Prolog Cafe's format to Gerrit's common format.
+ *
+ * can_submit/1 terminates when an ok(P) record is found. Therefore walk
+ * the results backwards, using only that ok(P) record if it exists. This
+ * skips partial results that occur early in the output. Later after the loop
+ * the out collection is reversed to restore it to the original ordering.
+ */
+ private List<SubmitRecord> resultsToSubmitRecord(
+ Term submitRule, List<Term> results) {
+ List<SubmitRecord> out = new ArrayList<>(results.size());
+ for (int resultIdx = results.size() - 1; 0 <= resultIdx; resultIdx--) {
+ Term submitRecord = results.get(resultIdx);
+ SubmitRecord rec = new SubmitRecord();
+ out.add(rec);
+
+ if (!submitRecord.isStructure() || 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 {
+ return invalidResult(submitRule, submitRecord);
+ }
+
+ // Unpack the one argument. This should also be a structure with one
+ // argument per label that needs to be reported on to the caller.
+ //
+ submitRecord = submitRecord.arg(0);
+
+ if (!submitRecord.isStructure()) {
+ return invalidResult(submitRule, submitRecord);
+ }
+
+ rec.labels = new ArrayList<>(submitRecord.arity());
+
+ for (Term state : ((StructureTerm) submitRecord).args()) {
+ if (!state.isStructure() || 2 != state.arity() || !"label".equals(state.name())) {
+ return invalidResult(submitRule, submitRecord);
+ }
+
+ SubmitRecord.Label lbl = new SubmitRecord.Label();
+ rec.labels.add(lbl);
+
+ lbl.label = state.arg(0).name();
+ Term status = state.arg(1);
+
+ try {
+ if ("ok".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.OK;
+ appliedBy(lbl, status);
+
+ } else if ("reject".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.REJECT;
+ appliedBy(lbl, status);
+
+ } else if ("need".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.NEED;
+
+ } else if ("may".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.MAY;
+
+ } else if ("impossible".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.IMPOSSIBLE;
+
+ } else {
+ return invalidResult(submitRule, submitRecord);
+ }
+ } catch (UserTermExpected e) {
+ return invalidResult(submitRule, submitRecord, e.getMessage());
+ }
+ }
+
+ if (rec.status == SubmitRecord.Status.OK) {
+ break;
+ }
+ }
+ Collections.reverse(out);
+
+ return out;
+ }
+
+ private List<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", rule, cd.getId(), getProjectName(), record,
+ (reason == null ? "" : ". Reason: " + reason)));
+ }
+
+ private List<SubmitRecord> invalidResult(Term rule, Term record) {
+ return invalidResult(rule, record, null);
+ }
+
+ private List<SubmitRecord> ruleError(String err) {
+ return ruleError(err, null);
+ }
+
+ private List<SubmitRecord> ruleError(String err, Exception e) {
+ if (logErrors) {
+ if (e == null) {
+ log.error(err);
+ } else {
+ log.error(err, e);
+ }
+ return defaultRuleError();
+ } else {
+ return createRuleError(err);
+ }
+ }
+
+ /**
+ * Evaluate the submit type rules to get the submit type.
*
- * @return List of {@link Term} objects returned from the evaluated rules.
- * @throws RuleEvalException
+ * @return record from the evaluated rules.
*/
- public List<Term> evaluate() throws RuleEvalException {
- PrologEnvironment env = getPrologEnvironment();
+ public SubmitTypeRecord getSubmitType() {
+ try {
+ initPatchSet();
+ } catch (OrmException e) {
+ return typeError("Error looking up patch set "
+ + control.getChange().currentPatchSetId());
+ }
+
+ try {
+ if (control.getChange().getStatus() == Change.Status.DRAFT
+ && !control.isDraftVisible(cd.db(), cd)) {
+ return createTypeError("Patch set " + patchSet.getId() + " not found");
+ }
+ if (patchSet.isDraft() && !control.isDraftVisible(cd.db(), cd)) {
+ return createTypeError("Patch set " + patchSet.getId() + " not found");
+ }
+ } catch (OrmException err) {
+ String msg = "Cannot read patch set " + patchSet.getId();
+ log.error(msg, err);
+ return createTypeError(msg);
+ }
+
+ List<Term> results;
try {
- submitRule = env.once("gerrit", userRuleLocatorName, new VariableTerm());
+ results = evaluateImpl("locate_submit_type", "get_submit_type",
+ "locate_submit_type_filter", "filter_submit_type_results",
+ // Do not include current user in submit type evaluation. This is used
+ // for mergeability checks, which are stored persistently and so must
+ // have a consistent view of the submit type.
+ null);
+ } catch (RuleEvalException e) {
+ return typeError(e.getMessage(), e);
+ }
+
+ if (results.isEmpty()) {
+ // Should never occur for a well written rule
+ return typeError("Submit rule '" + getSubmitRule() + "' for change "
+ + cd.getId() + " of " + getProjectName() + " has no solution.");
+ }
+
+ Term typeTerm = results.get(0);
+ if (!typeTerm.isSymbol()) {
+ return typeError("Submit rule '" + getSubmitRule() + "' for change "
+ + cd.getId() + " of " + getProjectName()
+ + " did not return a symbol.");
+ }
+
+ String typeName = ((SymbolTerm) typeTerm).name();
+ try {
+ return SubmitTypeRecord.OK(
+ SubmitType.valueOf(typeName.toUpperCase()));
+ } catch (IllegalArgumentException e) {
+ return typeError("Submit type rule " + getSubmitRule() + " for change "
+ + cd.getId() + " of " + getProjectName() + " output invalid result: "
+ + typeName);
+ }
+ }
+
+ private SubmitTypeRecord typeError(String err) {
+ return typeError(err, null);
+ }
+
+ private SubmitTypeRecord typeError(String err, Exception e) {
+ if (logErrors) {
+ if (e == null) {
+ log.error(err);
+ } else {
+ log.error(err, e);
+ }
+ return defaultTypeError();
+ } else {
+ return createTypeError(err);
+ }
+ }
+
+ private List<Term> evaluateImpl(
+ String userRuleLocatorName,
+ String userRuleWrapperName,
+ String filterRuleLocatorName,
+ String filterRuleWrapperName,
+ CurrentUser user) throws RuleEvalException {
+ PrologEnvironment env = getPrologEnvironment(user);
+ try {
+ Term sr = env.once("gerrit", userRuleLocatorName, new VariableTerm());
if (fastEvalLabels) {
env.once("gerrit", "assume_range_from_label");
}
List<Term> results = new ArrayList<>();
try {
- for (Term[] template : env.all("gerrit", userRuleWrapperName,
- submitRule, new VariableTerm())) {
+ for (Term[] template : env.all("gerrit", userRuleWrapperName, sr,
+ new VariableTerm())) {
results.add(template[1]);
}
- } catch (PrologException err) {
- throw new RuleEvalException("Exception calling " + submitRule
- + " on change " + change.getId() + " of " + getProjectName(),
- err);
+ } catch (ReductionLimitException err) {
+ throw new RuleEvalException(String.format(
+ "%s on change %d of %s",
+ err.getMessage(), cd.getId().get(), getProjectName()));
} catch (RuntimeException err) {
- throw new RuleEvalException("Exception calling " + submitRule
- + " on change " + change.getId() + " of " + getProjectName(),
- err);
+ throw new RuleEvalException(String.format(
+ "Exception calling %s on change %d of %s",
+ sr, cd.getId().get(), getProjectName()), err);
+ } finally {
+ reductionsConsumed = env.getReductions();
}
Term resultsTerm = toListTerm(results);
if (!skipFilters) {
- resultsTerm = runSubmitFilters(resultsTerm, env);
+ resultsTerm = runSubmitFilters(
+ resultsTerm, env, filterRuleLocatorName, filterRuleWrapperName);
}
+ List<Term> r;
if (resultsTerm.isList()) {
- List<Term> r = Lists.newArrayList();
+ r = Lists.newArrayList();
for (Term t = resultsTerm; t.isList();) {
ListTerm l = (ListTerm) t;
r.add(l.car().dereference());
t = l.cdr().dereference();
}
- return r;
+ } else {
+ r = Collections.emptyList();
}
- return Collections.emptyList();
+ submitRule = sr;
+ return r;
} finally {
env.close();
}
}
- private PrologEnvironment getPrologEnvironment() throws RuleEvalException {
- ProjectState projectState = projectControl.getProjectState();
+ private PrologEnvironment getPrologEnvironment(CurrentUser user)
+ throws RuleEvalException {
+ ProjectState projectState = control.getProjectControl().getProjectState();
PrologEnvironment env;
try {
- if (rulesInputStream == null) {
+ if (rule == null) {
env = projectState.newPrologEnvironment();
} else {
- env = projectState.newPrologEnvironment("stdin", rulesInputStream);
+ env = projectState.newPrologEnvironment(
+ "stdin", new ByteArrayInputStream(rule.getBytes(UTF_8)));
}
} catch (CompileException err) {
throw new RuleEvalException("Cannot consult rules.pl for "
+ getProjectName(), err);
}
- env.set(StoredValues.REVIEW_DB, db);
+ env.set(StoredValues.REVIEW_DB, cd.db());
env.set(StoredValues.CHANGE_DATA, cd);
- env.set(StoredValues.PATCH_SET, patchSet);
- env.set(StoredValues.CHANGE_CONTROL, changeControl);
+ env.set(StoredValues.CHANGE_CONTROL, control);
+ if (user != null) {
+ env.set(StoredValues.CURRENT_USER, user);
+ }
return env;
}
- private Term runSubmitFilters(Term results, PrologEnvironment env) throws RuleEvalException {
- ProjectState projectState = projectControl.getProjectState();
+ private Term runSubmitFilters(Term results, PrologEnvironment env,
+ String filterRuleLocatorName, String filterRuleWrapperName)
+ throws RuleEvalException {
+ ProjectState projectState = control.getProjectControl().getProjectState();
PrologEnvironment childEnv = env;
for (ProjectState parentState : projectState.parents()) {
PrologEnvironment parentEnv;
@@ -213,14 +552,16 @@ public class SubmitRuleEvaluator {
parentEnv.once("gerrit", filterRuleWrapperName, filterRule,
results, new VariableTerm());
results = template[2];
- } catch (PrologException err) {
- throw new RuleEvalException("Exception calling " + filterRule
- + " on change " + change.getId() + " of "
- + parentState.getProject().getName(), err);
+ } catch (ReductionLimitException err) {
+ throw new RuleEvalException(String.format(
+ "%s on change %d of %s",
+ err.getMessage(), cd.getId().get(), parentState.getProject().getName()));
} catch (RuntimeException err) {
- throw new RuleEvalException("Exception calling " + filterRule
- + " on change " + change.getId() + " of "
- + parentState.getProject().getName(), err);
+ throw new RuleEvalException(String.format(
+ "Exception calling %s on change %d of %s",
+ filterRule, cd.getId().get(), parentState.getProject().getName()), err);
+ } finally {
+ reductionsConsumed += env.getReductions();
}
childEnv = parentEnv;
}
@@ -235,14 +576,37 @@ public class SubmitRuleEvaluator {
return list;
}
+ private void appliedBy(SubmitRecord.Label label, Term status)
+ throws UserTermExpected {
+ if (status.isStructure() && status.arity() == 1) {
+ Term who = status.arg(0);
+ if (isUser(who)) {
+ label.appliedBy = new Account.Id(((IntegerTerm) who.arg(0)).intValue());
+ } else {
+ throw new UserTermExpected(label);
+ }
+ }
+ }
+
+ private static boolean isUser(Term who) {
+ return who.isStructure()
+ && who.arity() == 1
+ && who.name().equals("user")
+ && who.arg(0).isInteger();
+ }
+
public Term getSubmitRule() {
+ checkState(submitRule != null, "getSubmitRule() invalid before evaluation");
return submitRule;
}
- private String getProjectName() {
- if (projectName == null) {
- projectName = projectControl.getProjectState().getProject().getName();
+ private void initPatchSet() throws OrmException {
+ if (patchSet == null) {
+ patchSet = cd.currentPatchSet();
}
- return projectName;
+ }
+
+ private String getProjectName() {
+ return control.getProjectControl().getProjectState().getProject().getName();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SuggestParentCandidates.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SuggestParentCandidates.java
index 4b8d2a4ba8..a6717d5dd9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SuggestParentCandidates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SuggestParentCandidates.java
@@ -16,7 +16,6 @@ package com.google.gerrit.server.project;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -40,8 +39,7 @@ public class SuggestParentCandidates {
this.allProject = allProject;
}
- public List<Project.NameKey> getNameKeys() throws OrmException,
- NoSuchProjectException {
+ public List<Project.NameKey> getNameKeys() throws NoSuchProjectException {
List<Project> pList = getProjects();
final List<Project.NameKey> nameKeys = new ArrayList<>(pList.size());
for (Project p : pList) {
@@ -50,8 +48,7 @@ public class SuggestParentCandidates {
return nameKeys;
}
- public List<Project> getProjects() throws OrmException,
- NoSuchProjectException {
+ public List<Project> getProjects() throws NoSuchProjectException {
Set<Project> projects = new TreeSet<>(new Comparator<Project>() {
@Override
public int compare(Project o1, Project o2) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
new file mode 100644
index 0000000000..12be5d3b11
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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;
+
+import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class TagResource extends ProjectResource {
+ public static final TypeLiteral<RestView<TagResource>> TAG_KIND =
+ new TypeLiteral<RestView<TagResource>>() {};
+
+ private final TagInfo tag;
+
+ public TagResource(ProjectControl control, TagInfo tag) {
+ super(control);
+ this.tag = tag;
+ }
+
+ public TagInfo getTagInfo() {
+ return tag;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
new file mode 100644
index 0000000000..0c70285de8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2014 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;
+
+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.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class TagsCollection implements
+ ChildCollection<ProjectResource, TagResource> {
+ private final DynamicMap<RestView<TagResource>> views;
+ private final ListTags list;
+
+ @Inject
+ public TagsCollection(DynamicMap<RestView<TagResource>> views,
+ ListTags list) {
+ this.views = views;
+ this.list = list;
+ }
+
+ @Override
+ public RestView<ProjectResource> list() throws ResourceNotFoundException {
+ return list;
+ }
+
+ @Override
+ public TagResource parse(ProjectResource resource, IdString id)
+ throws ResourceNotFoundException, IOException {
+ return new TagResource(resource.getControl(), list.get(resource, id));
+ }
+
+ @Override
+ public DynamicMap<RestView<TagResource>> views() {
+ return views;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
index 953dabfdab..39b0fa300a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
@@ -92,8 +92,9 @@ public class AndPredicate<T> extends Predicate<T> {
@Override
public boolean equals(final Object other) {
- if (other == null)
+ if (other == null) {
return false;
+ }
return getClass() == other.getClass()
&& getChildren().equals(((Predicate<?>) other).getChildren());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
index 7b43572616..f4be013473 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
@@ -92,7 +92,7 @@ public abstract class Predicate<T> {
//
return that.getChild(0);
}
- return new NotPredicate<T>(that);
+ return new NotPredicate<>(that);
}
/** Get the children of this predicate, if any. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
index 5be42be1bb..bd1fa0c01e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
@@ -138,7 +138,8 @@ public abstract class QueryBuilder<T> {
* @param p the predicate to find.
* @param clazz type of the predicate instance.
* @param name name of the operator.
- * @return the predicate, null if not found.
+ * @return the first instance of a predicate having the given type, as found
+ * by a depth-first search.
*/
@SuppressWarnings("unchecked")
public static <T, P extends OperatorPredicate<T>> P find(Predicate<T> p,
@@ -159,16 +160,19 @@ public abstract class QueryBuilder<T> {
return null;
}
+ protected final Definition<T, ? extends QueryBuilder<T>> builderDef;
+
@SuppressWarnings("rawtypes")
private final Map<String, OperatorFactory> opFactories;
@SuppressWarnings({"unchecked", "rawtypes"})
protected QueryBuilder(Definition<T, ? extends QueryBuilder<T>> def) {
+ builderDef = def;
opFactories = (Map) def.opFactories;
}
/**
- * Parse a user supplied query string into a predicate.
+ * Parse a user-supplied query string into a predicate.
*
* @param query the query string.
* @return predicate representing the user query.
@@ -181,6 +185,27 @@ public abstract class QueryBuilder<T> {
return toPredicate(QueryParser.parse(query));
}
+ /**
+ * Parse multiple user-supplied query strings into a list of predicates.
+ *
+ * @param queries the query strings.
+ * @return predicates representing the user query, in the same order as the
+ * input.
+ * @throws QueryParseException one of the query strings is invalid and cannot
+ * be parsed by this parser. This may be due to a syntax error, may be
+ * due to an operator not being supported, or due to an invalid value
+ * being passed to a recognized operator.
+ *
+ */
+ public List<Predicate<T>> parse(final List<String> queries)
+ throws QueryParseException {
+ List<Predicate<T>> predicates = new ArrayList<>(queries.size());
+ for (String query : queries) {
+ predicates.add(parse(query));
+ }
+ return predicates;
+ }
+
private Predicate<T> toPredicate(final Tree r) throws QueryParseException,
IllegalArgumentException {
switch (r.getType()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
index cb0038ccda..aeb9619a58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.TimestampRangePredicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
@@ -24,9 +24,8 @@ import java.util.Date;
public class AfterPredicate extends TimestampRangePredicate<ChangeData> {
private final Date cut;
- AfterPredicate(Schema<ChangeData> schema, String value)
- throws QueryParseException {
- super(updatedField(schema), ChangeQueryBuilder.FIELD_BEFORE, value);
+ AfterPredicate(String value) throws QueryParseException {
+ super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_BEFORE, value);
cut = parse(value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
index 1894596afe..9a4ef19b58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -17,11 +17,11 @@ 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.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import java.sql.Timestamp;
@@ -29,8 +29,8 @@ import java.sql.Timestamp;
public class AgePredicate extends TimestampRangePredicate<ChangeData> {
private final long cut;
- AgePredicate(Schema<ChangeData> schema, String value) {
- super(updatedField(schema), ChangeQueryBuilder.FIELD_AGE, value);
+ AgePredicate(String value) {
+ super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_AGE, value);
long s = ConfigUtil.getTimeUnit(getValue(), 0, SECONDS);
long ms = MILLISECONDS.convert(s, SECONDS);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 55fd281ffb..f48cfd8f4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -108,10 +108,6 @@ public class AndSource extends AndPredicate<ChangeData>
if (source == null) {
throw new OrmException("No ChangeDataSource: " + this);
}
- @SuppressWarnings("unchecked")
- Predicate<ChangeData> pred = (Predicate<ChangeData>) source;
- boolean useSortKey = ChangeQueryBuilder.hasSortKey(pred);
-
List<ChangeData> r = Lists.newArrayList();
ChangeData last = null;
int nextStart = 0;
@@ -133,12 +129,8 @@ public class AndSource extends AndPredicate<ChangeData>
//
Paginated p = (Paginated) source;
while (skipped && r.size() < p.limit() + start) {
- ChangeData lastBeforeRestart = last;
skipped = false;
- last = null;
- ResultSet<ChangeData> next = useSortKey
- ? p.restart(lastBeforeRestart)
- : p.restart(nextStart);
+ ResultSet<ChangeData> next = p.restart(nextStart);
for (ChangeData data : buffer(source, next)) {
if (match(data)) {
@@ -146,7 +138,6 @@ public class AndSource extends AndPredicate<ChangeData>
} else {
skipped = true;
}
- last = data;
nextStart++;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
index 8ce6fa3772..1053d9214e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
@@ -16,51 +16,46 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.IntPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryRewriter;
import com.google.inject.Inject;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
-import com.google.inject.name.Named;
public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
- new ChangeQueryBuilder.Arguments( //
- new InvalidProvider<ReviewDb>(), //
- new InvalidProvider<ChangeQueryRewriter>(), //
- null, null, null, null, null, null, null, //
- null, null, null, null, null, null, null, null, null, null), null);
+ new ChangeQueryBuilder.Arguments(
+ new InvalidProvider<ReviewDb>(),
+ new InvalidProvider<InternalChangeQuery>(),
+ new InvalidProvider<ChangeQueryRewriter>(),
+ null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null));
private static final QueryRewriter.Definition<ChangeData, BasicChangeRewrites> mydef =
- new QueryRewriter.Definition<ChangeData, BasicChangeRewrites>(
- BasicChangeRewrites.class, BUILDER);
-
- protected final Provider<ReviewDb> dbProvider;
+ new QueryRewriter.Definition<>(BasicChangeRewrites.class, BUILDER);
@Inject
- public BasicChangeRewrites(Provider<ReviewDb> dbProvider) {
+ public BasicChangeRewrites() {
super(mydef);
- this.dbProvider = dbProvider;
}
@Rewrite("-status:open")
@NoCostComputation
public Predicate<ChangeData> r00_notOpen() {
- return ChangeStatusPredicate.closed(dbProvider);
+ return ChangeStatusPredicate.closed();
}
@Rewrite("-status:closed")
@NoCostComputation
public Predicate<ChangeData> r00_notClosed() {
- return ChangeStatusPredicate.open(dbProvider);
+ return ChangeStatusPredicate.open();
}
@SuppressWarnings("unchecked")
@NoCostComputation
@Rewrite("-status:merged")
public Predicate<ChangeData> r00_notMerged() {
- return or(ChangeStatusPredicate.open(dbProvider),
+ return or(ChangeStatusPredicate.open(),
new ChangeStatusPredicate(Change.Status.ABANDONED));
}
@@ -68,18 +63,10 @@ public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
@NoCostComputation
@Rewrite("-status:abandoned")
public Predicate<ChangeData> r00_notAbandoned() {
- return or(ChangeStatusPredicate.open(dbProvider),
+ return or(ChangeStatusPredicate.open(),
new ChangeStatusPredicate(Change.Status.MERGED));
}
- @NoCostComputation
- @Rewrite("A=(limit:*) B=(limit:*)")
- public Predicate<ChangeData> r00_smallestLimit(
- @Named("A") IntPredicate<ChangeData> a,
- @Named("B") IntPredicate<ChangeData> b) {
- return a.intValue() <= b.intValue() ? a : b;
- }
-
private static final class InvalidProvider<T> implements Provider<T> {
@Override
public T get() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
index f724676743..8f51476f23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.TimestampRangePredicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
@@ -24,9 +24,8 @@ import java.util.Date;
public class BeforePredicate extends TimestampRangePredicate<ChangeData> {
private final Date cut;
- BeforePredicate(Schema<ChangeData> schema, String value)
- throws QueryParseException {
- super(updatedField(schema), ChangeQueryBuilder.FIELD_BEFORE, value);
+ BeforePredicate(String value) throws QueryParseException {
+ super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_BEFORE, value);
cut = parse(value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeCosts.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeCosts.java
deleted file mode 100644
index ba42803cc7..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeCosts.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2010 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;
-
-public class ChangeCosts {
- public static final int IDS_MEMORY = 1;
- public static final int CHANGES_SCAN = 2;
- public static final int TR_SCAN = 20;
- public static final int APPROVALS_SCAN = 30;
- public static final int PATCH_SETS_SCAN = 30;
-
- /** Estimated matches for a Change-Id string. */
- public static final int CARD_KEY = 5;
-
- /** Estimated matches for a commit SHA-1 string. */
- public static final int CARD_COMMIT = 5;
-
- /** Estimated matches for a tracking/bug id string. */
- public static final int CARD_TRACKING_IDS = 5;
-
- public static int cost(int cost, int cardinality) {
- return Math.max(1, cost) * Math.max(0, cardinality);
- }
-
- private ChangeCosts() {
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index f726fe32e7..e4e94a12f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -16,13 +16,14 @@ package com.google.gerrit.server.query.change;
import static com.google.gerrit.server.ApprovalsUtil.sortApprovals;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -35,7 +36,10 @@ import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.change.MergeabilityCache;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.ReviewerState;
@@ -45,6 +49,8 @@ import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.assistedinject.Assisted;
@@ -54,6 +60,7 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -68,6 +75,24 @@ import java.util.List;
import java.util.Map;
public class ChangeData {
+ public static List<Change> asChanges(List<ChangeData> changeDatas)
+ throws OrmException {
+ List<Change> result = new ArrayList<>(changeDatas.size());
+ for (ChangeData cd : changeDatas) {
+ result.add(cd.change());
+ }
+ return result;
+ }
+
+ public static Map<Change.Id, ChangeData> asMap(List<ChangeData> changes) {
+ Map<Change.Id, ChangeData> result =
+ Maps.newHashMapWithExpectedSize(changes.size());
+ for (ChangeData cd : changes) {
+ result.put(cd.getId(), cd);
+ }
+ return result;
+ }
+
public static void ensureChangeLoaded(Iterable<ChangeData> changes)
throws OrmException {
Map<Change.Id, ChangeData> missing = Maps.newHashMap();
@@ -78,7 +103,7 @@ public class ChangeData {
}
if (!missing.isEmpty()) {
ChangeData first = missing.values().iterator().next();
- if (!first.notesMigration.readPatchSetApprovals()) {
+ if (!first.notesMigration.readChanges()) {
ReviewDb db = missing.values().iterator().next().db;
for (Change change : db.changes().get(missing.keySet())) {
missing.get(change.getId()).change = change;
@@ -119,7 +144,7 @@ public class ChangeData {
throws OrmException {
List<ResultSet<PatchSetApproval>> pending = Lists.newArrayList();
for (ChangeData cd : changes) {
- if (!cd.notesMigration.readPatchSetApprovals()) {
+ if (!cd.notesMigration.readChanges()) {
if (cd.currentApprovals == null) {
pending.add(cd.db.patchSetApprovals()
.byPatchSet(cd.change().currentPatchSetId()));
@@ -153,8 +178,8 @@ public class ChangeData {
* @return instance for testing.
*/
static ChangeData createForTest(Change.Id id, int currentPatchSetId) {
- ChangeData cd = new ChangeData(null, null, null, null, null,
- null, null, null, null, id);
+ ChangeData cd = new ChangeData(null, null, null, null, null, null, null,
+ null, null, null, null, null, null, id);
cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
return cd;
}
@@ -163,11 +188,15 @@ public class ChangeData {
private final GitRepositoryManager repoManager;
private final ChangeControl.GenericFactory changeControlFactory;
private final IdentifiedUser.GenericFactory userFactory;
+ private final ProjectCache projectCache;
+ private final MergeUtil.Factory mergeUtilFactory;
private final ChangeNotes.Factory notesFactory;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
+ private final PatchLineCommentsUtil plcUtil;
private final PatchListCache patchListCache;
private final NotesMigration notesMigration;
+ private final MergeabilityCache mergeabilityCache;
private final Change.Id legacyId;
private ChangeDataSource returnedBySource;
private Change change;
@@ -179,34 +208,43 @@ public class ChangeData {
private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals;
private List<PatchSetApproval> currentApprovals;
private Map<Integer, List<String>> files = new HashMap<>();
- private Collection<PatchLineComment> comments;
+ private Collection<PatchLineComment> publishedComments;
private CurrentUser visibleTo;
private ChangeControl changeControl;
private List<ChangeMessage> messages;
private List<SubmitRecord> submitRecords;
private ChangedLines changedLines;
+ private Boolean mergeable;
@AssistedInject
private ChangeData(
GitRepositoryManager repoManager,
ChangeControl.GenericFactory changeControlFactory,
IdentifiedUser.GenericFactory userFactory,
+ ProjectCache projectCache,
+ MergeUtil.Factory mergeUtilFactory,
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
NotesMigration notesMigration,
+ MergeabilityCache mergeabilityCache,
@Assisted ReviewDb db,
@Assisted Change.Id id) {
this.db = db;
this.repoManager = repoManager;
this.changeControlFactory = changeControlFactory;
this.userFactory = userFactory;
+ this.projectCache = projectCache;
+ this.mergeUtilFactory = mergeUtilFactory;
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.notesMigration = notesMigration;
+ this.mergeabilityCache = mergeabilityCache;
legacyId = id;
}
@@ -215,22 +253,30 @@ public class ChangeData {
GitRepositoryManager repoManager,
ChangeControl.GenericFactory changeControlFactory,
IdentifiedUser.GenericFactory userFactory,
+ ProjectCache projectCache,
+ MergeUtil.Factory mergeUtilFactory,
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
NotesMigration notesMigration,
+ MergeabilityCache mergeabilityCache,
@Assisted ReviewDb db,
@Assisted Change c) {
this.db = db;
this.repoManager = repoManager;
this.changeControlFactory = changeControlFactory;
this.userFactory = userFactory;
+ this.projectCache = projectCache;
+ this.mergeUtilFactory = mergeUtilFactory;
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.notesMigration = notesMigration;
+ this.mergeabilityCache = mergeabilityCache;
legacyId = c.getId();
change = c;
}
@@ -240,28 +286,40 @@ public class ChangeData {
GitRepositoryManager repoManager,
ChangeControl.GenericFactory changeControlFactory,
IdentifiedUser.GenericFactory userFactory,
+ ProjectCache projectCache,
+ MergeUtil.Factory mergeUtilFactory,
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
+ PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
NotesMigration notesMigration,
+ MergeabilityCache mergeabilityCache,
@Assisted ReviewDb db,
@Assisted ChangeControl c) {
this.db = db;
this.repoManager = repoManager;
this.changeControlFactory = changeControlFactory;
this.userFactory = userFactory;
+ this.projectCache = projectCache;
+ this.mergeUtilFactory = mergeUtilFactory;
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
+ this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.notesMigration = notesMigration;
+ this.mergeabilityCache = mergeabilityCache;
legacyId = c.getChange().getId();
change = c.getChange();
changeControl = c;
notes = c.getNotes();
}
+ public ReviewDb db() {
+ return db;
+ }
+
public boolean isFromSource(ChangeDataSource s) {
return s == returnedBySource;
}
@@ -387,11 +445,16 @@ public class ChangeData {
public Change change() throws OrmException {
if (change == null) {
- change = db.changes().get(legacyId);
+ reloadChange();
}
return change;
}
+ public Change reloadChange() throws OrmException {
+ change = db.changes().get(legacyId);
+ return change;
+ }
+
public ChangeNotes notes() throws OrmException {
if (notes == null) {
notes = notesFactory.create(change());
@@ -433,44 +496,40 @@ public class ChangeData {
currentApprovals = approvals;
}
- public String commitMessage() throws NoSuchChangeException, IOException,
- OrmException {
+ public String commitMessage() throws IOException, OrmException {
if (commitMessage == null) {
- loadCommitData();
+ if (!loadCommitData()) {
+ return null;
+ }
}
return commitMessage;
}
- public List<FooterLine> commitFooters() throws NoSuchChangeException,
- IOException, OrmException {
+ public List<FooterLine> commitFooters() throws IOException, OrmException {
if (commitFooters == null) {
- loadCommitData();
+ if (!loadCommitData()) {
+ return null;
+ }
}
return commitFooters;
}
- private void loadCommitData() throws NoSuchChangeException, OrmException,
+ private boolean loadCommitData() throws OrmException,
RepositoryNotFoundException, IOException, MissingObjectException,
IncorrectObjectTypeException {
PatchSet.Id psId = change().currentPatchSetId();
PatchSet ps = db.patchSets().get(psId);
if (ps == null) {
- throw new NoSuchChangeException(legacyId);
+ return false;
}
String sha1 = ps.getRevision().get();
- Repository repo = repoManager.openRepository(change().getProject());
- try {
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit c = walk.parseCommit(ObjectId.fromString(sha1));
- commitMessage = c.getFullMessage();
- commitFooters = c.getFooterLines();
- } finally {
- walk.close();
- }
- } finally {
- repo.close();
+ try (Repository repo = repoManager.openRepository(change().getProject());
+ RevWalk walk = new RevWalk(repo)) {
+ RevCommit c = walk.parseCommit(ObjectId.fromString(sha1));
+ commitMessage = c.getFullMessage();
+ commitFooters = c.getFooterLines();
}
+ return true;
}
/**
@@ -519,12 +578,12 @@ public class ChangeData {
return approvalsUtil.getReviewers(notes(), approvals().values());
}
- public Collection<PatchLineComment> comments()
+ public Collection<PatchLineComment> publishedComments()
throws OrmException {
- if (comments == null) {
- comments = db.patchComments().byChange(legacyId).toList();
+ if (publishedComments == null) {
+ publishedComments = plcUtil.publishedByChange(db, notes());
}
- return comments;
+ return publishedComments;
}
public List<ChangeMessage> messages()
@@ -543,9 +602,60 @@ public class ChangeData {
return submitRecords;
}
+ public void setMergeable(boolean mergeable) {
+ this.mergeable = mergeable;
+ }
+
+ public Boolean isMergeable() throws OrmException {
+ if (mergeable == null) {
+ Change c = change();
+ if (c == null) {
+ return null;
+ }
+ if (c.getStatus() == Change.Status.MERGED) {
+ mergeable = true;
+ } else {
+ PatchSet ps = currentPatchSet();
+ if (ps == null || !changeControl().isPatchVisible(ps, db)) {
+ return null;
+ }
+ Repository repo = null;
+ try {
+ repo = repoManager.openRepository(c.getProject());
+ Ref ref = repo.getRef(c.getDest().get());
+ SubmitTypeRecord rec = new SubmitRuleEvaluator(this)
+ .getSubmitType();
+ if (rec.status != SubmitTypeRecord.Status.OK) {
+ throw new OrmException(
+ "Error in mergeability check: " + rec.errorMessage);
+ }
+ String mergeStrategy = mergeUtilFactory
+ .create(projectCache.get(c.getProject()))
+ .mergeStrategyName();
+ mergeable = mergeabilityCache.get(
+ ObjectId.fromString(ps.getRevision().get()),
+ ref, rec.type, mergeStrategy, c.getDest(), repo, db);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ } finally {
+ if (repo != null) {
+ repo.close();
+ }
+ }
+ }
+ }
+ return mergeable;
+ }
+
@Override
public String toString() {
- return Objects.toStringHelper(this).addValue(getId()).toString();
+ MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
+ if (change != null) {
+ h.addValue(change);
+ } else {
+ h.addValue(legacyId);
+ }
+ return h.toString();
}
public static class ChangedLines {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
index d1a6c6e3d1..7f517d85ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -17,21 +17,16 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-class ChangeIdPredicate extends IndexPredicate<ChangeData> implements
- ChangeDataSource {
- private final Arguments args;
-
- ChangeIdPredicate(Arguments args, String id) {
+/** Predicate over Change-Id strings (aka Change.Key). */
+class ChangeIdPredicate extends IndexPredicate<ChangeData> {
+ ChangeIdPredicate(String id) {
super(ChangeField.ID, ChangeQueryBuilder.FIELD_CHANGE, id);
- this.args = args;
}
@Override
- public boolean match(final ChangeData cd) throws OrmException {
+ public boolean match(ChangeData cd) throws OrmException {
Change change = cd.change();
if (change == null) {
return false;
@@ -45,25 +40,7 @@ class ChangeIdPredicate extends IndexPredicate<ChangeData> implements
}
@Override
- public ResultSet<ChangeData> read() throws OrmException {
- Change.Key a = new Change.Key(getValue());
- Change.Key b = a.max();
- return ChangeDataResultSet.change(args.changeDataFactory, args.db,
- args.db.get().changes().byKeyRange(a, b));
- }
-
- @Override
- public boolean hasChange() {
- return true;
- }
-
- @Override
public int getCost() {
- return ChangeCosts.cost(ChangeCosts.CHANGES_SCAN, getCardinality());
- }
-
- @Override
- public int getCardinality() {
- return ChangeCosts.CARD_KEY;
+ return 1;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index ac7b9aed85..c9d7e6cd24 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -14,10 +14,14 @@
package com.google.gerrit.server.query.change;
+import static com.google.gerrit.server.query.change.ChangeData.asChanges;
+
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Branch;
@@ -25,31 +29,33 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
-import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ListChildProjects;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.IntPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryBuilder;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.assistedinject.Assisted;
+import com.google.inject.ProvisionException;
+import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Config;
@@ -69,8 +75,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$");
private static final Pattern PAT_CHANGE_ID =
Pattern.compile("^[iI][0-9a-f]{4,}.*$");
- private static final Pattern DEF_CHANGE =
- Pattern.compile("^([1-9][0-9]*|[iI][0-9a-f]{4,}.*)$");
+ private static final Pattern DEF_CHANGE = Pattern.compile(
+ "^(?:[1-9][0-9]*|(?:[^~]+~[^~]+~)?[iI][0-9a-f]{4,}.*)$");
// NOTE: As new search operations are added, please keep the
// SearchSuggestOracle up to date.
@@ -90,6 +96,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
public static final String FIELD_FILE = "file";
public static final String FIELD_IS = "is";
public static final String FIELD_HAS = "has";
+ public static final String FIELD_HASHTAG = "hashtag";
public static final String FIELD_LABEL = "label";
public static final String FIELD_LIMIT = "limit";
public static final String FIELD_MERGEABLE = "mergeable";
@@ -115,37 +122,19 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
- new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>(
- ChangeQueryBuilder.class);
-
- @SuppressWarnings("unchecked")
- public static Integer getLimit(Predicate<ChangeData> p) {
- IntPredicate<?> ip =
- (IntPredicate<?>) find(p, IntPredicate.class, FIELD_LIMIT);
- return ip != null ? ip.intValue() : null;
- }
-
- public static boolean hasNonTrivialSortKeyAfter(Schema<ChangeData> schema,
- Predicate<ChangeData> p) {
- SortKeyPredicate after =
- find(p, SortKeyPredicate.class, "sortkey_after");
- return after != null && after.getMaxValue(schema) > 0;
- }
-
- public static boolean hasSortKey(Predicate<ChangeData> p) {
- return find(p, SortKeyPredicate.class, "sortkey_after") != null
- || find(p, SortKeyPredicate.class, "sortkey_before") != null;
- }
+ new QueryBuilder.Definition<>(ChangeQueryBuilder.class);
@VisibleForTesting
public static class Arguments {
final Provider<ReviewDb> db;
+ final Provider<InternalChangeQuery> queryProvider;
final Provider<ChangeQueryRewriter> rewriter;
final IdentifiedUser.GenericFactory userFactory;
- final Provider<CurrentUser> self;
final CapabilityControl.Factory capabilityControlFactory;
final ChangeControl.GenericFactory changeControlGenericFactory;
final ChangeData.Factory changeDataFactory;
+ final FieldDef.FillArgs fillArgs;
+ final PatchLineCommentsUtil plcUtil;
final AccountResolver accountResolver;
final GroupBackend groupBackend;
final AllProjectsName allProjectsName;
@@ -159,15 +148,20 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
final TrackingFooters trackingFooters;
final boolean allowsDrafts;
+ private final Provider<CurrentUser> self;
+
@Inject
@VisibleForTesting
- public Arguments(Provider<ReviewDb> dbProvider,
+ public Arguments(Provider<ReviewDb> db,
+ Provider<InternalChangeQuery> queryProvider,
Provider<ChangeQueryRewriter> rewriter,
IdentifiedUser.GenericFactory userFactory,
Provider<CurrentUser> self,
CapabilityControl.Factory capabilityControlFactory,
ChangeControl.GenericFactory changeControlGenericFactory,
ChangeData.Factory changeDataFactory,
+ FieldDef.FillArgs fillArgs,
+ PatchLineCommentsUtil plcUtil,
AccountResolver accountResolver,
GroupBackend groupBackend,
AllProjectsName allProjectsName,
@@ -180,61 +174,134 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
ConflictsCache conflictsCache,
TrackingFooters trackingFooters,
@GerritServerConfig Config cfg) {
- this.db = dbProvider;
- this.rewriter = rewriter;
- this.userFactory = userFactory;
- this.self = self;
- this.capabilityControlFactory = capabilityControlFactory;
- this.changeControlGenericFactory = changeControlGenericFactory;
- this.changeDataFactory = changeDataFactory;
- this.accountResolver = accountResolver;
- this.groupBackend = groupBackend;
- this.allProjectsName = allProjectsName;
- this.patchListCache = patchListCache;
- this.repoManager = repoManager;
- this.projectCache = projectCache;
- this.listChildProjects = listChildProjects;
- this.indexes = indexes;
- this.submitStrategyFactory = submitStrategyFactory;
- this.conflictsCache = conflictsCache;
- this.trackingFooters = trackingFooters;
- this.allowsDrafts = cfg == null
- ? true
- : cfg.getBoolean("change", "allowDrafts", true);
- }
- }
-
- public interface Factory {
- ChangeQueryBuilder create(CurrentUser user);
+ this(db, queryProvider, rewriter, userFactory, self,
+ capabilityControlFactory, changeControlGenericFactory,
+ changeDataFactory, fillArgs, plcUtil, accountResolver, groupBackend,
+ allProjectsName, patchListCache, repoManager, projectCache,
+ listChildProjects, indexes, submitStrategyFactory, conflictsCache,
+ trackingFooters,
+ cfg == null ? true : cfg.getBoolean("change", "allowDrafts", true));
+ }
+
+ private Arguments(
+ Provider<ReviewDb> db,
+ Provider<InternalChangeQuery> queryProvider,
+ Provider<ChangeQueryRewriter> rewriter,
+ IdentifiedUser.GenericFactory userFactory,
+ Provider<CurrentUser> self,
+ CapabilityControl.Factory capabilityControlFactory,
+ ChangeControl.GenericFactory changeControlGenericFactory,
+ ChangeData.Factory changeDataFactory,
+ FieldDef.FillArgs fillArgs,
+ PatchLineCommentsUtil plcUtil,
+ AccountResolver accountResolver,
+ GroupBackend groupBackend,
+ AllProjectsName allProjectsName,
+ PatchListCache patchListCache,
+ GitRepositoryManager repoManager,
+ ProjectCache projectCache,
+ Provider<ListChildProjects> listChildProjects,
+ IndexCollection indexes,
+ SubmitStrategyFactory submitStrategyFactory,
+ ConflictsCache conflictsCache,
+ TrackingFooters trackingFooters,
+ boolean allowsDrafts) {
+ this.db = db;
+ this.queryProvider = queryProvider;
+ this.rewriter = rewriter;
+ this.userFactory = userFactory;
+ this.self = self;
+ this.capabilityControlFactory = capabilityControlFactory;
+ this.changeControlGenericFactory = changeControlGenericFactory;
+ this.changeDataFactory = changeDataFactory;
+ this.fillArgs = fillArgs;
+ this.plcUtil = plcUtil;
+ this.accountResolver = accountResolver;
+ this.groupBackend = groupBackend;
+ this.allProjectsName = allProjectsName;
+ this.patchListCache = patchListCache;
+ this.repoManager = repoManager;
+ this.projectCache = projectCache;
+ this.listChildProjects = listChildProjects;
+ this.indexes = indexes;
+ this.submitStrategyFactory = submitStrategyFactory;
+ this.conflictsCache = conflictsCache;
+ this.trackingFooters = trackingFooters;
+ this.allowsDrafts = allowsDrafts;
+ }
+
+ Arguments asUser(CurrentUser otherUser) {
+ return new Arguments(db, queryProvider, rewriter, userFactory,
+ Providers.of(otherUser),
+ capabilityControlFactory, changeControlGenericFactory,
+ changeDataFactory, fillArgs, plcUtil, accountResolver, groupBackend,
+ allProjectsName, patchListCache, repoManager, projectCache,
+ listChildProjects, indexes, submitStrategyFactory, conflictsCache,
+ trackingFooters, allowsDrafts);
+ }
+
+ Arguments asUser(Account.Id otherId) {
+ try {
+ CurrentUser u = self.get();
+ if (u.isIdentifiedUser()
+ && otherId.equals(((IdentifiedUser) u).getAccountId())) {
+ return this;
+ }
+ } catch (ProvisionException e) {
+ // Doesn't match current user, continue.
+ }
+ return asUser(userFactory.create(db, otherId));
+ }
+
+ IdentifiedUser getIdentifiedUser() throws QueryParseException {
+ try {
+ CurrentUser u = getCurrentUser();
+ if (u.isIdentifiedUser()) {
+ return (IdentifiedUser) u;
+ }
+ throw new QueryParseException(NotSignedInException.MESSAGE);
+ } catch (ProvisionException e) {
+ throw new QueryParseException(NotSignedInException.MESSAGE, e);
+ }
+ }
+
+ CurrentUser getCurrentUser() throws QueryParseException {
+ try {
+ return self.get();
+ } catch (ProvisionException e) {
+ throw new QueryParseException(NotSignedInException.MESSAGE, e);
+ }
+ }
}
private final Arguments args;
- private final CurrentUser currentUser;
@Inject
- public ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
+ ChangeQueryBuilder(Arguments args) {
super(mydef);
this.args = args;
- this.currentUser = currentUser;
}
@VisibleForTesting
protected ChangeQueryBuilder(
- QueryBuilder.Definition<ChangeData, ? extends ChangeQueryBuilder> def,
- Arguments args, CurrentUser currentUser) {
+ Definition<ChangeData, ? extends QueryBuilder<ChangeData>> def,
+ Arguments args) {
super(def);
this.args = args;
- this.currentUser = currentUser;
+ }
+
+ public ChangeQueryBuilder asUser(CurrentUser user) {
+ return new ChangeQueryBuilder(builderDef, args.asUser(user));
}
@Operator
public Predicate<ChangeData> age(String value) {
- return new AgePredicate(schema(args.indexes), value);
+ return new AgePredicate(value);
}
@Operator
public Predicate<ChangeData> before(String value) throws QueryParseException {
- return new BeforePredicate(schema(args.indexes), value);
+ return new BeforePredicate(value);
}
@Operator
@@ -244,7 +311,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> after(String value) throws QueryParseException {
- return new AfterPredicate(schema(args.indexes), value);
+ return new AfterPredicate(value);
}
@Operator
@@ -253,47 +320,46 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
- public Predicate<ChangeData> change(String query) {
+ public Predicate<ChangeData> change(String query) throws QueryParseException {
if (PAT_LEGACY_ID.matcher(query).matches()) {
- return new LegacyChangeIdPredicate(args, Change.Id.parse(query));
-
+ return new LegacyChangeIdPredicate(Change.Id.parse(query));
} else if (PAT_CHANGE_ID.matcher(query).matches()) {
- return new ChangeIdPredicate(args, parseChangeId(query));
+ return new ChangeIdPredicate(parseChangeId(query));
+ }
+ Optional<ChangeTriplet> triplet = ChangeTriplet.parse(query);
+ if (triplet.isPresent()) {
+ return Predicate.and(
+ project(triplet.get().project().get()),
+ branch(triplet.get().branch().get()),
+ new ChangeIdPredicate(parseChangeId(triplet.get().id().get())));
}
- throw new IllegalArgumentException();
+ throw new QueryParseException("Invalid change format");
}
@Operator
- public Predicate<ChangeData> comment(String value) throws QueryParseException {
+ public Predicate<ChangeData> comment(String value) {
ChangeIndex index = args.indexes.getSearchIndex();
- return new CommentPredicate(args, index, value);
+ return new CommentPredicate(index, value);
}
@Operator
public Predicate<ChangeData> status(String statusName) {
- if ("open".equals(statusName) || "pending".equals(statusName)) {
- return status_open();
-
- } else if ("closed".equals(statusName)) {
- return ChangeStatusPredicate.closed(args.db);
-
- } else if ("reviewed".equalsIgnoreCase(statusName)) {
+ if ("reviewed".equalsIgnoreCase(statusName)) {
return new IsReviewedPredicate();
-
} else {
- return new ChangeStatusPredicate(statusName);
+ return ChangeStatusPredicate.parse(statusName);
}
}
public Predicate<ChangeData> status_open() {
- return ChangeStatusPredicate.open(args.db);
+ return ChangeStatusPredicate.open();
}
@Operator
- public Predicate<ChangeData> has(String value) {
+ public Predicate<ChangeData> has(String value) throws QueryParseException {
if ("star".equalsIgnoreCase(value)) {
- return new IsStarredByPredicate(args, currentUser);
+ return new IsStarredByPredicate(args);
}
if ("draft".equalsIgnoreCase(value)) {
@@ -306,11 +372,11 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> is(String value) throws QueryParseException {
if ("starred".equalsIgnoreCase(value)) {
- return new IsStarredByPredicate(args, currentUser);
+ return new IsStarredByPredicate(args);
}
if ("watched".equalsIgnoreCase(value)) {
- return new IsWatchedByPredicate(args, currentUser, false);
+ return new IsWatchedByPredicate(args, false);
}
if ("visible".equalsIgnoreCase(value)) {
@@ -330,7 +396,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
if ("mergeable".equalsIgnoreCase(value)) {
- return new IsMergeablePredicate();
+ return new IsMergeablePredicate(schema(args.indexes), args.fillArgs);
}
try {
@@ -344,7 +410,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> commit(String id) {
- return new CommitPredicate(args, AbbreviatedObjectId.fromString(id));
+ return new CommitPredicate(AbbreviatedObjectId.fromString(id));
}
@Operator
@@ -366,17 +432,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
- public Predicate<ChangeData> projects(String name) throws QueryParseException {
- if (!schema(args.indexes).hasField(ChangeField.PROJECTS)) {
- throw new QueryParseException("Unsupported operator: " + FIELD_PROJECTS);
- }
+ public Predicate<ChangeData> projects(String name) {
return new ProjectPrefixPredicate(name);
}
@Operator
public Predicate<ChangeData> parentproject(String name) {
- return new ParentProjectPredicate(args.db, args.projectCache,
- args.listChildProjects, args.self, name);
+ return new ParentProjectPredicate(args.projectCache, args.listChildProjects,
+ args.self, name);
}
@Operator
@@ -393,10 +456,15 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
+ public Predicate<ChangeData> hashtag(String hashtag) {
+ return new HashtagPredicate(hashtag);
+ }
+
+ @Operator
public Predicate<ChangeData> topic(String name) {
if (name.startsWith("^"))
- return new RegexTopicPredicate(schema(args.indexes), name);
- return new TopicPredicate(schema(args.indexes), name);
+ return new RegexTopicPredicate(name);
+ return new TopicPredicate(name);
}
@Operator
@@ -407,23 +475,23 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
- public Predicate<ChangeData> f(String file) throws QueryParseException {
+ public Predicate<ChangeData> f(String file) {
return file(file);
}
@Operator
- public Predicate<ChangeData> file(String file) throws QueryParseException {
+ public Predicate<ChangeData> file(String file) {
if (file.startsWith("^")) {
- return new RegexPathPredicate(FIELD_FILE, file);
+ return new RegexPathPredicate(file);
} else {
return EqualsFilePredicate.create(args, file);
}
}
@Operator
- public Predicate<ChangeData> path(String path) throws QueryParseException {
+ public Predicate<ChangeData> path(String path) {
if (path.startsWith("^")) {
- return new RegexPathPredicate(FIELD_PATH, path);
+ return new RegexPathPredicate(path);
} else {
return new EqualsPathPredicate(FIELD_PATH, path);
}
@@ -484,22 +552,21 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
- public Predicate<ChangeData> message(String text) throws QueryParseException {
+ public Predicate<ChangeData> message(String text) {
ChangeIndex index = args.indexes.getSearchIndex();
- return new MessagePredicate(args, index, text);
+ return new MessagePredicate(index, text);
}
@Operator
public Predicate<ChangeData> starredby(String who)
throws QueryParseException, OrmException {
if ("self".equals(who)) {
- return new IsStarredByPredicate(args, currentUser);
+ return new IsStarredByPredicate(args);
}
Set<Account.Id> m = parseAccount(who);
List<IsStarredByPredicate> p = Lists.newArrayListWithCapacity(m.size());
for (Account.Id id : m) {
- p.add(new IsStarredByPredicate(args,
- args.userFactory.create(args.db, id)));
+ p.add(new IsStarredByPredicate(args.asUser(id)));
}
return Predicate.or(p);
}
@@ -509,14 +576,26 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
throws QueryParseException, OrmException {
Set<Account.Id> m = parseAccount(who);
List<IsWatchedByPredicate> p = Lists.newArrayListWithCapacity(m.size());
- for (Account.Id id : m) {
- if (currentUser.isIdentifiedUser()
- && id.equals(((IdentifiedUser) currentUser).getAccountId())) {
- p.add(new IsWatchedByPredicate(args, currentUser, false));
+
+ Account.Id callerId;
+ try {
+ CurrentUser caller = args.self.get();
+ if (caller.isIdentifiedUser()) {
+ callerId = ((IdentifiedUser) caller).getAccountId();
} else {
- p.add(new IsWatchedByPredicate(args,
- args.userFactory.create(args.db, id), true));
+ callerId = null;
}
+ } catch (ProvisionException e) {
+ callerId = null;
+ }
+
+ for (Account.Id id : m) {
+ // Each child IsWatchedByPredicate includes a visibility filter for the
+ // corresponding user, to ensure that predicate subtree only returns
+ // changes visible to that user. The exception is if one of the users is
+ // the caller of this method, in which case visibility is already being
+ // checked at the top level.
+ p.add(new IsWatchedByPredicate(args.asUser(id), !id.equals(callerId)));
}
return Predicate.or(p);
}
@@ -567,8 +646,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
user);
}
- public Predicate<ChangeData> is_visible() {
- return visibleto(currentUser);
+ public Predicate<ChangeData> is_visible() throws QueryParseException {
+ return visibleto(args.getCurrentUser());
}
@Operator
@@ -636,47 +715,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
- public Predicate<ChangeData> limit(String limit) {
- return limit(Integer.parseInt(limit));
- }
-
- static class LimitPredicate extends IntPredicate<ChangeData> {
- LimitPredicate(int limit) {
- super(FIELD_LIMIT, limit);
- }
-
- @Override
- public boolean match(ChangeData object) {
- return true;
- }
-
- @Override
- public int getCost() {
- return 0;
- }
- }
-
- public Predicate<ChangeData> limit(int limit) {
- return new LimitPredicate(limit);
- }
-
- boolean supportsSortKey() {
- return SortKeyPredicate.hasSortKeyField(schema(args.indexes));
- }
-
- @Operator
- public Predicate<ChangeData> sortkey_after(String sortKey) {
- return new SortKeyPredicate.After(schema(args.indexes), args.db, sortKey);
- }
-
- @Operator
- public Predicate<ChangeData> sortkey_before(String sortKey) {
- return new SortKeyPredicate.Before(schema(args.indexes), args.db, sortKey);
- }
-
- @Operator
- public Predicate<ChangeData> resume_sortkey(String sortKey) {
- return sortkey_before(sortKey);
+ public Predicate<ChangeData> limit(String limit) throws QueryParseException {
+ return new LimitPredicate(Integer.parseInt(limit));
}
@Operator
@@ -704,11 +744,15 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Override
- protected Predicate<ChangeData> defaultField(String query) {
+ protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
if (query.startsWith("refs/")) {
return ref(query);
} else if (DEF_CHANGE.matcher(query).matches()) {
- return change(query);
+ try {
+ return change(query);
+ } catch (QueryParseException e) {
+ // Skip.
+ }
}
List<Predicate<ChangeData>> predicates = Lists.newArrayListWithCapacity(9);
@@ -727,31 +771,15 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
} catch (OrmException | QueryParseException e) {
// Skip.
}
- try {
- predicates.add(file(query));
- } catch (QueryParseException e) {
- // Skip.
- }
+ predicates.add(file(query));
try {
predicates.add(label(query));
} catch (OrmException | QueryParseException e) {
// Skip.
}
- try {
- predicates.add(message(query));
- } catch (QueryParseException e) {
- // Skip.
- }
- try {
- predicates.add(comment(query));
- } catch (QueryParseException e) {
- // Skip.
- }
- try {
- predicates.add(projects(query));
- } catch (QueryParseException e) {
- // Skip.
- }
+ predicates.add(message(query));
+ predicates.add(comment(query));
+ predicates.add(projects(query));
predicates.add(ref(query));
predicates.add(branch(query));
predicates.add(topic(query));
@@ -785,9 +813,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
return Collections.singletonList(args.db.get().changes()
.get(Change.Id.parse(value)));
} else if (PAT_CHANGE_ID.matcher(value).matches()) {
- Change.Key a = new Change.Key(parseChangeId(value));
List<Change> changes =
- args.db.get().changes().byKeyRange(a, a.max()).toList();
+ asChanges(args.queryProvider.get().byKeyPrefix(parseChangeId(value)));
if (changes.isEmpty()) {
throw error("Change " + value + " not found");
}
@@ -804,11 +831,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
return value;
}
- private Account.Id self() {
- if (currentUser.isIdentifiedUser()) {
- return ((IdentifiedUser) currentUser).getAccountId();
- }
- throw new IllegalArgumentException();
+ private Account.Id self() throws QueryParseException {
+ return args.getIdentifiedUser().getAccountId();
}
private static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index bbf235b899..83492d2e50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -18,6 +18,6 @@ import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
public interface ChangeQueryRewriter {
- Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
+ Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start, int limit)
throws QueryParseException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index cea6af885e..8cbe71fcd5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -14,20 +14,18 @@
package com.google.gerrit.server.query.change;
-import static com.google.common.base.Preconditions.checkArgument;
-
-import com.google.common.collect.ImmutableBiMap;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
/**
* Predicate for a {@link Status}.
@@ -35,49 +33,64 @@ import java.util.List;
* The actual name of this operator can differ, it usually comes as {@code
* status:} but may also be {@code is:} to help do-what-i-meanery for end-users
* searching for changes. Either operator name has the same meaning.
+ * <p>
+ * Status names are looked up by prefix case-insensitively.
*/
public final class ChangeStatusPredicate extends IndexPredicate<ChangeData> {
- public static final ImmutableBiMap<Change.Status, String> VALUES;
+ private static final TreeMap<String, Predicate<ChangeData>> PREDICATES;
+ private static final Predicate<ChangeData> CLOSED;
+ private static final Predicate<ChangeData> OPEN;
static {
- ImmutableBiMap.Builder<Change.Status, String> values =
- ImmutableBiMap.builder();
+ PREDICATES = new TreeMap<>();
+ List<Predicate<ChangeData>> open = new ArrayList<>();
+ List<Predicate<ChangeData>> closed = new ArrayList<>();
+
for (Change.Status s : Change.Status.values()) {
- values.put(s, s.name().toLowerCase());
+ ChangeStatusPredicate p = new ChangeStatusPredicate(s);
+ PREDICATES.put(canonicalize(s), p);
+ (s.isOpen() ? open : closed).add(p);
}
- VALUES = values.build();
+
+ CLOSED = Predicate.or(closed);
+ OPEN = Predicate.or(open);
+
+ PREDICATES.put("closed", CLOSED);
+ PREDICATES.put("open", OPEN);
+ PREDICATES.put("pending", OPEN);
}
- public static Predicate<ChangeData> open(Provider<ReviewDb> dbProvider) {
- List<Predicate<ChangeData>> r = new ArrayList<>(4);
- for (final Change.Status e : Change.Status.values()) {
- if (e.isOpen()) {
- r.add(new ChangeStatusPredicate(e));
- }
- }
- return r.size() == 1 ? r.get(0) : or(r);
+ public static String canonicalize(Change.Status status) {
+ return status.name().toLowerCase();
}
- public static Predicate<ChangeData> closed(Provider<ReviewDb> dbProvider) {
- List<Predicate<ChangeData>> r = new ArrayList<>(4);
- for (final Change.Status e : Change.Status.values()) {
- if (e.isClosed()) {
- r.add(new ChangeStatusPredicate(e));
+ public static Predicate<ChangeData> parse(String value) {
+ String lower = value.toLowerCase();
+ NavigableMap<String, Predicate<ChangeData>> head =
+ PREDICATES.tailMap(lower, true);
+ if (!head.isEmpty()) {
+ // Assume no statuses share a common prefix so we can only walk one entry.
+ Map.Entry<String, Predicate<ChangeData>> e =
+ head.entrySet().iterator().next();
+ if (e.getKey().startsWith(lower)) {
+ return e.getValue();
}
}
- return r.size() == 1 ? r.get(0) : or(r);
+ throw new IllegalArgumentException("invalid change status: " + value);
}
- private final Change.Status status;
+ public static Predicate<ChangeData> open() {
+ return OPEN;
+ }
- ChangeStatusPredicate(String value) {
- super(ChangeField.STATUS, value);
- status = VALUES.inverse().get(value);
- checkArgument(status != null, "invalid change status: %s", value);
+ public static Predicate<ChangeData> closed() {
+ return CLOSED;
}
+ private final Change.Status status;
+
ChangeStatusPredicate(Change.Status status) {
- super(ChangeField.STATUS, VALUES.get(status));
+ super(ChangeField.STATUS, canonicalize(status));
this.status = status;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
index 5994e5cc2f..3983f62393 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -19,16 +19,13 @@ import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
class CommentPredicate extends IndexPredicate<ChangeData> {
- private final Arguments args;
private final ChangeIndex index;
- CommentPredicate(Arguments args, ChangeIndex index, String value) {
+ CommentPredicate(ChangeIndex index, String value) {
super(ChangeField.COMMENT, value);
- this.args = args;
this.index = index;
}
@@ -36,7 +33,7 @@ class CommentPredicate extends IndexPredicate<ChangeData> {
public boolean match(ChangeData object) throws OrmException {
try {
for (ChangeData cData : index.getSource(
- Predicate.and(new LegacyChangeIdPredicate(args, object.getId()), this), 0, 1)
+ Predicate.and(new LegacyChangeIdPredicate(object.getId()), this), 0, 1)
.read()) {
if (cData.getId().equals(object.getId())) {
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index 109d67ac75..14daa4d971 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -15,24 +15,18 @@
package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ObjectId;
-class CommitPredicate extends IndexPredicate<ChangeData> implements
- ChangeDataSource {
- private final Arguments args;
+class CommitPredicate extends IndexPredicate<ChangeData> {
private final AbbreviatedObjectId abbrevId;
- CommitPredicate(Arguments args, AbbreviatedObjectId id) {
+ CommitPredicate(AbbreviatedObjectId id) {
super(ChangeField.COMMIT, id.name());
- this.args = args;
this.abbrevId = id;
}
@@ -50,30 +44,7 @@ class CommitPredicate extends IndexPredicate<ChangeData> implements
}
@Override
- public ResultSet<ChangeData> read() throws OrmException {
- final RevId id = new RevId(abbrevId.name());
- if (id.isComplete()) {
- return ChangeDataResultSet.patchSet(args.changeDataFactory, args.db,
- args.db.get().patchSets().byRevision(id));
-
- } else {
- return ChangeDataResultSet.patchSet(args.changeDataFactory, args.db,
- args.db.get().patchSets().byRevisionRange(id, id.max()));
- }
- }
-
- @Override
- public boolean hasChange() {
- return false;
- }
-
- @Override
- public int getCardinality() {
- return ChangeCosts.CARD_COMMIT;
- }
-
- @Override
public int getCost() {
- return ChangeCosts.cost(ChangeCosts.PATCH_SETS_SCAN, getCardinality());
+ return 1;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
index e64ff13863..3b3d9860fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.query.change;
-import com.google.common.base.Objects;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import org.eclipse.jgit.lib.ObjectId;
import java.io.Serializable;
+import java.util.Objects;
public class ConflictKey implements Serializable {
private static final long serialVersionUID = 2L;
@@ -73,6 +73,6 @@ public class ConflictKey implements Serializable {
@Override
public int hashCode() {
- return Objects.hashCode(commit, otherCommit, submitType, contentMerge);
+ return Objects.hash(commit, otherCommit, submitType, contentMerge);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
index 1b3473e38b..8cca00d694 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
@@ -23,7 +23,7 @@ import com.google.inject.name.Named;
@Singleton
public class ConflictsCacheImpl implements ConflictsCache {
- public final static String NAME = "conflicts";
+ public static final String NAME = "conflicts";
public static Module module() {
return new CacheModule() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index ca958cf9b4..207074620f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -17,16 +17,16 @@ package com.google.gerrit.server.query.change;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.strategy.SubmitStrategy;
-import com.google.gerrit.server.project.NoSuchChangeException;
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.SubmitRuleEvaluator;
import com.google.gerrit.server.query.OperatorPredicate;
import com.google.gerrit.server.query.OrPredicate;
import com.google.gerrit.server.query.Predicate;
@@ -35,7 +35,6 @@ import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -77,7 +76,7 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
List<Predicate<ChangeData>> predicatesForOneChange =
Lists.newArrayListWithCapacity(5);
predicatesForOneChange.add(
- not(new LegacyChangeIdPredicate(args, c.getId())));
+ not(new LegacyChangeIdPredicate(c.getId())));
predicatesForOneChange.add(
new ProjectPredicate(c.getProject().get()));
predicatesForOneChange.add(
@@ -95,7 +94,7 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
if (!otherChange.getDest().equals(c.getDest())) {
return false;
}
- SubmitType submitType = getSubmitType(otherChange, object);
+ SubmitType submitType = getSubmitType(object);
if (submitType == null) {
return false;
}
@@ -108,42 +107,24 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
if (conflicts != null) {
return conflicts;
}
- try {
- Repository repo =
+ try (Repository repo =
args.repoManager.openRepository(otherChange.getProject());
- try {
- RevWalk rw = new RevWalk(repo) {
- @Override
- protected RevCommit createCommit(AnyObjectId id) {
- return new CodeReviewCommit(id);
- }
- };
- try {
- RevFlag canMergeFlag = rw.newFlag("CAN_MERGE");
- CodeReviewCommit commit =
- (CodeReviewCommit) rw.parseCommit(changeDataCache.getTestAgainst());
- SubmitStrategy strategy =
- args.submitStrategyFactory.create(submitType,
- db.get(), repo, rw, null, canMergeFlag,
- getAlreadyAccepted(repo, rw, commit),
- otherChange.getDest());
- CodeReviewCommit otherCommit =
- (CodeReviewCommit) rw.parseCommit(other);
- otherCommit.add(canMergeFlag);
- conflicts = !strategy.dryRun(commit, otherCommit);
- args.conflictsCache.put(conflictsKey, conflicts);
- return conflicts;
- } catch (MergeException e) {
- throw new IllegalStateException(e);
- } catch (NoSuchProjectException e) {
- throw new IllegalStateException(e);
- } finally {
- rw.close();
- }
- } finally {
- repo.close();
- }
- } catch (IOException e) {
+ RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
+ RevFlag canMergeFlag = rw.newFlag("CAN_MERGE");
+ CodeReviewCommit commit =
+ (CodeReviewCommit) rw.parseCommit(changeDataCache.getTestAgainst());
+ SubmitStrategy strategy =
+ args.submitStrategyFactory.create(submitType,
+ db.get(), repo, rw, null, canMergeFlag,
+ getAlreadyAccepted(repo, rw, commit),
+ otherChange.getDest());
+ CodeReviewCommit otherCommit =
+ (CodeReviewCommit) rw.parseCommit(other);
+ otherCommit.add(canMergeFlag);
+ conflicts = !strategy.dryRun(commit, otherCommit);
+ args.conflictsCache.put(conflictsKey, conflicts);
+ return conflicts;
+ } catch (MergeException | NoSuchProjectException | IOException e) {
throw new IllegalStateException(e);
}
}
@@ -153,19 +134,12 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
return 5;
}
- private SubmitType getSubmitType(Change change, ChangeData cd) throws OrmException {
- try {
- final SubmitTypeRecord r =
- args.changeControlGenericFactory.controlFor(change,
- args.userFactory.create(change.getOwner()))
- .getSubmitTypeRecord(db.get(), cd.currentPatchSet(), cd);
- if (r.status != SubmitTypeRecord.Status.OK) {
- return null;
- }
- return r.type;
- } catch (NoSuchChangeException e) {
+ private SubmitType getSubmitType(ChangeData cd) throws OrmException {
+ SubmitTypeRecord r = new SubmitRuleEvaluator(cd).getSubmitType();
+ if (r.status != SubmitTypeRecord.Status.OK) {
return null;
}
+ return r.type;
}
private Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
index 53d2bbda2c..ebb7389bc7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -33,7 +33,8 @@ class HasDraftByPredicate extends OperatorPredicate<ChangeData> implements
private final Arguments args;
private final Account.Id accountId;
- HasDraftByPredicate(Arguments args, Account.Id accountId) {
+ HasDraftByPredicate(Arguments args,
+ Account.Id accountId) {
super(ChangeQueryBuilder.FIELD_DRAFTBY, accountId.toString());
this.args = args;
this.accountId = accountId;
@@ -41,20 +42,16 @@ class HasDraftByPredicate extends OperatorPredicate<ChangeData> implements
@Override
public boolean match(final ChangeData object) throws OrmException {
- for (PatchLineComment c : object.comments()) {
- if (c.getStatus() == PatchLineComment.Status.DRAFT
- && c.getAuthor().equals(accountId)) {
- return true;
- }
- }
- return false;
+ return !args.plcUtil
+ .draftByChangeAuthor(args.db.get(), object.notes(), accountId)
+ .isEmpty();
}
@Override
public ResultSet<ChangeData> read() throws OrmException {
Set<Change.Id> ids = new HashSet<>();
- for (PatchLineComment sc : args.db.get().patchComments()
- .draftByAuthor(accountId)) {
+ for (PatchLineComment sc :
+ args.plcUtil.draftByAuthor(args.db.get(), accountId)) {
ids.add(sc.getKey().getParentKey().getParentKey().getParentKey());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java
new file mode 100644
index 0000000000..ea591ec98a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 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.server.change.HashtagsUtil;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+class HashtagPredicate extends IndexPredicate<ChangeData> {
+ HashtagPredicate(String hashtag) {
+ super(ChangeField.HASHTAG, HashtagsUtil.cleanupHashtag(hashtag));
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ for (String hashtag : object.notes().load().getHashtags()) {
+ if (hashtag.equalsIgnoreCase(getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
+
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
new file mode 100644
index 0000000000..e08847ad8a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -0,0 +1,133 @@
+// Copyright (C) 2014 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 static com.google.gerrit.server.query.Predicate.and;
+import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
+
+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.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import java.util.List;
+
+/**
+ * Execute a single query over changes, for use by Gerrit internals.
+ * <p>
+ * By default, visibility of returned changes is not enforced (unlike in {@link
+ * QueryProcessor}). The methods in this class are not typically used by
+ * user-facing paths, but rather by internal callers that need to process all
+ * matching results.
+ */
+public class InternalChangeQuery {
+ private static Predicate<ChangeData> ref(Branch.NameKey branch) {
+ return new RefPredicate(branch.get());
+ }
+
+ private static Predicate<ChangeData> change(Change.Key key) {
+ return new ChangeIdPredicate(key.get());
+ }
+
+ private static Predicate<ChangeData> project(Project.NameKey project) {
+ return new ProjectPredicate(project.get());
+ }
+
+ private static Predicate<ChangeData> status(Change.Status status) {
+ return new ChangeStatusPredicate(status);
+ }
+
+ private static Predicate<ChangeData> topic(String topic) {
+ return new TopicPredicate(topic);
+ }
+
+ private final QueryProcessor qp;
+
+ @Inject
+ InternalChangeQuery(QueryProcessor queryProcessor) {
+ qp = queryProcessor.enforceVisibility(false);
+ }
+
+ public InternalChangeQuery setLimit(int n) {
+ qp.setLimit(n);
+ return this;
+ }
+
+ public InternalChangeQuery enforceVisibility(boolean enforce) {
+ qp.enforceVisibility(enforce);
+ return this;
+ }
+
+ public List<ChangeData> byKey(Change.Key key) throws OrmException {
+ return byKeyPrefix(key.get());
+ }
+
+ public List<ChangeData> byKeyPrefix(String prefix) throws OrmException {
+ return query(new ChangeIdPredicate(prefix));
+ }
+
+ public List<ChangeData> byBranchKey(Branch.NameKey branch, Change.Key key)
+ throws OrmException {
+ return query(and(
+ ref(branch),
+ project(branch.getParentKey()),
+ change(key)));
+ }
+
+ public List<ChangeData> byProject(Project.NameKey project)
+ throws OrmException {
+ return query(project(project));
+ }
+
+ public List<ChangeData> submitted(Branch.NameKey branch) throws OrmException {
+ return query(and(
+ ref(branch),
+ project(branch.getParentKey()),
+ status(Change.Status.SUBMITTED)));
+ }
+
+ public List<ChangeData> allSubmitted() throws OrmException {
+ return query(status(Change.Status.SUBMITTED));
+ }
+
+ public List<ChangeData> byBranchOpen(Branch.NameKey branch)
+ throws OrmException {
+ return query(and(
+ ref(branch),
+ project(branch.getParentKey()),
+ open()));
+ }
+
+ public List<ChangeData> byProjectOpen(Project.NameKey project)
+ throws OrmException {
+ return query(and(project(project), open()));
+ }
+
+ public List<ChangeData> byTopicOpen(String topic)
+ throws OrmException {
+ return query(and(topic(topic), open()));
+ }
+
+ private List<ChangeData> query(Predicate<ChangeData> p) throws OrmException {
+ try {
+ return qp.queryChanges(p).changes();
+ } catch (QueryParseException e) {
+ throw new OrmException(e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
index 787b90e555..6ef4ab600a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
@@ -14,20 +14,39 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
+import static com.google.gerrit.server.index.ChangeField.MERGEABLE;
+
import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.Schema;
import com.google.gwtorm.server.OrmException;
class IsMergeablePredicate extends IndexPredicate<ChangeData> {
- IsMergeablePredicate() {
- super(ChangeField.MERGEABLE, "1");
+ @SuppressWarnings("deprecation")
+ static FieldDef<ChangeData, ?> mergeableField(Schema<ChangeData> schema) {
+ if (schema == null) {
+ return ChangeField.LEGACY_MERGEABLE;
+ }
+ FieldDef<ChangeData, ?> f = schema.getFields().get(MERGEABLE.getName());
+ if (f != null) {
+ return f;
+ }
+ return schema.getFields().get(ChangeField.LEGACY_MERGEABLE.getName());
+ }
+
+ private final FillArgs args;
+
+ IsMergeablePredicate(Schema<ChangeData> schema,
+ FillArgs args) {
+ super(mergeableField(schema), "1");
+ this.args = args;
}
@Override
public boolean match(ChangeData object) throws OrmException {
- Change c = object.change();
- return c != null && c.isMergeable();
+ return getValue().equals(getField().get(object, args));
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
index d25d5a5345..b5bef0735d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -20,6 +20,7 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.query.OrPredicate;
import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -36,12 +37,10 @@ class IsStarredByPredicate extends OrPredicate<ChangeData> implements
return user.toString();
}
- private static List<Predicate<ChangeData>> predicates(
- Arguments args,
- Set<Change.Id> ids) {
+ private static List<Predicate<ChangeData>> predicates(Set<Change.Id> ids) {
List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(ids.size());
for (Change.Id id : ids) {
- r.add(new LegacyChangeIdPredicate(args, id));
+ r.add(new LegacyChangeIdPredicate(id));
}
return r;
}
@@ -49,8 +48,12 @@ class IsStarredByPredicate extends OrPredicate<ChangeData> implements
private final Arguments args;
private final CurrentUser user;
- IsStarredByPredicate(Arguments args, CurrentUser user) {
- super(predicates(args, user.getStarredChanges()));
+ IsStarredByPredicate(Arguments args) throws QueryParseException {
+ this(args, args.getIdentifiedUser());
+ }
+
+ private IsStarredByPredicate(Arguments args, IdentifiedUser user) {
+ super(predicates(user.getStarredChanges()));
this.args = args;
this.user = user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index c1e99a311a..606f5771ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -37,18 +37,17 @@ class IsWatchedByPredicate extends AndPredicate<ChangeData> {
private final CurrentUser user;
IsWatchedByPredicate(ChangeQueryBuilder.Arguments args,
- CurrentUser user,
- boolean checkIsVisible) {
- super(filters(args, user, checkIsVisible));
- this.user = user;
+ boolean checkIsVisible) throws QueryParseException {
+ super(filters(args, checkIsVisible));
+ this.user = args.getCurrentUser();
}
private static List<Predicate<ChangeData>> filters(
ChangeQueryBuilder.Arguments args,
- CurrentUser user,
- boolean checkIsVisible) {
+ boolean checkIsVisible) throws QueryParseException {
+ CurrentUser user = args.getCurrentUser();
List<Predicate<ChangeData>> r = Lists.newArrayList();
- ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
+ ChangeQueryBuilder builder = new ChangeQueryBuilder(args);
for (AccountProjectWatch w : user.getNotificationFilters()) {
Predicate<ChangeData> f = null;
if (w.getFilter() != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 9b5aae3c0a..83364c36fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -90,14 +90,14 @@ public class LabelPredicate extends OrPredicate<ChangeData> {
try {
LabelVote lv = LabelVote.parse(v);
- parsed = new Parsed(lv.getLabel(), "=", lv.getValue());
+ parsed = new Parsed(lv.label(), "=", lv.value());
} catch (IllegalArgumentException e) {
// Try next format.
}
try {
LabelVote lv = LabelVote.parseWithEquals(v);
- parsed = new Parsed(lv.getLabel(), "=", lv.getValue());
+ parsed = new Parsed(lv.label(), "=", lv.value());
} catch (IllegalArgumentException e) {
// Try next format.
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
index 1cbb4998d9..9ecc7b2dbb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
@@ -17,21 +17,13 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
-import com.google.gwtorm.server.ListResultSet;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import java.util.Collections;
-
-class LegacyChangeIdPredicate extends IndexPredicate<ChangeData> implements
- ChangeDataSource {
- private final Arguments args;
+/** Predicate over change number (aka legacy ID or Change.Id). */
+class LegacyChangeIdPredicate extends IndexPredicate<ChangeData> {
private final Change.Id id;
- LegacyChangeIdPredicate(Arguments args, Change.Id id) {
+ LegacyChangeIdPredicate(Change.Id id) {
super(ChangeField.LEGACY_ID, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
- this.args = args;
this.id = id;
}
@@ -41,28 +33,7 @@ class LegacyChangeIdPredicate extends IndexPredicate<ChangeData> implements
}
@Override
- public ResultSet<ChangeData> read() throws OrmException {
- Change c = args.db.get().changes().get(id);
- if (c != null) {
- return new ListResultSet<>(Collections.singletonList(
- args.changeDataFactory.create(args.db.get(), c)));
- } else {
- return new ListResultSet<>(Collections.<ChangeData> emptyList());
- }
- }
-
- @Override
- public boolean hasChange() {
- return true;
- }
-
- @Override
- public int getCardinality() {
- return 1;
- }
-
- @Override
public int getCost() {
- return ChangeCosts.IDS_MEMORY;
+ return 1;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LimitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LimitPredicate.java
new file mode 100644
index 0000000000..0e90ddf761
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LimitPredicate.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 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 static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_LIMIT;
+
+import com.google.gerrit.server.query.IntPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryBuilder;
+import com.google.gerrit.server.query.QueryParseException;
+
+public class LimitPredicate extends IntPredicate<ChangeData> {
+ @SuppressWarnings("unchecked")
+ public static Integer getLimit(Predicate<ChangeData> p) {
+ IntPredicate<?> ip = QueryBuilder.find(p, IntPredicate.class, FIELD_LIMIT);
+ return ip != null ? ip.intValue() : null;
+ }
+
+ public LimitPredicate(int limit) throws QueryParseException {
+ super(ChangeQueryBuilder.FIELD_LIMIT, limit);
+ if (limit <= 0) {
+ throw new QueryParseException("limit must be positive: " + limit);
+ }
+ }
+
+ @Override
+ public boolean match(ChangeData object) {
+ return true;
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 04bdb1e93a..0a16d02766 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -19,7 +19,6 @@ import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
/**
@@ -27,12 +26,10 @@ import com.google.gwtorm.server.OrmException;
* body.
*/
class MessagePredicate extends IndexPredicate<ChangeData> {
- private final Arguments args;
private final ChangeIndex index;
- MessagePredicate(Arguments args, ChangeIndex index, String value) {
+ MessagePredicate(ChangeIndex index, String value) {
super(ChangeField.COMMIT_MESSAGE, value);
- this.args = args;
this.index = index;
}
@@ -40,7 +37,7 @@ class MessagePredicate extends IndexPredicate<ChangeData> {
public boolean match(ChangeData object) throws OrmException {
try {
for (ChangeData cData : index.getSource(
- Predicate.and(new LegacyChangeIdPredicate(args, object.getId()), this), 0, 1)
+ Predicate.and(new LegacyChangeIdPredicate(object.getId()), this), 0, 1)
.read()) {
if (cData.getId().equals(object.getId())) {
return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
new file mode 100644
index 0000000000..222c2bbb1c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -0,0 +1,404 @@
+// Copyright (C) 2014 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.common.TimeUtil;
+import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.TrackingFooters;
+import com.google.gerrit.server.data.ChangeAttribute;
+import com.google.gerrit.server.data.PatchSetAttribute;
+import com.google.gerrit.server.data.QueryStatsAttribute;
+import com.google.gerrit.server.events.EventFactory;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gson.Gson;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.util.io.DisabledOutputStream;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Change query implementation that outputs to a stream in the style of an SSH
+ * command.
+ */
+public class OutputStreamQuery {
+ private static final Logger log =
+ LoggerFactory.getLogger(OutputStreamQuery.class);
+
+ private static final DateTimeFormatter dtf =
+ DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss zzz");
+
+ public static enum OutputFormat {
+ TEXT, JSON
+ }
+
+ private final ChangeQueryBuilder queryBuilder;
+ private final QueryProcessor queryProcessor;
+ private final EventFactory eventFactory;
+ private final TrackingFooters trackingFooters;
+ private final CurrentUser user;
+
+ private OutputFormat outputFormat = OutputFormat.TEXT;
+ private boolean includePatchSets;
+ private boolean includeCurrentPatchSet;
+ private boolean includeApprovals;
+ private boolean includeComments;
+ private boolean includeFiles;
+ private boolean includeCommitMessage;
+ private boolean includeDependencies;
+ private boolean includeSubmitRecords;
+ private boolean includeAllReviewers;
+
+ private OutputStream outputStream = DisabledOutputStream.INSTANCE;
+ private PrintWriter out;
+
+ @Inject
+ OutputStreamQuery(
+ ChangeQueryBuilder queryBuilder,
+ QueryProcessor queryProcessor,
+ EventFactory eventFactory,
+ TrackingFooters trackingFooters,
+ CurrentUser user) {
+ this.queryBuilder = queryBuilder;
+ this.queryProcessor = queryProcessor;
+ this.eventFactory = eventFactory;
+ this.trackingFooters = trackingFooters;
+ this.user = user;
+ }
+
+ void setLimit(int n) {
+ queryProcessor.setLimit(n);
+ }
+
+ public void setStart(int n) {
+ queryProcessor.setStart(n);
+ }
+
+ public void setIncludePatchSets(boolean on) {
+ includePatchSets = on;
+ }
+
+ public boolean getIncludePatchSets() {
+ return includePatchSets;
+ }
+
+ public void setIncludeCurrentPatchSet(boolean on) {
+ includeCurrentPatchSet = on;
+ }
+
+ public boolean getIncludeCurrentPatchSet() {
+ return includeCurrentPatchSet;
+ }
+
+ public void setIncludeApprovals(boolean on) {
+ includeApprovals = on;
+ }
+
+ public void setIncludeComments(boolean on) {
+ includeComments = on;
+ }
+
+ public void setIncludeFiles(boolean on) {
+ includeFiles = on;
+ }
+
+ public boolean getIncludeFiles() {
+ return includeFiles;
+ }
+
+ public void setIncludeDependencies(boolean on) {
+ includeDependencies = on;
+ }
+
+ public boolean getIncludeDependencies() {
+ return includeDependencies;
+ }
+
+ public void setIncludeCommitMessage(boolean on) {
+ includeCommitMessage = on;
+ }
+
+ public void setIncludeSubmitRecords(boolean on) {
+ includeSubmitRecords = on;
+ }
+
+ public void setIncludeAllReviewers(boolean on) {
+ includeAllReviewers = on;
+ }
+
+ public void setOutput(OutputStream out, OutputFormat fmt) {
+ this.outputStream = out;
+ this.outputFormat = fmt;
+ }
+
+ public void query(String queryString) throws IOException {
+ out = new PrintWriter( //
+ new BufferedWriter( //
+ new OutputStreamWriter(outputStream, "UTF-8")));
+ try {
+ if (queryProcessor.isDisabled()) {
+ ErrorMessage m = new ErrorMessage();
+ m.message = "query disabled";
+ show(m);
+ return;
+ }
+
+ try {
+ final QueryStatsAttribute stats = new QueryStatsAttribute();
+ stats.runTimeMilliseconds = TimeUtil.nowMs();
+
+ QueryResult results =
+ queryProcessor.queryChanges(queryBuilder.parse(queryString));
+ ChangeAttribute c = null;
+ for (ChangeData d : results.changes()) {
+ ChangeControl cc = d.changeControl().forUser(user);
+
+ LabelTypes labelTypes = cc.getLabelTypes();
+ c = eventFactory.asChangeAttribute(d.change());
+ eventFactory.extend(c, d.change());
+
+ if (!trackingFooters.isEmpty()) {
+ eventFactory.addTrackingIds(c,
+ trackingFooters.extract(d.commitFooters()));
+ }
+
+ if (includeAllReviewers) {
+ eventFactory.addAllReviewers(c, d.notes());
+ }
+
+ if (includeSubmitRecords) {
+ eventFactory.addSubmitRecords(c, new SubmitRuleEvaluator(d)
+ .setAllowClosed(true)
+ .setAllowDraft(true)
+ .evaluate());
+ }
+
+ if (includeCommitMessage) {
+ eventFactory.addCommitMessage(c, d.commitMessage());
+ }
+
+ if (includePatchSets) {
+ if (includeFiles) {
+ eventFactory.addPatchSets(c, d.patches(),
+ includeApprovals ? d.approvals().asMap() : null,
+ includeFiles, d.change(), labelTypes);
+ } else {
+ eventFactory.addPatchSets(c, d.patches(),
+ includeApprovals ? d.approvals().asMap() : null,
+ labelTypes);
+ }
+ }
+
+ if (includeCurrentPatchSet) {
+ PatchSet current = d.currentPatchSet();
+ if (current != null) {
+ c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
+ eventFactory.addApprovals(c.currentPatchSet,
+ d.currentApprovals(), labelTypes);
+
+ if (includeFiles) {
+ eventFactory.addPatchSetFileNames(c.currentPatchSet,
+ d.change(), d.currentPatchSet());
+ }
+ }
+ }
+
+ if (includeComments) {
+ eventFactory.addComments(c, d.messages());
+ if (includePatchSets) {
+ for (PatchSetAttribute attribute : c.patchSets) {
+ eventFactory.addPatchSetComments(attribute, d.publishedComments());
+ }
+ }
+ }
+
+ if (includeDependencies) {
+ eventFactory.addDependencies(c, d.change());
+ }
+
+ show(c);
+ }
+
+ stats.rowCount = results.changes().size();
+ stats.moreChanges = results.moreChanges();
+ stats.runTimeMilliseconds =
+ TimeUtil.nowMs() - stats.runTimeMilliseconds;
+ show(stats);
+ } catch (OrmException err) {
+ log.error("Cannot execute query: " + queryString, err);
+
+ ErrorMessage m = new ErrorMessage();
+ m.message = "cannot query database";
+ show(m);
+
+ } catch (QueryParseException e) {
+ ErrorMessage m = new ErrorMessage();
+ m.message = e.getMessage();
+ show(m);
+ }
+ } finally {
+ try {
+ out.flush();
+ } finally {
+ out = null;
+ }
+ }
+ }
+
+ private void show(Object data) {
+ switch (outputFormat) {
+ default:
+ case TEXT:
+ if (data instanceof ChangeAttribute) {
+ out.print("change ");
+ out.print(((ChangeAttribute) data).id);
+ out.print("\n");
+ showText(data, 1);
+ } else {
+ showText(data, 0);
+ }
+ out.print('\n');
+ break;
+
+ case JSON:
+ out.print(new Gson().toJson(data));
+ out.print('\n');
+ break;
+ }
+ }
+
+ private void showText(Object data, int depth) {
+ for (Field f : fieldsOf(data.getClass())) {
+ Object val;
+ try {
+ val = f.get(data);
+ } catch (IllegalArgumentException err) {
+ continue;
+ } catch (IllegalAccessException err) {
+ continue;
+ }
+ if (val == null) {
+ continue;
+ }
+
+ showField(f.getName(), val, depth);
+ }
+ }
+
+ private String indent(int spaces) {
+ if (spaces == 0) {
+ return "";
+ } else {
+ return String.format("%" + spaces + "s", " ");
+ }
+ }
+
+ private void showField(String field, Object value, int depth) {
+ final int spacesDepthRatio = 2;
+ String indent = indent(depth * spacesDepthRatio);
+ out.print(indent);
+ out.print(field);
+ out.print(':');
+ if (value instanceof String && ((String) value).contains("\n")) {
+ out.print(' ');
+ // Idention for multi-line text is
+ // current depth indetion + length of field + length of ": "
+ indent = indent(indent.length() + field.length() + spacesDepthRatio);
+ out.print(((String) value).replaceAll("\n", "\n" + indent).trim());
+ out.print('\n');
+ } else if (value instanceof Long && isDateField(field)) {
+ out.print(' ');
+ out.print(dtf.print(((Long) value) * 1000L));
+ out.print('\n');
+ } else if (isPrimitive(value)) {
+ out.print(' ');
+ out.print(value);
+ out.print('\n');
+ } else if (value instanceof Collection) {
+ out.print('\n');
+ boolean firstElement = true;
+ for (Object thing : ((Collection<?>) value)) {
+ // The name of the collection was initially printed at the beginning
+ // of this routine. Beginning at the second sub-element, reprint
+ // the collection name so humans can separate individual elements
+ // with less strain and error.
+ //
+ if (firstElement) {
+ firstElement = false;
+ } else {
+ out.print(indent);
+ out.print(field);
+ out.print(":\n");
+ }
+ if (isPrimitive(thing)) {
+ out.print(' ');
+ out.print(value);
+ out.print('\n');
+ } else {
+ showText(thing, depth + 1);
+ }
+ }
+ } else {
+ out.print('\n');
+ showText(value, depth + 1);
+ }
+ }
+
+ private static boolean isPrimitive(Object value) {
+ return value instanceof String //
+ || value instanceof Number //
+ || value instanceof Boolean //
+ || value instanceof Enum;
+ }
+
+ private static boolean isDateField(String name) {
+ return "lastUpdated".equals(name) //
+ || "grantedOn".equals(name) //
+ || "timestamp".equals(name) //
+ || "createdOn".equals(name);
+ }
+
+ private List<Field> fieldsOf(Class<?> type) {
+ List<Field> r = new ArrayList<>();
+ if (type.getSuperclass() != null) {
+ r.addAll(fieldsOf(type.getSuperclass()));
+ }
+ r.addAll(Arrays.asList(type.getDeclaredFields()));
+ return r;
+ }
+
+ static class ErrorMessage {
+ public final String type = "error";
+ public String message;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
index e411cf9000..7afd934322 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
@@ -20,7 +20,5 @@ import com.google.gwtorm.server.ResultSet;
public interface Paginated {
int limit();
- ResultSet<ChangeData> restart(ChangeData last) throws OrmException;
-
ResultSet<ChangeData> restart(int start) throws OrmException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 253f719eb9..2cabfc5ea0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -17,7 +17,6 @@ package com.google.gerrit.server.query.change;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.ListChildProjects;
import com.google.gerrit.server.project.ProjectCache;
@@ -33,15 +32,15 @@ import java.util.List;
class ParentProjectPredicate extends OrPredicate<ChangeData> {
private final String value;
- ParentProjectPredicate(Provider<ReviewDb> dbProvider,
- ProjectCache projectCache, Provider<ListChildProjects> listChildProjects,
- Provider<CurrentUser> self, String value) {
- super(predicates(dbProvider, projectCache, listChildProjects, self, value));
+ ParentProjectPredicate(ProjectCache projectCache,
+ Provider<ListChildProjects> listChildProjects, Provider<CurrentUser> self,
+ String value) {
+ super(predicates(projectCache, listChildProjects, self, value));
this.value = value;
}
private static List<Predicate<ChangeData>> predicates(
- Provider<ReviewDb> dbProvider, ProjectCache projectCache,
+ ProjectCache projectCache,
Provider<ListChildProjects> listChildProjects,
Provider<CurrentUser> self, String value) {
ProjectState projectState = projectCache.get(new Project.NameKey(value));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 4808b47f3d..299f0f19d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -15,7 +15,8 @@
package com.google.gerrit.server.query.change;
import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.common.ListChangesOption;
+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.BadRequestException;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -23,7 +24,6 @@ import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeJson;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -31,7 +31,6 @@ import com.google.inject.Provider;
import org.kohsuke.args4j.Option;
-import java.util.BitSet;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -40,9 +39,9 @@ import java.util.regex.Pattern;
public class QueryChanges implements RestReadView<TopLevelResource> {
private final ChangeJson json;
+ private final ChangeQueryBuilder qb;
private final QueryProcessor imp;
private final Provider<CurrentUser> user;
- private boolean reverse;
private EnumSet<ListChangesOption> options;
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
@@ -63,31 +62,18 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
options.addAll(ListChangesOption.fromBits(Integer.parseInt(hex, 16)));
}
- @Option(name = "-P", metaVar = "SORTKEY", usage = "Previous changes before SORTKEY")
- public void setSortKeyAfter(String key) {
- // Querying for the prior page of changes requires sortkey_after predicate.
- // Changes are shown most recent->least recent. The previous page of
- // results contains changes that were updated after the given key.
- imp.setSortkeyAfter(key);
- reverse = true;
- }
-
- @Option(name = "-N", metaVar = "SORTKEY", usage = "Next changes after SORTKEY")
- public void setSortKeyBefore(String key) {
- // Querying for the next page of changes requires sortkey_before predicate.
- // Changes are shown most recent->least recent. The next page contains
- // changes that were updated before the given key.
- imp.setSortkeyBefore(key);
- }
-
@Option(name = "--start", aliases = {"-S"}, metaVar = "CNT", usage = "Number of changes to skip")
public void setStart(int start) {
imp.setStart(start);
}
@Inject
- QueryChanges(ChangeJson json, QueryProcessor qp, Provider<CurrentUser> user) {
+ QueryChanges(ChangeJson json,
+ ChangeQueryBuilder qb,
+ QueryProcessor qp,
+ Provider<CurrentUser> user) {
this.json = json;
+ this.qb = qb;
this.imp = qp;
this.user = user;
@@ -154,30 +140,13 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
private List<List<ChangeInfo>> query0() throws OrmException,
QueryParseException {
int cnt = queries.size();
- BitSet more = new BitSet(cnt);
- List<List<ChangeData>> data = imp.queryChanges(queries);
- for (int n = 0; n < cnt; n++) {
- List<ChangeData> changes = data.get(n);
- if (imp.getLimit() > 0 && changes.size() > imp.getLimit()) {
- if (reverse) {
- changes = changes.subList(1, changes.size());
- } else {
- changes = changes.subList(0, imp.getLimit());
- }
- data.set(n, changes);
- more.set(n, true);
- }
- }
-
- List<List<ChangeInfo>> res = json.addOptions(options).formatList2(data);
+ List<QueryResult> results = imp.queryChanges(qb.parse(queries));
+ List<List<ChangeInfo>> res = json.addOptions(options)
+ .formatQueryResults(results);
for (int n = 0; n < cnt; n++) {
List<ChangeInfo> info = res.get(n);
- if (more.get(n) && !info.isEmpty()) {
- if (reverse) {
- info.get(0)._moreChanges = true;
- } else {
- info.get(info.size() - 1)._moreChanges = true;
- }
+ if (results.get(n).moreChanges()) {
+ info.get(info.size() - 1)._moreChanges = true;
}
}
return res;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 7927cbb46f..51d971d6e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -14,553 +14,184 @@
package com.google.gerrit.server.query.change;
-import com.google.common.base.Objects;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
+
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.config.TrackingFooters;
-import com.google.gerrit.server.data.ChangeAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-import com.google.gerrit.server.data.QueryStatsAttribute;
-import com.google.gerrit.server.events.EventFactory;
+import com.google.gerrit.server.index.IndexConfig;
+import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.util.TimeUtil;
-import com.google.gson.Gson;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.util.io.DisabledOutputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.lang.reflect.Field;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
import java.util.List;
public class QueryProcessor {
- private static final Logger log =
- LoggerFactory.getLogger(QueryProcessor.class);
-
- private final Comparator<ChangeData> cmpAfter =
- new Comparator<ChangeData>() {
- @Override
- public int compare(ChangeData a, ChangeData b) {
- try {
- return a.change().getSortKey().compareTo(b.change().getSortKey());
- } catch (OrmException e) {
- return 0;
- }
- }
- };
-
- private final Comparator<ChangeData> cmpBefore =
- new Comparator<ChangeData>() {
- @Override
- public int compare(ChangeData a, ChangeData b) {
- try {
- return b.change().getSortKey().compareTo(a.change().getSortKey());
- } catch (OrmException e) {
- return 0;
- }
- }
- };
-
- public static enum OutputFormat {
- TEXT, JSON
- }
-
- private final Gson gson = new Gson();
- private final SimpleDateFormat sdf =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
-
- private final EventFactory eventFactory;
- private final ChangeQueryBuilder queryBuilder;
- private final ChangeQueryRewriter queryRewriter;
private final Provider<ReviewDb> db;
- private final TrackingFooters trackingFooters;
- private final CurrentUser user;
- private final int maxLimit;
+ private final Provider<CurrentUser> userProvider;
+ private final ChangeControl.GenericFactory changeControlFactory;
+ private final ChangeQueryRewriter queryRewriter;
+ private final IndexConfig indexConfig;
- private OutputFormat outputFormat = OutputFormat.TEXT;
- private int limit;
+ private int limitFromCaller;
private int start;
- private String sortkeyAfter;
- private String sortkeyBefore;
- private boolean includePatchSets;
- private boolean includeCurrentPatchSet;
- private boolean includeApprovals;
- private boolean includeComments;
- private boolean includeFiles;
- private boolean includeCommitMessage;
- private boolean includeDependencies;
- private boolean includeSubmitRecords;
- private boolean includeAllReviewers;
-
- private OutputStream outputStream = DisabledOutputStream.INSTANCE;
- private PrintWriter out;
- private boolean moreResults;
+ private boolean enforceVisibility = true;
@Inject
- QueryProcessor(EventFactory eventFactory,
- ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
- ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db,
- TrackingFooters trackingFooters) {
- this.eventFactory = eventFactory;
- this.queryBuilder = queryBuilder.create(currentUser);
- this.queryRewriter = queryRewriter;
+ QueryProcessor(Provider<ReviewDb> db,
+ Provider<CurrentUser> userProvider,
+ ChangeControl.GenericFactory changeControlFactory,
+ ChangeQueryRewriter queryRewriter,
+ IndexConfig indexConfig) {
this.db = db;
- this.trackingFooters = trackingFooters;
- this.user = currentUser;
- this.maxLimit = currentUser.getCapabilities()
- .getRange(GlobalCapability.QUERY_LIMIT)
- .getMax();
- this.moreResults = false;
+ this.userProvider = userProvider;
+ this.changeControlFactory = changeControlFactory;
+ this.queryRewriter = queryRewriter;
+ this.indexConfig = indexConfig;
}
- int getLimit() {
- return limit;
+ public QueryProcessor enforceVisibility(boolean enforce) {
+ enforceVisibility = enforce;
+ return this;
}
- void setLimit(int n) {
- limit = n;
+ public QueryProcessor setLimit(int n) {
+ limitFromCaller = n;
+ return this;
}
- public void setStart(int n) {
+ public QueryProcessor setStart(int n) {
start = n;
- }
-
- void setSortkeyAfter(String sortkey) {
- sortkeyAfter = sortkey;
- }
-
- void setSortkeyBefore(String sortkey) {
- sortkeyBefore = sortkey;
- }
-
- public void setIncludePatchSets(boolean on) {
- includePatchSets = on;
- }
-
- public boolean getIncludePatchSets() {
- return includePatchSets;
- }
-
- public void setIncludeCurrentPatchSet(boolean on) {
- includeCurrentPatchSet = on;
- }
-
- public boolean getIncludeCurrentPatchSet() {
- return includeCurrentPatchSet;
- }
-
- public void setIncludeApprovals(boolean on) {
- includeApprovals = on;
- }
-
- public void setIncludeComments(boolean on) {
- includeComments = on;
- }
-
- public void setIncludeFiles(boolean on) {
- includeFiles = on;
- }
-
- public boolean getIncludeFiles() {
- return includeFiles;
- }
-
- public void setIncludeDependencies(boolean on) {
- includeDependencies = on;
- }
-
- public boolean getIncludeDependencies() {
- return includeDependencies;
- }
-
- public void setIncludeCommitMessage(boolean on) {
- includeCommitMessage = on;
- }
-
- public void setIncludeSubmitRecords(boolean on) {
- includeSubmitRecords = on;
- }
-
- public void setIncludeAllReviewers(boolean on) {
- includeAllReviewers = on;
- }
-
- public void setOutput(OutputStream out, OutputFormat fmt) {
- this.outputStream = out;
- this.outputFormat = fmt;
+ return this;
}
/**
- * Query for changes that match the query string.
- * <p>
- * If a limit was specified using {@link #setLimit(int)} this method may
- * return up to {@code limit + 1} results, allowing the caller to determine if
- * there are more than {@code limit} matches and suggest to its own caller
- * that the query could be retried with {@link #setSortkeyBefore(String)}.
+ * Query for changes that match a structured query.
+ *
+ * @see #queryChanges(List)
+ * @param query the query.
+ * @return results of the query.
*/
- public List<ChangeData> queryChanges(String queryString)
+ public QueryResult queryChanges(Predicate<ChangeData> query)
throws OrmException, QueryParseException {
- return queryChanges(ImmutableList.of(queryString)).get(0);
+ return queryChanges(ImmutableList.of(query)).get(0);
}
- /**
- * Query for changes that match the query string.
+ /*
+ * Perform multiple queries over a list of query strings.
* <p>
* If a limit was specified using {@link #setLimit(int)} this method may
* return up to {@code limit + 1} results, allowing the caller to determine if
* there are more than {@code limit} matches and suggest to its own caller
- * that the query could be retried with {@link #setSortkeyBefore(String)}.
+ * that the query could be retried with {@link #setStart(int)}.
+ *
+ * @param queries the queries.
+ * @return results of the queries, one list per input query.
*/
- public List<List<ChangeData>> queryChanges(List<String> queries)
+ public List<QueryResult> queryChanges(List<Predicate<ChangeData>> queries)
throws OrmException, QueryParseException {
- final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
+ return queryChanges(null, queries);
+ }
+
+ static {
+ // In addition to this assumption, this queryChanges assumes the basic
+ // rewrites do not touch visibleto predicates either.
+ checkState(
+ !IsVisibleToPredicate.class.isAssignableFrom(IndexPredicate.class),
+ "QueryProcessor assumes visibleto is not used by the index rewriter.");
+ }
+
+ private List<QueryResult> queryChanges(List<String> queryStrings,
+ List<Predicate<ChangeData>> queries)
+ throws OrmException, QueryParseException {
+ Predicate<ChangeData> visibleToMe = enforceVisibility
+ ? new IsVisibleToPredicate(db, changeControlFactory, userProvider.get())
+ : null;
int cnt = queries.size();
// Parse and rewrite all queries.
- List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
- List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
- for (String query : queries) {
- Predicate<ChangeData> q = parseQuery(query, visibleToMe);
- Predicate<ChangeData> s = queryRewriter.rewrite(q, start);
+ List<Integer> limits = new ArrayList<>(cnt);
+ List<Predicate<ChangeData>> predicates = new ArrayList<>(cnt);
+ List<ChangeDataSource> sources = new ArrayList<>(cnt);
+ for (Predicate<ChangeData> q : queries) {
+ int limit = getEffectiveLimit(q);
+ limits.add(limit);
+
+ // Always bump limit by 1, even if this results in exceeding the permitted
+ // max for this user. The only way to see if there are more changes is to
+ // ask for one more result from the query.
+ if (limit == getBackendSupportedLimit()) {
+ limit--;
+ }
+ Predicate<ChangeData> s = queryRewriter.rewrite(q, start, limit + 1);
if (!(s instanceof ChangeDataSource)) {
- q = Predicate.and(queryBuilder.status_open(), q);
- s = queryRewriter.rewrite(q, start);
+ q = Predicate.and(open(), q);
+ s = queryRewriter.rewrite(q, start, limit);
}
if (!(s instanceof ChangeDataSource)) {
throw new QueryParseException("invalid query: " + s);
}
-
- // Don't trust QueryRewriter to have left the visible predicate.
- AndSource a = new AndSource(ImmutableList.of(s, visibleToMe), start);
- limits.add(limit(q));
- sources.add(a);
+ if (enforceVisibility) {
+ s = new AndSource(ImmutableList.of(s, visibleToMe), start);
+ }
+ predicates.add(s);
+ sources.add((ChangeDataSource) s);
}
// Run each query asynchronously, if supported.
- List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
+ List<ResultSet<ChangeData>> matches = new ArrayList<>(cnt);
for (ChangeDataSource s : sources) {
matches.add(s.read());
}
- List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
+ List<QueryResult> out = new ArrayList<>(cnt);
for (int i = 0; i < cnt; i++) {
- List<ChangeData> results = matches.get(i).toList();
- if (sortkeyAfter != null) {
- Collections.sort(results, cmpAfter);
- } else if (sortkeyBefore != null) {
- Collections.sort(results, cmpBefore);
- }
- if (results.size() > maxLimit) {
- moreResults = true;
- }
- int limit = limits.get(i);
- if (limit < results.size()) {
- results = results.subList(0, limit);
- }
- if (sortkeyAfter != null) {
- Collections.reverse(results);
- }
- out.add(results);
+ out.add(QueryResult.create(
+ queryStrings != null ? queryStrings.get(i) : null,
+ predicates.get(i),
+ limits.get(i),
+ matches.get(i).toList()));
}
return out;
}
- public void query(String queryString) throws IOException {
- out = new PrintWriter( //
- new BufferedWriter( //
- new OutputStreamWriter(outputStream, "UTF-8")));
- try {
- if (isDisabled()) {
- ErrorMessage m = new ErrorMessage();
- m.message = "query disabled";
- show(m);
- return;
- }
-
- try {
- final QueryStatsAttribute stats = new QueryStatsAttribute();
- stats.runTimeMilliseconds = TimeUtil.nowMs();
-
- List<ChangeData> results = queryChanges(queryString);
- ChangeAttribute c = null;
- for (ChangeData d : results) {
- ChangeControl cc = d.changeControl().forUser(user);
-
- LabelTypes labelTypes = cc.getLabelTypes();
- c = eventFactory.asChangeAttribute(d.change());
- eventFactory.extend(c, d.change());
-
- if (!trackingFooters.isEmpty()) {
- eventFactory.addTrackingIds(c,
- trackingFooters.extract(d.commitFooters()));
- }
-
- if (includeAllReviewers) {
- eventFactory.addAllReviewers(c, d.notes());
- }
-
- if (includeSubmitRecords) {
- PatchSet.Id psId = d.change().currentPatchSetId();
- PatchSet patchSet = db.get().patchSets().get(psId);
- List<SubmitRecord> submitResult = cc.canSubmit( //
- db.get(), patchSet, null, false, true, true);
- eventFactory.addSubmitRecords(c, submitResult);
- }
-
- if (includeCommitMessage) {
- eventFactory.addCommitMessage(c, d.commitMessage());
- }
-
- if (includePatchSets) {
- if (includeFiles) {
- eventFactory.addPatchSets(c, d.patches(),
- includeApprovals ? d.approvals().asMap() : null,
- includeFiles, d.change(), labelTypes);
- } else {
- eventFactory.addPatchSets(c, d.patches(),
- includeApprovals ? d.approvals().asMap() : null,
- labelTypes);
- }
- }
-
- if (includeCurrentPatchSet) {
- PatchSet current = d.currentPatchSet();
- if (current != null) {
- c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
- eventFactory.addApprovals(c.currentPatchSet,
- d.currentApprovals(), labelTypes);
-
- if (includeFiles) {
- eventFactory.addPatchSetFileNames(c.currentPatchSet,
- d.change(), d.currentPatchSet());
- }
- }
- }
-
- if (includeComments) {
- eventFactory.addComments(c, d.messages());
- if (includePatchSets) {
- for (PatchSetAttribute attribute : c.patchSets) {
- eventFactory.addPatchSetComments(attribute, d.comments());
- }
- }
- }
-
- if (includeDependencies) {
- eventFactory.addDependencies(c, d.change());
- }
-
- show(c);
- }
-
- stats.rowCount = results.size();
- if (moreResults) {
- stats.resumeSortKey = c.sortKey;
- }
- stats.runTimeMilliseconds =
- TimeUtil.nowMs() - stats.runTimeMilliseconds;
- show(stats);
- } catch (OrmException err) {
- log.error("Cannot execute query: " + queryString, err);
-
- ErrorMessage m = new ErrorMessage();
- m.message = "cannot query database";
- show(m);
-
- } catch (QueryParseException e) {
- ErrorMessage m = new ErrorMessage();
- m.message = e.getMessage();
- show(m);
- } catch (NoSuchChangeException e) {
- log.error("Missing change: " + e.getMessage(), e);
- ErrorMessage m = new ErrorMessage();
- m.message = "missing change " + e.getMessage();
- show(m);
- }
- } finally {
- try {
- out.flush();
- } finally {
- out = null;
- }
- }
- }
-
boolean isDisabled() {
- return maxLimit <= 0;
- }
-
- private int limit(Predicate<ChangeData> s) {
- int n = Objects.firstNonNull(ChangeQueryBuilder.getLimit(s), maxLimit);
- return limit > 0 ? Math.min(n, limit) + 1 : n + 1;
- }
-
- private Predicate<ChangeData> parseQuery(String queryString,
- final Predicate<ChangeData> visibleToMe) throws QueryParseException {
- Predicate<ChangeData> q = queryBuilder.parse(queryString);
- if (queryBuilder.supportsSortKey() && !ChangeQueryBuilder.hasSortKey(q)) {
- if (sortkeyBefore != null) {
- q = Predicate.and(q, queryBuilder.sortkey_before(sortkeyBefore));
- } else if (sortkeyAfter != null) {
- q = Predicate.and(q, queryBuilder.sortkey_after(sortkeyAfter));
- } else {
- q = Predicate.and(q, queryBuilder.sortkey_before("z"));
- }
- }
- return Predicate.and(q,
- queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit),
- visibleToMe);
- }
-
- private void show(Object data) {
- switch (outputFormat) {
- default:
- case TEXT:
- if (data instanceof ChangeAttribute) {
- out.print("change ");
- out.print(((ChangeAttribute) data).id);
- out.print("\n");
- showText(data, 1);
- } else {
- showText(data, 0);
- }
- out.print('\n');
- break;
-
- case JSON:
- out.print(gson.toJson(data));
- out.print('\n');
- break;
- }
+ return getPermittedLimit() <= 0;
}
- private void showText(Object data, int depth) {
- for (Field f : fieldsOf(data.getClass())) {
- Object val;
- try {
- val = f.get(data);
- } catch (IllegalArgumentException err) {
- continue;
- } catch (IllegalAccessException err) {
- continue;
- }
- if (val == null) {
- continue;
- }
-
- showField(f.getName(), val, depth);
+ private int getPermittedLimit() {
+ if (enforceVisibility) {
+ return userProvider.get().getCapabilities()
+ .getRange(GlobalCapability.QUERY_LIMIT)
+ .getMax();
}
+ return Integer.MAX_VALUE;
}
- private String indent(int spaces) {
- if (spaces == 0) {
- return "";
- } else {
- return String.format("%" + spaces + "s", " ");
- }
+ private int getBackendSupportedLimit() {
+ return indexConfig.maxLimit();
}
- private void showField(String field, Object value, int depth) {
- final int spacesDepthRatio = 2;
- String indent = indent(depth * spacesDepthRatio);
- out.print(indent);
- out.print(field);
- out.print(':');
- if (value instanceof String && ((String) value).contains("\n")) {
- out.print(' ');
- // Idention for multi-line text is
- // current depth indetion + length of field + length of ": "
- indent = indent(indent.length() + field.length() + spacesDepthRatio);
- out.print(((String) value).replaceAll("\n", "\n" + indent).trim());
- out.print('\n');
- } else if (value instanceof Long && isDateField(field)) {
- out.print(' ');
- out.print(sdf.format(new Date(((Long) value) * 1000L)));
- out.print('\n');
- } else if (isPrimitive(value)) {
- out.print(' ');
- out.print(value);
- out.print('\n');
- } else if (value instanceof Collection) {
- out.print('\n');
- boolean firstElement = true;
- for (Object thing : ((Collection<?>) value)) {
- // The name of the collection was initially printed at the beginning
- // of this routine. Beginning at the second sub-element, reprint
- // the collection name so humans can separate individual elements
- // with less strain and error.
- //
- if (firstElement) {
- firstElement = false;
- } else {
- out.print(indent);
- out.print(field);
- out.print(":\n");
- }
- if (isPrimitive(thing)) {
- out.print(' ');
- out.print(value);
- out.print('\n');
- } else {
- showText(thing, depth + 1);
- }
- }
- } else {
- out.print('\n');
- showText(value, depth + 1);
+ private int getEffectiveLimit(Predicate<ChangeData> p) {
+ List<Integer> possibleLimits = new ArrayList<>(4);
+ possibleLimits.add(getBackendSupportedLimit());
+ possibleLimits.add(getPermittedLimit());
+ if (limitFromCaller > 0) {
+ possibleLimits.add(limitFromCaller);
}
- }
-
- private static boolean isPrimitive(Object value) {
- return value instanceof String //
- || value instanceof Number //
- || value instanceof Boolean //
- || value instanceof Enum;
- }
-
- private static boolean isDateField(String name) {
- return "lastUpdated".equals(name) //
- || "grantedOn".equals(name) //
- || "timestamp".equals(name) //
- || "createdOn".equals(name);
- }
-
- private List<Field> fieldsOf(Class<?> type) {
- List<Field> r = new ArrayList<>();
- if (type.getSuperclass() != null) {
- r.addAll(fieldsOf(type.getSuperclass()));
+ Integer limitFromPredicate = LimitPredicate.getLimit(p);
+ if (limitFromPredicate != null) {
+ possibleLimits.add(limitFromPredicate);
}
- r.addAll(Arrays.asList(type.getDeclaredFields()));
- return r;
- }
-
- static class ErrorMessage {
- public final String type = "error";
- public String message;
+ return Ordering.natural().min(possibleLimits);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryResult.java
new file mode 100644
index 0000000000..a93f7ac405
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryResult.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2014 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.auto.value.AutoValue;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.query.Predicate;
+
+import java.util.List;
+
+/** Results of a query over changes. */
+@AutoValue
+public abstract class QueryResult {
+ static QueryResult create(@Nullable String query,
+ Predicate<ChangeData> predicate, int limit, List<ChangeData> changes) {
+ boolean moreChanges;
+ if (changes.size() > limit) {
+ moreChanges = true;
+ changes = changes.subList(0, limit);
+ } else {
+ moreChanges = false;
+ }
+ return new AutoValue_QueryResult(query, predicate, changes, moreChanges);
+ }
+
+ /**
+ * @return the original query string, or null if the query was created
+ * programmatically.
+ */
+ @Nullable public abstract String query();
+
+ /**
+ * @return the predicate after all rewriting and other modification by the
+ * query subsystem.
+ */
+ public abstract Predicate<ChangeData> predicate();
+
+ /** @return the query results. */
+ public abstract List<ChangeData> changes();
+
+ /**
+ * @return whether the query could be retried with
+ * {@link QueryProcessor#setStart(int)} to produce more results. Never
+ * true if {@link #changes()} is empty.
+ */
+ public abstract boolean moreChanges();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index d073002cbc..049aa4048f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -16,76 +16,21 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gwtorm.server.OrmException;
-import dk.brics.automaton.Automaton;
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
-
-import java.util.Collections;
import java.util.List;
class RegexPathPredicate extends RegexPredicate<ChangeData> {
- private final RunAutomaton pattern;
-
- private final String prefixBegin;
- private final String prefixEnd;
- private final int prefixLen;
- private final boolean prefixOnly;
-
- RegexPathPredicate(String fieldName, String re) {
+ RegexPathPredicate(String re) {
super(ChangeField.PATH, re);
-
- if (re.startsWith("^")) {
- re = re.substring(1);
- }
-
- if (re.endsWith("$") && !re.endsWith("\\$")) {
- re = re.substring(0, re.length() - 1);
- }
-
- Automaton automaton = new RegExp(re).toAutomaton();
- prefixBegin = automaton.getCommonPrefix();
- prefixLen = prefixBegin.length();
-
- if (0 < prefixLen) {
- char max = (char) (prefixBegin.charAt(prefixLen - 1) + 1);
- prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
- prefixOnly = re.equals(prefixBegin + ".*");
- } else {
- prefixEnd = "";
- prefixOnly = false;
- }
-
- pattern = prefixOnly ? null : new RunAutomaton(automaton);
}
@Override
public boolean match(ChangeData object) throws OrmException {
List<String> files = object.currentFilePaths();
if (files != null) {
- int begin, end;
-
- if (0 < prefixLen) {
- begin = find(files, prefixBegin);
- end = find(files, prefixEnd);
- } else {
- begin = 0;
- end = files.size();
- }
-
- if (prefixOnly) {
- return begin < end;
- }
-
- while (begin < end) {
- if (pattern.run(files.get(begin++))) {
- return true;
- }
- }
-
- return false;
-
+ return RegexListSearcher.ofStrings(getValue()).hasMatch(files);
} else {
// The ChangeData can't do expensive lookups right now. Bypass
// them and include the result anyway. We might be able to do
@@ -95,11 +40,6 @@ class RegexPathPredicate extends RegexPredicate<ChangeData> {
}
}
- private static int find(List<String> files, String p) {
- int r = Collections.binarySearch(files, p);
- return r < 0 ? -(r + 1) : r;
- }
-
@Override
public int getCost() {
return 1;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index 7d5f1dc4a2..3a9604f771 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.index.Schema;
import com.google.gwtorm.server.OrmException;
import dk.brics.automaton.RegExp;
@@ -25,8 +25,8 @@ import dk.brics.automaton.RunAutomaton;
class RegexTopicPredicate extends RegexPredicate<ChangeData> {
private final RunAutomaton pattern;
- RegexTopicPredicate(Schema<ChangeData> schema, String re) {
- super(TopicPredicate.topicField(schema), re);
+ RegexTopicPredicate(String re) {
+ super(ChangeField.TOPIC, re);
if (re.startsWith("^")) {
re = re.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
index 23bbb12e34..0947fae24e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
@@ -102,18 +102,9 @@ public abstract class RevWalkPredicate extends OperatorPredicate<ChangeData> {
Arguments args = new Arguments(patchSet, revision, objectId, change, projectName);
- try {
- final Repository repo = repoManager.openRepository(projectName);
- try {
- final RevWalk rw = new RevWalk(repo);
- try {
- return match(repo, rw, args);
- } finally {
- rw.close();
- }
- } finally {
- repo.close();
- }
+ try (Repository repo = repoManager.openRepository(projectName);
+ RevWalk rw = new RevWalk(repo)) {
+ return match(repo, rw, args);
} catch (RepositoryNotFoundException e) {
log.error("Repository \"" + projectName.get() + "\" unknown.", e);
} catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
deleted file mode 100644
index 6fa11fd2b2..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (C) 2010 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 static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.server.index.ChangeField.SORTKEY;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.index.ChangeField;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.Schema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
-
-public abstract class SortKeyPredicate extends IndexPredicate<ChangeData> {
- public static boolean hasSortKeyField(Schema<ChangeData> schema) {
- return sortkeyFieldOrNull(schema) != null;
- }
-
- @SuppressWarnings("deprecation")
- private static long parseSortKey(Schema<ChangeData> schema, String value) {
- FieldDef<ChangeData, ?> field = schema.getFields().get(SORTKEY.getName());
- if (field == SORTKEY) {
- return ChangeUtil.parseSortKey(value);
- } else {
- return ChangeField.legacyParseSortKey(value);
- }
- }
-
- @SuppressWarnings("deprecation")
- private static FieldDef<ChangeData, ?> sortkeyFieldOrNull(
- Schema<ChangeData> schema) {
- if (schema == null) {
- return ChangeField.LEGACY_SORTKEY;
- }
- FieldDef<ChangeData, ?> f = schema.getFields().get(SORTKEY.getName());
- if (f != null) {
- return f;
- }
- return schema.getFields().get(ChangeField.LEGACY_SORTKEY.getName());
- }
-
- private static FieldDef<ChangeData, ?> sortkeyField(Schema<ChangeData> schema) {
- return checkNotNull(
- sortkeyFieldOrNull(schema),
- "schema missing sortkey field, found: %s", schema);
- }
-
- protected final Schema<ChangeData> schema;
- protected final Provider<ReviewDb> dbProvider;
-
- SortKeyPredicate(Schema<ChangeData> schema, Provider<ReviewDb> dbProvider,
- String name, String value) {
- super(sortkeyField(schema), name, value);
- this.schema = schema;
- this.dbProvider = dbProvider;
- }
-
- @Override
- public int getCost() {
- return 1;
- }
-
- public abstract long getMinValue(Schema<ChangeData> schema);
- public abstract long getMaxValue(Schema<ChangeData> schema);
- public abstract SortKeyPredicate copy(String newValue);
-
- public static class Before extends SortKeyPredicate {
- Before(@Nullable Schema<ChangeData> schema, Provider<ReviewDb> dbProvider,
- String value) {
- super(schema, dbProvider, "sortkey_before", value);
- }
-
- @Override
- public long getMinValue(Schema<ChangeData> schema) {
- return 0;
- }
-
- @Override
- public long getMaxValue(Schema<ChangeData> schema) {
- return parseSortKey(schema, getValue());
- }
-
- @Override
- public boolean match(ChangeData cd) throws OrmException {
- Change change = cd.change();
- return change != null && change.getSortKey().compareTo(getValue()) < 0;
- }
-
- @Override
- public Before copy(String newValue) {
- return new Before(schema, dbProvider, newValue);
- }
- }
-
- public static class After extends SortKeyPredicate {
- After(@Nullable Schema<ChangeData> schema, Provider<ReviewDb> dbProvider,
- String value) {
- super(schema, dbProvider, "sortkey_after", value);
- }
-
- @Override
- public long getMinValue(Schema<ChangeData> schema) {
- return parseSortKey(schema, getValue());
- }
-
- @Override
- public long getMaxValue(Schema<ChangeData> schema) {
- return Long.MAX_VALUE;
- }
-
- @Override
- public boolean match(ChangeData cd) throws OrmException {
- Change change = cd.change();
- return change != null && change.getSortKey().compareTo(getValue()) > 0;
- }
-
- @Override
- public After copy(String newValue) {
- return new After(schema, dbProvider, newValue);
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
index 7196c9f721..07a671460b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -18,26 +18,12 @@ import static com.google.gerrit.server.index.ChangeField.TOPIC;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.ChangeField;
-import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.Schema;
import com.google.gwtorm.server.OrmException;
class TopicPredicate extends IndexPredicate<ChangeData> {
- @SuppressWarnings("deprecation")
- static FieldDef<ChangeData, ?> topicField(Schema<ChangeData> schema) {
- if (schema == null) {
- return ChangeField.LEGACY_TOPIC;
- }
- FieldDef<ChangeData, ?> f = schema.getFields().get(TOPIC.getName());
- if (f != null) {
- return f;
- }
- return schema.getFields().get(ChangeField.LEGACY_TOPIC.getName());
- }
-
- TopicPredicate(Schema<ChangeData> schema, String topic) {
- super(topicField(schema), topic);
+ TopicPredicate(String topic) {
+ super(ChangeField.TOPIC, topic);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
index 9e60a4225f..4f2a8d798d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
@@ -18,13 +18,14 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
+import org.eclipse.jgit.revwalk.FooterLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.List;
class TrackingIdPredicate extends IndexPredicate<ChangeData> {
private static final Logger log = LoggerFactory.getLogger(TrackingIdPredicate.class);
@@ -41,9 +42,10 @@ class TrackingIdPredicate extends IndexPredicate<ChangeData> {
Change c = object.change();
if (c != null) {
try {
- return trackingFooters.extract(object.commitFooters())
- .values().contains(getValue());
- } catch (NoSuchChangeException | IOException e) {
+ List<FooterLine> footers = object.commitFooters();
+ return footers != null && trackingFooters.extract(
+ object.commitFooters()).values().contains(getValue());
+ } catch (IOException e) {
log.warn("Cannot extract footers from " + c.getChangeId(), e);
}
}
@@ -52,8 +54,6 @@ class TrackingIdPredicate extends IndexPredicate<ChangeData> {
@Override
public int getCost() {
- return ChangeCosts.cost(
- ChangeCosts.TR_SCAN,
- ChangeCosts.CARD_TRACKING_IDS);
+ return 1;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index df49a01a05..1eefbf97b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -18,6 +18,8 @@ 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 com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.data.AccessSection;
@@ -28,7 +30,7 @@ import com.google.gerrit.common.data.LabelValue;
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.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
@@ -54,6 +56,7 @@ public class AllProjectsCreator {
private final GitRepositoryManager mgr;
private final AllProjectsName allProjectsName;
private final PersonIdent serverUser;
+ private String message;
private GroupReference admin;
private GroupReference batch;
@@ -85,6 +88,11 @@ public class AllProjectsCreator {
return this;
}
+ public AllProjectsCreator setCommitMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
public void create() throws IOException, ConfigInvalidException {
Repository git = null;
try {
@@ -118,7 +126,9 @@ public class AllProjectsCreator {
git);
md.getCommitBuilder().setAuthor(serverUser);
md.getCommitBuilder().setCommitter(serverUser);
- md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
+ md.setMessage(MoreObjects.firstNonNull(
+ Strings.emptyToNull(message),
+ "Initialized Gerrit Code Review " + Version.getVersion()));
ProjectConfig config = ProjectConfig.read(md);
Project p = config.getProject();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
index 49ac45297d..30ebaa72fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
@@ -23,7 +23,6 @@ import com.google.gerrit.extensions.persistence.DataSourceInterceptor;
import com.google.gerrit.server.config.ConfigSection;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.jdbc.SimpleDataSource;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -46,18 +45,15 @@ public class DataSourceProvider implements Provider<DataSource>,
LifecycleListener {
public static final int DEFAULT_POOL_LIMIT = 8;
- private final SitePaths site;
private final Config cfg;
private final Context ctx;
private final DataSourceType dst;
private DataSource ds;
@Inject
- protected DataSourceProvider(SitePaths site,
- @GerritServerConfig Config cfg,
+ protected DataSourceProvider(@GerritServerConfig Config cfg,
Context ctx,
DataSourceType dst) {
- this.site = site;
this.cfg = cfg;
this.ctx = ctx;
this.dst = dst;
@@ -66,7 +62,7 @@ public class DataSourceProvider implements Provider<DataSource>,
@Override
public synchronized DataSource get() {
if (ds == null) {
- ds = open(site, cfg, ctx, dst);
+ ds = open(cfg, ctx, dst);
}
return ds;
}
@@ -90,8 +86,8 @@ public class DataSourceProvider implements Provider<DataSource>,
SINGLE_USER, MULTI_USER
}
- private DataSource open(final SitePaths site, final Config cfg,
- final Context context, final DataSourceType dst) {
+ private DataSource open(final Config cfg, final Context context,
+ final DataSourceType dst) {
ConfigSection dbs = new ConfigSection(cfg, "database");
String driver = dbs.optional("driver");
if (Strings.isNullOrEmpty(driver)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 298c0d8e4d..daf1d4d679 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -46,23 +46,19 @@ public class SchemaCreator {
private final PersonIdent serverUser;
private final DataSourceType dataSourceType;
- private final int versionNbr;
-
private AccountGroup admin;
private AccountGroup batch;
@Inject
public SchemaCreator(SitePaths site,
- @Current SchemaVersion version,
AllProjectsCreator ap,
AllUsersCreator auc,
@GerritPersonIdent PersonIdent au,
DataSourceType dst) {
- this(site.site_path, version, ap, auc, au, dst);
+ this(site.site_path, ap, auc, au, dst);
}
public SchemaCreator(@SitePath File site,
- @Current SchemaVersion version,
AllProjectsCreator ap,
AllUsersCreator auc,
@GerritPersonIdent PersonIdent au,
@@ -72,21 +68,17 @@ public class SchemaCreator {
allUsersCreator = auc;
serverUser = au;
dataSourceType = dst;
- versionNbr = version.getVersionNbr();
}
public void create(final ReviewDb db) throws OrmException, IOException,
ConfigInvalidException {
final JdbcSchema jdbc = (JdbcSchema) db;
- final JdbcExecutor e = new JdbcExecutor(jdbc);
- try {
+ try (JdbcExecutor e = new JdbcExecutor(jdbc)) {
jdbc.updateSchema(e);
- } finally {
- e.close();
}
final CurrentSchemaVersion sVer = CurrentSchemaVersion.create();
- sVer.versionNbr = versionNbr;
+ sVer.versionNbr = SchemaVersion.getBinaryVersion();
db.schemaVersion().insert(Collections.singleton(sVer));
initSystemConfig(db);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
index aaf46070b8..6faf148a3c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
@@ -32,8 +32,6 @@ import org.eclipse.jgit.lib.PersonIdent;
public class SchemaModule extends FactoryModule {
@Override
protected void configure() {
- install(new SchemaVersion.Module());
-
bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
.toProvider(GerritPersonIdentProvider.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java
index 0cea4bc9df..2b9d4b4792 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java
@@ -17,13 +17,23 @@ package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.client.SystemConfig;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
import com.google.inject.Provider;
+import com.google.inject.Stage;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.PersonIdent;
import java.io.IOException;
import java.sql.SQLException;
@@ -37,12 +47,46 @@ public class SchemaUpdater {
private final Provider<SchemaVersion> updater;
@Inject
- SchemaUpdater(final SchemaFactory<ReviewDb> schema, final SitePaths site,
- final SchemaCreator creator, @Current final Provider<SchemaVersion> update) {
+ SchemaUpdater(SchemaFactory<ReviewDb> schema,
+ SitePaths site,
+ SchemaCreator creator,
+ Injector parent) {
this.schema = schema;
this.site = site;
this.creator = creator;
- this.updater = update;
+ this.updater = buildInjector(parent).getProvider(SchemaVersion.class);
+ }
+
+ private static Injector buildInjector(final Injector parent) {
+ // Use DEVELOPMENT mode to allow lazy initialization of the
+ // graph. This avoids touching ancient schema versions that
+ // are behind this installation's current version.
+ return Guice.createInjector(Stage.DEVELOPMENT, new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(SchemaVersion.class).to(SchemaVersion.C);
+
+ for (Key<?> k : new Key<?>[]{
+ Key.get(PersonIdent.class, GerritPersonIdent.class),
+ Key.get(String.class, AnonymousCowardName.class),
+ }) {
+ rebind(parent, k);
+ }
+
+ for (Class<?> c : new Class<?>[] {
+ AllProjectsName.class,
+ AllUsersCreator.class,
+ GitRepositoryManager.class,
+ SitePaths.class,
+ }) {
+ rebind(parent, Key.get(c));
+ }
+ }
+
+ private <T> void rebind(Injector parent, Key<T> c) {
+ bind(c).toProvider(parent.getProvider(c));
+ }
+ });
}
public void update(final UpdateUI ui) throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 11479ccf4b..945baa81e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -21,9 +21,9 @@ import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.AbstractModule;
import com.google.inject.Provider;
+import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
@@ -32,13 +32,10 @@ import java.util.List;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_98> C = Schema_98.class;
+ public static final Class<Schema_107> C = Schema_107.class;
- public static class Module extends AbstractModule {
- @Override
- protected void configure() {
- bind(SchemaVersion.class).annotatedWith(Current.class).to(C);
- }
+ public static int getBinaryVersion() {
+ return guessVersion(C);
}
private final Provider<? extends SchemaVersion> prior;
@@ -57,12 +54,6 @@ public abstract class SchemaVersion {
return Integer.parseInt(n);
}
- protected SchemaVersion(final Provider<? extends SchemaVersion> prior,
- final int versionNbr) {
- this.prior = prior;
- this.versionNbr = versionNbr;
- }
-
/** @return the {@link CurrentSchemaVersion#versionNbr} this step targets. */
public final int getVersionNbr() {
return versionNbr;
@@ -88,20 +79,23 @@ public abstract class SchemaVersion {
migrateData(pending, ui, curr, db);
JdbcSchema s = (JdbcSchema) db;
- JdbcExecutor e = new JdbcExecutor(s);
- try {
- final List<String> pruneList = Lists.newArrayList();
- s.pruneSchema(new StatementExecutor() {
- public void execute(String sql) {
- pruneList.add(sql);
- }
- });
+ final List<String> pruneList = Lists.newArrayList();
+ s.pruneSchema(new StatementExecutor() {
+ @Override
+ public void execute(String sql) {
+ pruneList.add(sql);
+ }
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ });
+
+ try (JdbcExecutor e = new JdbcExecutor(s)) {
if (!pruneList.isEmpty()) {
ui.pruneSchema(e, pruneList);
}
- } finally {
- e.close();
}
}
@@ -122,15 +116,18 @@ public abstract class SchemaVersion {
}
JdbcSchema s = (JdbcSchema) db;
- JdbcExecutor e = new JdbcExecutor(s);
- try {
+ try (JdbcExecutor e = new JdbcExecutor(s)) {
s.updateSchema(e);
- } finally {
- e.close();
}
}
- /** Invoke before updateSchema adds new columns/tables. */
+ /**
+ * Invoked before updateSchema adds new columns/tables.
+ *
+ * @param db open database handle.
+ * @throws OrmException if a Gerrit-specific exception occurred.
+ * @throws SQLException if an underlying SQL exception occurred.
+ */
protected void preUpdateSchema(ReviewDb db) throws OrmException, SQLException {
}
@@ -148,6 +145,11 @@ public abstract class SchemaVersion {
/**
* Invoked between updateSchema (adds new columns/tables) and pruneSchema
* (removes deleted columns/tables).
+ *
+ * @param db open database handle.
+ * @param ui interface for interacting with the user.
+ * @throws OrmException if a Gerrit-specific exception occurred.
+ * @throws SQLException if an underlying SQL exception occurred.
*/
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
}
@@ -160,36 +162,43 @@ public abstract class SchemaVersion {
}
/** Rename an existing table. */
- protected void renameTable(ReviewDb db, String from, String to)
+ protected static void renameTable(ReviewDb db, String from, String to)
throws OrmException {
- final JdbcSchema s = (JdbcSchema) db;
- final JdbcExecutor e = new JdbcExecutor(s);
- try {
+ JdbcSchema s = (JdbcSchema) db;
+ try (JdbcExecutor e = new JdbcExecutor(s)) {
s.renameTable(e, from, to);
- } finally {
- e.close();
}
}
/** Rename an existing column. */
- protected void renameColumn(ReviewDb db, String table, String from, String to)
+ protected static void renameColumn(ReviewDb db, String table, String from, String to)
throws OrmException {
- final JdbcSchema s = (JdbcSchema) db;
- final JdbcExecutor e = new JdbcExecutor(s);
- try {
+ JdbcSchema s = (JdbcSchema) db;
+ try (JdbcExecutor e = new JdbcExecutor(s)) {
s.renameField(e, table, from, to);
- } finally {
- e.close();
}
}
/** Execute an SQL statement. */
- protected void execute(ReviewDb db, String sql) throws SQLException {
- Statement s = ((JdbcSchema) db).getConnection().createStatement();
- try {
+ protected static void execute(ReviewDb db, String sql) throws SQLException {
+ try (Statement s = newStatement(db)) {
s.execute(sql);
- } finally {
- s.close();
}
}
+
+ /** Open a new single statement. */
+ protected static Statement newStatement(ReviewDb db) throws SQLException {
+ return ((JdbcSchema) db).getConnection().createStatement();
+ }
+
+ /** Open a new prepared statement. */
+ protected static PreparedStatement prepareStatement(ReviewDb db, String sql)
+ throws SQLException {
+ return ((JdbcSchema) db).getConnection().prepareStatement(sql);
+ }
+
+ /** Open a new statement executor. */
+ protected static JdbcExecutor newExecutor(ReviewDb db) throws OrmException {
+ return new JdbcExecutor(((JdbcSchema) db).getConnection());
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
index 2e12f5c62a..591601d6e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
@@ -23,7 +23,6 @@ import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
-import com.google.inject.Provider;
import com.google.inject.ProvisionException;
/** Validates the current schema version. */
@@ -40,24 +39,20 @@ public class SchemaVersionCheck implements LifecycleListener {
private final SchemaFactory<ReviewDb> schema;
private final SitePaths site;
- @Current
- private final Provider<SchemaVersion> version;
-
@Inject
public SchemaVersionCheck(SchemaFactory<ReviewDb> schemaFactory,
- final SitePaths site,
- @Current Provider<SchemaVersion> version) {
+ SitePaths site) {
this.schema = schemaFactory;
this.site = site;
- this.version = version;
}
+ @Override
public void start() {
try {
final ReviewDb db = schema.open();
try {
final CurrentSchemaVersion currentVer = getSchemaVersion(db);
- final int expectedVer = version.get().getVersionNbr();
+ final int expectedVer = SchemaVersion.getBinaryVersion();
if (currentVer == null) {
throw new ProvisionException("Schema not yet initialized."
@@ -84,6 +79,7 @@ public class SchemaVersionCheck implements LifecycleListener {
}
}
+ @Override
public void stop() {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_100.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_100.java
new file mode 100644
index 0000000000..090219498e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_100.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2014 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 com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_100 extends SchemaVersion {
+ @Inject
+ Schema_100(Provider<Schema_99> prior) {
+ super(prior);
+ }
+
+ // No database migration; merges are rechecked on reindex.
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpdatePrimaryKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_101.java
index 79bd73072a..4ef0d96864 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpdatePrimaryKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_101.java
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.pgm.init;
+package com.google.gerrit.server.schema;
import com.google.common.base.Joiner;
-import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -25,9 +24,9 @@ import com.google.gwtorm.schema.java.JavaSchemaModel;
import com.google.gwtorm.schema.sql.DialectPostgreSQL;
import com.google.gwtorm.schema.sql.SqlDialect;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.gwtorm.server.StatementExecutor;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -40,87 +39,60 @@ import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
-public class UpdatePrimaryKeys implements InitStep {
+public class Schema_101 extends SchemaVersion {
private static class PrimaryKey {
String oldNameInDb;
List<String> cols;
}
- private final ConsoleUI ui;
-
- private SchemaFactory<ReviewDb> dbFactory;
- private ReviewDb db;
private Connection conn;
private SqlDialect dialect;
@Inject
- UpdatePrimaryKeys(ConsoleUI ui) {
- this.ui = ui;
- }
-
- @Override
- public void run() throws Exception {
+ Schema_101(Provider<Schema_100> prior) {
+ super(prior);
}
@Override
- public void postRun() throws Exception {
- db = dbFactory.open();
- try {
- conn = ((JdbcSchema) db).getConnection();
- dialect = ((JdbcSchema) db).getDialect();
- Map<String, PrimaryKey> corrections = findPKUpdates();
- if (corrections.isEmpty()) {
- return;
- }
+ protected void migrateData(ReviewDb db, UpdateUI ui)
+ throws OrmException, SQLException {
+ conn = ((JdbcSchema) db).getConnection();
+ dialect = ((JdbcSchema) db).getDialect();
+ Map<String, PrimaryKey> corrections = findPKUpdates();
+ if (corrections.isEmpty()) {
+ return;
+ }
- ui.header("Wrong Primary Key Column Order Detected");
- ui.message("The following tables are affected:\n");
- ui.message("%s\n", Joiner.on(", ").join(corrections.keySet()));
- if (ui.yesno(true, "Fix primary keys column order")) {
- ui.message("fixing primary keys...\n");
- JdbcExecutor executor = new JdbcExecutor(conn);
- try {
- for (Map.Entry<String, PrimaryKey> c : corrections.entrySet()) {
- ui.message(" table: %s ... ", c.getKey());
- recreatePK(executor, c.getKey(), c.getValue());
- ui.message("done\n");
- }
- ui.message("done\n");
- } finally {
- executor.close();
- }
+ ui.message("Wrong Primary Key Column Order Detected");
+ ui.message("The following tables are affected:");
+ ui.message(Joiner.on(", ").join(corrections.keySet()));
+ ui.message("fixing primary keys...");
+ try (JdbcExecutor executor = new JdbcExecutor(conn)) {
+ for (Map.Entry<String, PrimaryKey> c : corrections.entrySet()) {
+ ui.message(String.format(" table: %s ... ", c.getKey()));
+ recreatePK(executor, c.getKey(), c.getValue(), ui);
+ ui.message("done");
}
- } finally {
- db.close();
+ ui.message("done");
}
}
- @Inject(optional = true)
- void setSchemaFactory(SchemaFactory<ReviewDb> dbFactory) {
- this.dbFactory = dbFactory;
- }
-
private Map<String, PrimaryKey> findPKUpdates()
throws OrmException, SQLException {
Map<String, PrimaryKey> corrections = new TreeMap<>();
- ReviewDb db = dbFactory.open();
- try {
- DatabaseMetaData meta = conn.getMetaData();
- JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
- for (RelationModel rm : jsm.getRelations()) {
- String tableName = rm.getRelationName();
- List<String> expectedPKCols = relationPK(rm);
- PrimaryKey actualPK = dbTablePK(meta, tableName);
- if (!expectedPKCols.equals(actualPK.cols)) {
- actualPK.cols = expectedPKCols;
- corrections.put(tableName, actualPK);
- }
+ DatabaseMetaData meta = conn.getMetaData();
+ JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
+ for (RelationModel rm : jsm.getRelations()) {
+ String tableName = rm.getRelationName();
+ List<String> expectedPKCols = relationPK(rm);
+ PrimaryKey actualPK = dbTablePK(meta, tableName);
+ if (!expectedPKCols.equals(actualPK.cols)) {
+ actualPK.cols = expectedPKCols;
+ corrections.put(tableName, actualPK);
}
- return corrections;
- } finally {
- db.close();
}
+ return corrections;
}
private List<String> relationPK(RelationModel rm) {
@@ -140,8 +112,7 @@ public class UpdatePrimaryKeys implements InitStep {
tableName = tableName.toLowerCase();
}
- ResultSet cols = meta.getPrimaryKeys(null, null, tableName);
- try {
+ try (ResultSet cols = meta.getPrimaryKeys(null, null, tableName)) {
PrimaryKey pk = new PrimaryKey();
Map<Short, String> seqToName = new TreeMap<>();
while (cols.next()) {
@@ -156,15 +127,14 @@ public class UpdatePrimaryKeys implements InitStep {
pk.cols.add(name.toLowerCase(Locale.US));
}
return pk;
- } finally {
- cols.close();
}
}
private void recreatePK(StatementExecutor executor, String tableName,
- PrimaryKey pk) throws OrmException {
+ PrimaryKey pk, UpdateUI ui) throws OrmException {
if (pk.oldNameInDb == null) {
- ui.message("WARN: primary key for table %s didn't exist ... ", tableName);
+ ui.message(String.format(
+ "warning: primary key for table %s didn't exist ... ", tableName));
} else {
if (dialect instanceof DialectPostgreSQL) {
// postgresql doesn't support the ALTER TABLE foo DROP PRIMARY KEY form
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java
new file mode 100644
index 0000000000..bcb3e1aa1d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2014 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 com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.gwtorm.schema.sql.SqlDialect;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class Schema_102 extends SchemaVersion {
+ @Inject
+ Schema_102(Provider<Schema_101> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui)
+ throws OrmException, SQLException {
+ JdbcSchema schema = (JdbcSchema) db;
+ SqlDialect dialect = schema.getDialect();
+ try (StatementExecutor e = newExecutor(db)) {
+ // Drop left over indexes that were missed to be removed in schema 84.
+ // See "Delete SQL index support" commit for more details:
+ // d4ae3a16d5e1464574bd04f429a63eb9c02b3b43
+ Pattern pattern =
+ Pattern.compile("^changes_(allOpen|allClosed|byBranchClosed)$",
+ Pattern.CASE_INSENSITIVE);
+ String table = "changes";
+ Set<String> listIndexes = dialect.listIndexes(
+ schema.getConnection(), table);
+ for (String index : listIndexes) {
+ if (pattern.matcher(index).matches()) {
+ dialect.dropIndex(e, table, index);
+ }
+ }
+
+ dialect.dropIndex(e, table, "changes_byProjectOpen");
+ if (dialect instanceof DialectPostgreSQL) {
+ e.execute("CREATE INDEX changes_byProjectOpen"
+ + " ON " + table + " (dest_project_name, last_updated_on)"
+ + " WHERE open = 'Y'");
+ } else {
+ e.execute("CREATE INDEX changes_byProjectOpen"
+ + " ON " + table + " (open, dest_project_name, last_updated_on)");
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
new file mode 100644
index 0000000000..60a5213cff
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2014 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 com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_103 extends SchemaVersion {
+ @Inject
+ Schema_103(Provider<Schema_102> prior) {
+ super(prior);
+ }
+
+ // Adds originalSubject column
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_104.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_104.java
new file mode 100644
index 0000000000..bebdacaa65
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_104.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2015 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 com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_104 extends SchemaVersion {
+ @Inject
+ Schema_104(Provider<Schema_103> prior) {
+ super(prior);
+ }
+
+ // Remove old change screen
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_105.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_105.java
new file mode 100644
index 0000000000..74f0cf5a25
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_105.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2015 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 com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.SqlDialect;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Schema_105 extends SchemaVersion {
+ private static final String TABLE = "changes";
+
+ @Inject
+ Schema_105(Provider<Schema_104> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui)
+ throws SQLException, OrmException {
+ JdbcSchema schema = (JdbcSchema) db;
+ SqlDialect dialect = schema.getDialect();
+
+ Map<String, OrmException> errors = new HashMap<>();
+ try (StatementExecutor e = newExecutor(db)) {
+ for (String index : listChangesIndexes(schema)) {
+ ui.message("Dropping index " + index + " on table " + TABLE);
+ try {
+ dialect.dropIndex(e, TABLE, index);
+ } catch (OrmException err) {
+ errors.put(index, err);
+ }
+ }
+ }
+
+ for (String index : listChangesIndexes(schema)) {
+ String msg = "Failed to drop index " + index;
+ OrmException err = errors.get(index);
+ if (err != null) {
+ msg += ": " + err.getMessage();
+ }
+ ui.message(msg);
+ }
+ }
+
+ private Set<String> listChangesIndexes(JdbcSchema schema)
+ throws SQLException {
+ // List of all changes indexes ever created or dropped, found with the
+ // following command:
+ // find g* -name \*.sql | xargs git log -i -p -S' index changes_' | grep -io ' index changes_\w*' | cut -d' ' -f3 | tr A-Z a-z | sort -u
+ // Used rather than listIndexes as we're not sure whether it might include
+ // primary key indexes.
+ Set<String> allChanges = ImmutableSet.of(
+ "changes_allclosed",
+ "changes_allopen",
+ "changes_bybranchclosed",
+ "changes_byownerclosed",
+ "changes_byowneropen",
+ "changes_byproject",
+ "changes_byprojectopen",
+ "changes_key",
+ "changes_submitted");
+ return Sets.intersection(
+ schema.getDialect().listIndexes(schema.getConnection(), TABLE),
+ allChanges);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_106.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_106.java
new file mode 100644
index 0000000000..ef7e291b8d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_106.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2015 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 java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.SortedSet;
+
+public class Schema_106 extends SchemaVersion {
+ private final GitRepositoryManager repoManager;
+ private final PersonIdent serverUser;
+
+ @Inject
+ Schema_106(Provider<Schema_105> prior,
+ GitRepositoryManager repoManager,
+ @GerritPersonIdent PersonIdent serverUser) {
+ super(prior);
+ this.repoManager = repoManager;
+ this.serverUser = serverUser;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
+ if (!(repoManager instanceof LocalDiskRepositoryManager)) {
+ return;
+ }
+
+ ui.message("listing all repositories ...");
+ SortedSet<Project.NameKey> repoList = repoManager.list();
+ ui.message("done");
+
+ ui.message(String.format("creating reflog files for %s branches ...",
+ RefNames.REFS_CONFIG));
+ for (Project.NameKey project : repoList) {
+ try {
+ Repository repo = repoManager.openRepository(project);
+ try {
+ File metaConfigLog =
+ new File(repo.getDirectory(), "logs/" + RefNames.REFS_CONFIG);
+ if (metaConfigLog.exists()) {
+ continue;
+ }
+
+ if (!metaConfigLog.getParentFile().mkdirs()
+ || !metaConfigLog.createNewFile()) {
+ throw new IOException(String.format(
+ "Failed to create reflog for %s in repository %s",
+ RefNames.REFS_CONFIG, project));
+ }
+
+ ObjectId metaConfigId = repo.resolve(RefNames.REFS_CONFIG);
+ if (metaConfigId != null) {
+ try (PrintWriter writer =
+ new PrintWriter(metaConfigLog, UTF_8.name())) {
+ writer.print(ObjectId.zeroId().name());
+ writer.print(" ");
+ writer.print(metaConfigId.name());
+ writer.print(" ");
+ writer.print(serverUser.toExternalString());
+ writer.print("\t");
+ writer.print("create reflog");
+ writer.println();
+ }
+ }
+ } finally {
+ repo.close();
+ }
+ } catch (IOException e) {
+ ui.message(String.format("ERROR: Failed to create reflog file for the"
+ + " %s branch in repository %s", RefNames.REFS_CONFIG, project.get()));
+ }
+ }
+ ui.message("done");
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java
new file mode 100644
index 0000000000..13ab09aa40
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2015 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 com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_107 extends SchemaVersion {
+
+ @Inject
+ Schema_107(Provider<Schema_106> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ stmt.executeUpdate("UPDATE accounts set mute_common_path_prefixes = 'Y'");
+ } finally {
+ stmt.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
index 451f5edc77..8f4028dbe7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
@@ -17,7 +17,6 @@ package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -51,18 +50,14 @@ public class Schema_87 extends SchemaVersion {
private Set<AccountGroup.Id> scanSystemGroups(ReviewDb db)
throws SQLException {
- JdbcSchema s = (JdbcSchema) db;
- Statement stmt = s.getConnection().createStatement();
- try {
- ResultSet rs =
- stmt.executeQuery("SELECT group_id FROM account_groups WHERE group_type = 'SYSTEM'");
+ try (Statement stmt = newStatement(db);
+ ResultSet rs = stmt.executeQuery(
+ "SELECT group_id FROM account_groups WHERE group_type = 'SYSTEM'")) {
Set<AccountGroup.Id> ids = new HashSet<>();
while (rs.next()) {
ids.add(new AccountGroup.Id(rs.getInt(1)));
}
return ids;
- } finally {
- stmt.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java
index 34f6b606c9..a818e0dce9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java
@@ -14,17 +14,15 @@
package com.google.gerrit.server.schema;
-import com.google.common.collect.ImmutableList;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectMySQL;
import com.google.gwtorm.schema.sql.SqlDialect;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StatementExecutor;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.sql.SQLException;
-import java.sql.Statement;
public class Schema_89 extends SchemaVersion {
@Inject
@@ -36,19 +34,11 @@ public class Schema_89 extends SchemaVersion {
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
SQLException {
SqlDialect dialect = ((JdbcSchema) db).getDialect();
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- for (String name : ImmutableList.of(
- "patch_set_approvals_openByUser",
- "patch_set_approvals_closedByU")) {
- if (dialect instanceof DialectMySQL) {
- stmt.executeUpdate("DROP INDEX " + name + " ON patch_set_approvals");
- } else {
- stmt.executeUpdate("DROP INDEX " + name);
- }
- }
- } finally {
- stmt.close();
+ try (StatementExecutor e = newExecutor(db)) {
+ dialect.dropIndex(e, "patch_set_approvals",
+ "patch_set_approvals_openByUser");
+ dialect.dropIndex(e, "patch_set_approvals",
+ "patch_set_approvals_closedByU");
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java
index c117509e6e..8f1fc5d4f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -30,11 +29,8 @@ public class Schema_90 extends SchemaVersion {
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
+ try (Statement stmt = newStatement(db)) {
stmt.executeUpdate("UPDATE accounts set size_bar_in_change_table = 'Y'");
- } finally {
- stmt.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java
index 3d45274265..02f78ca716 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -30,7 +29,7 @@ public class Schema_94 extends SchemaVersion {
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement()) {
+ try (Statement stmt = newStatement(db)) {
stmt.execute("CREATE INDEX patch_sets_byRevision"
+ " ON patch_sets (revision)");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_98.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_98.java
index aea5abc0dc..752dcd8dcc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_98.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_98.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -32,13 +31,10 @@ public class Schema_98 extends SchemaVersion {
protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
ui.message("Migrate user preference showUserInReview to "
+ "reviewCategoryStrategy");
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
+ try (Statement stmt = newStatement(db)) {
stmt.executeUpdate("UPDATE accounts SET "
+ "REVIEW_CATEGORY_STRATEGY='NAME' "
+ "WHERE (SHOW_USER_IN_REVIEW='Y')");
- } finally {
- stmt.close();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_99.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_99.java
new file mode 100644
index 0000000000..b7fab7f659
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_99.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 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 com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_99 extends SchemaVersion {
+ @Inject
+ Schema_99(Provider<Schema_98> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
index d03cb3e776..2347f87594 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
@@ -36,6 +36,7 @@ class ScriptRunner {
private final List<String> commands;
static final ScriptRunner NOOP = new ScriptRunner(null, null) {
+ @Override
void run(final ReviewDb db) {
}
};
@@ -51,11 +52,10 @@ class ScriptRunner {
void run(final ReviewDb db) throws OrmException {
try {
- final JdbcSchema schema = (JdbcSchema)db;
+ final JdbcSchema schema = (JdbcSchema) db;
final Connection c = schema.getConnection();
final SqlDialect dialect = schema.getDialect();
- final Statement stmt = c.createStatement();
- try {
+ try (Statement stmt = c.createStatement()) {
for (String sql : commands) {
try {
if (!dialect.isStatementDelimiterSupported()) {
@@ -66,8 +66,6 @@ class ScriptRunner {
throw new OrmException("Error in " + name + ":\n" + sql, e);
}
}
- } finally {
- stmt.close();
}
} catch (SQLException e) {
throw new OrmException("Cannot run statements for " + name, e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
index e0e8237538..b852217555 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.securestore;
import com.google.gerrit.common.FileUtil;
-import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -27,12 +26,11 @@ import org.eclipse.jgit.util.FS;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
@Singleton
-@Export(DefaultSecureStore.NAME)
-public class DefaultSecureStore implements SecureStore {
- public static final String NAME = "default";
-
+public class DefaultSecureStore extends SecureStore {
private final FileBasedConfig sec;
@Inject
@@ -47,14 +45,15 @@ public class DefaultSecureStore implements SecureStore {
}
@Override
- public String get(String section, String subsection, String name) {
- return sec.getString(section, subsection, name);
+ public String[] getList(String section, String subsection, String name) {
+ return sec.getStringList(section, subsection, name);
}
@Override
- public void set(String section, String subsection, String name, String value) {
- if (value != null) {
- sec.setString(section, subsection, name, value);
+ public void setList(String section, String subsection, String name,
+ List<String> values) {
+ if (values != null) {
+ sec.setStringList(section, subsection, name, values);
} else {
sec.unset(section, subsection, name);
}
@@ -67,6 +66,22 @@ public class DefaultSecureStore implements SecureStore {
save();
}
+ @Override
+ public Iterable<EntryKey> list() {
+ List<EntryKey> result = new ArrayList<>();
+ for (String section : sec.getSections()) {
+ for (String subsection : sec.getSubsections(section)) {
+ for (String name : sec.getNames(section, subsection)) {
+ result.add(new EntryKey(section, subsection, name));
+ }
+ }
+ for (String name : sec.getNames(section)) {
+ result.add(new EntryKey(section, null, name));
+ }
+ }
+ return result;
+ }
+
private void save() {
try {
saveSecure(sec);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java
index 3fe00f4b55..2a0086ec87 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java
@@ -14,14 +14,115 @@
package com.google.gerrit.server.securestore;
-import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.common.collect.Lists;
-@ExtensionPoint
-public interface SecureStore {
+import java.util.List;
- String get(String section, String subsection, String name);
+/**
+ * Abstract class for providing new SecureStore implementation for Gerrit.
+ *
+ * SecureStore is responsible for storing sensitive data like passwords in a
+ * secure manner.
+ *
+ * It is implementator's responsibility to encrypt and store values.
+ *
+ * To deploy new SecureStore one needs to provide a jar file with explicitly one
+ * class that extends {@code SecureStore} and put it in Gerrit server. Then run:
+ *
+ * `java -jar gerrit.war SwitchSecureStore -d $gerrit_site --new-secure-store-lib
+ * $path_to_new_secure_store.jar`
+ *
+ * on stopped Gerrit instance.
+ */
+public abstract class SecureStore {
+ /**
+ * Describes {@link SecureStore} entry
+ */
+ public static class EntryKey {
+ public final String name;
+ public final String section;
+ public final String subsection;
- void set(String section, String subsection, String name, String value);
+ /**
+ * Creates EntryKey.
+ *
+ * @param section
+ * @param subsection
+ * @param name
+ */
+ public EntryKey(String section, String subsection, String name) {
+ this.name = name;
+ this.section = section;
+ this.subsection = subsection;
+ }
+ }
- void unset(String section, String subsection, String name);
+ /**
+ * Extract decrypted value of stored property from SecureStore or {@code null}
+ * when property was not found.
+ *
+ * @param section
+ * @param subsection
+ * @param name
+ * @return decrypted String value or {@code null} if not found
+ */
+ public final String get(String section, String subsection, String name) {
+ String[] values = getList(section, subsection, name);
+ if (values != null && values.length > 0) {
+ return values[0];
+ }
+ return null;
+ }
+
+ /**
+ * Extract list of values from SecureStore and decrypt every value in that
+ * list or {@code null} when property was not found.
+ *
+ * @param section
+ * @param subsection
+ * @param name
+ * @return decrypted list of string values or {@code null}
+ */
+ public abstract String[] getList(String section, String subsection, String name);
+
+ /**
+ * Store single value in SecureStore.
+ *
+ * This method is responsible for encrypting value and storing it.
+ *
+ * @param section
+ * @param subsection
+ * @param name
+ * @param value plain text value
+ */
+ public final void set(String section, String subsection, String name, String value) {
+ setList(section, subsection, name, Lists.newArrayList(value));
+ }
+
+ /**
+ * Store list of values in SecureStore.
+ *
+ * This method is responsible for encrypting all values in the list and storing them.
+ *
+ * @param section
+ * @param subsection
+ * @param name
+ * @param values list of plain text values
+ */
+ public abstract void setList(String section, String subsection, String name, List<String> values);
+
+ /**
+ * Remove value for given {@code section}, {@code subsection} and {@code name}
+ * from SecureStore.
+ *
+ * @param section
+ * @param subsection
+ * @param name
+ */
+ public abstract void unset(String section, String subsection, String name);
+
+ /**
+ * @return list of stored entries.
+ */
+ public abstract Iterable<EntryKey> list();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreClassName.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreClassName.java
new file mode 100644
index 0000000000..07635bd4ae
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreClassName.java
@@ -0,0 +1,12 @@
+package com.google.gerrit.server.securestore;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface SecureStoreClassName {
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
deleted file mode 100644
index b92510547f..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2014 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.securestore;
-
-import com.google.common.base.Objects;
-
-import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
-
-public class SecureStoreData {
- public final File pluginFile;
- public final String storeName;
- public final String className;
-
- public SecureStoreData(String pluginName, String className, File jarFile,
- String storeName) {
- this.className = className;
- this.pluginFile = jarFile;
- this.storeName = String.format("%s/%s", pluginName, storeName);
- }
-
- public String getStoreName() {
- return storeName;
- }
-
- public Class<? extends SecureStore> load() {
- return load(pluginFile);
- }
-
- @SuppressWarnings("unchecked")
- public Class<? extends SecureStore> load(File pluginFile) {
- try {
- URL[] pluginJarUrls = new URL[] {pluginFile.toURI().toURL()};
- ClassLoader currentCL = Thread.currentThread().getContextClassLoader();
- final URLClassLoader newClassLoader =
- new URLClassLoader(pluginJarUrls, currentCL);
- Thread.currentThread().setContextClassLoader(newClassLoader);
- return (Class<? extends SecureStore>) newClassLoader.loadClass(className);
- } catch (Exception e) {
- throw new SecureStoreException(String.format(
- "Cannot load secure store implementation for %s", storeName), e);
- }
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this).add("storeName", storeName)
- .add("className", className).add("file", pluginFile).toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SecureStoreData) {
- SecureStoreData o = (SecureStoreData) obj;
- return storeName.equals(o.storeName);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(storeName);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java
new file mode 100644
index 0000000000..e8305906f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java
@@ -0,0 +1,71 @@
+// 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.securestore;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.SiteLibraryLoaderUtil;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+@Singleton
+public class SecureStoreProvider implements Provider<SecureStore> {
+ private static final Logger log = LoggerFactory
+ .getLogger(SecureStoreProvider.class);
+
+ private final File libdir;
+ private final Injector injector;
+ private final String className;
+
+ @Inject
+ protected SecureStoreProvider(
+ Injector injector,
+ SitePaths sitePaths,
+ @Nullable @SecureStoreClassName String className) {
+ this.injector = injector;
+ this.libdir = sitePaths.lib_dir;
+ this.className = className;
+ }
+
+ @Override
+ public synchronized SecureStore get() {
+ return injector.getInstance(getSecureStoreImpl());
+ }
+
+ @SuppressWarnings("unchecked")
+ private Class<? extends SecureStore> getSecureStoreImpl() {
+ if (Strings.isNullOrEmpty(className)) {
+ return DefaultSecureStore.class;
+ }
+
+ SiteLibraryLoaderUtil.loadSiteLib(libdir);
+ try {
+ return (Class<? extends SecureStore>) Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ String msg =
+ String.format("Cannot load secure store class: %s", className);
+ log.error(msg, e);
+ throw new RuntimeException(msg, e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java
index ea4b7f9842..c5852700ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java
@@ -28,6 +28,7 @@ public final class HostPlatform {
private static final boolean computeWin32() {
final String osDotName =
AccessController.doPrivileged(new PrivilegedAction<String>() {
+ @Override
public String run() {
return System.getProperty("os.name");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
index 13bc766e39..2e01613c07 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
@@ -16,17 +16,18 @@ package com.google.gerrit.server.util;
import static com.google.common.base.Preconditions.checkArgument;
-import com.google.common.base.Objects;
+import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
/** A single vote on a label, consisting of a label name and a value. */
-public class LabelVote {
+@AutoValue
+public abstract class LabelVote {
public static LabelVote parse(String text) {
checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
if (text.charAt(0) == '-') {
- return new LabelVote(text.substring(1), (short) 0);
+ return create(text.substring(1), (short) 0);
}
short sign = 0;
int i;
@@ -43,9 +44,9 @@ public class LabelVote {
}
}
if (sign == 0) {
- return new LabelVote(text, (short) 1);
+ return create(text, (short) 1);
}
- return new LabelVote(text.substring(0, i),
+ return create(text.substring(0, i),
(short)(sign * Short.parseShort(text.substring(i + 1))));
}
@@ -53,56 +54,37 @@ public class LabelVote {
checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
int e = text.lastIndexOf('=');
checkArgument(e >= 0, "Label vote missing '=': %s", text);
- return new LabelVote(text.substring(0, e),
+ return create(text.substring(0, e),
Short.parseShort(text.substring(e + 1), text.length()));
}
- private final String name;
- private final short value;
-
- public LabelVote(String name, short value) {
- this.name = LabelType.checkNameInternal(name);
- this.value = value;
- }
-
- public LabelVote(PatchSetApproval psa) {
- this(psa.getLabel(), psa.getValue());
+ public static LabelVote create(String label, short value) {
+ return new AutoValue_LabelVote(LabelType.checkNameInternal(label), value);
}
- public String getLabel() {
- return name;
+ public static LabelVote create(PatchSetApproval psa) {
+ return create(psa.getLabel(), psa.getValue());
}
- public short getValue() {
- return value;
- }
+ public abstract String label();
+ public abstract short value();
public String format() {
- if (value == (short) 0) {
- return '-' + name;
- } else if (value < 0) {
- return name + value;
+ if (value() == (short) 0) {
+ return '-' + label();
+ } else if (value() < 0) {
+ return label() + value();
} else {
- return name + '+' + value;
+ return label() + '+' + value();
}
}
public String formatWithEquals() {
- if (value <= (short) 0) {
- return name + '=' + value;
+ if (value() <= (short) 0) {
+ return label() + '=' + value();
} else {
- return name + "=+" + value;
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof LabelVote) {
- LabelVote l = (LabelVote) o;
- return Objects.equal(name, l.name)
- && value == l.value;
+ return label() + "=+" + value();
}
- return false;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java
new file mode 100644
index 0000000000..0ef4a18452
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2015 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 com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+
+/**
+ * Closeable version of a {@link RequestContext} with manually-specified
+ * providers.
+ */
+public class ManualRequestContext implements RequestContext, AutoCloseable {
+ private final CurrentUser user;
+ private final Provider<ReviewDb> db;
+ private final ThreadLocalRequestContext requestContext;
+ private final RequestContext old;
+
+ ManualRequestContext(CurrentUser user, SchemaFactory<ReviewDb> schemaFactory,
+ ThreadLocalRequestContext requestContext) throws OrmException {
+ this.user = user;
+ this.db = Providers.of(schemaFactory.open());
+ this.requestContext = requestContext;
+ old = requestContext.setContext(this);
+ }
+
+ @Override
+ public CurrentUser getCurrentUser() {
+ return user;
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return db;
+ }
+
+ @Override
+ public void close() {
+ requestContext.setContext(old);
+ db.get().close();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/OneOffRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/OneOffRequestContext.java
new file mode 100644
index 0000000000..6feb182ddf
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/OneOffRequestContext.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2015 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 com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.InternalUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Helper to create one-off request contexts.
+ * <p>
+ * Each call to {@link #open()} opens a new {@link ReviewDb}, so this class
+ * should only be used in a bounded try/finally block.
+ * <p>
+ * The user in the request context is {@link InternalUser}.
+ */
+@Singleton
+public class OneOffRequestContext {
+ private final InternalUser.Factory userFactory;
+ private final SchemaFactory<ReviewDb> schemaFactory;
+ private final ThreadLocalRequestContext requestContext;
+
+ @Inject
+ OneOffRequestContext(InternalUser.Factory userFactory,
+ SchemaFactory<ReviewDb> schemaFactory,
+ ThreadLocalRequestContext requestContext) {
+ this.userFactory = userFactory;
+ this.schemaFactory = schemaFactory;
+ this.requestContext = requestContext;
+ }
+
+ public ManualRequestContext open() throws OrmException {
+ return new ManualRequestContext(userFactory.create(),
+ schemaFactory, requestContext);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
new file mode 100644
index 0000000000..4b0fd35e1e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2014 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 static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Chars;
+
+import dk.brics.automaton.Automaton;
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+import java.util.Collections;
+import java.util.List;
+
+/** Helper to search sorted lists for elements matching a regex. */
+public abstract class RegexListSearcher<T> implements Function<T, String> {
+ public static RegexListSearcher<String> ofStrings(String re) {
+ return new RegexListSearcher<String>(re) {
+ @Override
+ public String apply(String in) {
+ return in;
+ }
+ };
+ }
+
+ private final RunAutomaton pattern;
+
+ private final String prefixBegin;
+ private final String prefixEnd;
+ private final int prefixLen;
+ private final boolean prefixOnly;
+
+ public RegexListSearcher(String re) {
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ Automaton automaton = new RegExp(re).toAutomaton();
+ prefixBegin = automaton.getCommonPrefix();
+ prefixLen = prefixBegin.length();
+
+ if (0 < prefixLen) {
+ char max = Chars.checkedCast(prefixBegin.charAt(prefixLen - 1) + 1);
+ prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
+ prefixOnly = re.equals(prefixBegin + ".*");
+ } else {
+ prefixEnd = "";
+ prefixOnly = false;
+ }
+
+ pattern = prefixOnly ? null : new RunAutomaton(automaton);
+ }
+
+ public Iterable<T> search(List<T> list) {
+ checkNotNull(list);
+ int begin, end;
+
+ if (0 < prefixLen) {
+ // Assumes many consecutive elements may have the same prefix, so the cost
+ // of two binary searches is less than iterating to find the endpoints.
+ begin = find(list, prefixBegin);
+ end = find(list, prefixEnd);
+ } else {
+ begin = 0;
+ end = list.size();
+ }
+
+ if (prefixOnly) {
+ return begin < end ? list.subList(begin, end) : ImmutableList.<T> of();
+ }
+
+ return Iterables.filter(
+ list.subList(begin, end),
+ new Predicate<T>() {
+ @Override
+ public boolean apply(T in) {
+ return pattern.run(RegexListSearcher.this.apply(in));
+ }
+ });
+ }
+
+ public boolean hasMatch(List<T> list) {
+ return !Iterables.isEmpty(search(list));
+ }
+
+ private int find(List<T> list, String p) {
+ int r = Collections.binarySearch(Lists.transform(list, this), p);
+ return r < 0 ? -(r + 1) : r;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
index 6730e300fe..2b6b86e5a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.util;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.InternalUser;
+import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
-import com.google.inject.Inject;
/** RequestContext with an InternalUser making the internals visible. */
public class ServerRequestContext implements RequestContext {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
index ba31f565fe..cf7f11f15f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
@@ -38,10 +38,11 @@ import java.io.IOException;
@Singleton
public class SystemLog {
+ private static final org.slf4j.Logger log =
+ LoggerFactory.getLogger(SystemLog.class);
+
+ public static final String LOG4J_CONFIGURATION = "log4j.configuration";
- private static final org.slf4j.Logger log = LoggerFactory
- .getLogger(SystemLog.class);
- private static final String LOG4J_CONFIGURATION = "log4j.configuration";
private final SitePaths site;
private final Config config;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
index f8bad77884..d1b1da4b0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.util;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -48,7 +48,7 @@ public class ThreadLocalRequestContext {
@Provides
RequestContext provideRequestContext(
@Named(FALLBACK) RequestContext fallback) {
- return Objects.firstNonNull(local.get(), fallback);
+ return MoreObjects.firstNonNull(local.get(), fallback);
}
@Provides
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java
new file mode 100644
index 0000000000..c1d509e27b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 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.validators;
+
+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.
+ */
+@ExtensionPoint
+public interface HashtagValidationListener {
+ /**
+ * Invoked by Gerrit before hashtags are changed.
+ *
+ * @param change the change on which the hashtags are changed
+ * @param toAdd the hashtags to be added
+ * @param toRemove the hashtags to be removed
+ * @throws ValidationException if validation fails
+ */
+ public void validateHashtags(Change change, Set<String> toAdd,
+ Set<String> toRemove) throws ValidationException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
new file mode 100644
index 0000000000..5b3f1583cc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2014 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.validators;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.EmailHeader;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Listener to provide validation on outgoing email notification.
+ */
+@ExtensionPoint
+public interface OutgoingEmailValidationListener {
+ /**
+ * Arguments supplied to validateOutgoingEmail.
+ */
+ public static class Args {
+ // in arguments
+ public String messageClass;
+
+ // in/out arguments
+ public Address smtpFromAddress;
+ public Set<Address> smtpRcptTo;
+ public String body;
+ public Map<String, EmailHeader> headers;
+ }
+
+ /**
+ * Outgoing e-mail validation.
+ *
+ * Invoked by Gerrit just before an e-mail is sent, after all e-mail templates
+ * have been applied.
+ *
+ * Plugins may modify the following fields in args:
+ * - smtpFromAddress
+ * - smtpRcptTo
+ * - body
+ * - headers
+ *
+ * @param args E-mail properties. Some are mutable.
+ * @throws ValidationException if validation fails.
+ */
+ public void validateOutgoingEmail(OutgoingEmailValidationListener.Args args)
+ throws ValidationException;
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
index 8e9126232e..f0806a5cf0 100644
--- a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
@@ -43,10 +43,6 @@ class PRED__load_commit_labels_1 extends Predicate.P1 {
StoredValues.CHANGE_CONTROL.get(engine).getLabelTypes();
for (PatchSetApproval a : cd.currentApprovals()) {
- if (a.getValue() == 0) {
- continue;
- }
-
LabelType t = types.byLabel(a.getLabelId());
if (t == null) {
continue;
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java b/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java
index cddbf1f368..509faf0313 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_edits_2.java
@@ -73,11 +73,10 @@ public class PRED_commit_edits_2 extends Predicate.P2 {
PatchList pl = StoredValues.PATCH_LIST.get(engine);
Repository repo = StoredValues.REPOSITORY.get(engine);
- final ObjectReader reader = repo.newObjectReader();
- final RevTree aTree;
- final RevTree bTree;
- try {
- final RevWalk rw = new RevWalk(reader);
+ try (ObjectReader reader = repo.newObjectReader();
+ RevWalk rw = new RevWalk(reader)) {
+ final RevTree aTree;
+ final RevTree bTree;
final RevCommit bCommit = rw.parseCommit(pl.getNewId());
if (pl.getOldId() != null) {
@@ -129,8 +128,6 @@ public class PRED_commit_edits_2 extends Predicate.P2 {
}
} catch (IOException err) {
throw new JavaException(this, 1, err);
- } finally {
- reader.close();
}
return engine.fail();
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
index daf9948752..83878be4e7 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
@@ -16,6 +16,7 @@ package gerrit;
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.patch.PatchList;
+
import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.Operation;
import com.googlecode.prolog_cafe.lang.Predicate;
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java
index dd2a008894..6d0dd0f8df 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java
@@ -20,7 +20,6 @@ import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
-import com.google.gerrit.server.project.ChangeControl;
import com.googlecode.prolog_cafe.lang.EvaluationException;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -47,8 +46,11 @@ public class PRED_current_user_1 extends Predicate.P1 {
engine.setB0();
Term a1 = arg1.dereference();
- ChangeControl cControl = StoredValues.CHANGE_CONTROL.get(engine);
- CurrentUser curUser = cControl.getCurrentUser();
+ CurrentUser curUser = StoredValues.CURRENT_USER.getOrNull(engine);
+ if (curUser == null) {
+ throw new EvaluationException(
+ "Current user not available in this rule type");
+ }
Term resultTerm;
if (curUser.isIdentifiedUser()) {
@@ -67,4 +69,4 @@ public class PRED_current_user_1 extends Predicate.P1 {
}
return cont;
}
-} \ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java b/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
index a471450440..824c6efe61 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.project.ChangeControl;
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 4738d15a28..2a458197f5 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -343,7 +343,7 @@ filter_submit_results(Filter, [I | In], Tmp, Out) :-
( is_all_ok(Ls) -> T = ok(S) ; T = not_ready(S) ),
filter_submit_results(Filter, In, [T | Tmp], Out).
filter_submit_results(Filter, [_ | In], Tmp, Out) :-
- filter_submit_results(Filter, In, Tmp, Out),
+ filter_submit_results(Filter, In, Tmp, Out),
!
.
filter_submit_results(Filter, [], Out, Out).
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
index 9eb7d9b420..9c48292403 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -1,11 +1,13 @@
accessDatabase = Access Database
administrateServer = Administrate Server
+batchChangesLimit = Batch Changes Limit
createAccount = Create Account
createGroup = Create Group
createProject = Create Project
emailReviewers = Email Reviewers
flushCaches = Flush Caches
killTask = Kill Task
+modifyAccount = Modify Account
priority = Priority
queryLimit = Query Limit
runAs = Run As
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
index 1d5b33a83b..4fd9a23150 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
@@ -31,6 +31,11 @@
## The ChangeSubject.vm template will determine the contents of the email
## subject line for ALL emails related to changes.
##
+## Optionally $change.originalSubject can be used for the first subject
+## in a change. This allows subject based email clients such as GMail
+## to thread comments together even if subsequent patch sets change the
+## first line of the commit message.
+##
#macro(ellipsis $length $str)
#if($str.length() > $length)#set($length = $length - 3)${str.substring(0,$length)}...#else$str#end
#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
index 817790f1da..3d8d271240 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -5,21 +5,28 @@ clj = text/x-clojure
cl = text/x-common-lisp
coffee = text/x-coffeescript
cs = text/x-csharp
+cpp = text/x-c++src
cxx = text/x-c++src
d = text/x-d
+dart = application/dart
defs = text/x-python
diff = text/x-diff
+Dockerfile = text/x-dockerfile
dtd = application/xml-dtd
el = text/x-common-lisp
erl = text/x-erlang
+frag = x-shader/x-fragment
gitmodules = text/x-ini
+glsl = x-shader/x-vertex
go = text/x-go
groovy = text/x-groovy
hs = text/x-haskell
-hxx = text/x-c++hdr
+hpp = text/x-c++src
+hxx = text/x-c++src
lisp = text/x-common-lisp
lsp = text/x-common-lisp
lua = text/x-lua
+m = text/x-objectivec
md = text/x-markdown
patch = text/x-diff
php = text/x-php
@@ -31,9 +38,14 @@ properties = text/x-ini
py = text/x-python
r = text/r-src
rb = text/x-ruby
+rng = application/xml
+rst = text/x-rst
scala = text/x-scala
+soy = text/x-soy
st = text/x-stsrc
+stex = text/x-stex
v = text/x-verilog
+vert = x-shader/x-vertex
vh = text/x-verilog
vm = text/velocity
yaml = text/x-yaml
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
index 1ed35bed7f..f5dca09bd2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
@@ -14,11 +14,14 @@
package com.google.gerrit.rules;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.common.data.Permission.LABEL;
import static com.google.gerrit.server.project.Util.allow;
import static com.google.gerrit.server.project.Util.category;
import static com.google.gerrit.server.project.Util.value;
+import static org.junit.Assert.fail;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
@@ -27,13 +30,21 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.Util;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
import com.google.inject.AbstractModule;
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+
+import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
+import java.io.PushbackReader;
+import java.io.StringReader;
import java.util.Arrays;
public class GerritCommonTest extends PrologTestCase {
@@ -56,6 +67,9 @@ public class GerritCommonTest extends PrologTestCase {
load("gerrit", "gerrit_common_test.pl", new AbstractModule() {
@Override
protected void configure() {
+ Config cfg = new Config();
+ cfg.setInt("rules", null, "reductionLimit", 1300);
+ cfg.setInt("rules", null, "compileReductionLimit", (int) 1e6);
bind(PrologEnvironment.Args.class).toInstance(
new PrologEnvironment.Args(
null,
@@ -63,7 +77,8 @@ public class GerritCommonTest extends PrologTestCase {
null,
null,
null,
- null));
+ null,
+ cfg));
}
});
@@ -92,4 +107,30 @@ public class GerritCommonTest extends PrologTestCase {
public void testGerritCommon() {
runPrologBasedTests();
}
+
+ @Test
+ public void testReductionLimit() throws CompileException {
+ PrologEnvironment env = envFactory.create(machine);
+ setUpEnvironment(env);
+ env.setEnabled(Prolog.Feature.IO, true);
+
+ String script = "loopy :- b(5).\n"
+ + "b(N) :- N > 0, !, S = N - 1, b(S).\n"
+ + "b(_) :- true.\n";
+
+ SymbolTerm nameTerm = SymbolTerm.create("testReductionLimit");
+ JavaObjectTerm inTerm = new JavaObjectTerm(
+ new PushbackReader(new StringReader(script), Prolog.PUSHBACK_SIZE));
+ if (!env.execute(Prolog.BUILTIN, "consult_stream", nameTerm, inTerm)) {
+ throw new CompileException("Cannot consult " + nameTerm);
+ }
+
+ try {
+ env.once(Prolog.BUILTIN, "call", new StructureTerm(":",
+ SymbolTerm.create("user"), SymbolTerm.create("loopy")));
+ fail("long running loop did not abort with ReductionLimitException");
+ } catch (ReductionLimitException e) {
+ assertThat(e.getMessage()).isEqualTo("exceeded reduction limit of 1300");
+ }
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
index c6974005be..aaab1739f9 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
@@ -14,7 +14,11 @@
package com.google.gerrit.rules;
-import com.google.gerrit.server.util.TimeUtil;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.gerrit.common.TimeUtil;
import com.google.inject.Guice;
import com.google.inject.Module;
@@ -39,10 +43,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
/** Base class for any tests written in Prolog. */
public abstract class PrologTestCase {
@@ -52,8 +52,8 @@ public abstract class PrologTestCase {
private boolean hasSetup;
private boolean hasTeardown;
private List<Term> tests;
- private PrologMachineCopy machine;
- private PrologEnvironment.Factory envFactory;
+ protected PrologMachineCopy machine;
+ protected PrologEnvironment.Factory envFactory;
protected void load(String pkg, String prologResource, Module... modules)
throws CompileException, IOException {
@@ -82,6 +82,11 @@ public abstract class PrologTestCase {
machine = PrologMachineCopy.save(env);
}
+ /**
+ * Set up the Prolog environment.
+ *
+ * @param env Prolog environment.
+ */
protected void setUpEnvironment(PrologEnvironment env) {
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
index 0bbec8a3ce..b4d6e96818 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
public class StringUtilTest {
/**
* Test the boundary condition that the first character of a string
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/account/UniversalGroupBackendTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/account/UniversalGroupBackendTest.java
new file mode 100644
index 0000000000..3cec25cd3f
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/account/UniversalGroupBackendTest.java
@@ -0,0 +1,148 @@
+// Copyright (C) 2014 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.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 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.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import org.easymock.IAnswer;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Set;
+
+public class UniversalGroupBackendTest {
+ static {
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+ }
+
+ private static final AccountGroup.UUID OTHER_UUID =
+ new AccountGroup.UUID("other");
+
+ private UniversalGroupBackend backend;
+ private IdentifiedUser user;
+
+ private DynamicSet<GroupBackend> backends;
+
+ @Before
+ public void setup() {
+ user = createNiceMock(IdentifiedUser.class);
+ replay(user);
+ backends = new DynamicSet<>();
+ backends.add(new SystemGroupBackend());
+ backend = new UniversalGroupBackend(backends);
+ }
+
+ @Test
+ public void testHandles() {
+ assertTrue(backend.handles(ANONYMOUS_USERS));
+ assertTrue(backend.handles(PROJECT_OWNERS));
+ assertFalse(backend.handles(OTHER_UUID));
+ }
+
+ @Test
+ public void testGet() {
+ assertEquals("Registered Users",
+ backend.get(REGISTERED_USERS).getName());
+ assertEquals("Project Owners",
+ backend.get(PROJECT_OWNERS).getName());
+ assertNull(backend.get(OTHER_UUID));
+ }
+
+ @Test
+ public void testSuggest() {
+ assertTrue(backend.suggest("X", null).isEmpty());
+ assertEquals(1, backend.suggest("project", null).size());
+ assertEquals(1, backend.suggest("reg", null).size());
+ }
+
+ @Test
+ public void testSytemGroupMemberships() {
+ GroupMembership checker = backend.membershipsOf(user);
+ assertTrue(checker.contains(REGISTERED_USERS));
+ assertFalse(checker.contains(OTHER_UUID));
+ assertFalse(checker.contains(PROJECT_OWNERS));
+ }
+
+ @Test
+ public void testKnownGroups() {
+ GroupMembership checker = backend.membershipsOf(user);
+ Set<UUID> knownGroups = checker.getKnownGroups();
+ assertEquals(2, knownGroups.size());
+ assertTrue(knownGroups.contains(REGISTERED_USERS));
+ assertTrue(knownGroups.contains(ANONYMOUS_USERS));
+ }
+
+ @Test
+ public void testOtherMemberships() {
+ 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(new IAnswer<GroupMembership>() {
+ @Override
+ public GroupMembership answer() throws Throwable {
+ 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;
+ }
+ });
+ replay(member, notMember, backend);
+
+ backends = new DynamicSet<>();
+ backends.add(backend);
+ backend = new UniversalGroupBackend(backends);
+
+ GroupMembership checker =
+ backend.membershipsOf(member);
+ assertFalse(checker.contains(REGISTERED_USERS));
+ assertFalse(checker.contains(OTHER_UUID));
+ assertTrue(checker.contains(handled));
+ assertFalse(checker.contains(notHandled));
+ checker = backend.membershipsOf(notMember);
+ assertFalse(checker.contains(handled));
+ assertFalse(checker.contains(notHandled));
+ }
+
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index a30fa92d35..8bedd176c1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -14,45 +14,51 @@
package com.google.gerrit.server.change;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.inject.Scopes.SINGLETON;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
-import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestReadView;
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.CommentRange;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.Project;
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.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.FakeRealm;
import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.DisableReverseDnsLookup;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitModule;
@@ -62,22 +68,23 @@ import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.util.TimeUtil;
-import com.google.gerrit.testutil.TestChanges;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeAccountCache;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestChanges;
import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
+import com.google.inject.Inject;
import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Providers;
import org.easymock.IAnswer;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Before;
@@ -85,7 +92,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.sql.Timestamp;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -101,58 +107,88 @@ public class CommentsTest {
@ConfigSuite.Config
public static @GerritServerConfig Config noteDbEnabled() {
- @GerritServerConfig Config cfg = new Config();
- cfg.setBoolean("notedb", null, "write", true);
- cfg.setBoolean("notedb", "publishedComments", "read", true);
- return cfg;
+ return NotesMigration.allEnabledConfig();
}
private Injector injector;
+ private ReviewDb db;
private Project.NameKey project;
- private InMemoryRepositoryManager repoManager;
- private PatchLineCommentsUtil plcUtil;
+ private Account.Id ownerId;
private RevisionResource revRes1;
private RevisionResource revRes2;
+ private RevisionResource revRes3;
private PatchLineComment plc1;
private PatchLineComment plc2;
private PatchLineComment plc3;
+ private PatchLineComment plc4;
+ private PatchLineComment plc5;
+ private PatchLineComment plc6;
private IdentifiedUser changeOwner;
+ @Inject private AllUsersNameProvider allUsers;
+ @Inject private Comments comments;
+ @Inject private DraftComments drafts;
+ @Inject private GetComment getComment;
+ @Inject private IdentifiedUser.GenericFactory userFactory;
+ @Inject private InMemoryRepositoryManager repoManager;
+ @Inject private NotesMigration migration;
+ @Inject private PatchLineCommentsUtil plcUtil;
+
@Before
public void setUp() throws Exception {
@SuppressWarnings("unchecked")
- final DynamicMap<RestView<CommentResource>> views =
+ final DynamicMap<RestView<CommentResource>> commentViews =
createMock(DynamicMap.class);
- final TypeLiteral<DynamicMap<RestView<CommentResource>>> viewsType =
+ final TypeLiteral<DynamicMap<RestView<CommentResource>>> commentViewsType =
new TypeLiteral<DynamicMap<RestView<CommentResource>>>() {};
- final AccountInfo.Loader.Factory alf =
- createMock(AccountInfo.Loader.Factory.class);
- final ReviewDb db = createMock(ReviewDb.class);
+ @SuppressWarnings("unchecked")
+ final DynamicMap<RestView<DraftCommentResource>> draftViews =
+ createMock(DynamicMap.class);
+ final TypeLiteral<DynamicMap<RestView<DraftCommentResource>>> draftViewsType =
+ new TypeLiteral<DynamicMap<RestView<DraftCommentResource>>>() {};
+
+ final AccountLoader.Factory alf =
+ createMock(AccountLoader.Factory.class);
+ db = createMock(ReviewDb.class);
final FakeAccountCache accountCache = new FakeAccountCache();
final PersonIdent serverIdent = new PersonIdent(
"Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
project = new Project.NameKey("test-project");
- repoManager = new InMemoryRepositoryManager();
- @SuppressWarnings("unused")
- InMemoryRepository repo = repoManager.createRepository(project);
+ Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
+ co.setFullName("Change Owner");
+ co.setPreferredEmail("change@owner.com");
+ accountCache.put(co);
+ ownerId = co.getId();
+
+ Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
+ ou.setFullName("Other Account");
+ ou.setPreferredEmail("other@account.com");
+ accountCache.put(ou);
+ Account.Id otherUserId = ou.getId();
AbstractModule mod = new AbstractModule() {
@Override
protected void configure() {
- bind(viewsType).toInstance(views);
- bind(AccountInfo.Loader.Factory.class).toInstance(alf);
- bind(ReviewDb.class).toProvider(Providers.<ReviewDb>of(db));
+ bind(commentViewsType).toInstance(commentViews);
+ bind(draftViewsType).toInstance(draftViews);
+ bind(AccountLoader.Factory.class).toInstance(alf);
+ bind(ReviewDb.class).toInstance(db);
+ bind(Realm.class).to(FakeRealm.class);
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(config);
bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
install(new GitModule());
- bind(GitRepositoryManager.class).toInstance(repoManager);
+ bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
+ bind(InMemoryRepositoryManager.class)
+ .toInstance(new InMemoryRepositoryManager());
bind(CapabilityControl.Factory.class)
.toProvider(Providers.<CapabilityControl.Factory> of(null));
bind(String.class).annotatedWith(AnonymousCowardName.class)
.toProvider(AnonymousCowardNameProvider.class);
bind(String.class).annotatedWith(CanonicalWebUrl.class)
.toInstance("http://localhost:8080/");
+ bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class)
+ .toInstance(Boolean.FALSE);
bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
bind(AccountCache.class).toInstance(accountCache);
bind(GitReferenceUpdated.class)
@@ -160,50 +196,47 @@ public class CommentsTest {
bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
.toInstance(serverIdent);
}
+
+ @Provides
+ @Singleton
+ CurrentUser getCurrentUser(IdentifiedUser.GenericFactory userFactory) {
+ return userFactory.create(ownerId);
+ }
};
injector = Guice.createInjector(mod);
+ injector.injectMembers(this);
- NotesMigration migration = injector.getInstance(NotesMigration.class);
- plcUtil = new PatchLineCommentsUtil(migration);
-
- Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
- co.setFullName("Change Owner");
- co.setPreferredEmail("change@owner.com");
- accountCache.put(co);
- Account.Id ownerId = co.getId();
-
- Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
- ou.setFullName("Other Account");
- ou.setPreferredEmail("other@account.com");
- accountCache.put(ou);
- Account.Id otherUserId = ou.getId();
-
- IdentifiedUser.GenericFactory userFactory =
- injector.getInstance(IdentifiedUser.GenericFactory.class);
+ repoManager.createRepository(project);
changeOwner = userFactory.create(ownerId);
IdentifiedUser otherUser = userFactory.create(otherUserId);
- AccountInfo.Loader accountLoader = createMock(AccountInfo.Loader.class);
+ AccountLoader accountLoader = createMock(AccountLoader.class);
accountLoader.fill();
expectLastCall().anyTimes();
expect(accountLoader.get(ownerId))
- .andReturn(new AccountInfo(ownerId)).anyTimes();
+ .andReturn(new AccountInfo(ownerId.get())).anyTimes();
expect(accountLoader.get(otherUserId))
- .andReturn(new AccountInfo(otherUserId)).anyTimes();
+ .andReturn(new AccountInfo(otherUserId.get())).anyTimes();
expect(alf.create(true)).andReturn(accountLoader).anyTimes();
replay(accountLoader, alf);
+ repoManager.createRepository(allUsers.get());
+
PatchLineCommentAccess plca = createMock(PatchLineCommentAccess.class);
expect(db.patchComments()).andReturn(plca).anyTimes();
- Change change = newChange();
- PatchSet.Id psId1 = new PatchSet.Id(change.getId(), 1);
+ Change change1 = newChange();
+ PatchSet.Id psId1 = new PatchSet.Id(change1.getId(), 1);
PatchSet ps1 = new PatchSet(psId1);
- PatchSet.Id psId2 = new PatchSet.Id(change.getId(), 2);
+ PatchSet.Id psId2 = new PatchSet.Id(change1.getId(), 2);
PatchSet ps2 = new PatchSet(psId2);
- long timeBase = TimeUtil.nowMs();
+ Change change2 = newChange();
+ PatchSet.Id psId3 = new PatchSet.Id(change2.getId(), 1);
+ PatchSet ps3 = new PatchSet(psId3);
+
+ long timeBase = TimeUtil.roundToSecond(TimeUtil.nowTs()).getTime();
plc1 = newPatchLineComment(psId1, "Comment1", null,
"FileOne.txt", Side.REVISION, 3, ownerId, timeBase,
"First Comment", new CommentRange(1, 2, 3, 4));
@@ -216,69 +249,139 @@ public class CommentsTest {
"FileOne.txt", Side.PARENT, 3, ownerId, timeBase + 2000,
"First Parent Comment", new CommentRange(1, 2, 3, 4));
plc3.setRevId(new RevId("CDEFCDEFCDEFCDEFCDEFCDEFCDEFCDEFCDEFCDEF"));
+ plc4 = newPatchLineComment(psId2, "Comment4", null, "FileOne.txt",
+ Side.REVISION, 3, ownerId, timeBase + 3000, "Second Comment",
+ new CommentRange(1, 2, 3, 4), Status.DRAFT);
+ plc4.setRevId(new RevId("BCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDE"));
+ plc5 = newPatchLineComment(psId2, "Comment5", null, "FileOne.txt",
+ Side.REVISION, 5, ownerId, timeBase + 4000, "Third Comment",
+ new CommentRange(3, 4, 5, 6), Status.DRAFT);
+ plc5.setRevId(new RevId("BCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDE"));
+ plc5.setRevId(new RevId("BCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDEBCDE"));
+ plc6 = newPatchLineComment(psId3, "Comment6", null, "FileOne.txt",
+ Side.REVISION, 5, ownerId, timeBase + 5000, "Sixth Comment",
+ new CommentRange(3, 4, 5, 6), Status.DRAFT);
+ plc6.setRevId(new RevId("1234123412341234123412341234123412341234"));
List<PatchLineComment> commentsByOwner = Lists.newArrayList();
commentsByOwner.add(plc1);
commentsByOwner.add(plc3);
List<PatchLineComment> commentsByReviewer = Lists.newArrayList();
commentsByReviewer.add(plc2);
+ List<PatchLineComment> drafts1 = Lists.newArrayList();
+ drafts1.add(plc4);
+ drafts1.add(plc5);
+ List<PatchLineComment> drafts2 = Lists.newArrayList();
+ drafts2.add(plc6);
plca.upsert(commentsByOwner);
expectLastCall().anyTimes();
plca.upsert(commentsByReviewer);
expectLastCall().anyTimes();
+ plca.upsert(drafts1);
+ expectLastCall().anyTimes();
+ plca.upsert(drafts2);
+ expectLastCall().anyTimes();
expect(plca.publishedByPatchSet(psId1))
.andAnswer(results(plc1, plc2, plc3)).anyTimes();
expect(plca.publishedByPatchSet(psId2))
.andAnswer(results()).anyTimes();
+ expect(plca.draftByPatchSetAuthor(psId1, ownerId))
+ .andAnswer(results()).anyTimes();
+ expect(plca.draftByPatchSetAuthor(psId2, ownerId))
+ .andAnswer(results(plc4, plc5)).anyTimes();
+ expect(plca.byChange(change1.getId()))
+ .andAnswer(results(plc1, plc2, plc3, plc4, plc5)).anyTimes();
+ expect(plca.draftByAuthor(ownerId))
+ .andAnswer(results(plc4, plc5, plc6)).anyTimes();
replay(db, plca);
- ChangeUpdate update = newUpdate(change, changeOwner);
+ ChangeUpdate update = newUpdate(change1, changeOwner);
update.setPatchSetId(psId1);
- plcUtil.addPublishedComments(db, update, commentsByOwner);
+ plcUtil.upsertComments(db, update, commentsByOwner);
update.commit();
- update = newUpdate(change, otherUser);
+ update = newUpdate(change1, otherUser);
update.setPatchSetId(psId1);
- plcUtil.addPublishedComments(db, update, commentsByReviewer);
+ plcUtil.upsertComments(db, update, commentsByReviewer);
+ update.commit();
+
+ update = newUpdate(change1, changeOwner);
+ update.setPatchSetId(psId2);
+ plcUtil.upsertComments(db, update, drafts1);
update.commit();
- ChangeControl ctl = stubChangeControl(change);
- revRes1 = new RevisionResource(new ChangeResource(ctl), ps1);
- revRes2 = new RevisionResource(new ChangeResource(ctl), ps2);
+ update = newUpdate(change2, changeOwner);
+ update.setPatchSetId(psId3);
+ plcUtil.upsertComments(db, update, drafts2);
+ update.commit();
+
+ ChangeControl ctl = stubChangeControl(change1);
+ revRes1 = new RevisionResource(new ChangeResource(ctl, null), ps1);
+ revRes2 = new RevisionResource(new ChangeResource(ctl, null), ps2);
+ revRes3 = new RevisionResource(new ChangeResource(stubChangeControl(change2), null), ps3);
}
private ChangeControl stubChangeControl(Change c) throws OrmException {
- return TestChanges.stubChangeControl(repoManager, c, changeOwner);
+ return TestChanges.stubChangeControl(
+ repoManager, migration, c, allUsers, changeOwner);
}
private Change newChange() {
- return TestChanges.newChange(project, changeOwner);
+ return TestChanges.newChange(project, changeOwner.getAccountId());
}
private ChangeUpdate newUpdate(Change c, final IdentifiedUser user) throws Exception {
- return TestChanges.newUpdate(injector, repoManager, c, user);
+ return TestChanges.newUpdate(
+ injector, repoManager, migration, c, allUsers, user);
}
@Test
public void testListComments() throws Exception {
// test ListComments for patch set 1
- assertListComments(injector, revRes1, ImmutableMap.of(
+ assertListComments(revRes1, ImmutableMap.of(
"FileOne.txt", Lists.newArrayList(plc3, plc1, plc2)));
// test ListComments for patch set 2
- assertListComments(injector, revRes2,
- Collections.<String, ArrayList<PatchLineComment>>emptyMap());
+ assertListComments(revRes2,
+ Collections.<String, List<PatchLineComment>>emptyMap());
}
@Test
public void testGetComment() throws Exception {
// test GetComment for existing comment
- assertGetComment(injector, revRes1, plc1, plc1.getKey().get());
+ assertGetComment(revRes1, plc1, plc1.getKey().get());
// test GetComment for non-existent comment
- assertGetComment(injector, revRes1, null, "BadComment");
+ assertGetComment(revRes1, null, "BadComment");
+ }
+
+ @Test
+ public void testListDrafts() throws Exception {
+ // test ListDrafts for patch set 1
+ assertListDrafts(revRes1,
+ Collections.<String, List<PatchLineComment>> emptyMap());
+
+ // test ListDrafts for patch set 2
+ assertListDrafts(revRes2, ImmutableMap.of(
+ "FileOne.txt", Lists.newArrayList(plc4, plc5)));
+ }
+
+ @Test
+ public void testPatchLineCommentsUtilByCommentStatus() throws OrmException {
+ assertThat(plcUtil.publishedByChange(db, revRes2.getNotes()))
+ .containsExactly(plc1, plc2, plc3).inOrder();
+ assertThat(plcUtil.draftByChange(db, revRes2.getNotes()))
+ .containsExactly(plc4, plc5).inOrder();
+ }
+
+ @Test
+ public void testPatchLineCommentsUtilDraftByChangeAuthor() throws Exception {
+ assertThat(plcUtil.draftByChangeAuthor(db, revRes1.getNotes(), ownerId))
+ .containsExactly(plc4, plc5).inOrder();
+ assertThat(plcUtil.draftByChangeAuthor(db, revRes3.getNotes(), ownerId))
+ .containsExactly(plc6);
}
private static IAnswer<ResultSet<PatchLineComment>> results(
@@ -290,17 +393,15 @@ public class CommentsTest {
}};
}
- private static void assertGetComment(Injector inj, RevisionResource res,
- PatchLineComment expected, String uuid) throws Exception {
- GetComment getComment = inj.getInstance(GetComment.class);
- Comments comments = inj.getInstance(Comments.class);
+ private void assertGetComment(RevisionResource res, PatchLineComment expected,
+ String uuid) throws Exception {
try {
CommentResource commentRes = comments.parse(res, IdString.fromUrl(uuid));
if (expected == null) {
fail("Expected no comment");
}
CommentInfo actual = getComment.apply(commentRes);
- assertComment(expected, actual);
+ assertComment(expected, actual, true);
} catch (ResourceNotFoundException e) {
if (expected != null) {
fail("Expected to find comment");
@@ -308,45 +409,58 @@ public class CommentsTest {
}
}
- private static void assertListComments(Injector inj, RevisionResource res,
- Map<String, ArrayList<PatchLineComment>> expected) throws Exception {
- Comments comments = inj.getInstance(Comments.class);
- RestReadView<RevisionResource> listView =
- (RestReadView<RevisionResource>) comments.list();
- @SuppressWarnings("unchecked")
- Map<String, List<CommentInfo>> actual =
- (Map<String, List<CommentInfo>>) listView.apply(res);
- assertNotNull(actual);
- assertEquals(expected.size(), actual.size());
- assertEquals(expected.keySet(), actual.keySet());
- for (Map.Entry<String, ArrayList<PatchLineComment>> entry : expected.entrySet()) {
- List<PatchLineComment> expectedComments = entry.getValue();
- List<CommentInfo> actualComments = actual.get(entry.getKey());
- assertNotNull(actualComments);
- assertEquals(expectedComments.size(), actualComments.size());
- for (int i = 0; i < expectedComments.size(); i++) {
- assertComment(expectedComments.get(i), actualComments.get(i));
+ private void assertListComments(RevisionResource res,
+ Map<String, ? extends List<PatchLineComment>> expected) throws Exception {
+ assertCommentMap(comments.list().apply(res), expected, true);
+ }
+
+ private void assertListDrafts(RevisionResource res,
+ Map<String, ? extends List<PatchLineComment>> expected) throws Exception {
+ assertCommentMap(drafts.list().apply(res), expected, false);
+ }
+
+ private void assertCommentMap(Map<String, List<CommentInfo>> actual,
+ Map<String, ? extends List<PatchLineComment>> expected,
+ boolean isPublished) {
+ assertThat((Iterable<?>)actual.keySet()).containsExactlyElementsIn(expected.keySet());
+ for (Map.Entry<String, List<CommentInfo>> entry : actual.entrySet()) {
+ List<CommentInfo> actualList = entry.getValue();
+ List<PatchLineComment> expectedList = expected.get(entry.getKey());
+ assertThat(actualList).hasSize(expectedList.size());
+ for (int i = 0; i < expectedList.size(); i++) {
+ assertComment(expectedList.get(i), actualList.get(i), isPublished);
}
}
}
- private static void assertComment(PatchLineComment plc, CommentInfo ci) {
- assertEquals(plc.getKey().get(), ci.id);
- assertEquals(plc.getParentUuid(), ci.inReplyTo);
- assertEquals(plc.getMessage(), ci.message);
- assertNotNull(ci.author);
- assertEquals(plc.getAuthor(), ci.author._id);
- assertEquals(plc.getLine(), (int) ci.line);
- assertEquals(plc.getSide() == 0 ? Side.PARENT : Side.REVISION,
- Objects.firstNonNull(ci.side, Side.REVISION));
- assertEquals(TimeUtil.roundTimestampToSecond(plc.getWrittenOn()),
- TimeUtil.roundTimestampToSecond(ci.updated));
- assertEquals(plc.getRange(), ci.range);
+ private static void assertComment(PatchLineComment plc, CommentInfo ci,
+ boolean isPublished) {
+ assertThat(ci.id).isEqualTo(plc.getKey().get());
+ assertThat(ci.inReplyTo).isEqualTo(plc.getParentUuid());
+ assertThat(ci.message).isEqualTo(plc.getMessage());
+ if (isPublished) {
+ assertThat(ci.author).isNotNull();
+ assertThat(new Account.Id(ci.author._accountId))
+ .isEqualTo(plc.getAuthor());
+ }
+ assertThat((int) ci.line).isEqualTo(plc.getLine());
+ assertThat(MoreObjects.firstNonNull(ci.side, Side.REVISION))
+ .isEqualTo(plc.getSide() == 0 ? Side.PARENT : Side.REVISION);
+ assertThat(TimeUtil.roundToSecond(ci.updated))
+ .isEqualTo(TimeUtil.roundToSecond(plc.getWrittenOn()));
+ assertThat(ci.updated).isEqualTo(plc.getWrittenOn());
+ assertThat(ci.range.startLine).isEqualTo(plc.getRange().getStartLine());
+ assertThat(ci.range.startCharacter)
+ .isEqualTo(plc.getRange().getStartCharacter());
+ assertThat(ci.range.endLine).isEqualTo(plc.getRange().getEndLine());
+ assertThat(ci.range.endCharacter)
+ .isEqualTo(plc.getRange().getEndCharacter());
}
private static PatchLineComment newPatchLineComment(PatchSet.Id psId,
String uuid, String inReplyToUuid, String filename, Side side, int line,
- Account.Id authorId, long millis, String message, CommentRange range) {
+ Account.Id authorId, long millis, String message, CommentRange range,
+ PatchLineComment.Status status) {
Patch.Key p = new Patch.Key(psId, filename);
PatchLineComment.Key id = new PatchLineComment.Key(p, uuid);
PatchLineComment plc =
@@ -354,8 +468,15 @@ public class CommentsTest {
plc.setMessage(message);
plc.setRange(range);
plc.setSide(side == Side.PARENT ? (short) 0 : (short) 1);
- plc.setStatus(Status.PUBLISHED);
+ plc.setStatus(status);
plc.setWrittenOn(new Timestamp(millis));
return plc;
}
+
+ private static PatchLineComment newPatchLineComment(PatchSet.Id psId,
+ String uuid, String inReplyToUuid, String filename, Side side, int line,
+ Account.Id authorId, long millis, String message, CommentRange range) {
+ return newPatchLineComment(psId, uuid, inReplyToUuid, filename, side, line,
+ authorId, millis, message, range, Status.PUBLISHED);
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java
new file mode 100644
index 0000000000..358620f5ba
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/ConsistencyCheckerTest.java
@@ -0,0 +1,449 @@
+// Copyright (C) 2014 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 static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testutil.TestChanges.newChange;
+import static com.google.gerrit.testutil.TestChanges.newPatchSet;
+import static java.util.Collections.singleton;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.FixInput;
+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.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.InternalUser;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.testutil.FakeAccountByEmailCache;
+import com.google.gerrit.testutil.InMemoryDatabase;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestChanges;
+import com.google.inject.util.Providers;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class ConsistencyCheckerTest {
+ private InMemoryDatabase schemaFactory;
+ private ReviewDb db;
+ private InMemoryRepositoryManager repoManager;
+ private ConsistencyChecker checker;
+
+ private TestRepository<InMemoryRepository> repo;
+ private Project.NameKey project;
+ private Account.Id userId;
+ private RevCommit tip;
+
+ @Before
+ public void setUp() throws Exception {
+ FakeAccountByEmailCache accountCache = new FakeAccountByEmailCache();
+ schemaFactory = InMemoryDatabase.newDatabase();
+ schemaFactory.create();
+ db = schemaFactory.open();
+ repoManager = new InMemoryRepositoryManager();
+ checker = new ConsistencyChecker(
+ Providers.<ReviewDb> of(db),
+ repoManager,
+ Providers.<CurrentUser> of(new InternalUser(null)),
+ Providers.of(new PersonIdent("server", "noreply@example.com")),
+ new PatchSetInfoFactory(repoManager, accountCache));
+ project = new Project.NameKey("repo");
+ repo = new TestRepository<>(repoManager.createRepository(project));
+ userId = new Account.Id(1);
+ accountCache.putAny(userId);
+ db.accounts().insert(singleton(new Account(userId, TimeUtil.nowTs())));
+ tip = repo.branch("master").commit().create();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (db != null) {
+ db.close();
+ }
+ if (schemaFactory != null) {
+ InMemoryDatabase.drop(schemaFactory);
+ }
+ }
+
+ @Test
+ public void validNewChange() throws Exception {
+ Change c = insertChange();
+ insertPatchSet(c);
+ incrementPatchSet(c);
+ insertPatchSet(c);
+ assertProblems(c);
+ }
+
+ @Test
+ public void validMergedChange() throws Exception {
+ Change c = insertChange();
+ c.setStatus(Change.Status.MERGED);
+ insertPatchSet(c);
+ incrementPatchSet(c);
+
+ incrementPatchSet(c);
+ RevCommit commit2 = repo.branch(c.currentPatchSetId().toRefName()).commit()
+ .parent(tip).create();
+ PatchSet ps2 = newPatchSet(c.currentPatchSetId(), commit2, userId);
+ db.patchSets().insert(singleton(ps2));
+
+ repo.branch(c.getDest().get()).update(commit2);
+ assertProblems(c);
+ }
+
+ @Test
+ public void missingOwner() throws Exception {
+ Change c = newChange(project, new Account.Id(2));
+ db.changes().insert(singleton(c));
+ RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
+ .parent(tip).create();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ db.patchSets().insert(singleton(ps));
+
+ assertProblems(c, "Missing change owner: 2");
+ }
+
+ @Test
+ public void missingRepo() throws Exception {
+ Change c = newChange(new Project.NameKey("otherproject"), userId);
+ db.changes().insert(singleton(c));
+ insertMissingPatchSet(c, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertProblems(c, "Destination repository not found: otherproject");
+ }
+
+ @Test
+ public void invalidRevision() throws Exception {
+ Change c = insertChange();
+
+ db.patchSets().insert(singleton(newPatchSet(c.currentPatchSetId(),
+ "fooooooooooooooooooooooooooooooooooooooo", userId)));
+ incrementPatchSet(c);
+ insertPatchSet(c);
+
+ assertProblems(c,
+ "Invalid revision on patch set 1:"
+ + " fooooooooooooooooooooooooooooooooooooooo");
+ }
+
+ // No test for ref existing but object missing; InMemoryRepository won't let
+ // us do such a thing.
+
+ @Test
+ public void patchSetObjectAndRefMissing() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(),
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId);
+ db.patchSets().insert(singleton(ps));
+
+ assertProblems(c,
+ "Ref missing: " + ps.getId().toRefName(),
+ "Object missing: patch set 1: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ }
+
+ @Test
+ public void patchSetObjectAndRefMissingWithFix() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(),
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), userId);
+ db.patchSets().insert(singleton(ps));
+
+ String refName = ps.getId().toRefName();
+ List<ProblemInfo> problems = checker.check(c, new FixInput()).problems();
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + refName);
+ assertThat(p.status).isNull();
+ }
+
+ @Test
+ public void patchSetRefMissing() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = insertPatchSet(c);
+ String refName = ps.getId().toRefName();
+ repo.update("refs/other/foo", ObjectId.fromString(ps.getRevision().get()));
+ deleteRef(refName);
+
+ assertProblems(c, "Ref missing: " + refName);
+ }
+
+ @Test
+ public void patchSetRefMissingWithFix() throws Exception {
+ Change c = insertChange();
+ PatchSet ps = insertPatchSet(c);
+ String refName = ps.getId().toRefName();
+ repo.update("refs/other/foo", ObjectId.fromString(ps.getRevision().get()));
+ deleteRef(refName);
+
+ List<ProblemInfo> problems = checker.check(c, new FixInput()).problems();
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + refName);
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Repaired patch set ref");
+
+ assertThat(repo.getRepository().getRef(refName).getObjectId().name())
+ .isEqualTo(ps.getRevision().get());
+ }
+
+ @Test
+ public void patchSetObjectAndRefMissingWithDeletingPatchSet()
+ throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertPatchSet(c);
+ incrementPatchSet(c);
+ PatchSet ps2 = insertMissingPatchSet(c,
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ FixInput fix = new FixInput();
+ fix.deletePatchSetIfCommitMissing = true;
+ List<ProblemInfo> problems = checker.check(c, fix).problems();
+ assertThat(problems).hasSize(2);
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps2.getId().toRefName());
+ assertThat(p.status).isNull();
+ p = problems.get(1);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 2: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Deleted patch set");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.currentPatchSetId().get()).isEqualTo(1);
+ assertThat(db.patchSets().get(ps1.getId())).isNotNull();
+ assertThat(db.patchSets().get(ps2.getId())).isNull();
+ }
+
+ @Test
+ public void patchSetMultipleObjectsMissingWithDeletingPatchSets()
+ throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertPatchSet(c);
+
+ incrementPatchSet(c);
+ PatchSet ps2 = insertMissingPatchSet(c,
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ incrementPatchSet(c);
+ PatchSet ps3 = insertPatchSet(c);
+
+ incrementPatchSet(c);
+ PatchSet ps4 = insertMissingPatchSet(c,
+ "c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee");
+
+ FixInput fix = new FixInput();
+ fix.deletePatchSetIfCommitMissing = true;
+ List<ProblemInfo> problems = checker.check(c, fix).problems();
+ assertThat(problems).hasSize(4);
+
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps4.getId().toRefName());
+ assertThat(p.status).isNull();
+
+ p = problems.get(1);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 4: c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Deleted patch set");
+
+ p = problems.get(2);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps2.getId().toRefName());
+ assertThat(p.status).isNull();
+
+ p = problems.get(3);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 2: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Deleted patch set");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.currentPatchSetId().get()).isEqualTo(3);
+ assertThat(db.patchSets().get(ps1.getId())).isNotNull();
+ assertThat(db.patchSets().get(ps2.getId())).isNull();
+ assertThat(db.patchSets().get(ps3.getId())).isNotNull();
+ assertThat(db.patchSets().get(ps4.getId())).isNull();
+ }
+
+ @Test
+ public void onlyPatchSetObjectMissingWithFix() throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertMissingPatchSet(c,
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ FixInput fix = new FixInput();
+ fix.deletePatchSetIfCommitMissing = true;
+ List<ProblemInfo> problems = checker.check(c, fix).problems();
+ assertThat(problems).hasSize(2);
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo("Ref missing: " + ps1.getId().toRefName());
+ assertThat(p.status).isNull();
+ p = problems.get(1);
+ assertThat(p.message).isEqualTo(
+ "Object missing: patch set 1: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIX_FAILED);
+ assertThat(p.outcome)
+ .isEqualTo("Cannot delete patch set; no patch sets would remain");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.currentPatchSetId().get()).isEqualTo(1);
+ assertThat(db.patchSets().get(ps1.getId())).isNotNull();
+ }
+
+ @Test
+ public void currentPatchSetMissing() throws Exception {
+ Change c = insertChange();
+ assertProblems(c, "Current patch set 1 not found");
+ }
+
+ @Test
+ public void duplicatePatchSetRevisions() throws Exception {
+ Change c = insertChange();
+ PatchSet ps1 = insertPatchSet(c);
+ String rev = ps1.getRevision().get();
+ incrementPatchSet(c);
+ PatchSet ps2 = insertMissingPatchSet(c, rev);
+ updatePatchSetRef(ps2);
+
+ assertProblems(c,
+ "Multiple patch sets pointing to " + rev + ": [1, 2]");
+ }
+
+ @Test
+ public void missingDestRef() throws Exception {
+ RefUpdate ru = repo.getRepository().updateRef("refs/heads/master");
+ ru.setForceUpdate(true);
+ assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
+ Change c = insertChange();
+ RevCommit commit = repo.commit().create();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ updatePatchSetRef(ps);
+ db.patchSets().insert(singleton(ps));
+
+ assertProblems(c, "Destination ref not found (may be new branch): master");
+ }
+
+ @Test
+ public void mergedChangeIsNotMerged() throws Exception {
+ Change c = insertChange();
+ c.setStatus(Change.Status.MERGED);
+ PatchSet ps = insertPatchSet(c);
+ String rev = ps.getRevision().get();
+
+ assertProblems(c,
+ "Patch set 1 (" + rev + ") is not merged into destination ref"
+ + " master (" + tip.name() + "), but change status is MERGED");
+ }
+
+ @Test
+ public void newChangeIsMerged() throws Exception {
+ Change c = insertChange();
+ RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
+ .parent(tip).create();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ db.patchSets().insert(singleton(ps));
+ repo.branch(c.getDest().get()).update(commit);
+
+ assertProblems(c,
+ "Patch set 1 (" + commit.name() + ") is merged into destination ref"
+ + " master (" + commit.name() + "), but change status is NEW");
+ }
+
+ @Test
+ public void newChangeIsMergedWithFix() throws Exception {
+ Change c = insertChange();
+ RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
+ .parent(tip).create();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ db.patchSets().insert(singleton(ps));
+ repo.branch(c.getDest().get()).update(commit);
+
+ List<ProblemInfo> problems = checker.check(c, new FixInput()).problems();
+ assertThat(problems).hasSize(1);
+ ProblemInfo p = problems.get(0);
+ assertThat(p.message).isEqualTo(
+ "Patch set 1 (" + commit.name() + ") is merged into destination ref"
+ + " master (" + commit.name() + "), but change status is NEW");
+ assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
+ assertThat(p.outcome).isEqualTo("Marked change as merged");
+
+ c = db.changes().get(c.getId());
+ assertThat(c.getStatus()).isEqualTo(Change.Status.MERGED);
+ assertProblems(c);
+ }
+
+ private Change insertChange() throws Exception {
+ Change c = newChange(project, userId);
+ db.changes().insert(singleton(c));
+ return c;
+ }
+
+ private void incrementPatchSet(Change c) throws Exception {
+ TestChanges.incrementPatchSet(c);
+ db.changes().upsert(singleton(c));
+ }
+
+ private PatchSet insertPatchSet(Change c) throws Exception {
+ db.changes().upsert(singleton(c));
+ RevCommit commit = repo.branch(c.currentPatchSetId().toRefName()).commit()
+ .parent(tip).create();
+ PatchSet ps = newPatchSet(c.currentPatchSetId(), commit, userId);
+ updatePatchSetRef(ps);
+ db.patchSets().insert(singleton(ps));
+ return ps;
+ }
+
+ private PatchSet insertMissingPatchSet(Change c, String id) throws Exception {
+ PatchSet ps = newPatchSet(c.currentPatchSetId(),
+ ObjectId.fromString(id), userId);
+ db.patchSets().insert(singleton(ps));
+ return ps;
+ }
+
+ private void updatePatchSetRef(PatchSet ps) throws Exception {
+ repo.update(ps.getId().toRefName(),
+ ObjectId.fromString(ps.getRevision().get()));
+ }
+
+ private void deleteRef(String refName) throws Exception {
+ RefUpdate ru = repo.getRepository().updateRef(refName, true);
+ ru.setForceUpdate(true);
+ assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
+ }
+
+ private void assertProblems(Change c, String... expected) {
+ assertThat(Lists.transform(checker.check(c).problems(),
+ new Function<ProblemInfo, String>() {
+ @Override
+ public String apply(ProblemInfo in) {
+ checkArgument(in.status == null,
+ "Status is not null: " + in.message);
+ return in.message;
+ }
+ })).containsExactly((Object[]) expected);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java
new file mode 100644
index 0000000000..d5b722c354
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2014 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 static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Sets;
+
+import org.junit.Test;
+
+public class HashtagsTest {
+ @Test
+ public void emptyCommitMessage() throws Exception {
+ assertThat((Iterable<?>)HashtagsUtil.extractTags("")).isEmpty();
+ }
+
+ @Test
+ public void nullCommitMessage() throws Exception {
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(null)).isEmpty();
+ }
+
+ @Test
+ public void noHashtags() throws Exception {
+ String commitMessage = "Subject\n\nLine 1\n\nLine 2";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage)).isEmpty();
+ }
+
+ @Test
+ public void singleHashtag() throws Exception {
+ String commitMessage = "#Subject\n\nLine 1\n\nLine 2";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(Sets.newHashSet("Subject"));
+ }
+
+ @Test
+ public void singleHashtagNumeric() throws Exception {
+ String commitMessage = "Subject\n\n#123\n\nLine 2";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(Sets.newHashSet("123"));
+ }
+
+ @Test
+ public void multipleHashtags() throws Exception {
+ String commitMessage = "#Subject\n\n#Hashtag\n\nLine 2";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(Sets.newHashSet("Subject", "Hashtag"));
+ }
+
+ @Test
+ public void repeatedHashtag() throws Exception {
+ String commitMessage = "#Subject\n\n#Hashtag1\n\n#Hashtag2\n\n#Hashtag1";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(
+ Sets.newHashSet("Subject", "Hashtag1", "Hashtag2"));
+ }
+
+ @Test
+ public void multipleHashtagsNoSpaces() throws Exception {
+ String commitMessage = "Subject\n\n#Hashtag1#Hashtag2";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(Sets.newHashSet("Hashtag1"));
+ }
+
+ @Test
+ public void hyphenatedHashtag() throws Exception {
+ String commitMessage = "Subject\n\n#Hyphenated-Hashtag";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(Sets.newHashSet("Hyphenated-Hashtag"));
+ }
+
+ @Test
+ public void underscoredHashtag() throws Exception {
+ String commitMessage = "Subject\n\n#Underscored_Hashtag";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(Sets.newHashSet("Underscored_Hashtag"));
+ }
+
+ @Test
+ public void hashtagsWithAccentedCharacters() throws Exception {
+ String commitMessage = "Jag #måste #öva på min #Svenska!\n\n"
+ + "Jag behöver en #läkare.";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage))
+ .containsExactlyElementsIn(
+ Sets.newHashSet("måste", "öva", "Svenska", "läkare"));
+ }
+
+ @Test
+ public void hashWithoutHashtag() throws Exception {
+ String commitMessage = "Subject\n\n# Text";
+ assertThat((Iterable<?>)HashtagsUtil.extractTags(commitMessage)).isEmpty();
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
index 1d0626cefc..4cd31abdea 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -63,6 +63,7 @@ public class IncludedInResolverTest extends RepositoryTestCase {
private RevWalk revWalk;
+ @Override
@Before
public void setUp() throws Exception {
super.setUp();
@@ -85,6 +86,8 @@ public class IncludedInResolverTest extends RepositoryTestCase {
*/
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
Git git = new Git(db);
revWalk = new RevWalk(db);
// Version 1.0
@@ -125,6 +128,7 @@ public class IncludedInResolverTest extends RepositoryTestCase {
.setAnnotated(true).call();
}
+ @Override
@After
public void tearDown() throws Exception {
revWalk.close();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
index cc19811cf6..480efb451e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.config;
-import org.junit.Test;
-
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -23,6 +21,8 @@ import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
import java.util.concurrent.TimeUnit;
public class ConfigUtilTest {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
index e6e15eb76b..d5f68cc15f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
@@ -19,14 +19,11 @@ import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.joda.time.DateTime;
import org.junit.Test;
-import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
public class ScheduleConfigTest {
@@ -59,43 +56,31 @@ public class ScheduleConfigTest {
}
@Test
- public void testCustomKeys() throws ConfigInvalidException {
- Config rc = readConfig(MessageFormat.format(
- "[section \"subsection\"]\n{0} = {1}\n{2} = {3}\n",
- "myStartTime", "01:00", "myInterval", "1h"));
-
- ScheduleConfig scheduleConfig;
-
- scheduleConfig = new ScheduleConfig(rc, "section",
- "subsection", "myInterval", "myStartTime");
- assertNotEquals(scheduleConfig.getInterval(), ScheduleConfig.MISSING_CONFIG);
- assertNotEquals(scheduleConfig.getInitialDelay(), ScheduleConfig.MISSING_CONFIG);
-
- scheduleConfig = new ScheduleConfig(rc, "section",
- "subsection", "nonExistent", "myStartTime");
- assertEquals(scheduleConfig.getInterval(), ScheduleConfig.MISSING_CONFIG);
- assertEquals(scheduleConfig.getInitialDelay(), ScheduleConfig.MISSING_CONFIG);
+ public void testCustomKeys() {
+ Config rc = new Config();
+ rc.setString("a", "b", "i", "1h");
+ rc.setString("a", "b", "s", "01:00");
+
+ ScheduleConfig s = new ScheduleConfig(rc, "a", "b", "i", "s", NOW);
+ assertEquals(ms(1, HOURS), s.getInterval());
+ assertEquals(ms(1, HOURS), s.getInitialDelay());
+
+ s = new ScheduleConfig(rc, "a", "b", "myInterval", "myStart", NOW);
+ assertEquals(s.getInterval(), ScheduleConfig.MISSING_CONFIG);
+ assertEquals(s.getInitialDelay(), ScheduleConfig.MISSING_CONFIG);
}
- private static long initialDelay(String startTime, String interval)
- throws ConfigInvalidException {
- return config(startTime, interval).getInitialDelay();
+ private static long initialDelay(String startTime, String interval) {
+ return new ScheduleConfig(
+ config(startTime, interval),
+ "section", "subsection", NOW).getInitialDelay();
}
- private static ScheduleConfig config(String startTime, String interval)
- throws ConfigInvalidException {
- Config rc =
- readConfig(MessageFormat.format(
- "[section \"subsection\"]\nstartTime = {0}\ninterval = {1}\n",
- startTime, interval));
- return new ScheduleConfig(rc, "section", "subsection", NOW);
- }
-
- private static Config readConfig(String dat)
- throws ConfigInvalidException {
- Config config = new Config();
- config.fromText(dat);
- return config;
+ private static Config config(String startTime, String interval) {
+ Config rc = new Config();
+ rc.setString("section", "subsection", "startTime", startTime);
+ rc.setString("section", "subsection", "interval", interval);
+ return rc;
}
private static long ms(int cnt, TimeUnit unit) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
new file mode 100644
index 0000000000..8c963bd759
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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.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 org.junit.Test;
+
+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);
+ String refName = RefNames.refsEdit(accountId, changeId, psId);
+ assertEquals("refs/users/42/1000042/edit-56414/50", refName);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
new file mode 100644
index 0000000000..7eed35f6ef
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2015 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 com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class EventTypesTest {
+ public static class TestEvent extends Event {
+ public TestEvent() {
+ super("test-event");
+ }
+ }
+
+ public static class AnotherTestEvent extends Event {
+ public AnotherTestEvent() {
+ super("another-test-event");
+ }
+ }
+
+ @Test
+ public void testEventTypeRegistration() {
+ EventTypes.registerClass(new TestEvent());
+ EventTypes.registerClass(new AnotherTestEvent());
+ assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
+ assertThat(EventTypes.getClass("another-test-event"))
+ .isEqualTo(AnotherTestEvent.class);
+ }
+
+ @Test
+ public void testGetClassForNonExistingType() {
+ Class<?> clazz = EventTypes.getClass("does-not-exist-event");
+ assertThat(clazz).isNull();
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/GroupListTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/GroupListTest.java
new file mode 100644
index 0000000000..e741a91c1c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/GroupListTest.java
@@ -0,0 +1,121 @@
+// Copyright (C) 2014 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 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 com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+public class GroupListTest {
+
+ private static final String TEXT =
+ "# UUID \tGroup Name\n" + "#\n"
+ + "d96b998f8a66ff433af50befb975d0e2bb6e0999\tNon-Interactive Users\n"
+ + "ebe31c01aec2c9ac3b3c03e87a47450829ff4310\tAdministrators\n";
+
+ private GroupList groupList;
+
+ @Before
+ public void setup() throws IOException {
+ ValidationError.Sink sink = createNiceMock(ValidationError.Sink.class);
+ replay(sink);
+ groupList = GroupList.parse(TEXT, sink);
+ }
+
+ @Test
+ public void testByUUID() throws Exception {
+ AccountGroup.UUID uuid =
+ new AccountGroup.UUID("d96b998f8a66ff433af50befb975d0e2bb6e0999");
+
+ GroupReference groupReference = groupList.byUUID(uuid);
+
+ assertEquals(uuid, groupReference.getUUID());
+ assertEquals("Non-Interactive Users", groupReference.getName());
+ }
+
+ @Test
+ public void testPut() {
+ AccountGroup.UUID uuid = new AccountGroup.UUID("abc");
+ GroupReference groupReference = new GroupReference(uuid, "Hutzliputz");
+
+ groupList.put(uuid, groupReference);
+
+ assertEquals(3, groupList.references().size());
+ GroupReference found = groupList.byUUID(uuid);
+ assertEquals(groupReference, found);
+ }
+
+ @Test
+ public void testReferences() throws Exception {
+ Collection<GroupReference> result = groupList.references();
+
+ assertEquals(2, result.size());
+ AccountGroup.UUID uuid =
+ new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
+ GroupReference expected = new GroupReference(uuid, "Administrators");
+
+ assertTrue(result.contains(expected));
+ }
+
+ @Test
+ public void testUUIDs() throws Exception {
+ Set<AccountGroup.UUID> result = groupList.uuids();
+
+ assertEquals(2, result.size());
+ AccountGroup.UUID expected =
+ new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
+ assertTrue(result.contains(expected));
+ }
+
+ @Test
+ public void testValidationError() throws Exception {
+ ValidationError.Sink sink = createMock(ValidationError.Sink.class);
+ sink.error(anyObject(ValidationError.class));
+ expectLastCall().times(2);
+ replay(sink);
+ groupList = GroupList.parse(TEXT.replace("\t", " "), sink);
+ verify(sink);
+ }
+
+ @Test
+ public void testRetainAll() throws Exception {
+ AccountGroup.UUID uuid =
+ new AccountGroup.UUID("d96b998f8a66ff433af50befb975d0e2bb6e0999");
+ groupList.retainUUIDs(Collections.singleton(uuid));
+
+ assertNotNull(groupList.byUUID(uuid));
+ assertNull(groupList.byUUID(new AccountGroup.UUID(
+ "ebe31c01aec2c9ac3b3c03e87a47450829ff4310")));
+ }
+
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
index 40088e9506..78a4c5cf7b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -22,18 +22,18 @@ import static com.google.gerrit.server.project.Util.value;
import static org.junit.Assert.assertEquals;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.lifecycle.LifecycleManager;
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.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
@@ -41,7 +41,6 @@ import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.LabelNormalizer.Result;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.schema.SchemaCreator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.inject.Guice;
@@ -112,7 +111,6 @@ public class LabelNormalizerTest {
new Change.Id(1), userId,
new Branch.NameKey(allProjects, "refs/heads/master"),
TimeUtil.nowTs());
- ChangeUtil.computeSortKey(change);
PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(change.getId(), 1));
ps.setSubject("Test change");
change.setCurrentPatchSet(ps);
@@ -139,7 +137,7 @@ public class LabelNormalizerTest {
PatchSetApproval cr = psa(userId, "Code-Review", 2);
PatchSetApproval v = psa(userId, "Verified", 1);
- assertEquals(new Result(
+ assertEquals(Result.create(
list(v),
list(copy(cr, 1)),
list()),
@@ -155,7 +153,7 @@ public class LabelNormalizerTest {
PatchSetApproval cr = psa(userId, "Code-Review", 5);
PatchSetApproval v = psa(userId, "Verified", 5);
- assertEquals(new Result(
+ assertEquals(Result.create(
list(),
list(copy(cr, 2), copy(v, 1)),
list()),
@@ -166,7 +164,7 @@ public class LabelNormalizerTest {
public void emptyPermissionRangeOmitsResult() throws Exception {
PatchSetApproval cr = psa(userId, "Code-Review", 1);
PatchSetApproval v = psa(userId, "Verified", 1);
- assertEquals(new Result(
+ assertEquals(Result.create(
list(),
list(),
list(cr, v)),
@@ -181,7 +179,7 @@ public class LabelNormalizerTest {
PatchSetApproval cr = psa(userId, "Code-Review", 0);
PatchSetApproval v = psa(userId, "Verified", 0);
- assertEquals(new Result(
+ assertEquals(Result.create(
list(cr),
list(),
list(v)),
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
index bb109f8b00..c2f3f6f4cc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
@@ -21,6 +21,7 @@ import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -29,7 +30,6 @@ import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.SubmoduleSubscriptionAccess;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.ResultSet;
@@ -118,34 +118,37 @@ public class SubmoduleOpTest extends LocalDiskRepositoryTestCase {
public void testEmptyCommit() throws Exception {
expect(schemaFactory.open()).andReturn(schema);
- final Repository realDb = createWorkRepository();
- final Git git = new Git(realDb);
+ try (Repository realDb = createWorkRepository()) {
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
+ final Git git = new Git(realDb);
- final RevCommit mergeTip = git.commit().setMessage("test").call();
+ final RevCommit mergeTip = git.commit().setMessage("test").call();
- final Branch.NameKey branchNameKey =
- new Branch.NameKey(new Project.NameKey("test-project"), "test-branch");
+ final Branch.NameKey branchNameKey =
+ new Branch.NameKey(new Project.NameKey("test-project"), "test-branch");
- expect(urlProvider.get()).andReturn("http://localhost:8080");
+ expect(urlProvider.get()).andReturn("http://localhost:8080");
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- final ResultSet<SubmoduleSubscription> emptySubscriptions =
- new ListResultSet<>(new ArrayList<SubmoduleSubscription>());
- expect(subscriptions.bySubmodule(branchNameKey)).andReturn(
- emptySubscriptions);
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ final ResultSet<SubmoduleSubscription> emptySubscriptions =
+ new ListResultSet<>(new ArrayList<SubmoduleSubscription>());
+ expect(subscriptions.bySubmodule(branchNameKey)).andReturn(
+ emptySubscriptions);
- schema.close();
+ schema.close();
- doReplay();
+ doReplay();
- final SubmoduleOp submoduleOp =
- new SubmoduleOp(branchNameKey, mergeTip, new RevWalk(realDb), urlProvider,
- schemaFactory, realDb, null, new ArrayList<Change>(), null, null,
- null, null, null, null);
+ final SubmoduleOp submoduleOp =
+ new SubmoduleOp(branchNameKey, mergeTip, new RevWalk(realDb), urlProvider,
+ schemaFactory, realDb, null, new ArrayList<Change>(), null, null,
+ null, null, null, null);
- submoduleOp.update();
+ submoduleOp.update();
- doVerify();
+ doVerify();
+ }
}
/**
@@ -588,85 +591,89 @@ public class SubmoduleOpTest extends LocalDiskRepositoryTestCase {
public void testOneSubscriberToUpdate() throws Exception {
expect(schemaFactory.open()).andReturn(schema);
- final Repository sourceRepository = createWorkRepository();
- final Git sourceGit = new Git(sourceRepository);
+ try (Repository sourceRepository = createWorkRepository();
+ Repository targetRepository = createWorkRepository()) {
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
+ final Git sourceGit = new Git(sourceRepository);
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
+ final Git targetGit = new Git(targetRepository);
- addRegularFileToIndex("file.txt", "test content", sourceRepository);
+ addRegularFileToIndex("file.txt", "test content", sourceRepository);
- final RevCommit sourceMergeTip =
- sourceGit.commit().setMessage("test").call();
-
- final Branch.NameKey sourceBranchNameKey =
- new Branch.NameKey(new Project.NameKey("source-project"),
- "refs/heads/master");
+ final RevCommit sourceMergeTip =
+ sourceGit.commit().setMessage("test").call();
- final CodeReviewCommit codeReviewCommit =
- new CodeReviewCommit(sourceMergeTip.toObjectId());
- final Change submittedChange = new Change(
- new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
- new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
+ final Branch.NameKey sourceBranchNameKey =
+ new Branch.NameKey(new Project.NameKey("source-project"),
+ "refs/heads/master");
- final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>();
- mergedCommits.put(submittedChange.getId(), codeReviewCommit);
+ final CodeReviewCommit codeReviewCommit =
+ new CodeReviewCommit(sourceMergeTip.toObjectId());
+ final Change submittedChange = new Change(
+ new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
+ new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
- final List<Change> submitted = new ArrayList<>();
- submitted.add(submittedChange);
+ final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>();
+ mergedCommits.put(submittedChange.getId(), codeReviewCommit);
- final Repository targetRepository = createWorkRepository();
- final Git targetGit = new Git(targetRepository);
+ final List<Change> submitted = new ArrayList<>();
+ submitted.add(submittedChange);
- addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository);
+ addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository);
- targetGit.commit().setMessage("test").call();
+ targetGit.commit().setMessage("test").call();
- final Branch.NameKey targetBranchNameKey =
- new Branch.NameKey(new Project.NameKey("target-project"),
- sourceBranchNameKey.get());
+ final Branch.NameKey targetBranchNameKey =
+ new Branch.NameKey(new Project.NameKey("target-project"),
+ sourceBranchNameKey.get());
- expect(urlProvider.get()).andReturn("http://localhost:8080");
+ expect(urlProvider.get()).andReturn("http://localhost:8080");
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- final ResultSet<SubmoduleSubscription> subscribers =
- new ListResultSet<>(Collections
- .singletonList(new SubmoduleSubscription(targetBranchNameKey,
- sourceBranchNameKey, "source-project")));
- expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn(
- subscribers);
-
- expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
- .andReturn(targetRepository).anyTimes();
-
- Capture<RefUpdate> ruCapture = new Capture<>();
- gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
- capture(ruCapture));
- changeHooks.doRefUpdatedHook(eq(targetBranchNameKey),
- anyObject(RefUpdate.class), EasyMock.<Account>isNull());
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ final ResultSet<SubmoduleSubscription> subscribers =
+ new ListResultSet<>(Collections
+ .singletonList(new SubmoduleSubscription(targetBranchNameKey,
+ sourceBranchNameKey, "source-project")));
+ expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn(
+ subscribers);
+
+ expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
+ .andReturn(targetRepository).anyTimes();
+
+ Capture<RefUpdate> ruCapture = new Capture<>();
+ gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
+ capture(ruCapture));
+ changeHooks.doRefUpdatedHook(eq(targetBranchNameKey),
+ anyObject(RefUpdate.class), EasyMock.<Account>isNull());
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- final ResultSet<SubmoduleSubscription> emptySubscriptions =
- new ListResultSet<>(new ArrayList<SubmoduleSubscription>());
- expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn(
- emptySubscriptions);
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ final ResultSet<SubmoduleSubscription> emptySubscriptions =
+ new ListResultSet<>(new ArrayList<SubmoduleSubscription>());
+ expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn(
+ emptySubscriptions);
- schema.close();
+ schema.close();
- final PersonIdent myIdent =
- new PersonIdent("test-user", "test-user@email.com");
+ final PersonIdent myIdent =
+ new PersonIdent("test-user", "test-user@email.com");
- doReplay();
+ doReplay();
- final SubmoduleOp submoduleOp =
- new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
- sourceRepository), urlProvider, schemaFactory, sourceRepository,
- new Project(sourceBranchNameKey.getParentKey()), submitted,
- mergedCommits, myIdent, repoManager, gitRefUpdated, null,
- changeHooks);
+ final SubmoduleOp submoduleOp =
+ new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
+ sourceRepository), urlProvider, schemaFactory, sourceRepository,
+ new Project(sourceBranchNameKey.getParentKey()), submitted,
+ mergedCommits, myIdent, repoManager, gitRefUpdated, null,
+ changeHooks);
- submoduleOp.update();
+ submoduleOp.update();
- doVerify();
- RefUpdate ru = ruCapture.getValue();
- assertEquals(ru.getName(), targetBranchNameKey.get());
+ doVerify();
+ RefUpdate ru = ruCapture.getValue();
+ assertEquals(ru.getName(), targetBranchNameKey.get());
+ }
}
/**
@@ -693,86 +700,90 @@ public class SubmoduleOpTest extends LocalDiskRepositoryTestCase {
public void testAvoidingCircularReference() throws Exception {
expect(schemaFactory.open()).andReturn(schema);
- final Repository sourceRepository = createWorkRepository();
- final Git sourceGit = new Git(sourceRepository);
-
- addRegularFileToIndex("file.txt", "test content", sourceRepository);
+ try (Repository sourceRepository = createWorkRepository();
+ Repository targetRepository = createWorkRepository()) {
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
+ final Git sourceGit = new Git(sourceRepository);
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
+ final Git targetGit = new Git(targetRepository);
- final RevCommit sourceMergeTip =
- sourceGit.commit().setMessage("test").call();
+ addRegularFileToIndex("file.txt", "test content", sourceRepository);
- final Branch.NameKey sourceBranchNameKey =
- new Branch.NameKey(new Project.NameKey("source-project"),
- "refs/heads/master");
-
- final CodeReviewCommit codeReviewCommit =
- new CodeReviewCommit(sourceMergeTip.toObjectId());
- final Change submittedChange = new Change(
- new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
- new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
+ final RevCommit sourceMergeTip =
+ sourceGit.commit().setMessage("test").call();
- final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>();
- mergedCommits.put(submittedChange.getId(), codeReviewCommit);
+ final Branch.NameKey sourceBranchNameKey =
+ new Branch.NameKey(new Project.NameKey("source-project"),
+ "refs/heads/master");
- final List<Change> submitted = new ArrayList<>();
- submitted.add(submittedChange);
+ final CodeReviewCommit codeReviewCommit =
+ new CodeReviewCommit(sourceMergeTip.toObjectId());
+ final Change submittedChange = new Change(
+ new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
+ new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
- final Repository targetRepository = createWorkRepository();
- final Git targetGit = new Git(targetRepository);
+ final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>();
+ mergedCommits.put(submittedChange.getId(), codeReviewCommit);
- addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository);
+ final List<Change> submitted = new ArrayList<>();
+ submitted.add(submittedChange);
- targetGit.commit().setMessage("test").call();
+ addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository);
- final Branch.NameKey targetBranchNameKey =
- new Branch.NameKey(new Project.NameKey("target-project"),
- sourceBranchNameKey.get());
+ targetGit.commit().setMessage("test").call();
- expect(urlProvider.get()).andReturn("http://localhost:8080");
+ final Branch.NameKey targetBranchNameKey =
+ new Branch.NameKey(new Project.NameKey("target-project"),
+ sourceBranchNameKey.get());
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- final ResultSet<SubmoduleSubscription> subscribers =
- new ListResultSet<>(Collections
- .singletonList(new SubmoduleSubscription(targetBranchNameKey,
- sourceBranchNameKey, "source-project")));
- expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn(
- subscribers);
+ expect(urlProvider.get()).andReturn("http://localhost:8080");
- expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
- .andReturn(targetRepository).anyTimes();
-
- Capture<RefUpdate> ruCapture = new Capture<>();
- gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
- capture(ruCapture));
- changeHooks.doRefUpdatedHook(eq(targetBranchNameKey),
- anyObject(RefUpdate.class), EasyMock.<Account>isNull());
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ final ResultSet<SubmoduleSubscription> subscribers =
+ new ListResultSet<>(Collections
+ .singletonList(new SubmoduleSubscription(targetBranchNameKey,
+ sourceBranchNameKey, "source-project")));
+ expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn(
+ subscribers);
+
+ expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
+ .andReturn(targetRepository).anyTimes();
+
+ Capture<RefUpdate> ruCapture = new Capture<>();
+ gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
+ capture(ruCapture));
+ changeHooks.doRefUpdatedHook(eq(targetBranchNameKey),
+ anyObject(RefUpdate.class), EasyMock.<Account>isNull());
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- final ResultSet<SubmoduleSubscription> incorrectSubscriptions =
- new ListResultSet<SubmoduleSubscription>(Collections
- .singletonList(new SubmoduleSubscription(sourceBranchNameKey,
- targetBranchNameKey, "target-project")));
- expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn(
- incorrectSubscriptions);
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ final ResultSet<SubmoduleSubscription> incorrectSubscriptions =
+ new ListResultSet<>(Collections
+ .singletonList(new SubmoduleSubscription(sourceBranchNameKey,
+ targetBranchNameKey, "target-project")));
+ expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn(
+ incorrectSubscriptions);
- schema.close();
+ schema.close();
- final PersonIdent myIdent =
- new PersonIdent("test-user", "test-user@email.com");
+ final PersonIdent myIdent =
+ new PersonIdent("test-user", "test-user@email.com");
- doReplay();
+ doReplay();
- final SubmoduleOp submoduleOp =
- new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
- sourceRepository), urlProvider, schemaFactory, sourceRepository,
- new Project(sourceBranchNameKey.getParentKey()), submitted,
- mergedCommits, myIdent, repoManager, gitRefUpdated, null, changeHooks);
+ final SubmoduleOp submoduleOp =
+ new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
+ sourceRepository), urlProvider, schemaFactory, sourceRepository,
+ new Project(sourceBranchNameKey.getParentKey()), submitted,
+ mergedCommits, myIdent, repoManager, gitRefUpdated, null, changeHooks);
- submoduleOp.update();
+ submoduleOp.update();
- doVerify();
- RefUpdate ru = ruCapture.getValue();
- assertEquals(ru.getName(), targetBranchNameKey.get());
+ doVerify();
+ RefUpdate ru = ruCapture.getValue();
+ assertEquals(ru.getName(), targetBranchNameKey.get());
+ }
}
/**
@@ -862,67 +873,70 @@ public class SubmoduleOpTest extends LocalDiskRepositoryTestCase {
final List<SubmoduleSubscription> previousSubscriptions) throws Exception {
expect(schemaFactory.open()).andReturn(schema);
- final Repository realDb = createWorkRepository();
- final Git git = new Git(realDb);
+ try (Repository realDb = createWorkRepository()) {
+ // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
+ @SuppressWarnings("resource")
+ final Git git = new Git(realDb);
- addRegularFileToIndex(".gitmodules", gitModulesFileContent, realDb);
+ addRegularFileToIndex(".gitmodules", gitModulesFileContent, realDb);
- final RevCommit mergeTip = git.commit().setMessage("test").call();
+ final RevCommit mergeTip = git.commit().setMessage("test").call();
- expect(urlProvider.get()).andReturn("http://localhost:8080").times(2);
+ expect(urlProvider.get()).andReturn("http://localhost:8080").times(2);
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- expect(subscriptions.bySuperProject(mergedBranch)).andReturn(
- new ListResultSet<>(previousSubscriptions));
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ expect(subscriptions.bySuperProject(mergedBranch)).andReturn(
+ new ListResultSet<>(previousSubscriptions));
- SortedSet<Project.NameKey> existingProjects = new TreeSet<>();
+ SortedSet<Project.NameKey> existingProjects = new TreeSet<>();
- for (SubmoduleSubscription extracted : extractedSubscriptions) {
- existingProjects.add(extracted.getSubmodule().getParentKey());
- }
+ for (SubmoduleSubscription extracted : extractedSubscriptions) {
+ existingProjects.add(extracted.getSubmodule().getParentKey());
+ }
- for (int index = 0; index < extractedSubscriptions.size(); index++) {
- expect(repoManager.list()).andReturn(existingProjects);
- }
+ for (int index = 0; index < extractedSubscriptions.size(); index++) {
+ expect(repoManager.list()).andReturn(existingProjects);
+ }
- final Set<SubmoduleSubscription> alreadySubscribeds = new HashSet<>();
- for (SubmoduleSubscription s : extractedSubscriptions) {
- if (previousSubscriptions.contains(s)) {
- alreadySubscribeds.add(s);
+ final Set<SubmoduleSubscription> alreadySubscribeds = new HashSet<>();
+ for (SubmoduleSubscription s : extractedSubscriptions) {
+ if (previousSubscriptions.contains(s)) {
+ alreadySubscribeds.add(s);
+ }
}
- }
- final Set<SubmoduleSubscription> subscriptionsToRemove =
- new HashSet<>(previousSubscriptions);
- final List<SubmoduleSubscription> subscriptionsToInsert =
- new ArrayList<>(extractedSubscriptions);
+ final Set<SubmoduleSubscription> subscriptionsToRemove =
+ new HashSet<>(previousSubscriptions);
+ final List<SubmoduleSubscription> subscriptionsToInsert =
+ new ArrayList<>(extractedSubscriptions);
- subscriptionsToRemove.removeAll(subscriptionsToInsert);
- subscriptionsToInsert.removeAll(alreadySubscribeds);
+ subscriptionsToRemove.removeAll(subscriptionsToInsert);
+ subscriptionsToInsert.removeAll(alreadySubscribeds);
- if (!subscriptionsToRemove.isEmpty()) {
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- subscriptions.delete(subscriptionsToRemove);
- }
+ if (!subscriptionsToRemove.isEmpty()) {
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ subscriptions.delete(subscriptionsToRemove);
+ }
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- subscriptions.insert(subscriptionsToInsert);
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ subscriptions.insert(subscriptionsToInsert);
- expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
- expect(subscriptions.bySubmodule(mergedBranch)).andReturn(
- new ListResultSet<>(new ArrayList<SubmoduleSubscription>()));
+ expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
+ expect(subscriptions.bySubmodule(mergedBranch)).andReturn(
+ new ListResultSet<>(new ArrayList<SubmoduleSubscription>()));
- schema.close();
+ schema.close();
- doReplay();
+ doReplay();
- final SubmoduleOp submoduleOp =
- new SubmoduleOp(mergedBranch, mergeTip, new RevWalk(realDb),
- urlProvider, schemaFactory, realDb, new Project(mergedBranch
- .getParentKey()), new ArrayList<Change>(), null, null,
- repoManager, null, null, null);
+ final SubmoduleOp submoduleOp =
+ new SubmoduleOp(mergedBranch, mergeTip, new RevWalk(realDb),
+ urlProvider, schemaFactory, realDb, new Project(mergedBranch
+ .getParentKey()), new ArrayList<Change>(), null, null,
+ repoManager, null, null, null);
- submoduleOp.update();
+ submoduleOp.update();
+ }
}
/**
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
index fc0fb32114..9b3d5ede76 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.index;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -22,14 +23,12 @@ import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
-import java.io.IOException;
-
class FakeIndex implements ChangeIndex {
- static Schema<ChangeData> V1 = new Schema<>(1, false,
+ static Schema<ChangeData> V1 = new Schema<>(1,
ImmutableList.<FieldDef<ChangeData, ?>> of(
ChangeField.STATUS));
- static Schema<ChangeData> V2 = new Schema<>(2, false,
+ static Schema<ChangeData> V2 = new Schema<>(2,
ImmutableList.of(
ChangeField.STATUS,
ChangeField.PATH,
@@ -70,17 +69,12 @@ class FakeIndex implements ChangeIndex {
}
@Override
- public void insert(ChangeData cd) {
- throw new UnsupportedOperationException();
- }
-
- @Override
public void replace(ChangeData cd) {
throw new UnsupportedOperationException();
}
@Override
- public void delete(ChangeData cd) {
+ public void delete(Change.Id id) {
throw new UnsupportedOperationException();
}
@@ -108,8 +102,4 @@ class FakeIndex implements ChangeIndex {
public void markReady(boolean ready) {
throw new UnsupportedOperationException();
}
-
- @Override
- public void delete(int id) throws IOException {
- }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
index 50e57642aa..c9a2056c87 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
@@ -26,9 +26,8 @@ public class FakeQueryBuilder extends ChangeQueryBuilder {
new FakeQueryBuilder.Definition<>(
FakeQueryBuilder.class),
new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, indexes, null, null,
- null, null),
- null);
+ null, null, null, null, null, null, null, null, null, null, null,
+ indexes, null, null, null, null));
}
@Operator
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
index fe66ca5638..042459beda 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
@@ -54,9 +54,7 @@ public class IndexRewriteTest {
indexes = new IndexCollection();
indexes.setSearchIndex(index);
queryBuilder = new FakeQueryBuilder(indexes);
- rewrite = new IndexRewriteImpl(
- indexes,
- new BasicChangeRewrites(null));
+ rewrite = new IndexRewriteImpl(indexes, new BasicChangeRewrites());
}
@Test
@@ -99,7 +97,7 @@ public class IndexRewriteTest {
parse("-status:abandoned (status:open OR status:merged)");
assertEquals(
query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
- rewrite.rewrite(in, 0));
+ rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT));
}
@Test
@@ -160,24 +158,26 @@ public class IndexRewriteTest {
}
@Test
- public void testLimit() throws Exception {
- Predicate<ChangeData> in = parse("file:a limit:3");
- Predicate<ChangeData> out = rewrite(in);
+ public void testLimitArgumentOverridesAllLimitPredicates() throws Exception {
+ Predicate<ChangeData> in = parse("limit:1 file:a limit:3");
+ Predicate<ChangeData> out = rewrite(in, 5);
assertSame(AndSource.class, out.getClass());
assertEquals(ImmutableList.of(
- query(in.getChild(0), 3),
- in.getChild(1)),
+ query(in.getChild(1), 5),
+ parse("limit:5"),
+ parse("limit:5")),
out.getChildren());
}
@Test
public void testStartIncreasesLimit() throws Exception {
+ int n = 3;
Predicate<ChangeData> f = parse("file:a");
- Predicate<ChangeData> l = parse("limit:3");
+ Predicate<ChangeData> l = parse("limit:" + n);
Predicate<ChangeData> in = and(f, l);
- assertEquals(and(query(f, 3), l), rewrite.rewrite(in, 0));
- assertEquals(and(query(f, 4), l), rewrite.rewrite(in, 1));
- assertEquals(and(query(f, 5), l), rewrite.rewrite(in, 2));
+ assertEquals(and(query(f, 3), parse("limit:3")), rewrite.rewrite(in, 0, n));
+ assertEquals(and(query(f, 4), parse("limit:4")), rewrite.rewrite(in, 1, n));
+ assertEquals(and(query(f, 5), parse("limit:5")), rewrite.rewrite(in, 2, n));
}
@Test
@@ -222,7 +222,12 @@ public class IndexRewriteTest {
private Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
throws QueryParseException {
- return rewrite.rewrite(in, 0);
+ return rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT);
+ }
+
+ private Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int limit)
+ throws QueryParseException {
+ return rewrite.rewrite(in, 0, limit);
}
private IndexedChangeQuery query(Predicate<ChangeData> p)
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
index 622b31ef36..dbc8f02fd8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.ioutil;
-import org.junit.Test;
-
import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
@@ -25,6 +23,8 @@ import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import org.junit.Test;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -137,8 +137,9 @@ public class BasicSerializationTest {
private static void assertOutput(final byte[] expect,
final ByteArrayOutputStream out) {
final byte[] buf = out.toByteArray();
- for (int i = 0; i < expect.length; i++)
+ for (int i = 0; i < expect.length; i++) {
assertEquals(expect[i], buf[i]);
+ }
}
private static InputStream r(final byte[] buf) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
index 625e4b6ef6..c77e0f6925 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
@@ -14,14 +14,14 @@
package com.google.gerrit.server.mail;
-import org.junit.Test;
-
-import java.io.UnsupportedEncodingException;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import org.junit.Test;
+
+import java.io.UnsupportedEncodingException;
+
public class AddressTest {
@Test
public void testParse_NameEmail1() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index 4f20b63f10..f33f720c5c 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -23,12 +23,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
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.util.TimeUtil;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
new file mode 100644
index 0000000000..1308723945
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -0,0 +1,239 @@
+// Copyright (C) 2014 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.inject.Scopes.SINGLETON;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.CommentRange;
+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.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.FakeRealm;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.AnonymousCowardNameProvider;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.DisableReverseDnsLookup;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitModule;
+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.testutil.FakeAccountCache;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestChanges;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StandardKeyEncoder;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.util.Providers;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.After;
+import org.junit.Before;
+
+import java.sql.Timestamp;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class AbstractChangeNotesTest {
+ private static final TimeZone TZ =
+ TimeZone.getTimeZone("America/Los_Angeles");
+
+ private static final NotesMigration MIGRATION = NotesMigration.allEnabled();
+
+ protected Account.Id otherUserId;
+ protected FakeAccountCache accountCache;
+ protected IdentifiedUser changeOwner;
+ protected IdentifiedUser otherUser;
+ protected InMemoryRepository repo;
+ protected InMemoryRepositoryManager repoManager;
+ protected PersonIdent serverIdent;
+ protected Project.NameKey project;
+
+ @Inject protected IdentifiedUser.GenericFactory userFactory;
+
+ private Injector injector;
+ private String systemTimeZone;
+ private volatile long clockStepMs;
+
+ @Inject private AllUsersNameProvider allUsers;
+
+ @Before
+ public void setUp() throws Exception {
+ setTimeForTesting();
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+
+ serverIdent = new PersonIdent(
+ "Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
+ project = new Project.NameKey("test-project");
+ repoManager = new InMemoryRepositoryManager();
+ repo = repoManager.createRepository(project);
+ accountCache = new FakeAccountCache();
+ Account co = new Account(new 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());
+ ou.setFullName("Other Account");
+ ou.setPreferredEmail("other@account.com");
+ accountCache.put(ou);
+
+ injector = Guice.createInjector(new FactoryModule() {
+ @Override
+ public void configure() {
+ install(new GitModule());
+ bind(NotesMigration.class).toInstance(MIGRATION);
+ bind(GitRepositoryManager.class).toInstance(repoManager);
+ bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
+ bind(CapabilityControl.Factory.class)
+ .toProvider(Providers.<CapabilityControl.Factory> of(null));
+ bind(Config.class).annotatedWith(GerritServerConfig.class)
+ .toInstance(new Config());
+ bind(String.class).annotatedWith(AnonymousCowardName.class)
+ .toProvider(AnonymousCowardNameProvider.class);
+ bind(String.class).annotatedWith(CanonicalWebUrl.class)
+ .toInstance("http://localhost:8080/");
+ bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class)
+ .toInstance(Boolean.FALSE);
+ bind(Realm.class).to(FakeRealm.class);
+ bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
+ bind(AccountCache.class).toInstance(accountCache);
+ bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
+ .toInstance(serverIdent);
+ bind(GitReferenceUpdated.class)
+ .toInstance(GitReferenceUpdated.DISABLED);
+ }
+ });
+
+ injector.injectMembers(this);
+ repoManager.createRepository(allUsers.get());
+ changeOwner = userFactory.create(co.getId());
+ otherUser = userFactory.create(ou.getId());
+ otherUserId = otherUser.getAccountId();
+ }
+
+ private void setTimeForTesting() {
+ systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
+ clockStepMs = MILLISECONDS.convert(1, SECONDS);
+ final AtomicLong clockMs = new AtomicLong(
+ new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
+
+ DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
+ @Override
+ public long getMillis() {
+ return clockMs.getAndAdd(clockStepMs);
+ }
+ });
+ }
+
+ @After
+ public void resetTime() {
+ DateTimeUtils.setCurrentMillisSystem();
+ System.setProperty("user.timezone", systemTimeZone);
+ }
+
+ protected Change newChange() {
+ return TestChanges.newChange(project, changeOwner.getAccountId());
+ }
+
+ protected ChangeUpdate newUpdate(Change c, IdentifiedUser user)
+ throws OrmException {
+ return TestChanges.newUpdate(
+ injector, repoManager, MIGRATION, c, allUsers, user);
+ }
+
+ protected ChangeNotes newNotes(Change c) throws OrmException {
+ return new ChangeNotes(repoManager, MIGRATION, allUsers, c).load();
+ }
+
+ protected static SubmitRecord submitRecord(String status,
+ String errorMessage, SubmitRecord.Label... labels) {
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.valueOf(status);
+ rec.errorMessage = errorMessage;
+ if (labels.length > 0) {
+ rec.labels = ImmutableList.copyOf(labels);
+ }
+ return rec;
+ }
+
+ protected static SubmitRecord.Label submitLabel(String name, String status,
+ Account.Id appliedBy) {
+ SubmitRecord.Label label = new SubmitRecord.Label();
+ label.label = name;
+ label.status = SubmitRecord.Label.Status.valueOf(status);
+ label.appliedBy = appliedBy;
+ return label;
+ }
+
+ protected PatchLineComment newPublishedPatchLineComment(PatchSet.Id psId,
+ String filename, String UUID, CommentRange range, int line,
+ IdentifiedUser commenter, String parentUUID, Timestamp t,
+ String message, short side, String commitSHA1) {
+ return newPatchLineComment(psId, filename, UUID, range, line, commenter,
+ parentUUID, t, message, side, commitSHA1,
+ PatchLineComment.Status.PUBLISHED);
+ }
+
+ protected PatchLineComment newPatchLineComment(PatchSet.Id psId,
+ String filename, String UUID, CommentRange range, int line,
+ IdentifiedUser commenter, String parentUUID, Timestamp t,
+ String message, short side, String commitSHA1,
+ PatchLineComment.Status status) {
+ PatchLineComment comment = new PatchLineComment(
+ new PatchLineComment.Key(
+ new Patch.Key(psId, filename), UUID),
+ line, commenter.getAccountId(), parentUUID, t);
+ comment.setSide(side);
+ comment.setMessage(message);
+ comment.setRange(range);
+ comment.setRevId(new RevId(commitSHA1));
+ comment.setStatus(status);
+ return comment;
+ }
+
+ protected static Timestamp truncate(Timestamp ts) {
+ return new Timestamp((ts.getTime() / 1000) * 1000);
+ }
+
+ protected static Timestamp after(Change c, long millis) {
+ return new Timestamp(c.getCreatedOn().getTime() + millis);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
new file mode 100644
index 0000000000..c41c4ecf0c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -0,0 +1,215 @@
+// Copyright (C) 2014 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 org.junit.Assert.fail;
+
+import com.google.gerrit.common.TimeUtil;
+
+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.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ChangeNotesParserTest extends AbstractChangeNotesTest {
+ private TestRepository<InMemoryRepository> testRepo;
+ private RevWalk walk;
+
+ @Before
+ public void setUpTestRepo() throws Exception {
+ testRepo = new TestRepository<>(repo);
+ walk = new RevWalk(repo);
+ }
+
+ @After
+ public void tearDownTestRepo() throws Exception {
+ walk.close();
+ }
+
+ @Test
+ public void parseAuthor() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n");
+ assertParseFails(writeCommit("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n",
+ new PersonIdent("Change Owner", "owner@example.com",
+ serverIdent.getWhen(), serverIdent.getTimeZone())));
+ assertParseFails(writeCommit("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n",
+ new PersonIdent("Change Owner", "x@gerrit",
+ serverIdent.getWhen(), serverIdent.getTimeZone())));
+ }
+
+ @Test
+ public void parseStatus() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: NEW\n");
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: new\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: OOPS\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Status: NEW\n"
+ + "Status: NEW\n");
+ }
+
+ @Test
+ public void parsePatchSetId() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n");
+ assertParseFails("Update change\n"
+ + "\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Patch-Set: 1\n");
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: x\n");
+ }
+
+ @Test
+ public void parseApproval() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1=+1\n"
+ + "Label: Label2=1\n"
+ + "Label: Label3=0\n"
+ + "Label: Label4=-1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1=X\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: Label1 = 1\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Label: X+Y\n");
+ }
+
+ @Test
+ public void parseSubmitRecords() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Code-Review\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Alternative-Code-Review\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: OOPS\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: NEED: X+Y\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: OK: X+Y: Change Owner <1@gerrit>\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Submitted-with: OK: Code-Review: 1@gerrit\n");
+ }
+
+ @Test
+ public void parseReviewer() throws Exception {
+ assertParseSucceeds("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Reviewer: Change Owner <1@gerrit>\n"
+ + "CC: Other Account <2@gerrit>\n");
+ assertParseFails("Update change\n"
+ + "\n"
+ + "Patch-Set: 1\n"
+ + "Reviewer: 1@gerrit\n");
+ }
+
+ private RevCommit writeCommit(String body) throws Exception {
+ return writeCommit(body, ChangeNoteUtil.newIdent(
+ changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent,
+ "Anonymous Coward"));
+ }
+
+ private RevCommit writeCommit(String body, PersonIdent author)
+ throws Exception {
+ try (ObjectInserter ins = testRepo.getRepository().newObjectInserter()) {
+ CommitBuilder cb = new CommitBuilder();
+ cb.setAuthor(author);
+ cb.setCommitter(new PersonIdent(serverIdent, author.getWhen()));
+ cb.setTreeId(testRepo.tree());
+ cb.setMessage(body);
+ ObjectId id = ins.insert(cb);
+ ins.flush();
+ RevCommit commit = walk.parseCommit(id);
+ walk.parseBody(commit);
+ return commit;
+ }
+ }
+
+ private void assertParseSucceeds(String body) throws Exception {
+ try (ChangeNotesParser parser = newParser(writeCommit(body))) {
+ parser.parseAll();
+ }
+ }
+
+ private void assertParseFails(String body) throws Exception {
+ assertParseFails(writeCommit(body));
+ }
+
+ private void assertParseFails(RevCommit commit) throws Exception {
+ try (ChangeNotesParser parser = newParser(commit)) {
+ parser.parseAll();
+ fail("Expected parse to fail:\n" + commit.getFullMessage());
+ } catch (ConfigInvalidException e) {
+ // Expected.
+ }
+ }
+
+ private ChangeNotesParser newParser(ObjectId tip) throws Exception {
+ return new ChangeNotesParser(newChange(), tip, walk, repoManager);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 4b3b206b47..aea966a281 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -17,330 +17,51 @@ package com.google.gerrit.server.notedb;
import static com.google.gerrit.server.notedb.ReviewerState.CC;
import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER;
import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
-import static com.google.inject.Scopes.SINGLETON;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.SubmitRecord;
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.CommentRange;
-import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
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.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.AnonymousCowardNameProvider;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitModule;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.notedb.CommentsInNotesUtil;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.util.TimeUtil;
-import com.google.gerrit.testutil.TestChanges;
-import com.google.gerrit.testutil.FakeAccountCache;
-import com.google.gerrit.testutil.FakeRealm;
-import com.google.gerrit.testutil.InMemoryRepositoryManager;
-import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StandardKeyEncoder;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.util.Providers;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeUtils;
-import org.joda.time.DateTimeUtils.MillisProvider;
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.junit.Test;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
-import java.util.Date;
+import java.util.LinkedHashSet;
import java.util.List;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicLong;
-
-public class ChangeNotesTest {
- private static final TimeZone TZ =
- TimeZone.getTimeZone("America/Los_Angeles");
-
- private PersonIdent serverIdent;
- private Project.NameKey project;
- private InMemoryRepositoryManager repoManager;
- private InMemoryRepository repo;
- private FakeAccountCache accountCache;
- private IdentifiedUser changeOwner;
- private IdentifiedUser otherUser;
- private Injector injector;
- private String systemTimeZone;
- private volatile long clockStepMs;
-
- @Before
- public void setUp() throws Exception {
- setTimeForTesting();
- KeyUtil.setEncoderImpl(new StandardKeyEncoder());
-
- serverIdent = new PersonIdent(
- "Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
- project = new Project.NameKey("test-project");
- repoManager = new InMemoryRepositoryManager();
- repo = repoManager.createRepository(project);
- accountCache = new FakeAccountCache();
- Account co = new Account(new 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());
- ou.setFullName("Other Account");
- ou.setPreferredEmail("other@account.com");
- accountCache.put(ou);
-
- injector = Guice.createInjector(new FactoryModule() {
- @Override
- public void configure() {
- install(new GitModule());
- bind(NotesMigration.class).toInstance(NotesMigration.allEnabled());
- bind(GitRepositoryManager.class).toInstance(repoManager);
- bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
- bind(CapabilityControl.Factory.class)
- .toProvider(Providers.<CapabilityControl.Factory> of(null));
- bind(Config.class).annotatedWith(GerritServerConfig.class)
- .toInstance(new Config());
- bind(String.class).annotatedWith(AnonymousCowardName.class)
- .toProvider(AnonymousCowardNameProvider.class);
- bind(String.class).annotatedWith(CanonicalWebUrl.class)
- .toInstance("http://localhost:8080/");
- bind(Realm.class).to(FakeRealm.class);
- bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
- bind(AccountCache.class).toInstance(accountCache);
- bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
- .toInstance(serverIdent);
- bind(GitReferenceUpdated.class)
- .toInstance(GitReferenceUpdated.DISABLED);
- }
- });
-
- IdentifiedUser.GenericFactory userFactory =
- injector.getInstance(IdentifiedUser.GenericFactory.class);
- changeOwner = userFactory.create(co.getId());
- otherUser = userFactory.create(ou.getId());
- }
-
- private void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- clockStepMs = MILLISECONDS.convert(1, SECONDS);
- final AtomicLong clockMs = new AtomicLong(
- new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
-
- DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
- @Override
- public long getMillis() {
- return clockMs.getAndAdd(clockStepMs);
- }
- });
- }
-
- @After
- public void resetTime() {
- DateTimeUtils.setCurrentMillisSystem();
- System.setProperty("user.timezone", systemTimeZone);
- }
-
- @Test
- public void approvalsCommitFormatSimple() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(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.commit();
- assertEquals("refs/changes/01/1/meta", update.getRefName());
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Reviewer: Change Owner <1@gerrit>\n"
- + "CC: Other Account <2@gerrit>\n"
- + "Label: Code-Review=-1\n"
- + "Label: Verified=+1\n",
- commit.getFullMessage());
-
- PersonIdent author = commit.getAuthorIdent();
- assertEquals("Change Owner", author.getName());
- assertEquals("1@gerrit", author.getEmailAddress());
- assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
- author.getWhen());
- assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
-
- PersonIdent committer = commit.getCommitterIdent();
- assertEquals("Gerrit Server", committer.getName());
- assertEquals("noreply@gerrit.com", committer.getEmailAddress());
- assertEquals(author.getWhen(), committer.getWhen());
- assertEquals(author.getTimeZone(), committer.getTimeZone());
- } finally {
- walk.close();
- }
- }
-
- @Test
- public void changeMessageCommitFormatSimple() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setChangeMessage("Just a little code change.\n"
- + "How about a new line");
- update.commit();
- assertEquals("refs/changes/01/1/meta", update.getRefName());
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Just a little code change.\n"
- + "How about a new line\n"
- + "\n"
- + "Patch-set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.close();
- }
- }
-
- @Test
- public void approvalTombstoneCommitFormat() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.removeApproval("Code-Review");
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Label: -Code-Review\n",
- commit.getFullMessage());
- } finally {
- walk.close();
- }
- }
-
- @Test
- public void submitCommitFormat() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setSubject("Submit patch set 1");
-
- update.submit(ImmutableList.of(
- submitRecord("NOT_READY", null,
- submitLabel("Verified", "OK", changeOwner.getAccountId()),
- submitLabel("Code-Review", "NEED", null)),
- submitRecord("NOT_READY", null,
- submitLabel("Verified", "OK", changeOwner.getAccountId()),
- submitLabel("Alternative-Code-Review", "NEED", null))));
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Submit patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Status: submitted\n"
- + "Submitted-with: NOT_READY\n"
- + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
- + "Submitted-with: NEED: Code-Review\n"
- + "Submitted-with: NOT_READY\n"
- + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
- + "Submitted-with: NEED: Alternative-Code-Review\n",
- commit.getFullMessage());
-
- PersonIdent author = commit.getAuthorIdent();
- assertEquals("Change Owner", author.getName());
- assertEquals("1@gerrit", author.getEmailAddress());
- assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
- author.getWhen());
- assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
-
- PersonIdent committer = commit.getCommitterIdent();
- assertEquals("Gerrit Server", committer.getName());
- assertEquals("noreply@gerrit.com", committer.getEmailAddress());
- assertEquals(author.getWhen(), committer.getWhen());
- assertEquals(author.getTimeZone(), committer.getTimeZone());
- } finally {
- walk.close();
- }
- }
-
- @Test
- public void submitWithErrorMessage() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.setSubject("Submit patch set 1");
-
- update.submit(ImmutableList.of(
- submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
- update.commit();
-
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Submit patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Status: submitted\n"
- + "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.close();
- }
- }
+public class ChangeNotesTest extends AbstractChangeNotesTest {
@Test
public void approvalsOnePatchSet() throws Exception {
Change c = newChange();
@@ -612,6 +333,51 @@ public class ChangeNotesTest {
}
@Test
+ public void emptyChangeUpdate() throws Exception {
+ ChangeUpdate update = newUpdate(newChange(), changeOwner);
+ update.commit();
+ assertNull(update.getRevision());
+ }
+
+ @Test
+ public void hashtagCommit() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ LinkedHashSet<String> hashtags = new LinkedHashSet<>();
+ hashtags.add("tag1");
+ hashtags.add("tag2");
+ update.setHashtags(hashtags);
+ update.commit();
+ try (RevWalk walk = new RevWalk(repo)) {
+ RevCommit commit = walk.parseCommit(update.getRevision());
+ walk.parseBody(commit);
+ assertTrue(commit.getFullMessage().endsWith("Hashtags: tag1,tag2\n"));
+ }
+ }
+
+ @Test
+ public void hashtagChangeNotes() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ LinkedHashSet<String> hashtags = new LinkedHashSet<>();
+ hashtags.add("tag1");
+ hashtags.add("tag2");
+ update.setHashtags(hashtags);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ assertEquals(hashtags, notes.getHashtags());
+ }
+
+ @Test
+ public void emptyExceptSubject() throws Exception {
+ ChangeUpdate update = newUpdate(newChange(), changeOwner);
+ update.setSubject("Create change");
+ update.commit();
+ assertNotNull(update.getRevision());
+ }
+
+ @Test
public void multipleUpdatesInBatch() throws Exception {
Change c = newChange();
ChangeUpdate update1 = newUpdate(c, changeOwner);
@@ -644,6 +410,109 @@ public class ChangeNotesTest {
}
@Test
+ public void multipleUpdatesIncludingComments() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update1 = newUpdate(c, otherUser);
+ String uuid1 = "uuid1";
+ String message1 = "comment 1";
+ CommentRange range1 = new CommentRange(1, 1, 2, 1);
+ Timestamp time1 = TimeUtil.nowTs();
+ PatchSet.Id psId = c.currentPatchSetId();
+ BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ BatchMetaDataUpdate batch = update1.openUpdateInBatch(bru);
+ PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
+ uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
+ (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+ update1.setPatchSetId(psId);
+ update1.upsertComment(comment1);
+ update1.writeCommit(batch);
+ ChangeUpdate update2 = newUpdate(c, otherUser);
+ update2.putApproval("Code-Review", (short) 2);
+ update2.writeCommit(batch);
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ batch.commit();
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+
+ ChangeNotes notes = newNotes(c);
+ ObjectId tip = notes.getRevision();
+ RevCommit commitWithApprovals = rw.parseCommit(tip);
+ assertNotNull(commitWithApprovals);
+ RevCommit commitWithComments = commitWithApprovals.getParent(0);
+ assertNotNull(commitWithComments);
+
+ ChangeNotesParser notesWithComments =
+ new ChangeNotesParser(c, commitWithComments.copy(), rw, repoManager);
+ notesWithComments.parseAll();
+ ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals1 =
+ notesWithComments.buildApprovals();
+ assertEquals(0, approvals1.size());
+ assertEquals(1, notesWithComments.commentsForBase.size());
+ notesWithComments.close();
+
+ ChangeNotesParser notesWithApprovals =
+ new ChangeNotesParser(c, commitWithApprovals.copy(), rw, repoManager);
+ notesWithApprovals.parseAll();
+ ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals2 =
+ notesWithApprovals.buildApprovals();
+ assertEquals(1, approvals2.size());
+ assertEquals(1, notesWithApprovals.commentsForBase.size());
+ notesWithApprovals.close();
+ } finally {
+ batch.close();
+ }
+ }
+
+ @Test
+ public void multipleUpdatesAcrossRefs() throws Exception {
+ Change c1 = newChange();
+ ChangeUpdate update1 = newUpdate(c1, changeOwner);
+ update1.putApproval("Verified", (short) 1);
+
+ Change c2 = newChange();
+ ChangeUpdate update2 = newUpdate(c2, otherUser);
+ update2.putApproval("Code-Review", (short) 2);
+
+ BatchMetaDataUpdate batch1 = null;
+ BatchMetaDataUpdate batch2 = null;
+
+ BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ try {
+ batch1 = update1.openUpdateInBatch(bru);
+ batch1.write(update1, new CommitBuilder());
+ batch1.commit();
+ assertNull(repo.getRef(update1.getRefName()));
+
+ batch2 = update2.openUpdateInBatch(bru);
+ batch2.write(update2, new CommitBuilder());
+ batch2.commit();
+ assertNull(repo.getRef(update2.getRefName()));
+ } finally {
+ if (batch1 != null) {
+ batch1.close();
+ }
+ if (batch2 != null) {
+ batch2.close();
+ }
+ }
+
+ List<ReceiveCommand> cmds = bru.getCommands();
+ assertEquals(2, cmds.size());
+ assertEquals(update1.getRefName(), cmds.get(0).getRefName());
+ assertEquals(update2.getRefName(), cmds.get(1).getRefName());
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+
+ assertEquals(ReceiveCommand.Result.OK, cmds.get(0).getResult());
+ assertEquals(ReceiveCommand.Result.OK, cmds.get(1).getResult());
+
+ assertNotNull(repo.getRef(update1.getRefName()));
+ assertNotNull(repo.getRef(update2.getRefName()));
+ }
+
+ @Test
public void changeMessageOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -666,60 +535,12 @@ public class ChangeNotesTest {
}
@Test
- public void changeMessagesMultiplePatchSets() throws Exception {
- Change c = newChange();
- ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
- update.setChangeMessage("This is the change message for the first PS.");
- update.commit();
- PatchSet.Id ps1 = c.currentPatchSetId();
-
- incrementPatchSet(c);
- update = newUpdate(c, changeOwner);
-
- update.setChangeMessage("This is the change message for the second PS.");
- update.commit();
- PatchSet.Id ps2 = c.currentPatchSetId();
-
- ChangeNotes notes = newNotes(c);
- ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
- notes.getChangeMessages();
- assertEquals(2, changeMessages.keySet().size());
-
- ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
- assertEquals("This is the change message for the first PS.",
- cm1.getMessage());
- assertEquals(changeOwner.getAccount().getId(),
- cm1.getAuthor());
-
- ChangeMessage cm2 = Iterables.getOnlyElement(changeMessages.get(ps2));
- assertEquals(ps1, cm1.getPatchSetId());
- assertEquals("This is the change message for the second PS.",
- cm2.getMessage());
- assertEquals(changeOwner.getAccount().getId(), cm2.getAuthor());
- assertEquals(ps2, cm2.getPatchSetId());
- }
-
- @Test
public void noChangeMessage() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.commit();
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Patch-set: 1\n"
- + "Reviewer: Change Owner <1@gerrit>\n",
- commit.getFullMessage());
- } finally {
- walk.close();
- }
-
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
@@ -735,22 +556,6 @@ public class ChangeNotesTest {
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Testing trailing double newline\n"
- + "\n"
- + "\n"
- + "\n"
- + "Patch-set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.close();
- }
-
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
@@ -759,7 +564,6 @@ public class ChangeNotesTest {
ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
assertEquals("Testing trailing double newline\n" + "\n", cm1.getMessage());
assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
-
}
@Test
@@ -774,24 +578,6 @@ public class ChangeNotesTest {
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
- RevWalk walk = new RevWalk(repo);
- try {
- RevCommit commit = walk.parseCommit(update.getRevision());
- walk.parseBody(commit);
- assertEquals("Update patch set 1\n"
- + "\n"
- + "Testing paragraph 1\n"
- + "\n"
- + "Testing paragraph 2\n"
- + "\n"
- + "Testing paragraph 3\n"
- + "\n"
- + "Patch-set: 1\n",
- commit.getFullMessage());
- } finally {
- walk.close();
- }
-
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
@@ -807,6 +593,41 @@ public class ChangeNotesTest {
}
@Test
+ public void changeMessagesMultiplePatchSets() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.setChangeMessage("This is the change message for the first PS.");
+ update.commit();
+ PatchSet.Id ps1 = c.currentPatchSetId();
+
+ incrementPatchSet(c);
+ update = newUpdate(c, changeOwner);
+
+ update.setChangeMessage("This is the change message for the second PS.");
+ update.commit();
+ PatchSet.Id ps2 = c.currentPatchSetId();
+
+ ChangeNotes notes = newNotes(c);
+ ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+ notes.getChangeMessages();
+ assertEquals(2, changeMessages.keySet().size());
+
+ ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+ assertEquals("This is the change message for the first PS.",
+ cm1.getMessage());
+ assertEquals(changeOwner.getAccount().getId(),
+ cm1.getAuthor());
+
+ ChangeMessage cm2 = Iterables.getOnlyElement(changeMessages.get(ps2));
+ assertEquals(ps1, cm1.getPatchSetId());
+ assertEquals("This is the change message for the second PS.",
+ cm2.getMessage());
+ assertEquals(changeOwner.getAccount().getId(), cm2.getAuthor());
+ assertEquals(ps2, cm2.getPatchSetId());
+ }
+
+ @Test
public void changeMessageMultipleInOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -844,7 +665,9 @@ public class ChangeNotesTest {
public void patchLineCommentNotesFormatSide1() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
+ String uuid3 = "uuid3";
String message1 = "comment 1";
String message2 = "comment 2";
String message3 = "comment 3";
@@ -855,76 +678,78 @@ public class ChangeNotesTest {
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
- uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
+ uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
- uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
+ uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range3 = new CommentRange(3, 1, 4, 1);
PatchLineComment comment3 = newPublishedPatchLineComment(psId, "file2",
- uuid, range3, range3.getEndLine(), otherUser, null, time3, message3,
+ uuid3, range3, range3.getEndLine(), otherUser, null, time3, message3,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment3);
+ update.upsertComment(comment3);
update.commit();
ChangeNotes notes = newNotes(c);
- RevWalk walk = new RevWalk(repo);
- ArrayList<Note> notesInTree =
- Lists.newArrayList(notes.getNoteMap().iterator());
- Note note = Iterables.getOnlyElement(notesInTree);
-
- byte[] bytes =
- walk.getObjectReader().open(
- note.getData(), Constants.OBJ_BLOB).getBytes();
- String noteString = new String(bytes, UTF_8);
- assertEquals("Patch-set: 1\n"
- + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
- + "File: file1\n"
- + "\n"
- + "1:1-2:1\n"
- + CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
- + "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
- + "Bytes: 9\n"
- + "comment 1\n"
- + "\n"
- + "2:1-3:1\n"
- + CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
- + "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
- + "Bytes: 9\n"
- + "comment 2\n"
- + "\n"
- + "File: file2\n"
- + "\n"
- + "3:1-4:1\n"
- + CommentsInNotesUtil.formatTime(serverIdent, time3) + "\n"
- + "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
- + "Bytes: 9\n"
- + "comment 3\n"
- + "\n",
- noteString);
+ try (RevWalk walk = new RevWalk(repo)) {
+ ArrayList<Note> notesInTree =
+ Lists.newArrayList(notes.getNoteMap().iterator());
+ Note note = Iterables.getOnlyElement(notesInTree);
+
+ byte[] bytes =
+ walk.getObjectReader().open(
+ note.getData(), Constants.OBJ_BLOB).getBytes();
+ String noteString = new String(bytes, UTF_8);
+ assertEquals("Patch-set: 1\n"
+ + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
+ + "File: file1\n"
+ + "\n"
+ + "1:1-2:1\n"
+ + CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
+ + "Author: Other Account <2@gerrit>\n"
+ + "UUID: uuid1\n"
+ + "Bytes: 9\n"
+ + "comment 1\n"
+ + "\n"
+ + "2:1-3:1\n"
+ + CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
+ + "Author: Other Account <2@gerrit>\n"
+ + "UUID: uuid2\n"
+ + "Bytes: 9\n"
+ + "comment 2\n"
+ + "\n"
+ + "File: file2\n"
+ + "\n"
+ + "3:1-4:1\n"
+ + CommentsInNotesUtil.formatTime(serverIdent, time3) + "\n"
+ + "Author: Other Account <2@gerrit>\n"
+ + "UUID: uuid3\n"
+ + "Bytes: 9\n"
+ + "comment 3\n"
+ + "\n",
+ noteString);
+ }
}
@Test
public void patchLineCommentNotesFormatSide0() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
String message1 = "comment 1";
String message2 = "comment 2";
CommentRange range1 = new CommentRange(1, 1, 2, 1);
@@ -933,60 +758,61 @@ public class ChangeNotesTest {
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
- uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
+ uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
- uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
+ uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
- RevWalk walk = new RevWalk(repo);
- ArrayList<Note> notesInTree =
- Lists.newArrayList(notes.getNoteMap().iterator());
- Note note = Iterables.getOnlyElement(notesInTree);
-
- byte[] bytes =
- walk.getObjectReader().open(
- note.getData(), Constants.OBJ_BLOB).getBytes();
- String noteString = new String(bytes, UTF_8);
- assertEquals("Base-for-patch-set: 1\n"
- + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
- + "File: file1\n"
- + "\n"
- + "1:1-2:1\n"
- + CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
- + "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
- + "Bytes: 9\n"
- + "comment 1\n"
- + "\n"
- + "2:1-3:1\n"
- + CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
- + "Author: Other Account <2@gerrit>\n"
- + "UUID: uuid\n"
- + "Bytes: 9\n"
- + "comment 2\n"
- + "\n",
- noteString);
+ try (RevWalk walk = new RevWalk(repo)) {
+ ArrayList<Note> notesInTree =
+ Lists.newArrayList(notes.getNoteMap().iterator());
+ Note note = Iterables.getOnlyElement(notesInTree);
+
+ byte[] bytes =
+ walk.getObjectReader().open(
+ note.getData(), Constants.OBJ_BLOB).getBytes();
+ String noteString = new String(bytes, UTF_8);
+ assertEquals("Base-for-patch-set: 1\n"
+ + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
+ + "File: file1\n"
+ + "\n"
+ + "1:1-2:1\n"
+ + CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
+ + "Author: Other Account <2@gerrit>\n"
+ + "UUID: uuid1\n"
+ + "Bytes: 9\n"
+ + "comment 1\n"
+ + "\n"
+ + "2:1-3:1\n"
+ + CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
+ + "Author: Other Account <2@gerrit>\n"
+ + "UUID: uuid2\n"
+ + "Bytes: 9\n"
+ + "comment 2\n"
+ + "\n",
+ noteString);
+ }
}
-
@Test
public void patchLineCommentMultipleOnePatchsetOneFileBothSides()
throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
String messageForBase = "comment for base";
String messageForPS = "comment for ps";
CommentRange range = new CommentRange(1, 1, 2, 1);
@@ -994,20 +820,20 @@ public class ChangeNotesTest {
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment commentForBase =
- newPublishedPatchLineComment(psId, "filename", uuid,
+ newPublishedPatchLineComment(psId, "filename", uuid1,
range, range.getEndLine(), otherUser, null, now, messageForBase,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(commentForBase);
+ update.upsertComment(commentForBase);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment commentForPS =
- newPublishedPatchLineComment(psId, "filename", uuid,
+ newPublishedPatchLineComment(psId, "filename", uuid2,
range, range.getEndLine(), otherUser, null, now, messageForPS,
(short) 1, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(psId);
- update.putComment(commentForPS);
+ update.upsertComment(commentForPS);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1027,7 +853,8 @@ public class ChangeNotesTest {
@Test
public void patchLineCommentMultipleOnePatchsetOneFile() throws Exception {
Change c = newChange();
- String uuid = "uuid";
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename = "filename";
@@ -1037,18 +864,18 @@ public class ChangeNotesTest {
Timestamp timeForComment1 = TimeUtil.nowTs();
Timestamp timeForComment2 = TimeUtil.nowTs();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, filename,
- uuid, range, range.getEndLine(), otherUser, null, timeForComment1,
+ uuid1, range, range.getEndLine(), otherUser, null, timeForComment1,
"comment 1", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, filename,
- uuid, range, range.getEndLine(), otherUser, null, timeForComment2,
+ uuid2, range, range.getEndLine(), otherUser, null, timeForComment2,
"comment 2", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1086,7 +913,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -1094,7 +921,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1130,7 +957,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(ps1);
- update.putComment(comment1);
+ update.upsertComment(comment1);
update.commit();
incrementPatchSet(c);
@@ -1142,7 +969,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
side, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(ps2);
- update.putComment(comment2);
+ update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1165,68 +992,213 @@ public class ChangeNotesTest {
assertEquals(comment2, commentFromPs2);
}
- private Change newChange() {
- return TestChanges.newChange(project, changeOwner);
- }
+ @Test
+ public void patchLineCommentSingleDraftToPublished() throws Exception {
+ Change c = newChange();
+ String uuid = "uuid";
+ CommentRange range = new CommentRange(1, 1, 2, 1);
+ PatchSet.Id ps1 = c.currentPatchSetId();
+ String filename = "filename1";
+ short side = (short) 1;
- private PatchLineComment newPublishedPatchLineComment(PatchSet.Id psId,
- String filename, String UUID, CommentRange range, int line,
- IdentifiedUser commenter, String parentUUID, Timestamp t,
- String message, short side, String commitSHA1) {
- return newPatchLineComment(psId, filename, UUID, range, line, commenter,
- parentUUID, t, message, side, commitSHA1, Status.PUBLISHED);
- }
+ ChangeUpdate update = newUpdate(c, otherUser);
+ Timestamp now = TimeUtil.nowTs();
+ PatchLineComment comment1 = newPatchLineComment(ps1, filename, uuid,
+ range, range.getEndLine(), otherUser, null, now, "comment on ps1", side,
+ "abcd4567abcd4567abcd4567abcd4567abcd4567", Status.DRAFT);
+ update.setPatchSetId(ps1);
+ update.insertComment(comment1);
+ update.commit();
- private PatchLineComment newPatchLineComment(PatchSet.Id psId,
- String filename, String UUID, CommentRange range, int line,
- IdentifiedUser commenter, String parentUUID, Timestamp t,
- String message, short side, String commitSHA1, Status status) {
- PatchLineComment comment = new PatchLineComment(
- new PatchLineComment.Key(
- new Patch.Key(psId, filename), UUID),
- line, commenter.getAccountId(), parentUUID, t);
- comment.setSide(side);
- comment.setMessage(message);
- comment.setRange(range);
- comment.setRevId(new RevId(commitSHA1));
- comment.setStatus(status);
- return comment;
- }
+ ChangeNotes notes = newNotes(c);
+ assertEquals(1, notes.getDraftPsComments(otherUserId).values().size());
+ assertEquals(0, notes.getDraftBaseComments(otherUserId).values().size());
- private ChangeUpdate newUpdate(Change c, IdentifiedUser user)
- throws Exception {
- return TestChanges.newUpdate(injector, repoManager, c, user);
- }
+ comment1.setStatus(Status.PUBLISHED);
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(ps1);
+ update.updateComment(comment1);
+ update.commit();
+
+ notes = newNotes(c);
- private ChangeNotes newNotes(Change c) throws OrmException {
- return new ChangeNotes(repoManager, c).load();
+ assertTrue(notes.getDraftPsComments(otherUserId).values().isEmpty());
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
+
+ assertTrue(notes.getBaseComments().values().isEmpty());
+ PatchLineComment commentFromNotes =
+ Iterables.getOnlyElement(notes.getPatchSetComments().values());
+ assertEquals(comment1, commentFromNotes);
}
- private static Timestamp truncate(Timestamp ts) {
- return new Timestamp((ts.getTime() / 1000) * 1000);
+ @Test
+ public void patchLineCommentMultipleDraftsSameSidePublishOne()
+ throws OrmException, IOException {
+ Change c = newChange();
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
+ CommentRange range1 = new CommentRange(1, 1, 2, 2);
+ CommentRange range2 = new CommentRange(2, 2, 3, 3);
+ String filename = "filename1";
+ short side = (short) 1;
+ Timestamp now = TimeUtil.nowTs();
+ String commitSHA1 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ PatchSet.Id psId = c.currentPatchSetId();
+
+ // Write two drafts on the same side of one patch set.
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+ PatchLineComment comment1 = newPatchLineComment(psId, filename, uuid1,
+ range1, range1.getEndLine(), otherUser, null, now, "comment on ps1",
+ side, commitSHA1, Status.DRAFT);
+ PatchLineComment comment2 = newPatchLineComment(psId, filename, uuid2,
+ range2, range2.getEndLine(), otherUser, null, now, "other on ps1",
+ side, commitSHA1, Status.DRAFT);
+ update.insertComment(comment1);
+ update.insertComment(comment2);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
+ assertEquals(2, notes.getDraftPsComments(otherUserId).values().size());
+
+ assertTrue(notes.getDraftPsComments(otherUserId).containsValue(comment1));
+ assertTrue(notes.getDraftPsComments(otherUserId).containsValue(comment2));
+
+ // Publish first draft.
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+ comment1.setStatus(Status.PUBLISHED);
+ update.updateComment(comment1);
+ update.commit();
+
+ notes = newNotes(c);
+ assertEquals(comment1,
+ Iterables.getOnlyElement(notes.getPatchSetComments().get(psId)));
+ assertEquals(comment2,
+ Iterables.getOnlyElement(
+ notes.getDraftPsComments(otherUserId).values()));
+
+ assertTrue(notes.getBaseComments().values().isEmpty());
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
}
- private static Timestamp after(Change c, long millis) {
- return new Timestamp(c.getCreatedOn().getTime() + millis);
+ @Test
+ public void patchLineCommentsMultipleDraftsBothSidesPublishAll()
+ throws OrmException, IOException {
+ Change c = newChange();
+ String uuid1 = "uuid1";
+ String uuid2 = "uuid2";
+ CommentRange range1 = new CommentRange(1, 1, 2, 2);
+ CommentRange range2 = new CommentRange(2, 2, 3, 3);
+ String filename = "filename1";
+ Timestamp now = TimeUtil.nowTs();
+ String commitSHA1 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ String baseSHA1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ PatchSet.Id psId = c.currentPatchSetId();
+
+ // Write two drafts, one on each side of the patchset.
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+ PatchLineComment baseComment = newPatchLineComment(psId, filename, uuid1,
+ range1, range1.getEndLine(), otherUser, null, now, "comment on base",
+ (short) 0, baseSHA1, Status.DRAFT);
+ PatchLineComment psComment = newPatchLineComment(psId, filename, uuid2,
+ range2, range2.getEndLine(), otherUser, null, now, "comment on ps",
+ (short) 1, commitSHA1, Status.DRAFT);
+
+ update.insertComment(baseComment);
+ update.insertComment(psComment);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ PatchLineComment baseDraftCommentFromNotes =
+ Iterables.getOnlyElement(
+ notes.getDraftBaseComments(otherUserId).values());
+ PatchLineComment psDraftCommentFromNotes =
+ Iterables.getOnlyElement(
+ notes.getDraftPsComments(otherUserId).values());
+
+ assertEquals(baseComment, baseDraftCommentFromNotes);
+ assertEquals(psComment, psDraftCommentFromNotes);
+
+ // Publish both comments.
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(psId);
+
+ baseComment.setStatus(Status.PUBLISHED);
+ psComment.setStatus(Status.PUBLISHED);
+ update.updateComment(baseComment);
+ update.updateComment(psComment);
+ update.commit();
+
+ notes = newNotes(c);
+
+ PatchLineComment baseCommentFromNotes =
+ Iterables.getOnlyElement(notes.getBaseComments().values());
+ PatchLineComment psCommentFromNotes =
+ Iterables.getOnlyElement(notes.getPatchSetComments().values());
+
+ assertEquals(baseComment, baseCommentFromNotes);
+ assertEquals(psComment, psCommentFromNotes);
+
+ assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
+ assertTrue(notes.getDraftPsComments(otherUserId).values().isEmpty());
}
- private static SubmitRecord submitRecord(String status,
- String errorMessage, SubmitRecord.Label... labels) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.valueOf(status);
- rec.errorMessage = errorMessage;
- if (labels.length > 0) {
- rec.labels = ImmutableList.copyOf(labels);
- }
- return rec;
+ @Test
+ public void fileComment() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+ String uuid = "uuid";
+ String messageForBase = "comment for base";
+ Timestamp now = TimeUtil.nowTs();
+ PatchSet.Id psId = c.currentPatchSetId();
+
+ PatchLineComment commentForBase =
+ newPublishedPatchLineComment(psId, "filename", uuid,
+ null, 0, otherUser, null, now, messageForBase,
+ (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+ update.setPatchSetId(psId);
+ update.upsertComment(commentForBase);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
+ notes.getBaseComments();
+ Multimap<PatchSet.Id, PatchLineComment> commentsForPs =
+ notes.getPatchSetComments();
+
+ assertTrue(commentsForPs.isEmpty());
+ assertEquals(commentForBase,
+ Iterables.getOnlyElement(commentsForBase.get(psId)));
}
- private static SubmitRecord.Label submitLabel(String name, String status,
- Account.Id appliedBy) {
- SubmitRecord.Label label = new SubmitRecord.Label();
- label.label = name;
- label.status = SubmitRecord.Label.Status.valueOf(status);
- label.appliedBy = appliedBy;
- return label;
+ @Test
+ public void patchLineCommentNoRange() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+ String uuid = "uuid";
+ String messageForBase = "comment for base";
+ Timestamp now = TimeUtil.nowTs();
+ PatchSet.Id psId = c.currentPatchSetId();
+
+ PatchLineComment commentForBase =
+ newPublishedPatchLineComment(psId, "filename", uuid,
+ null, 1, otherUser, null, now, messageForBase,
+ (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+ update.setPatchSetId(psId);
+ update.upsertComment(commentForBase);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
+ notes.getBaseComments();
+ Multimap<PatchSet.Id, PatchLineComment> commentsForPs =
+ notes.getPatchSetComments();
+
+ assertTrue(commentsForPs.isEmpty());
+ assertEquals(commentForBase,
+ Iterables.getOnlyElement(commentsForBase.get(psId)));
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
new file mode 100644
index 0000000000..328509ae7c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -0,0 +1,257 @@
+// Copyright (C) 2014 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.ReviewerState.CC;
+import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.testutil.TestChanges;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Test;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+public class CommitMessageOutputTest extends AbstractChangeNotesTest {
+ @Test
+ public void approvalsCommitFormatSimple() throws Exception {
+ Change c = TestChanges.newChange(project, changeOwner.getAccountId(), 1);
+ ChangeUpdate update = newUpdate(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.commit();
+ assertEquals("refs/changes/01/1/meta", update.getRefName());
+
+ RevCommit commit = parseCommit(update.getRevision());
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Reviewer: Change Owner <1@gerrit>\n"
+ + "CC: Other Account <2@gerrit>\n"
+ + "Label: Code-Review=-1\n"
+ + "Label: Verified=+1\n",
+ commit);
+
+ PersonIdent author = commit.getAuthorIdent();
+ assertEquals("Change Owner", author.getName());
+ assertEquals("1@gerrit", author.getEmailAddress());
+ assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
+ author.getWhen());
+ assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
+
+ PersonIdent committer = commit.getCommitterIdent();
+ assertEquals("Gerrit Server", committer.getName());
+ assertEquals("noreply@gerrit.com", committer.getEmailAddress());
+ assertEquals(author.getWhen(), committer.getWhen());
+ assertEquals(author.getTimeZone(), committer.getTimeZone());
+ }
+
+ @Test
+ public void changeMessageCommitFormatSimple() throws Exception {
+ Change c = TestChanges.newChange(project, changeOwner.getAccountId(), 1);
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Just a little code change.\n"
+ + "How about a new line");
+ update.commit();
+ assertEquals("refs/changes/01/1/meta", update.getRefName());
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Just a little code change.\n"
+ + "How about a new line\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void approvalTombstoneCommitFormat() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.removeApproval("Code-Review");
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Label: -Code-Review\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void submitCommitFormat() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setSubject("Submit patch set 1");
+
+ update.submit(ImmutableList.of(
+ submitRecord("NOT_READY", null,
+ submitLabel("Verified", "OK", changeOwner.getAccountId()),
+ submitLabel("Code-Review", "NEED", null)),
+ submitRecord("NOT_READY", null,
+ submitLabel("Verified", "OK", changeOwner.getAccountId()),
+ submitLabel("Alternative-Code-Review", "NEED", null))));
+ update.commit();
+
+ RevCommit commit = parseCommit(update.getRevision());
+ assertBodyEquals("Submit patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Status: submitted\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Code-Review\n"
+ + "Submitted-with: NOT_READY\n"
+ + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ + "Submitted-with: NEED: Alternative-Code-Review\n",
+ commit);
+
+ PersonIdent author = commit.getAuthorIdent();
+ assertEquals("Change Owner", author.getName());
+ assertEquals("1@gerrit", author.getEmailAddress());
+ assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
+ author.getWhen());
+ assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
+
+ PersonIdent committer = commit.getCommitterIdent();
+ assertEquals("Gerrit Server", committer.getName());
+ assertEquals("noreply@gerrit.com", committer.getEmailAddress());
+ assertEquals(author.getWhen(), committer.getWhen());
+ assertEquals(author.getTimeZone(), committer.getTimeZone());
+ }
+
+ @Test
+ public void anonymousUser() throws Exception {
+ Account anon = new Account(new Account.Id(3), TimeUtil.nowTs());
+ accountCache.put(anon);
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, userFactory.create(anon.getId()));
+ update.setChangeMessage("Comment on the change.");
+ update.commit();
+
+ RevCommit commit = parseCommit(update.getRevision());
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Comment on the change.\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ commit);
+
+ PersonIdent author = commit.getAuthorIdent();
+ assertEquals("Anonymous Coward (3)", author.getName());
+ assertEquals("3@gerrit", author.getEmailAddress());
+ }
+
+ @Test
+ public void submitWithErrorMessage() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setSubject("Submit patch set 1");
+
+ update.submit(ImmutableList.of(
+ submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
+ update.commit();
+
+ assertBodyEquals("Submit patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Status: submitted\n"
+ + "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void noChangeMessage() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Patch-set: 1\n"
+ + "Reviewer: Change Owner <1@gerrit>\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void changeMessageWithTrailingDoubleNewline() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Testing trailing double newline\n"
+ + "\n");
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Testing trailing double newline\n"
+ + "\n"
+ + "\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ update.getRevision());
+ }
+
+ @Test
+ public void changeMessageWithMultipleParagraphs() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setChangeMessage("Testing paragraph 1\n"
+ + "\n"
+ + "Testing paragraph 2\n"
+ + "\n"
+ + "Testing paragraph 3");
+ update.commit();
+
+ assertBodyEquals("Update patch set 1\n"
+ + "\n"
+ + "Testing paragraph 1\n"
+ + "\n"
+ + "Testing paragraph 2\n"
+ + "\n"
+ + "Testing paragraph 3\n"
+ + "\n"
+ + "Patch-set: 1\n",
+ update.getRevision());
+ }
+
+ private RevCommit parseCommit(ObjectId id) throws Exception {
+ if (id instanceof RevCommit) {
+ return (RevCommit) id;
+ }
+ try (RevWalk walk = new RevWalk(repo)) {
+ RevCommit commit = walk.parseCommit(id);
+ walk.parseBody(commit);
+ return commit;
+ }
+ }
+
+ private void assertBodyEquals(String expected, ObjectId commitId)
+ throws Exception {
+ RevCommit commit = parseCommit(commitId);
+ assertEquals(expected, commit.getFullMessage());
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
index af60ea8fac..bff557c8c1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
@@ -14,14 +14,15 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.reviewdb.client.Patch;
-import org.junit.Test;
-
import static org.junit.Assert.assertEquals;
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 org.junit.Test;
+
public class PatchListEntryTest {
@Test
public void testEmpty1() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
new file mode 100644
index 0000000000..b5b321ed49
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
@@ -0,0 +1,176 @@
+// Copyright (C) 2014 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;
+
+import static com.google.gerrit.common.data.Permission.READ;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.Util.allow;
+import static com.google.gerrit.server.project.Util.deny;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.schema.SchemaCreator;
+import com.google.gerrit.testutil.InMemoryDatabase;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Unit tests for {@link ProjectControl}. */
+public class ProjectControlTest {
+ @Inject private AccountManager accountManager;
+ @Inject private IdentifiedUser.RequestFactory userFactory;
+ @Inject private InMemoryDatabase schemaFactory;
+ @Inject private InMemoryRepositoryManager repoManager;
+ @Inject private ProjectControl.GenericFactory projectControlFactory;
+ @Inject private SchemaCreator schemaCreator;
+
+ private LifecycleManager lifecycle;
+ private ReviewDb db;
+ private TestRepository<InMemoryRepository> repo;
+ private ProjectConfig project;
+ private IdentifiedUser user;
+
+ @Before
+ public void setUp() throws Exception {
+ Injector injector = Guice.createInjector(new InMemoryModule());
+ injector.injectMembers(this);
+ lifecycle = new LifecycleManager();
+ lifecycle.add(injector);
+ lifecycle.start();
+
+ db = schemaFactory.open();
+ schemaCreator.create(db);
+ Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user"))
+ .getAccountId();
+ user = userFactory.create(userId);
+
+ Project.NameKey name = new Project.NameKey("project");
+ InMemoryRepository inMemoryRepo = repoManager.createRepository(name);
+ project = new ProjectConfig(name);
+ project.load(inMemoryRepo);
+ repo = new TestRepository<>(inMemoryRepo);
+ }
+
+ @After
+ public void tearDown() {
+ if (repo != null) {
+ repo.getRepository().close();
+ }
+ if (lifecycle != null) {
+ lifecycle.stop();
+ }
+ if (db != null) {
+ db.close();
+ }
+ InMemoryDatabase.drop(schemaFactory);
+ }
+
+ @Test
+ public void canReadCommitWhenAllRefsVisible() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/*");
+ ObjectId id = repo.branch("master").commit().create();
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id)));
+ }
+
+ @Test
+ public void canReadCommitIfRefVisible() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+ deny(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+
+ ObjectId id1 = repo.branch("branch1").commit().create();
+ ObjectId id2 = repo.branch("branch2").commit().create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(id2)));
+ }
+
+ @Test
+ public void canReadCommitIfReachableFromVisibleRef() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+ deny(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+
+ RevCommit parent1 = repo.commit().create();
+ repo.branch("branch1").commit().parent(parent1).create();
+
+ RevCommit parent2 = repo.commit().create();
+ repo.branch("branch2").commit().parent(parent2).create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(parent2)));
+ }
+
+ @Test
+ public void cannotReadAfterRollbackWithRestrictedRead() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+
+ RevCommit parent1 = repo.commit().create();
+ ObjectId id1 = repo.branch("branch1").commit().parent(parent1).create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+
+ repo.branch("branch1").update(parent1);
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+ }
+
+ @Test
+ public void canReadAfterRollbackWithAllRefsVisible() throws Exception {
+ allow(project, READ, REGISTERED_USERS, "refs/*");
+
+ RevCommit parent1 = repo.commit().create();
+ ObjectId id1 = repo.branch("branch1").commit().parent(parent1).create();
+
+ ProjectControl pc = newProjectControl();
+ RevWalk rw = repo.getRevWalk();
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+
+ repo.branch("branch1").update(parent1);
+ assertTrue(pc.canReadCommit(db, rw, rw.parseCommit(parent1)));
+ assertFalse(pc.canReadCommit(db, rw, rw.parseCommit(id1)));
+ }
+
+ private ProjectControl newProjectControl() throws Exception {
+ return projectControlFactory.controlFor(project.getName(), user);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 5478a6c9fe..4c123fde22 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -448,7 +448,7 @@ public class RefControlTest {
}
@Test
- public void testUnblockVisibilityByREGISTEREDUsers() {
+ public void testUnblockVisibilityByRegisteredUsers() {
block(local, READ, ANONYMOUS_USERS, "refs/heads/*");
allow(local, READ, REGISTERED_USERS, "refs/heads/*");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
index ac1e0d7cad..e39700cddc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -29,27 +29,31 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.FakeRealm;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.change.ChangeKindCache;
import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.change.MergeabilityCache;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.DisableReverseDnsLookup;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.patch.PatchListCache;
@@ -58,9 +62,11 @@ import com.google.gerrit.testutil.FakeAccountCache;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.Provider;
import com.google.inject.util.Providers;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -178,7 +184,7 @@ public class Util {
private final CapabilityControl.Factory capabilityControlFactory;
private final ChangeControl.AssistedFactory changeControlFactory;
private final PermissionCollection.Factory sectionSorter;
- private final GitRepositoryManager repoManager;
+ private final InMemoryRepositoryManager repoManager;
private final AllProjectsName allProjectsName =
new AllProjectsName("All-Projects");
@@ -241,36 +247,44 @@ public class Util {
}
@Override
- public ProjectState checkedGet(NameKey projectName) throws IOException {
+ public ProjectState checkedGet(Project.NameKey projectName)
+ throws IOException {
return all.get(projectName);
}
@Override
- public void evict(NameKey p) {
+ public void evict(Project.NameKey p) {
}
};
Injector injector = Guice.createInjector(new FactoryModule() {
+ @SuppressWarnings({"rawtypes", "unchecked"})
@Override
protected void configure() {
+ Provider nullProvider = Providers.of(null);
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(
new Config());
- bind(ReviewDb.class).toProvider(Providers.<ReviewDb> of(null));
+ bind(ReviewDb.class).toProvider(nullProvider);
bind(GitRepositoryManager.class).toInstance(repoManager);
- bind(PatchListCache.class)
- .toProvider(Providers.<PatchListCache> of(null));
+ bind(PatchListCache.class).toProvider(nullProvider);
+ bind(Realm.class).to(FakeRealm.class);
factory(CapabilityControl.Factory.class);
factory(ChangeControl.AssistedFactory.class);
factory(ChangeData.Factory.class);
+ factory(MergeUtil.Factory.class);
bind(ProjectCache.class).toInstance(projectCache);
bind(AccountCache.class).toInstance(new FakeAccountCache());
bind(GroupBackend.class).to(SystemGroupBackend.class);
bind(String.class).annotatedWith(CanonicalWebUrl.class)
.toProvider(CanonicalWebUrlProvider.class);
+ bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class)
+ .toInstance(Boolean.FALSE);
bind(String.class).annotatedWith(AnonymousCowardName.class)
.toProvider(AnonymousCowardNameProvider.class);
bind(ChangeKindCache.class).to(ChangeKindCacheImpl.NoCache.class);
+ bind(MergeabilityCache.class)
+ .to(MergeabilityCache.NotImplemented.class);
}
});
@@ -283,21 +297,26 @@ public class Util {
injector.getInstance(ChangeControl.AssistedFactory.class);
}
- public void add(ProjectConfig pc) {
+ public InMemoryRepository add(ProjectConfig pc) {
PrologEnvironment.Factory envFactory = null;
ProjectControl.AssistedFactory projectControlFactory = null;
RulesCache rulesCache = null;
SitePaths sitePaths = null;
List<CommentLinkInfo> commentLinks = null;
+ InMemoryRepository repo;
try {
- repoManager.createRepository(pc.getProject().getNameKey());
- } catch (IOException e) {
+ repo = repoManager.createRepository(pc.getName());
+ if (pc.getProject() == null) {
+ pc.load(repo);
+ }
+ } catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e);
}
- all.put(pc.getProject().getNameKey(), new ProjectState(sitePaths,
+ all.put(pc.getName(), new ProjectState(sitePaths,
projectCache, allProjectsName, projectControlFactory, envFactory,
repoManager, rulesCache, commentLinks, pc));
+ return repo;
}
public ProjectControl user(ProjectConfig local, AccountGroup.UUID... memberOf) {
@@ -310,8 +329,8 @@ public class Util {
return new ProjectControl(Collections.<AccountGroup.UUID> emptySet(),
Collections.<AccountGroup.UUID> emptySet(), projectCache,
- sectionSorter, repoManager, changeControlFactory, canonicalWebUrl,
- new MockUser(name, memberOf), newProjectState(local));
+ sectionSorter, repoManager, changeControlFactory, null, null,
+ canonicalWebUrl, new MockUser(name, memberOf), newProjectState(local));
}
private ProjectState newProjectState(ProjectConfig local) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
index 0eca069d7d..e34927309e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
@@ -13,11 +13,11 @@
// limitations under the License.
package com.google.gerrit.server.query;
+import static org.junit.Assert.assertEquals;
+
import org.antlr.runtime.tree.Tree;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-
public class QueryParserTest {
@Test
public void testProjectBare() throws QueryParseException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index fb507446e6..34588fab99 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -14,21 +14,29 @@
package com.google.gerrit.server.query.change;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.TruthJUnit.assume;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
@@ -42,16 +50,17 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.change.ChangeInserter;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
+import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.CreateProject;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
import com.google.inject.Inject;
@@ -61,6 +70,7 @@ import com.google.inject.util.Providers;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
@@ -69,21 +79,31 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
@Ignore
+@RunWith(ConfigSuite.class)
public abstract class AbstractQueryChangesTest {
private static final TopLevelResource TLR = TopLevelResource.INSTANCE;
+ @ConfigSuite.Config
+ public static Config noteDbEnabled() {
+ return NotesMigration.allEnabledConfig();
+ }
+
+ @ConfigSuite.Parameter public Config config;
@Inject protected AccountManager accountManager;
@Inject protected ChangeInserter.Factory changeFactory;
@Inject protected ChangesCollection changes;
@Inject protected CreateProject.Factory projectFactory;
+ @Inject protected GerritApi gApi;
@Inject protected IdentifiedUser.RequestFactory userFactory;
@Inject protected InMemoryDatabase schemaFactory;
@Inject protected InMemoryRepositoryManager repoManager;
+ @Inject protected NotesMigration notesMigration;
@Inject protected PostReview postReview;
@Inject protected ProjectControl.GenericFactory projectControlFactory;
@Inject protected Provider<QueryChanges> queryProvider;
@@ -116,18 +136,22 @@ public abstract class AbstractQueryChangesTest {
userAccount.setPreferredEmail("user@example.com");
db.accounts().update(ImmutableList.of(userAccount));
user = userFactory.create(userId);
+ requestContext.setContext(newRequestContext(userAccount.getId()));
+ }
- requestContext.setContext(new RequestContext() {
+ private RequestContext newRequestContext(Account.Id requestUserId) {
+ final CurrentUser requestUser = userFactory.create(requestUserId);
+ return new RequestContext() {
@Override
public CurrentUser getCurrentUser() {
- return user;
+ return requestUser;
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return Providers.of(db);
}
- });
+ };
}
@After
@@ -169,7 +193,7 @@ public abstract class AbstractQueryChangesTest {
Change change1 = newChange(repo, null, null, null, null).insert();
Change change2 = newChange(repo, null, null, null, null).insert();
- assertTrue(query("12345").isEmpty());
+ assertThat(query("12345")).isEmpty();
assertResultEquals(change1, queryOne(change1.getId().get()));
assertResultEquals(change2, queryOne(change2.getId().get()));
}
@@ -180,7 +204,7 @@ public abstract class AbstractQueryChangesTest {
Change change = newChange(repo, null, null, null, null).insert();
String key = change.getKey().get();
- assertTrue(query("I0000000000000000000000000000000000000000").isEmpty());
+ assertThat(query("I0000000000000000000000000000000000000000")).isEmpty();
for (int i = 0; i <= 36; i++) {
String q = key.substring(0, 41 - i);
assertResultEquals("result for " + q, change, queryOne(q));
@@ -188,6 +212,33 @@ public abstract class AbstractQueryChangesTest {
}
@Test
+ public void byTriplet() throws Exception {
+ TestRepository<InMemoryRepository> repo = createProject("repo");
+ Change change = newChange(repo, null, null, null, "branch").insert();
+ String k = change.getKey().get();
+
+ assertResultEquals(change, queryOne("repo~branch~" + k));
+ assertResultEquals(change, queryOne("change:repo~branch~" + k));
+ assertResultEquals(change, queryOne("repo~refs/heads/branch~" + k));
+ assertResultEquals(change, queryOne("change:repo~refs/heads/branch~" + k));
+ assertResultEquals(change, queryOne("repo~branch~" + k.substring(0, 10)));
+ assertResultEquals(change,
+ queryOne("change:repo~branch~" + k.substring(0, 10)));
+
+ assertThat(query("foo~bar")).isEmpty();
+ assertBadQuery("change:foo~bar");
+ assertThat(query("otherrepo~branch~" + k)).isEmpty();
+ assertThat(query("change:otherrepo~branch~" + k)).isEmpty();
+ assertThat(query("repo~otherbranch~" + k)).isEmpty();
+ assertThat(query("change:repo~otherbranch~" + k)).isEmpty();
+ assertThat(query("repo~branch~I0000000000000000000000000000000000000000"))
+ .isEmpty();
+ assertThat(query(
+ "change:repo~branch~I0000000000000000000000000000000000000000"))
+ .isEmpty();
+ }
+
+ @Test
public void byStatus() throws Exception {
TestRepository<InMemoryRepository> repo = createProject("repo");
ChangeInserter ins1 = newChange(repo, null, null, null, null);
@@ -200,6 +251,7 @@ public abstract class AbstractQueryChangesTest {
ins2.insert();
assertResultEquals(change1, queryOne("status:new"));
+ assertResultEquals(change1, queryOne("status:NEW"));
assertResultEquals(change1, queryOne("is:new"));
assertResultEquals(change2, queryOne("status:merged"));
assertResultEquals(change2, queryOne("is:merged"));
@@ -223,11 +275,22 @@ public abstract class AbstractQueryChangesTest {
List<ChangeInfo> results;
results = query("status:open");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
+
+ assertThat(query("status:OPEN")).hasSize(2);
+ assertThat(query("status:o")).hasSize(2);
+ assertThat(query("status:op")).hasSize(2);
+ assertThat(query("status:ope")).hasSize(2);
+ assertThat(query("status:pending")).hasSize(2);
+ assertThat(query("status:PENDING")).hasSize(2);
+ assertThat(query("status:p")).hasSize(2);
+ assertThat(query("status:pe")).hasSize(2);
+ assertThat(query("status:pen")).hasSize(2);
+
results = query("is:open");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@@ -250,23 +313,54 @@ public abstract class AbstractQueryChangesTest {
List<ChangeInfo> results;
results = query("status:closed");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
+
+ assertThat(query("status:CLOSED")).hasSize(2);
+ assertThat(query("status:c")).hasSize(2);
+ assertThat(query("status:cl")).hasSize(2);
+ assertThat(query("status:clo")).hasSize(2);
+ assertThat(query("status:clos")).hasSize(2);
+ assertThat(query("status:close")).hasSize(2);
+ assertThat(query("status:closed")).hasSize(2);
+
results = query("is:closed");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@Test
+ public void byStatusPrefix() throws Exception {
+ TestRepository<InMemoryRepository> repo = createProject("repo");
+ ChangeInserter ins1 = newChange(repo, null, null, null, null);
+ Change change1 = ins1.getChange();
+ change1.setStatus(Change.Status.NEW);
+ ins1.insert();
+ ChangeInserter ins2 = newChange(repo, null, null, null, null);
+ Change change2 = ins2.getChange();
+ change2.setStatus(Change.Status.MERGED);
+ ins2.insert();
+
+ assertResultEquals(change1, queryOne("status:n"));
+ assertResultEquals(change1, queryOne("status:ne"));
+ assertResultEquals(change1, queryOne("status:new"));
+ assertResultEquals(change1, queryOne("status:N"));
+ assertResultEquals(change1, queryOne("status:nE"));
+ assertResultEquals(change1, queryOne("status:neW"));
+ assertBadQuery("status:nx");
+ assertBadQuery("status:newx");
+ }
+
+ @Test
public void byCommit() throws Exception {
TestRepository<InMemoryRepository> repo = createProject("repo");
ChangeInserter ins = newChange(repo, null, null, null, null);
ins.insert();
String sha = ins.getPatchSet().getRevision().get();
- assertTrue(query("0000000000000000000000000000000000000000").isEmpty());
+ assertThat(query("0000000000000000000000000000000000000000")).isEmpty();
for (int i = 0; i <= 36; i++) {
String q = sha.substring(0, 40 - i);
assertResultEquals("result for " + q, ins.getChange(), queryOne(q));
@@ -295,7 +389,7 @@ public abstract class AbstractQueryChangesTest {
assertResultEquals(change1, queryOne("ownerin:Administrators"));
List<ChangeInfo> results = query("ownerin:\"Registered Users\"");
- assertEquals(results.toString(), 2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@@ -307,8 +401,8 @@ public abstract class AbstractQueryChangesTest {
Change change1 = newChange(repo1, null, null, null, null).insert();
Change change2 = newChange(repo2, null, null, null, null).insert();
- assertTrue(query("project:foo").isEmpty());
- assertTrue(query("project:repo").isEmpty());
+ assertThat(query("project:foo")).isEmpty();
+ assertThat(query("project:repo")).isEmpty();
assertResultEquals(change1, queryOne("project:repo1"));
assertResultEquals(change2, queryOne("project:repo2"));
}
@@ -320,13 +414,13 @@ public abstract class AbstractQueryChangesTest {
Change change1 = newChange(repo1, null, null, null, null).insert();
Change change2 = newChange(repo2, null, null, null, null).insert();
- assertTrue(query("projects:foo").isEmpty());
+ assertThat(query("projects:foo")).isEmpty();
assertResultEquals(change1, queryOne("projects:repo1"));
assertResultEquals(change2, queryOne("projects:repo2"));
List<ChangeInfo> results;
results = query("projects:repo");
- assertEquals(results.toString(), 2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@@ -337,15 +431,15 @@ public abstract class AbstractQueryChangesTest {
Change change1 = newChange(repo, null, null, null, "master").insert();
Change change2 = newChange(repo, null, null, null, "branch").insert();
- assertTrue(query("branch:foo").isEmpty());
+ assertThat(query("branch:foo")).isEmpty();
assertResultEquals(change1, queryOne("branch:master"));
assertResultEquals(change1, queryOne("branch:refs/heads/master"));
- assertTrue(query("ref:master").isEmpty());
+ assertThat(query("ref:master")).isEmpty();
assertResultEquals(change1, queryOne("ref:refs/heads/master"));
assertResultEquals(change1, queryOne("branch:refs/heads/master"));
assertResultEquals(change2, queryOne("branch:branch"));
assertResultEquals(change2, queryOne("branch:refs/heads/branch"));
- assertTrue(query("ref:branch").isEmpty());
+ assertThat(query("ref:branch")).isEmpty();
assertResultEquals(change2, queryOne("ref:refs/heads/branch"));
}
@@ -364,7 +458,7 @@ public abstract class AbstractQueryChangesTest {
Change change3 = newChange(repo, null, null, null, null).insert();
- assertTrue(query("topic:foo").isEmpty());
+ assertThat(query("topic:foo")).isEmpty();
assertResultEquals(change1, queryOne("topic:feature1"));
assertResultEquals(change2, queryOne("topic:feature2"));
assertResultEquals(change3, queryOne("topic:\"\""));
@@ -378,7 +472,7 @@ public abstract class AbstractQueryChangesTest {
RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
Change change2 = newChange(repo, commit2, null, null, null).insert();
- assertTrue(query("message:foo").isEmpty());
+ assertThat(query("message:foo")).isEmpty();
assertResultEquals(change1, queryOne("message:one"));
assertResultEquals(change2, queryOne("message:two"));
}
@@ -393,7 +487,7 @@ public abstract class AbstractQueryChangesTest {
repo.parseBody(repo.commit().message("12346 67891").create());
Change change2 = newChange(repo, commit2, null, null, null).insert();
- assertTrue(query("message:1234").isEmpty());
+ assertThat(query("message:1234")).isEmpty();
assertResultEquals(change1, queryOne("message:12345"));
assertResultEquals(change2, queryOne("message:12346"));
}
@@ -411,31 +505,31 @@ public abstract class AbstractQueryChangesTest {
postReview.apply(new RevisionResource(
changes.parse(change.getId()), ins.getPatchSet()), input);
- assertTrue(query("label:Code-Review=-2").isEmpty());
- assertTrue(query("label:Code-Review-2").isEmpty());
- assertTrue(query("label:Code-Review=-1").isEmpty());
- assertTrue(query("label:Code-Review-1").isEmpty());
- assertTrue(query("label:Code-Review=0").isEmpty());
+ assertThat(query("label:Code-Review=-2")).isEmpty();
+ assertThat(query("label:Code-Review-2")).isEmpty();
+ assertThat(query("label:Code-Review=-1")).isEmpty();
+ assertThat(query("label:Code-Review-1")).isEmpty();
+ assertThat(query("label:Code-Review=0")).isEmpty();
assertResultEquals(change, queryOne("label:Code-Review=+1"));
assertResultEquals(change, queryOne("label:Code-Review=1"));
assertResultEquals(change, queryOne("label:Code-Review+1"));
- assertTrue(query("label:Code-Review=+2").isEmpty());
- assertTrue(query("label:Code-Review=2").isEmpty());
- assertTrue(query("label:Code-Review+2").isEmpty());
+ assertThat(query("label:Code-Review=+2")).isEmpty();
+ assertThat(query("label:Code-Review=2")).isEmpty();
+ assertThat(query("label:Code-Review+2")).isEmpty();
assertResultEquals(change, queryOne("label:Code-Review>=0"));
assertResultEquals(change, queryOne("label:Code-Review>0"));
assertResultEquals(change, queryOne("label:Code-Review>=1"));
- assertTrue(query("label:Code-Review>1").isEmpty());
- assertTrue(query("label:Code-Review>=2").isEmpty());
+ assertThat(query("label:Code-Review>1")).isEmpty();
+ assertThat(query("label:Code-Review>=2")).isEmpty();
assertResultEquals(change, queryOne("label: Code-Review<=2"));
assertResultEquals(change, queryOne("label: Code-Review<2"));
assertResultEquals(change, queryOne("label: Code-Review<=1"));
- assertTrue(query("label:Code-Review<1").isEmpty());
- assertTrue(query("label:Code-Review<=0").isEmpty());
+ assertThat(query("label:Code-Review<1")).isEmpty();
+ assertThat(query("label:Code-Review<=0")).isEmpty();
- assertTrue(query("label:Code-Review=+1,anotheruser").isEmpty());
+ assertThat(query("label:Code-Review=+1,anotheruser")).isEmpty();
assertResultEquals(change, queryOne("label:Code-Review=+1,user"));
assertResultEquals(change, queryOne("label:Code-Review=+1,user=user"));
assertResultEquals(change, queryOne("label:Code-Review=+1,Administrators"));
@@ -453,9 +547,22 @@ public abstract class AbstractQueryChangesTest {
List<ChangeInfo> results;
for (int i = 1; i <= n + 2; i++) {
+ int expectedSize;
+ Boolean expectedMoreChanges;
+ if (i < n) {
+ expectedSize = i;
+ expectedMoreChanges = true;
+ } else {
+ expectedSize = n;
+ expectedMoreChanges = null;
+ }
results = query("status:new limit:" + i);
- assertEquals(Math.min(i, n), results.size());
+ String msg = "i=" + i;
+ assert_().withFailureMessage(msg).that(results).hasSize(expectedSize);
assertResultEquals(last, results.get(0));
+ assert_().withFailureMessage(msg)
+ .that(results.get(results.size() - 1)._moreChanges)
+ .isEqualTo(expectedMoreChanges);
}
}
@@ -470,25 +577,25 @@ public abstract class AbstractQueryChangesTest {
QueryChanges q;
List<ChangeInfo> results;
results = query("status:new");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(changes.get(1), results.get(0));
assertResultEquals(changes.get(0), results.get(1));
q = newQuery("status:new");
q.setStart(1);
results = query(q);
- assertEquals(1, results.size());
+ assertThat(results).hasSize(1);
assertResultEquals(changes.get(0), results.get(0));
q = newQuery("status:new");
q.setStart(2);
results = query(q);
- assertEquals(0, results.size());
+ assertThat(results).isEmpty();
q = newQuery("status:new");
q.setStart(3);
results = query(q);
- assertEquals(0, results.size());
+ assertThat(results).isEmpty();
}
@Test
@@ -502,27 +609,27 @@ public abstract class AbstractQueryChangesTest {
QueryChanges q;
List<ChangeInfo> results;
results = query("status:new limit:2");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(changes.get(2), results.get(0));
assertResultEquals(changes.get(1), results.get(1));
q = newQuery("status:new limit:2");
q.setStart(1);
results = query(q);
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(changes.get(1), results.get(0));
assertResultEquals(changes.get(0), results.get(1));
q = newQuery("status:new limit:2");
q.setStart(2);
results = query(q);
- assertEquals(1, results.size());
+ assertThat(results).hasSize(1);
assertResultEquals(changes.get(0), results.get(0));
q = newQuery("status:new limit:2");
q.setStart(3);
results = query(q);
- assertEquals(0, results.size());
+ assertThat(results).isEmpty();
}
@Test
@@ -548,7 +655,7 @@ public abstract class AbstractQueryChangesTest {
}
List<ChangeInfo> results = query("status:new");
- assertEquals(5, results.size());
+ assertThat(results).hasSize(5);
assertResultEquals(changes.get(3), results.get(0));
assertResultEquals(changes.get(4), results.get(1));
assertResultEquals(changes.get(1), results.get(2));
@@ -564,11 +671,11 @@ public abstract class AbstractQueryChangesTest {
Change change1 = ins1.insert();
Change change2 = newChange(repo, null, null, null, null).insert();
- assertTrue(lastUpdatedMs(change1) < lastUpdatedMs(change2));
+ assertThat(lastUpdatedMs(change1) < lastUpdatedMs(change2)).isTrue();
List<ChangeInfo> results;
results = query("status:new");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
@@ -578,12 +685,12 @@ public abstract class AbstractQueryChangesTest {
changes.parse(change1.getId()), ins1.getPatchSet()), input);
change1 = db.changes().get(change1.getId());
- assertTrue(lastUpdatedMs(change1) > lastUpdatedMs(change2));
- assertTrue(lastUpdatedMs(change1) - lastUpdatedMs(change2)
- > MILLISECONDS.convert(1, MINUTES));
+ assertThat(lastUpdatedMs(change1) > lastUpdatedMs(change2)).isTrue();
+ assertThat(lastUpdatedMs(change1) - lastUpdatedMs(change2)
+ > MILLISECONDS.convert(1, MINUTES)).isTrue();
results = query("status:new");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
// change1 moved to the top.
assertResultEquals(change1, results.get(0));
assertResultEquals(change2, results.get(1));
@@ -596,11 +703,11 @@ public abstract class AbstractQueryChangesTest {
Change change1 = ins1.insert();
Change change2 = newChange(repo, null, null, null, null).insert();
- assertTrue(lastUpdatedMs(change1) < lastUpdatedMs(change2));
+ assertThat(lastUpdatedMs(change1) < lastUpdatedMs(change2)).isTrue();
List<ChangeInfo> results;
results = query("status:new");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
@@ -610,12 +717,12 @@ public abstract class AbstractQueryChangesTest {
changes.parse(change1.getId()), ins1.getPatchSet()), input);
change1 = db.changes().get(change1.getId());
- assertTrue(lastUpdatedMs(change1) > lastUpdatedMs(change2));
- assertTrue(lastUpdatedMs(change1) - lastUpdatedMs(change2)
- < MILLISECONDS.convert(1, MINUTES));
+ assertThat(lastUpdatedMs(change1) > lastUpdatedMs(change2)).isTrue();
+ assertThat(lastUpdatedMs(change1) - lastUpdatedMs(change2)
+ < MILLISECONDS.convert(1, MINUTES)).isTrue();
results = query("status:new");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
// change1 moved to the top.
assertResultEquals(change1, results.get(0));
assertResultEquals(change2, results.get(1));
@@ -631,7 +738,7 @@ public abstract class AbstractQueryChangesTest {
newChange(repo, null, null, user2, null).insert();
}
- //assertResultEquals(change, queryOne("status:new ownerin:Administrators"));
+ assertResultEquals(change, queryOne("status:new ownerin:Administrators"));
assertResultEquals(change,
queryOne("status:new ownerin:Administrators limit:2"));
}
@@ -645,8 +752,8 @@ public abstract class AbstractQueryChangesTest {
newChange(repo, null, null, user2, null).insert();
}
- assertTrue(query("status:new ownerin:Administrators").isEmpty());
- assertTrue(query("status:new ownerin:Administrators limit:2").isEmpty());
+ assertThat(query("status:new ownerin:Administrators")).isEmpty();
+ assertThat(query("status:new ownerin:Administrators limit:2")).isEmpty();
}
@Test
@@ -658,7 +765,7 @@ public abstract class AbstractQueryChangesTest {
.create());
Change change = newChange(repo, commit, null, null, null).insert();
- assertTrue(query("file:file").isEmpty());
+ assertThat(query("file:file")).isEmpty();
assertResultEquals(change, queryOne("file:dir"));
assertResultEquals(change, queryOne("file:file1"));
assertResultEquals(change, queryOne("file:file2"));
@@ -675,8 +782,8 @@ public abstract class AbstractQueryChangesTest {
.create());
Change change = newChange(repo, commit, null, null, null).insert();
- assertTrue(query("file:.*file.*").isEmpty());
- assertTrue(query("file:^file.*").isEmpty()); // Whole path only.
+ assertThat(query("file:.*file.*")).isEmpty();
+ assertThat(query("file:^file.*")).isEmpty(); // Whole path only.
assertResultEquals(change, queryOne("file:^dir.file.*"));
}
@@ -689,10 +796,10 @@ public abstract class AbstractQueryChangesTest {
.create());
Change change = newChange(repo, commit, null, null, null).insert();
- assertTrue(query("path:file").isEmpty());
- assertTrue(query("path:dir").isEmpty());
- assertTrue(query("path:file1").isEmpty());
- assertTrue(query("path:file2").isEmpty());
+ assertThat(query("path:file")).isEmpty();
+ assertThat(query("path:dir")).isEmpty();
+ assertThat(query("path:file1")).isEmpty();
+ assertThat(query("path:file2")).isEmpty();
assertResultEquals(change, queryOne("path:dir/file1"));
assertResultEquals(change, queryOne("path:dir/file2"));
}
@@ -706,7 +813,7 @@ public abstract class AbstractQueryChangesTest {
.create());
Change change = newChange(repo, commit, null, null, null).insert();
- assertTrue(query("path:.*file.*").isEmpty());
+ assertThat(query("path:.*file.*")).isEmpty();
assertResultEquals(change, queryOne("path:^dir.file.*"));
}
@@ -726,7 +833,7 @@ public abstract class AbstractQueryChangesTest {
postReview.apply(new RevisionResource(
changes.parse(change.getId()), ins.getPatchSet()), input);
- assertTrue(query("comment:foo").isEmpty());
+ assertThat(query("comment:foo")).isEmpty();
assertResultEquals(change, queryOne("comment:toplevel"));
assertResultEquals(change, queryOne("comment:inline"));
}
@@ -740,25 +847,25 @@ public abstract class AbstractQueryChangesTest {
Change change2 = newChange(repo, null, null, null, null).insert();
clockStepMs = 0; // Queried by AgePredicate constructor.
long now = TimeUtil.nowMs();
- assertEquals(thirtyHours, lastUpdatedMs(change2) - lastUpdatedMs(change1));
- assertEquals(thirtyHours, now - lastUpdatedMs(change2));
- assertEquals(now, TimeUtil.nowMs());
+ assertThat(lastUpdatedMs(change2) - lastUpdatedMs(change1)).isEqualTo(thirtyHours);
+ assertThat(now - lastUpdatedMs(change2)).isEqualTo(thirtyHours);
+ assertThat(TimeUtil.nowMs()).isEqualTo(now);
- assertTrue(query("-age:1d").isEmpty());
- assertTrue(query("-age:" + (30*60-1) + "m").isEmpty());
+ assertThat(query("-age:1d")).isEmpty();
+ assertThat(query("-age:" + (30 * 60 - 1) + "m")).isEmpty();
assertResultEquals(change2, queryOne("-age:2d"));
List<ChangeInfo> results;
results = query("-age:3d");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
- assertTrue(query("age:3d").isEmpty());
+ assertThat(query("age:3d")).isEmpty();
assertResultEquals(change1, queryOne("age:2d"));
results = query("age:1d");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@@ -771,11 +878,11 @@ public abstract class AbstractQueryChangesTest {
Change change2 = newChange(repo, null, null, null, null).insert();
clockStepMs = 0;
- assertTrue(query("before:2009-09-29").isEmpty());
- assertTrue(query("before:2009-09-30").isEmpty());
- assertTrue(query("before:\"2009-09-30 16:59:00 -0400\"").isEmpty());
- assertTrue(query("before:\"2009-09-30 20:59:00 -0000\"").isEmpty());
- assertTrue(query("before:\"2009-09-30 20:59:00\"").isEmpty());
+ assertThat(query("before:2009-09-29")).isEmpty();
+ assertThat(query("before:2009-09-30")).isEmpty();
+ assertThat(query("before:\"2009-09-30 16:59:00 -0400\"")).isEmpty();
+ assertThat(query("before:\"2009-09-30 20:59:00 -0000\"")).isEmpty();
+ assertThat(query("before:\"2009-09-30 20:59:00\"")).isEmpty();
assertResultEquals(change1,
queryOne("before:\"2009-09-30 17:02:00 -0400\""));
assertResultEquals(change1,
@@ -786,7 +893,7 @@ public abstract class AbstractQueryChangesTest {
List<ChangeInfo> results;
results = query("before:2009-10-03");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@@ -799,7 +906,7 @@ public abstract class AbstractQueryChangesTest {
Change change2 = newChange(repo, null, null, null, null).insert();
clockStepMs = 0;
- assertTrue(query("after:2009-10-03").isEmpty());
+ assertThat(query("after:2009-10-03")).isEmpty();
assertResultEquals(change2,
queryOne("after:\"2009-10-01 20:59:59 -0400\""));
assertResultEquals(change2,
@@ -808,7 +915,7 @@ public abstract class AbstractQueryChangesTest {
List<ChangeInfo> results;
results = query("after:2009-09-30");
- assertEquals(2, results.size());
+ assertThat(results).hasSize(2);
assertResultEquals(change2, results.get(0));
assertResultEquals(change1, results.get(1));
}
@@ -827,14 +934,14 @@ public abstract class AbstractQueryChangesTest {
Change change1 = newChange(repo, commit1, null, null, null).insert();
Change change2 = newChange(repo, commit2, null, null, null).insert();
- assertTrue(query("added:>4").isEmpty());
+ assertThat(query("added:>4")).isEmpty();
assertResultEquals(change1, queryOne("added:3"));
assertResultEquals(change1, queryOne("added:>2"));
assertResultEquals(change1, queryOne("added:>=3"));
assertResultEquals(change2, queryOne("added:<1"));
assertResultEquals(change2, queryOne("added:<=0"));
- assertTrue(query("deleted:>3").isEmpty());
+ assertThat(query("deleted:>3")).isEmpty();
assertResultEquals(change2, queryOne("deleted:2"));
assertResultEquals(change2, queryOne("deleted:>1"));
assertResultEquals(change2, queryOne("deleted:>=2"));
@@ -842,7 +949,7 @@ public abstract class AbstractQueryChangesTest {
assertResultEquals(change1, queryOne("deleted:<=0"));
for (String str : Lists.newArrayList("delta", "size")) {
- assertTrue(query(str + ":<2").isEmpty());
+ assertThat(query(str + ":<2")).isEmpty();
assertResultEquals(change1, queryOne(str + ":3"));
assertResultEquals(change1, queryOne(str + ":>2"));
assertResultEquals(change1, queryOne(str + ":>=3"));
@@ -851,6 +958,50 @@ public abstract class AbstractQueryChangesTest {
}
}
+ private List<Change> setUpHashtagChanges() throws Exception {
+ TestRepository<InMemoryRepository> repo = createProject("repo");
+ Change change1 = newChange(repo, null, null, null, null).insert();
+ Change change2 = newChange(repo, null, null, null, null).insert();
+
+ HashtagsInput in = new HashtagsInput();
+ in.add = ImmutableSet.of("foo");
+ gApi.changes().id(change1.getId().get()).setHashtags(in);
+
+ in.add = ImmutableSet.of("foo", "bar", "a tag");
+ gApi.changes().id(change2.getId().get()).setHashtags(in);
+
+ return ImmutableList.of(change1, change2);
+ }
+
+ @Test
+ public void byHashtagWithNotedb() throws Exception {
+ assume().that(notesMigration.enabled()).isTrue();
+ List<Change> changes = setUpHashtagChanges();
+ List<ChangeInfo> results = query("hashtag:foo");
+ assertThat(results).hasSize(2);
+ assertResultEquals(changes.get(1), results.get(0));
+ assertResultEquals(changes.get(0), results.get(1));
+ assertResultEquals(changes.get(1), queryOne("hashtag:bar"));
+ assertResultEquals(changes.get(1), queryOne("hashtag:\"a tag\""));
+ assertResultEquals(changes.get(1), queryOne("hashtag:\"a tag \""));
+ assertResultEquals(changes.get(1), queryOne("hashtag:\" a tag \""));
+ assertResultEquals(changes.get(1), queryOne("hashtag:\"#a tag\""));
+ assertResultEquals(changes.get(1), queryOne("hashtag:\"# #a tag\""));
+ }
+
+ @Test
+ public void byHashtagWithoutNotedb() throws Exception {
+ assume().that(notesMigration.enabled()).isFalse();
+ setUpHashtagChanges();
+ assertThat(query("hashtag:foo")).isEmpty();
+ assertThat(query("hashtag:bar")).isEmpty();
+ assertThat(query("hashtag:\" bar \"")).isEmpty();
+ assertThat(query("hashtag:\"a tag\"")).isEmpty();
+ assertThat(query("hashtag:\" a tag \"")).isEmpty();
+ assertThat(query("hashtag:#foo")).isEmpty();
+ assertThat(query("hashtag:\"# #foo\"")).isEmpty();
+ }
+
@Test
public void byDefault() throws Exception {
TestRepository<InMemoryRepository> repo = createProject("repo");
@@ -884,6 +1035,7 @@ public abstract class AbstractQueryChangesTest {
assertResultEquals(change1,
queryOne(Integer.toString(change1.getId().get())));
+ assertResultEquals(change1, queryOne(ChangeTriplet.format(change1)));
assertResultEquals(change2, queryOne("foosubject"));
assertResultEquals(change3, queryOne("Foo.java"));
assertResultEquals(change4, queryOne("Code-Review+1"));
@@ -892,8 +1044,51 @@ public abstract class AbstractQueryChangesTest {
assertResultEquals(change6, queryOne("branch6"));
assertResultEquals(change6, queryOne("refs/heads/branch6"));
- assertEquals(6, query("user@example.com").size());
- assertEquals(6, query("repo").size());
+ assertThat(query("user@example.com")).hasSize(6);
+ assertThat(query("repo")).hasSize(6);
+ }
+
+ @Test
+ public void implicitVisibleTo() throws Exception {
+ TestRepository<InMemoryRepository> repo = createProject("repo");
+ Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
+ Change change2 = ins2.getChange();
+ change2.setStatus(Change.Status.DRAFT);
+ ins2.insert();
+
+ String q = "project:repo";
+ List<ChangeInfo> results = query(q);
+ assertThat(results).hasSize(2);
+ assertResultEquals(change2, results.get(0));
+ assertResultEquals(change1, results.get(1));
+
+ // Second user cannot see first user's drafts.
+ requestContext.setContext(newRequestContext(accountManager
+ .authenticate(AuthRequest.forUser("anotheruser")).getAccountId()));
+ assertResultEquals(change1, queryOne(q));
+ }
+
+ @Test
+ public void explicitVisibleTo() throws Exception {
+ TestRepository<InMemoryRepository> repo = createProject("repo");
+ Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
+ Change change2 = ins2.getChange();
+ change2.setStatus(Change.Status.DRAFT);
+ ins2.insert();
+
+ String q = "project:repo";
+ List<ChangeInfo> results = query(q);
+ assertThat(results).hasSize(2);
+ assertResultEquals(change2, results.get(0));
+ assertResultEquals(change1, results.get(1));
+
+ // Second user cannot see first user's drafts.
+ Account.Id user2 = accountManager
+ .authenticate(AuthRequest.forUser("anotheruser"))
+ .getAccountId();
+ assertResultEquals(change1, queryOne(q + " visibleto:" + user2.get()));
}
protected ChangeInserter newChange(
@@ -904,7 +1099,7 @@ public abstract class AbstractQueryChangesTest {
commit = repo.parseBody(repo.commit().message("message").create());
}
Account.Id ownerId = owner != null ? new Account.Id(owner) : userId;
- branch = Objects.firstNonNull(branch, "refs/heads/master");
+ branch = MoreObjects.firstNonNull(branch, "refs/heads/master");
if (!branch.startsWith("refs/heads/")) {
branch = "refs/heads/" + branch;
}
@@ -926,26 +1121,35 @@ public abstract class AbstractQueryChangesTest {
Change change = new Change(new Change.Key(key), id, ownerId,
new Branch.NameKey(project, branch), TimeUtil.nowTs());
return changeFactory.create(
- projectControlFactory.controlFor(project,
- userFactory.create(ownerId)).controlFor(change).getRefControl(),
+ projectControlFactory.controlFor(project, userFactory.create(ownerId)),
change,
commit);
}
protected void assertResultEquals(Change expected, ChangeInfo actual) {
- assertEquals(expected.getId().get(), actual._number);
+ assertThat(actual._number).isEqualTo(expected.getId().get());
}
protected void assertResultEquals(String message, Change expected,
ChangeInfo actual) {
- assertEquals(message, expected.getId().get(), actual._number);
+ assert_().withFailureMessage(message).that(actual._number)
+ .isEqualTo(expected.getId().get());
+ }
+
+ protected void assertBadQuery(Object query) throws Exception {
+ try {
+ query(query);
+ fail("expected BadRequestException for query: " + query);
+ } catch (BadRequestException e) {
+ // Expected.
+ }
}
protected TestRepository<InMemoryRepository> createProject(String name)
throws Exception {
CreateProject create = projectFactory.create(name);
create.apply(TLR, new ProjectInput());
- return new TestRepository<InMemoryRepository>(
+ return new TestRepository<>(
repoManager.openRepository(new Project.NameKey(name)));
}
@@ -958,16 +1162,17 @@ public abstract class AbstractQueryChangesTest {
@SuppressWarnings({"rawtypes", "unchecked"})
protected List<ChangeInfo> query(QueryChanges q) throws Exception {
Object result = q.apply(TLR);
- assertTrue(
- String.format("expected List<ChangeInfo>, found %s for [%s]",
- result, q.getQuery(0)),
- result instanceof List);
+ assert_()
+ .withFailureMessage(
+ String.format("expected List<ChangeInfo>, found %s for [%s]",
+ result, q.getQuery(0))).that(result).isInstanceOf(List.class);
List results = (List) result;
if (!results.isEmpty()) {
- assertTrue(
- String.format("expected ChangeInfo, found %s for [%s]",
- result, q.getQuery(0)),
- results.get(0) instanceof ChangeInfo);
+ assert_()
+ .withFailureMessage(
+ String.format("expected ChangeInfo, found %s for [%s]", result,
+ q.getQuery(0))).that(results.get(0))
+ .isInstanceOf(ChangeInfo.class);
}
return (List<ChangeInfo>) result;
}
@@ -978,10 +1183,11 @@ public abstract class AbstractQueryChangesTest {
protected ChangeInfo queryOne(Object query) throws Exception {
List<ChangeInfo> results = query(query);
- assertTrue(
- String.format("expected singleton List<ChangeInfo>, found %s for [%s]",
- results, query),
- results.size() == 1);
+ assert_()
+ .withFailureMessage(
+ String.format(
+ "expected singleton List<ChangeInfo>, found %s for [%s]",
+ results, query)).that(results).hasSize(1);
return results.get(0);
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 370dd9d0c5..742c230b31 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -14,12 +14,43 @@
package com.google.gerrit.server.query.change;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+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 {
+ @Override
protected Injector createInjector() {
- return Guice.createInjector(new InMemoryModule());
+ Config luceneConfig = new Config(config);
+ InMemoryModule.setDefaults(luceneConfig);
+ return Guice.createInjector(new InMemoryModule(luceneConfig));
+ }
+
+ @Test
+ public void fullTextWithSpecialChars() throws Exception {
+ TestRepository<InMemoryRepository> repo = createProject("repo");
+ RevCommit commit1 =
+ repo.parseBody(repo.commit().message("foo_bar_foo").create());
+ Change change1 = newChange(repo, commit1, null, null, null).insert();
+ RevCommit commit2 =
+ repo.parseBody(repo.commit().message("one.two.three").create());
+ Change change2 = newChange(repo, commit2, null, null, null).insert();
+
+ assertTrue(query("message:foo_ba").isEmpty());
+ assertResultEquals(change1, queryOne("message:bar"));
+ assertResultEquals(change1, queryOne("message:foo_bar"));
+ assertResultEquals(change1, queryOne("message:foo bar"));
+ assertResultEquals(change2, queryOne("message:two"));
+ assertResultEquals(change2, queryOne("message:one.two"));
+ assertResultEquals(change2, queryOne("message:one two"));
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
deleted file mode 100644
index 1dcd83b796..0000000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
+++ /dev/null
@@ -1,185 +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.query.change;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.change.ChangeInserter;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.testutil.InMemoryModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.List;
-
-public class LuceneQueryChangesV7Test extends AbstractQueryChangesTest {
- protected Injector createInjector() {
- Config cfg = InMemoryModule.newDefaultConfig();
- cfg.setInt("index", "lucene", "testVersion", 7);
- return Guice.createInjector(new InMemoryModule(cfg));
- }
-
- // Tests for features not supported in V7.
- @Ignore
- @Override
- @Test
- public void byProjectPrefix() {}
-
- @Ignore
- @Override
- @Test
- public void byDefault() {}
-
- @Ignore
- @Override
- @Test
- public void bySize() {}
- // End tests for features not supported in V7.
-
- @Test
- public void pagination() throws Exception {
- TestRepository<InMemoryRepository> repo = createProject("repo");
- List<Change> changes = Lists.newArrayList();
- for (int i = 0; i < 5; i++) {
- changes.add(newChange(repo, null, null, null, null).insert());
- }
-
- // Page forward and back through 3 pages of results.
- QueryChanges q;
- List<ChangeInfo> results;
- results = query("status:new limit:2");
- assertEquals(2, results.size());
- assertResultEquals(changes.get(4), results.get(0));
- assertResultEquals(changes.get(3), results.get(1));
-
- q = newQuery("status:new limit:2");
- q.setSortKeyBefore(results.get(1)._sortkey);
- results = query(q);
- assertEquals(2, results.size());
- assertResultEquals(changes.get(2), results.get(0));
- assertResultEquals(changes.get(1), results.get(1));
-
- q = newQuery("status:new limit:2");
- q.setSortKeyBefore(results.get(1)._sortkey);
- results = query(q);
- assertEquals(1, results.size());
- assertResultEquals(changes.get(0), results.get(0));
-
- q = newQuery("status:new limit:2");
- q.setSortKeyAfter(results.get(0)._sortkey);
- results = query(q);
- assertEquals(2, results.size());
- assertResultEquals(changes.get(2), results.get(0));
- assertResultEquals(changes.get(1), results.get(1));
-
- q = newQuery("status:new limit:2");
- q.setSortKeyAfter(results.get(0)._sortkey);
- results = query(q);
- assertEquals(2, results.size());
- assertResultEquals(changes.get(4), results.get(0));
- assertResultEquals(changes.get(3), results.get(1));
- }
-
- @Override
- @Test
- public void updatedOrderWithSubMinuteResolution() throws Exception {
- TestRepository<InMemoryRepository> repo = createProject("repo");
- ChangeInserter ins1 = newChange(repo, null, null, null, null);
- Change change1 = ins1.insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
-
- assertTrue(lastUpdatedMs(change1) < lastUpdatedMs(change2));
-
- List<ChangeInfo> results;
- results = query("status:new");
- assertEquals(2, results.size());
- assertResultEquals(change2, results.get(0));
- assertResultEquals(change1, results.get(1));
-
- ReviewInput input = new ReviewInput();
- input.message = "toplevel";
- postReview.apply(new RevisionResource(
- changes.parse(change1.getId()), ins1.getPatchSet()), input);
- change1 = db.changes().get(change1.getId());
-
- assertTrue(lastUpdatedMs(change1) > lastUpdatedMs(change2));
- assertTrue(lastUpdatedMs(change1) - lastUpdatedMs(change2)
- < MILLISECONDS.convert(1, MINUTES));
-
- results = query("status:new");
- assertEquals(2, results.size());
- // Same order as before change1 was modified.
- assertResultEquals(change2, results.get(0));
- assertResultEquals(change1, results.get(1));
- }
-
- @Test
- public void sortKeyBreaksTiesOnChangeId() throws Exception {
- clockStepMs = 0;
- TestRepository<InMemoryRepository> repo = createProject("repo");
- ChangeInserter ins1 = newChange(repo, null, null, null, null);
- Change change1 = ins1.insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
-
- ReviewInput input = new ReviewInput();
- input.message = "toplevel";
- postReview.apply(new RevisionResource(
- changes.parse(change1.getId()), ins1.getPatchSet()), input);
- change1 = db.changes().get(change1.getId());
-
- assertEquals(change1.getLastUpdatedOn(), change2.getLastUpdatedOn());
-
- List<ChangeInfo> results = query("status:new");
- assertEquals(2, results.size());
- // Updated at the same time, 2 > 1.
- assertResultEquals(change2, results.get(0));
- assertResultEquals(change1, results.get(1));
- }
-
- @Override
- @Test
- public void byTopic() throws Exception {
- TestRepository<InMemoryRepository> repo = createProject("repo");
- ChangeInserter ins1 = newChange(repo, null, null, null, null);
- Change change1 = ins1.getChange();
- change1.setTopic("feature1");
- ins1.insert();
-
- ChangeInserter ins2 = newChange(repo, null, null, null, null);
- Change change2 = ins2.getChange();
- change2.setTopic("feature2");
- ins2.insert();
-
- newChange(repo, null, null, null, null).insert();
-
- assertTrue(query("topic:\"\"").isEmpty());
- assertTrue(query("topic:foo").isEmpty());
- assertResultEquals(change1, queryOne("topic:feature1"));
- assertResultEquals(change2, queryOne("topic:feature2"));
- }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
index 8f1fa868a5..55d0f38c10 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
@@ -79,7 +79,7 @@ public class RegexPathPredicateTest {
}
private static RegexPathPredicate predicate(String pattern) {
- return new RegexPathPredicate(ChangeQueryBuilder.FIELD_PATH, pattern);
+ return new RegexPathPredicate(pattern);
}
private static ChangeData change(String... files) throws OrmException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index 996aafa3c6..0c8157d444 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -70,21 +70,11 @@ public class SchemaCreatorTest {
public void testGetCauses_CreateSchema() throws OrmException, SQLException,
IOException {
// Initially the schema should be empty.
- //
- {
- final JdbcSchema d = (JdbcSchema) db.open();
- try {
- final String[] types = {"TABLE", "VIEW"};
- final ResultSet rs =
- d.getConnection().getMetaData().getTables(null, null, null, types);
- try {
- assertFalse(rs.next());
- } finally {
- rs.close();
- }
- } finally {
- d.close();
- }
+ String[] types = {"TABLE", "VIEW"};
+ try (JdbcSchema d = (JdbcSchema) db.open();
+ ResultSet rs = d.getConnection().getMetaData()
+ .getTables(null, null, null, types)) {
+ assertFalse(rs.next());
}
// Create the schema using the current schema version.
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index d8b6048f15..8686fe6214 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.schema;
+import static org.junit.Assert.assertEquals;
+
import com.google.gerrit.reviewdb.client.SystemConfig;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@@ -47,8 +49,6 @@ import java.io.IOException;
import java.util.List;
import java.util.UUID;
-import static org.junit.Assert.assertEquals;
-
public class SchemaUpdaterTest {
private InMemoryDatabase db;
@@ -74,7 +74,6 @@ public class SchemaUpdaterTest {
protected void configure() {
bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toInstance(db);
bind(SitePaths.class).toInstance(paths);
- install(new SchemaVersion.Module());
Config cfg = new Config();
cfg.setString("user", null, "name", "Gerrit Code Review");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
index 8d41e0ab2a..9c8e86aa9d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
@@ -423,21 +423,17 @@ public class CommitMsgHookTest extends HookTestCase {
}
private DirCacheEntry file(final String name) throws IOException {
- final ObjectInserter oi = repository.newObjectInserter();
- try {
+ try (ObjectInserter oi = repository.newObjectInserter()) {
final DirCacheEntry e = new DirCacheEntry(name);
e.setFileMode(FileMode.REGULAR_FILE);
e.setObjectId(oi.insert(Constants.OBJ_BLOB, Constants.encode(name)));
oi.flush();
return e;
- } finally {
- oi.close();
}
}
private void setHEAD() throws Exception {
- final ObjectInserter oi = repository.newObjectInserter();
- try {
+ try (ObjectInserter oi = repository.newObjectInserter()) {
final CommitBuilder commit = new CommitBuilder();
commit.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {}));
commit.setAuthor(author);
@@ -456,8 +452,6 @@ public class CommitMsgHookTest extends HookTestCase {
default:
fail(Constants.HEAD + " did not change: " + ref.getResult());
}
- } finally {
- oi.close();
}
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
index a2228d8019..3be4f8aa39 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.util;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import java.util.HashSet;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
public class IdGeneratorTest {
@Test
public void test1234() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
index 0ed0ba8df8..4fdbdb2f6f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
@@ -23,23 +23,23 @@ public class LabelVoteTest {
public void parse() {
LabelVote l;
l = LabelVote.parse("Code-Review-2");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) -2, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) -2, l.value());
l = LabelVote.parse("Code-Review-1");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) -1, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) -1, l.value());
l = LabelVote.parse("-Code-Review");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 0, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 0, l.value());
l = LabelVote.parse("Code-Review");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 1, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 1, l.value());
l = LabelVote.parse("Code-Review+1");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 1, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 1, l.value());
l = LabelVote.parse("Code-Review+2");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 2, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 2, l.value());
}
@Test
@@ -55,26 +55,26 @@ public class LabelVoteTest {
public void parseWithEquals() {
LabelVote l;
l = LabelVote.parseWithEquals("Code-Review=-2");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) -2, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) -2, l.value());
l = LabelVote.parseWithEquals("Code-Review=-1");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) -1, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) -1, l.value());
l = LabelVote.parseWithEquals("Code-Review=0");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 0, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 0, l.value());
l = LabelVote.parseWithEquals("Code-Review=1");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 1, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 1, l.value());
l = LabelVote.parseWithEquals("Code-Review=+1");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 1, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 1, l.value());
l = LabelVote.parseWithEquals("Code-Review=2");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 2, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 2, l.value());
l = LabelVote.parseWithEquals("Code-Review=+2");
- assertEquals("Code-Review", l.getLabel());
- assertEquals((short) 2, l.getValue());
+ assertEquals("Code-Review", l.label());
+ assertEquals((short) 2, l.value());
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/ParboiledTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/ParboiledTest.java
new file mode 100644
index 0000000000..f406de1d1d
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/ParboiledTest.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2014 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 static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.parboiled.BaseParser;
+import org.parboiled.Parboiled;
+import org.parboiled.Rule;
+import org.parboiled.annotations.BuildParseTree;
+import org.parboiled.parserunners.ReportingParseRunner;
+import org.parboiled.support.ParseTreeUtils;
+import org.parboiled.support.ParsingResult;
+
+public class ParboiledTest {
+
+ private static final String EXPECTED =
+ "[Expression] '42'\n" +
+ " [Term] '42'\n" +
+ " [Factor] '42'\n" +
+ " [Number] '42'\n" +
+ " [0..9] '4'\n" +
+ " [0..9] '2'\n" +
+ " [ZeroOrMore]\n" +
+ " [ZeroOrMore]\n";
+
+ private CalculatorParser parser;
+
+ @Before
+ public void setUp() {
+ parser = Parboiled.createParser(CalculatorParser.class);
+ }
+
+ @Test
+ public void test() {
+ ParsingResult<String> result =
+ new ReportingParseRunner<String>(parser.Expression()).run("42");
+ assertThat(result.hasErrors()).isFalse();
+ // next test is optional; we could stop here.
+ assertThat(ParseTreeUtils.printNodeTree(result)).isEqualTo(EXPECTED);
+ }
+
+ @BuildParseTree
+ static class CalculatorParser extends BaseParser<Object> {
+ Rule Expression() {
+ return Sequence(Term(), ZeroOrMore(AnyOf("+-"), Term()));
+ }
+
+ Rule Term() {
+ return Sequence(Factor(), ZeroOrMore(AnyOf("*/"), Factor()));
+ }
+
+ Rule Factor() {
+ return FirstOf(Number(), Sequence('(', Expression(), ')'));
+ }
+
+ Rule Number() {
+ return OneOrMore(CharRange('0', '9'));
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java
new file mode 100644
index 0000000000..8f7300516c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2014 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class RegexListSearcherTest {
+ private static final List<String> EMPTY = ImmutableList.of();
+
+ @Test
+ public void emptyList() {
+ assertSearchReturns(EMPTY, "pat", EMPTY);
+ }
+
+ @Test
+ public void hasMatch() {
+ List<String> list = ImmutableList.of("bar", "foo", "quux");
+ assertTrue(RegexListSearcher.ofStrings("foo").hasMatch(list));
+ assertFalse(RegexListSearcher.ofStrings("xyz").hasMatch(list));
+ }
+
+ @Test
+ public void anchors() {
+ List<String> list = ImmutableList.of("foo");
+ assertSearchReturns(list, "^f.*", list);
+ assertSearchReturns(list, "^f.*o$", list);
+ assertSearchReturns(list, "f.*o$", list);
+ assertSearchReturns(list, "f.*o$", list);
+ assertSearchReturns(EMPTY, "^.*\\$", list);
+ }
+
+ @Test
+ public void noCommonPrefix() {
+ List<String> list = ImmutableList.of("bar", "foo", "quux");
+ assertSearchReturns(ImmutableList.of("foo"), "f.*", list);
+ assertSearchReturns(ImmutableList.of("foo"), ".*o.*", list);
+ assertSearchReturns(ImmutableList.of("bar", "foo", "quux"), ".*[aou].*",
+ list);
+ }
+
+ @Test
+ public void commonPrefix() {
+ List<String> list = ImmutableList.of(
+ "bar",
+ "baz",
+ "foo1",
+ "foo2",
+ "foo3",
+ "quux");
+ assertSearchReturns(ImmutableList.of("bar", "baz"), "b.*", list);
+ assertSearchReturns(ImmutableList.of("foo1", "foo2"), "foo[12]", list);
+ assertSearchReturns(ImmutableList.of("foo1", "foo2", "foo3"), "foo.*",
+ list);
+ assertSearchReturns(ImmutableList.of("quux"), "q.*", list);
+ }
+
+ private void assertSearchReturns(List<?> expected, String re,
+ List<String> inputs) {
+ assertTrue(Ordering.natural().isOrdered(inputs));
+ assertEquals(expected,
+ ImmutableList.copyOf(RegexListSearcher.ofStrings(re).search(inputs)));
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
index 1da3c3082a..707dd12df6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
@@ -19,7 +19,7 @@ import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import org.junit.runner.Runner;
@@ -43,26 +43,26 @@ import java.util.List;
* tests is created with the {@link Parameter} field set to the config.
*
* <pre>
- * @RunWith(ConfigSuite.class)
+ * {@literal @}RunWith(ConfigSuite.class)
* public abstract class MyAbstractTest {
- * @ConfigSuite.Parameter
+ * {@literal @}ConfigSuite.Parameter
* protected Config cfg;
*
- * @ConfigSuite.Config
+ * {@literal @}ConfigSuite.Config
* public static Config firstConfig() {
* Config cfg = new Config();
* cfg.setString("gerrit", null, "testValue", "a");
* }
* }
*
- * public class MyTest {
- * @ConfigSuite.Config
+ * public class MyTest extends MyAbstractTest {
+ * {@literal @}ConfigSuite.Config
* public static Config secondConfig() {
* Config cfg = new Config();
* cfg.setString("gerrit", null, "testValue", "b");
* }
*
- * @Test
+ * {@literal @}Test
* public void myTest() {
* // Test using cfg.
* }
@@ -75,12 +75,20 @@ import java.util.List;
* <li><strong>firstConfig</strong>: {@code MyTest.myTest[firstConfig]}</li>
* <li><strong>secondConfig</strong>: {@code MyTest.myTest[secondConfig]}</li>
* </ul>
+ *
+ * Additionally, config values used by <strong>default</strong> can be set
+ * in a method annotated with {@code @ConfigSuite.Default}.
*/
public class ConfigSuite extends Suite {
private static final String DEFAULT = "default";
@Target({METHOD})
@Retention(RUNTIME)
+ public static @interface Default {
+ }
+
+ @Target({METHOD})
+ @Retention(RUNTIME)
public static @interface Config {
}
@@ -111,7 +119,7 @@ public class ConfigSuite extends Suite {
@Override
protected String getName() {
- return Objects.firstNonNull(name, DEFAULT);
+ return MoreObjects.firstNonNull(name, DEFAULT);
}
@Override
@@ -122,11 +130,12 @@ public class ConfigSuite extends Suite {
}
private static List<Runner> runnersFor(Class<?> clazz) {
+ Method defaultConfig = getDefaultConfig(clazz);
List<Method> configs = getConfigs(clazz);
Field field = getParameterField(clazz);
List<Runner> result = Lists.newArrayListWithCapacity(configs.size() + 1);
try {
- result.add(new ConfigRunner(clazz, field, null, null));
+ result.add(new ConfigRunner(clazz, field, null, defaultConfig));
for (Method m : configs) {
result.add(new ConfigRunner(clazz, field, m.getName(), m));
}
@@ -136,6 +145,20 @@ public class ConfigSuite extends Suite {
}
}
+ private static Method getDefaultConfig(Class<?> clazz) {
+ Method result = null;
+ for (Method m : clazz.getMethods()) {
+ Default ann = m.getAnnotation(Default.class);
+ if (ann != null) {
+ checkArgument(result == null,
+ "Multiple methods annotated with @ConfigSuite.Method: %s, %s",
+ result, m);
+ result = m;
+ }
+ }
+ return result;
+ }
+
private static List<Method> getConfigs(Class<?> clazz) {
List<Method> result = Lists.newArrayListWithExpectedSize(3);
for (Method m : clazz.getMethods()) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java
new file mode 100644
index 0000000000..c3bfe1ef11
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountByEmailCache.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2015 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.testutil;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountByEmailCache;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Fake implementation of {@link AccountByEmailCache} for testing. */
+public class FakeAccountByEmailCache implements AccountByEmailCache {
+ private final SetMultimap<String, Account.Id> byEmail;
+ private final Set<Account.Id> anyEmail;
+
+ public FakeAccountByEmailCache() {
+ byEmail = HashMultimap.create();
+ anyEmail = new HashSet<>();
+ }
+
+ @Override
+ public synchronized Set<Account.Id> get(String email) {
+ return Collections.unmodifiableSet(
+ Sets.union(byEmail.get(email), anyEmail));
+ }
+
+ @Override
+ public synchronized void evict(String email) {
+ // Do nothing.
+ }
+
+ public synchronized void put(String email, Account.Id id) {
+ byEmail.put(email, id);
+ }
+
+ public synchronized void putAny(Account.Id id) {
+ anyEmail.add(id);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
index db1ed05026..011d69d0be 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
@@ -16,12 +16,12 @@ package com.google.gerrit.testutil;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
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.util.TimeUtil;
import java.util.Map;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
index 0ccdae77ae..7f8fc329fc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
@@ -170,6 +170,7 @@ public abstract class FilesystemLoggingMockingTestCase extends LoggingMockingTes
}
}
+ @Override
public void tearDown() throws Exception {
cleanupCreatedFiles();
super.tearDown();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
index 6353904681..49fcc96694 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
@@ -67,7 +67,6 @@ public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
}
}
- private final SchemaVersion schemaVersion;
private final SchemaCreator schemaCreator;
private Connection openHandle;
@@ -75,9 +74,7 @@ public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
private boolean created;
@Inject
- InMemoryDatabase(SchemaVersion schemaVersion,
- SchemaCreator schemaCreator) throws OrmException {
- this.schemaVersion = schemaVersion;
+ InMemoryDatabase(SchemaCreator schemaCreator) throws OrmException {
this.schemaCreator = schemaCreator;
try {
@@ -161,6 +158,6 @@ public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
public void assertSchemaVersion() throws OrmException {
final CurrentSchemaVersion act = getSchemaVersion();
- assertEquals(schemaVersion.getVersionNbr(), act.versionNbr);
+ assertEquals(SchemaVersion.getBinaryVersion(), act.versionNbr);
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index d96e86126c..76af6f169c 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -27,7 +27,6 @@ import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.AllUsersName;
@@ -42,7 +41,9 @@ import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.config.TrackingFootersProvider;
+import com.google.gerrit.server.git.ChangeCacheImplModule;
import com.google.gerrit.server.git.EmailReviewCommentsExecutor;
+import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.PerThreadRequestScope;
import com.google.gerrit.server.git.WorkQueue;
@@ -51,10 +52,10 @@ import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.DiffExecutor;
-import com.google.gerrit.server.schema.Current;
import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.server.schema.SchemaCreator;
-import com.google.gerrit.server.schema.SchemaVersion;
+import com.google.gerrit.server.securestore.DefaultSecureStore;
+import com.google.gerrit.server.securestore.SecureStore;
import com.google.gerrit.server.ssh.NoSshKeyCache;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
@@ -82,6 +83,11 @@ import java.util.concurrent.ExecutorService;
public class InMemoryModule extends FactoryModule {
public static Config newDefaultConfig() {
Config cfg = new Config();
+ setDefaults(cfg);
+ return cfg;
+ }
+
+ public static void setDefaults(Config cfg) {
cfg.setEnum("auth", null, "type", AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT);
cfg.setString("gerrit", null, "basePath", "git");
cfg.setString("gerrit", null, "allProjects", "Test-Projects");
@@ -93,7 +99,6 @@ public class InMemoryModule extends FactoryModule {
cfg.setBoolean("index", "lucene", "testInmemory", true);
cfg.setInt("index", "lucene", "testVersion",
ChangeSchemas.getLatest().getVersion());
- return cfg;
}
private final Config cfg;
@@ -122,11 +127,11 @@ public class InMemoryModule extends FactoryModule {
}
});
install(cfgInjector.getInstance(GerritGlobalModule.class));
+ install(new ChangeCacheImplModule(false));
+ factory(GarbageCollection.Factory.class);
bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
- install(new SchemaVersion.Module());
-
bind(File.class).annotatedWith(SitePath.class).toInstance(new File("."));
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toInstance(
@@ -152,6 +157,8 @@ public class InMemoryModule extends FactoryModule {
bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {})
.to(InMemoryDatabase.class);
+ bind(SecureStore.class).to(DefaultSecureStore.class);
+
bind(ChangeHooks.class).to(DisabledChangeHooks.class);
install(NoSshKeyCache.module());
install(new CanonicalWebUrlModule() {
@@ -169,13 +176,12 @@ public class InMemoryModule extends FactoryModule {
@Singleton
@DiffExecutor
public ExecutorService createDiffExecutor() {
- return MoreExecutors.sameThreadExecutor();
+ return MoreExecutors.newDirectExecutorService();
}
});
install(new DefaultCacheFactory.Module());
install(new SmtpEmailSender.Module());
install(new SignedTokenEmailTokenVerifier.Module());
- install(new MergeabilityChecksExecutorModule());
IndexType indexType = null;
try {
@@ -206,9 +212,9 @@ public class InMemoryModule extends FactoryModule {
@Provides
@Singleton
- InMemoryDatabase getInMemoryDatabase(@Current SchemaVersion schemaVersion,
- SchemaCreator schemaCreator) throws OrmException {
- return new InMemoryDatabase(schemaVersion, schemaCreator);
+ InMemoryDatabase getInMemoryDatabase(SchemaCreator schemaCreator)
+ throws OrmException {
+ return new InMemoryDatabase(schemaCreator);
}
private Module luceneIndexModule() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
index 328df75dc2..06354640b7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
@@ -80,6 +80,12 @@ public class InMemoryRepositoryManager implements GitRepositoryManager {
}
@Override
+ public InMemoryRepository openMetadataRepository(Project.NameKey name)
+ throws RepositoryNotFoundException {
+ return openRepository(name);
+ }
+
+ @Override
public SortedSet<Project.NameKey> list() {
SortedSet<Project.NameKey> names = Sets.newTreeSet();
for (DfsRepository repo : repos.values()) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TempFileUtil.java
index 0b78f5717c..72c2b5ab97 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TempFileUtil.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.acceptance;
+package com.google.gerrit.testutil;
import java.io.File;
import java.io.IOException;
@@ -22,7 +22,7 @@ import java.util.List;
public class TempFileUtil {
private static List<File> allDirsCreated = new ArrayList<>();
- public synchronized static File createTempDirectory() throws IOException {
+ public static synchronized File createTempDirectory() throws IOException {
File tmp = File.createTempFile("gerrit_test_", "").getCanonicalFile();
if (!tmp.delete() || !tmp.mkdir()) {
throw new IOException("Cannot create " + tmp.getPath());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
index 1a0ccaf504..675634e6f0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestChanges.java
@@ -17,62 +17,96 @@ package com.google.gerrit.testutil;
import static org.easymock.EasyMock.expect;
import com.google.common.collect.Ordering;
+import com.google.gerrit.common.TimeUtil;
+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.IdentifiedUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeDraftUpdate;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Injector;
import org.easymock.EasyMock;
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Utility functions to create and manipulate Change, ChangeUpdate, and
* ChangeControl objects for testing.
*/
public class TestChanges {
- public static Change newChange(Project.NameKey project, IdentifiedUser user) {
- Change.Id changeId = new Change.Id(1);
+ private static final AtomicInteger nextChangeId = new AtomicInteger(1);
+
+ public static Change newChange(Project.NameKey project, Account.Id userId) {
+ return newChange(project, userId, nextChangeId.getAndIncrement());
+ }
+
+ public static Change newChange(Project.NameKey project, Account.Id userId,
+ int id) {
+ Change.Id changeId = new Change.Id(id);
Change c = new Change(
new Change.Key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
changeId,
- user.getAccount().getId(),
+ userId,
new Branch.NameKey(project, "master"),
TimeUtil.nowTs());
incrementPatchSet(c);
return c;
}
+ public static PatchSet newPatchSet(PatchSet.Id id, ObjectId revision,
+ Account.Id userId) {
+ return newPatchSet(id, revision.name(), userId);
+ }
+
+ 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;
+ }
+
public static ChangeUpdate newUpdate(Injector injector,
- GitRepositoryManager repoManager, Change c, final IdentifiedUser user)
+ GitRepositoryManager repoManager, NotesMigration migration, Change c,
+ final AllUsersNameProvider allUsers, final IdentifiedUser user)
throws OrmException {
return injector.createChildInjector(new FactoryModule() {
@Override
public void configure() {
factory(ChangeUpdate.Factory.class);
+ factory(ChangeDraftUpdate.Factory.class);
bind(IdentifiedUser.class).toInstance(user);
+ bind(AllUsersName.class).toProvider(allUsers);
}
}).getInstance(ChangeUpdate.Factory.class).create(
- stubChangeControl(repoManager, c, user), TimeUtil.nowTs(),
- Ordering.<String> natural());
+ stubChangeControl(repoManager, migration, c, allUsers, user),
+ TimeUtil.nowTs(), Ordering.<String> natural());
}
public static ChangeControl stubChangeControl(
- GitRepositoryManager repoManager, Change c, IdentifiedUser user)
- throws OrmException {
+ GitRepositoryManager repoManager, NotesMigration migration,
+ Change c, AllUsersNameProvider allUsers,
+ IdentifiedUser user) throws OrmException {
ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class);
expect(ctl.getChange()).andStubReturn(c);
expect(ctl.getCurrentUser()).andStubReturn(user);
- ChangeNotes notes = new ChangeNotes(repoManager, c);
- notes = notes.load();
+ ChangeNotes notes = new ChangeNotes(repoManager, migration, allUsers, c)
+ .load();
expect(ctl.getNotes()).andStubReturn(notes);
EasyMock.replay(ctl);
return ctl;
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
index 830db27106..78f5265e1f 100644
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
@@ -41,8 +41,6 @@ import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.query.change.SortKeyPredicate;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Provider;
@@ -50,7 +48,6 @@ import com.google.inject.Provider;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.search.Query;
-import org.apache.lucene.util.Version;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.SortClause;
import org.apache.solr.client.solrj.SolrServer;
@@ -108,13 +105,8 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
throw new IllegalStateException("index.url must be supplied");
}
- // Version is only used to determine the list of stop words used by the
- // analyzer, so use the latest version rather than trying to match the Solr
- // server version.
- @SuppressWarnings("deprecation")
- Version v = Version.LUCENE_CURRENT;
queryBuilder = new QueryBuilder(
- schema, new StandardAnalyzer(v, CharArraySet.EMPTY_SET));
+ new StandardAnalyzer(CharArraySet.EMPTY_SET));
base = Strings.nullToEmpty(base);
openIndex = new CloudSolrServer(url);
@@ -147,25 +139,6 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
}
@Override
- public void insert(ChangeData cd) throws IOException {
- String id = cd.getId().toString();
- SolrInputDocument doc = toDocument(cd);
- try {
- if (cd.change().getStatus().isOpen()) {
- closedIndex.deleteById(id);
- openIndex.add(doc);
- } else {
- openIndex.deleteById(id);
- closedIndex.add(doc);
- }
- } catch (OrmException | SolrServerException e) {
- throw new IOException(e);
- }
- commit(openIndex);
- commit(closedIndex);
- }
-
- @Override
public void replace(ChangeData cd) throws IOException {
String id = cd.getId().toString();
SolrInputDocument doc = toDocument(cd);
@@ -185,22 +158,8 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
}
@Override
- public void delete(ChangeData cd) throws IOException {
- String id = cd.getId().toString();
- try {
- if (cd.change().getStatus().isOpen()) {
- delete(id, openIndex);
- } else {
- delete(id, closedIndex);
- }
- } catch (OrmException e) {
- throw new IOException(e);
- }
- }
-
- @Override
- public void delete(int id) throws IOException {
- String idString = Integer.toString(id);
+ public void delete(Change.Id id) throws IOException {
+ String idString = Integer.toString(id.get());
delete(idString, openIndex);
delete(idString, closedIndex);
}
@@ -238,23 +197,15 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
indexes.add(closedIndex);
}
return new QuerySource(indexes, queryBuilder.toQuery(p), start, limit,
- getSorts(schema, p));
+ getSorts());
}
- @SuppressWarnings("deprecation")
- private static List<SortClause> getSorts(Schema<ChangeData> schema,
- Predicate<ChangeData> p) {
- if (SortKeyPredicate.hasSortKeyField(schema)) {
- boolean reverse = ChangeQueryBuilder.hasNonTrivialSortKeyAfter(schema, p);
- return ImmutableList.of(new SortClause(ChangeField.SORTKEY.getName(),
- !reverse ? SolrQuery.ORDER.desc : SolrQuery.ORDER.asc));
- } else {
- return ImmutableList.of(
- new SortClause(
- ChangeField.UPDATED.getName(), SolrQuery.ORDER.desc),
- new SortClause(
- ChangeField.LEGACY_ID.getName(), SolrQuery.ORDER.desc));
- }
+ private static List<SortClause> getSorts() {
+ return ImmutableList.of(
+ new SortClause(
+ ChangeField.UPDATED.getName(), SolrQuery.ORDER.desc),
+ new SortClause(
+ ChangeField.LEGACY_ID.getName(), SolrQuery.ORDER.desc));
}
private void commit(SolrServer server) throws IOException {
@@ -266,12 +217,12 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
}
private class QuerySource implements ChangeDataSource {
- private final List<SolrServer> indexes;
+ private final List<SolrServer> servers;
private final SolrQuery query;
public QuerySource(List<SolrServer> indexes, Query q, int start, int limit,
List<SortClause> sorts) {
- this.indexes = indexes;
+ this.servers = indexes;
query = new SolrQuery(q.toString());
query.setParam("shards.tolerant", true);
@@ -303,7 +254,7 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
try {
// TODO Sort documents during merge to select only top N.
SolrDocumentList docs = new SolrDocumentList();
- for (SolrServer index : indexes) {
+ for (SolrServer index : servers) {
docs.addAll(index.query(query).getResults());
}
@@ -337,49 +288,35 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
}
}
- private SolrInputDocument toDocument(ChangeData cd) throws IOException {
- try {
- SolrInputDocument result = new SolrInputDocument();
- for (Values<ChangeData> values : schema.buildFields(cd, fillArgs)) {
- add(result, values);
- }
- return result;
- } catch (OrmException e) {
- throw new IOException(e);
+ private SolrInputDocument toDocument(ChangeData cd) {
+ SolrInputDocument result = new SolrInputDocument();
+ for (Values<ChangeData> values : schema.buildFields(cd, fillArgs)) {
+ add(result, values);
}
+ return result;
}
- private void add(SolrInputDocument doc, Values<ChangeData> values)
- throws OrmException {
+ private void add(SolrInputDocument doc, Values<ChangeData> values) {
String name = values.getField().getName();
FieldType<?> type = values.getField().getType();
if (type == FieldType.INTEGER) {
for (Object value : values.getValues()) {
- doc.addField(name, (Integer) value);
+ doc.addField(name, value);
}
} else if (type == FieldType.LONG) {
for (Object value : values.getValues()) {
- doc.addField(name, (Long) value);
+ doc.addField(name, value);
}
} else if (type == FieldType.TIMESTAMP) {
- @SuppressWarnings("deprecation")
- boolean legacy = values.getField() == ChangeField.LEGACY_UPDATED;
- if (legacy) {
- for (Object value : values.getValues()) {
- int t = queryBuilder.toIndexTimeInMinutes((Timestamp) value);
- doc.addField(name, t);
- }
- } else {
- for (Object value : values.getValues()) {
- doc.addField(name, ((Timestamp) value).getTime());
- }
+ for (Object value : values.getValues()) {
+ doc.addField(name, ((Timestamp) value).getTime());
}
} else if (type == FieldType.EXACT
|| type == FieldType.PREFIX
|| type == FieldType.FULL_TEXT) {
for (Object value : values.getValues()) {
- doc.addField(name, (String) value);
+ doc.addField(name, value);
}
} else {
throw QueryBuilder.badFieldType(type);
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
index 7474d0f499..38de6eeb38 100644
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
@@ -22,6 +22,7 @@ import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.ChangeSchemas;
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Provider;
@@ -49,6 +50,7 @@ public class SolrIndexModule extends LifecycleModule {
@Override
protected void configure() {
+ bind(IndexConfig.class).toInstance(IndexConfig.createDefault());
install(new IndexModule(threads));
bind(ChangeIndex.class).to(SolrChangeIndex.class);
listener().to(SolrChangeIndex.class);
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index fad371a340..4774cb3868 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -17,6 +17,7 @@ java_library(
'//lib:guava',
'//lib:gwtorm',
'//lib:jsch',
+ '//lib/auto:auto-value',
'//lib/commons:codec',
'//lib/commons:collections',
'//lib/guice:guice',
@@ -51,6 +52,7 @@ java_test(
'//gerrit-server:server',
'//lib:guava',
'//lib:junit',
+ '//lib/mina:sshd',
],
source_under_test = [':sshd'],
)
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 40f7c7db53..c0fd2ac76a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -16,9 +16,9 @@ package com.google.gerrit.sshd;
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
@@ -27,7 +27,6 @@ import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.SshScope.Context;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.EndOfOptionsHandler;
@@ -111,18 +110,22 @@ public abstract class BaseCommand implements Command {
task = Atomics.newReference();
}
+ @Override
public void setInputStream(final InputStream in) {
this.in = in;
}
+ @Override
public void setOutputStream(final OutputStream out) {
this.out = out;
}
+ @Override
public void setErrorStream(final OutputStream err) {
this.err = err;
}
+ @Override
public void setExitCallback(final ExitCallback callback) {
this.exit = callback;
}
@@ -475,7 +478,7 @@ public abstract class BaseCommand implements Command {
}
@Override
- public NameKey getProjectNameKey() {
+ public Project.NameKey getProjectNameKey() {
return projectName;
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index f394766b43..56441fba40 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -95,6 +95,7 @@ class CommandFactoryProvider implements Provider<CommandFactory>,
@Override
public CommandFactory get() {
return new CommandFactory() {
+ @Override
public Command createCommand(final String requestCommand) {
return new Trampoline(requestCommand);
}
@@ -121,31 +122,38 @@ class CommandFactoryProvider implements Provider<CommandFactory>,
task = Atomics.newReference();
}
+ @Override
public void setInputStream(final InputStream in) {
this.in = in;
}
+ @Override
public void setOutputStream(final OutputStream out) {
this.out = out;
}
+ @Override
public void setErrorStream(final OutputStream err) {
this.err = err;
}
+ @Override
public void setExitCallback(final ExitCallback callback) {
this.exit = callback;
}
+ @Override
public void setSession(final ServerSession session) {
final SshSession s = session.getAttribute(SshSession.KEY);
this.ctx = sshScope.newContext(schemaFactory, s, commandLine);
}
+ @Override
public void start(final Environment env) throws IOException {
this.env = env;
final Context ctx = this.ctx;
task.set(startExecutor.submit(new Runnable() {
+ @Override
public void run() {
try {
onStart();
@@ -245,7 +253,7 @@ class CommandFactoryProvider implements Provider<CommandFactory>,
}
/** Split a command line into a string array. */
- static public String[] split(String commandLine) {
+ public static String[] split(String commandLine) {
final List<String> list = new ArrayList<>();
boolean inquote = false;
boolean inDblQuote = false;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
index 78f006b1b6..a63abee73b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
@@ -54,6 +54,7 @@ class NoShell implements Factory<Command> {
this.shell = shell;
}
+ @Override
public Command create() {
return shell.get();
}
@@ -77,27 +78,33 @@ class NoShell implements Factory<Command> {
this.sshScope = sshScope;
}
+ @Override
public void setInputStream(final InputStream in) {
this.in = in;
}
+ @Override
public void setOutputStream(final OutputStream out) {
this.out = out;
}
+ @Override
public void setErrorStream(final OutputStream err) {
this.err = err;
}
+ @Override
public void setExitCallback(final ExitCallback callback) {
this.exit = callback;
}
+ @Override
public void setSession(final ServerSession session) {
SshSession s = session.getAttribute(SshSession.KEY);
this.context = sshScope.newContext(schemaFactory, s, "");
}
+ @Override
public void start(final Environment env) throws IOException {
Context old = sshScope.set(context);
String message;
@@ -115,6 +122,7 @@ class NoShell implements Factory<Command> {
exit.onExit(127);
}
+ @Override
public void destroy() {
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java
index 334f155638..b5378f1d4d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java
@@ -16,12 +16,11 @@ package com.google.gerrit.sshd;
import com.google.common.base.Preconditions;
import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.inject.Inject;
import com.google.inject.binder.LinkedBindingBuilder;
import org.apache.sshd.server.Command;
-import javax.inject.Inject;
-
public abstract class PluginCommandModule extends CommandModule {
private CommandName command;
@@ -39,6 +38,7 @@ public abstract class PluginCommandModule extends CommandModule {
protected abstract void configureCommands();
+ @Override
protected LinkedBindingBuilder<Command> command(String subCmd) {
return bind(Commands.key(command, subCmd));
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
index 0c5fca3e72..14911b5245 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
@@ -16,12 +16,11 @@ package com.google.gerrit.sshd;
import com.google.common.base.Preconditions;
import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.inject.Inject;
import com.google.inject.binder.LinkedBindingBuilder;
import org.apache.sshd.server.Command;
-import javax.inject.Inject;
-
/**
* Binds one SSH command to the plugin name itself.
* <p>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
index afb2537bd5..8c43438a46 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
@@ -58,6 +58,7 @@ class SshAutoRegisterModuleGenerator
}
}
+ @Override
public void setPluginName(String name) {
command = Commands.named(name);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 7b910b2665..39eb720c9b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -45,6 +45,7 @@ import org.apache.sshd.common.ForwardingFilter;
import org.apache.sshd.common.KeyExchange;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.Random;
import org.apache.sshd.common.RequestHandler;
import org.apache.sshd.common.Session;
import org.apache.sshd.common.Signature;
@@ -102,6 +103,8 @@ import org.apache.sshd.server.global.TcpipForwardHandler;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.session.SessionFactory;
+import org.bouncycastle.crypto.prng.RandomGenerator;
+import org.bouncycastle.crypto.prng.VMPCRandomGenerator;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -142,6 +145,7 @@ import java.util.List;
*/
@Singleton
public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
+ @SuppressWarnings("hiding") // Don't use AbstractCloseable's logger.
private static final Logger log = LoggerFactory.getLogger(SshDaemon.class);
public static enum SshSessionBackend {
@@ -153,7 +157,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private final List<String> advertised;
private final boolean keepAlive;
private final List<HostKey> hostKeys;
- private volatile IoAcceptor acceptor;
+ private volatile IoAcceptor daemonAcceptor;
private final Config cfg;
@Inject
@@ -219,7 +223,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
: Nio2ServiceFactoryFactory.class.getName());
if (SecurityUtils.isBouncyCastleRegistered()) {
- initProviderBouncyCastle();
+ initProviderBouncyCastle(cfg);
} else {
initProviderJce();
}
@@ -289,26 +293,26 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
public IoAcceptor getIoAcceptor() {
- return acceptor;
+ return daemonAcceptor;
}
@Override
public synchronized void start() {
- if (acceptor == null && !listen.isEmpty()) {
+ if (daemonAcceptor == null && !listen.isEmpty()) {
checkConfig();
if (sessionFactory == null) {
sessionFactory = createSessionFactory();
}
sessionFactory.setServer(this);
- acceptor = createAcceptor();
+ daemonAcceptor = createAcceptor();
try {
String listenAddress = cfg.getString("sshd", null, "listenAddress");
boolean rewrite = !Strings.isNullOrEmpty(listenAddress)
&& listenAddress.endsWith(":0");
- acceptor.bind(listen);
+ daemonAcceptor.bind(listen);
if (rewrite) {
- SocketAddress bound = Iterables.getOnlyElement(acceptor.getBoundAddresses());
+ SocketAddress bound = Iterables.getOnlyElement(daemonAcceptor.getBoundAddresses());
cfg.setString("sshd", null, "listenAddress", format((InetSocketAddress)bound));
}
} catch (IOException e) {
@@ -326,14 +330,14 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
@Override
public synchronized void stop() {
- if (acceptor != null) {
+ if (daemonAcceptor != null) {
try {
- acceptor.close(true).await();
+ daemonAcceptor.close(true).await();
log.info("Stopped Gerrit SSHD");
} catch (InterruptedException e) {
log.warn("Exception caught while closing", e);
} finally {
- acceptor = null;
+ daemonAcceptor = null;
}
}
}
@@ -396,11 +400,69 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
return r.toString();
}
- private void initProviderBouncyCastle() {
+ private void initProviderBouncyCastle(Config cfg) {
setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>> asList(
new DHG14.Factory(), new DHG1.Factory()));
- setRandomFactory(new SingletonRandomFactory(
- new BouncyCastleRandom.Factory()));
+ NamedFactory<Random> factory;
+ if (cfg.getBoolean("sshd", null, "testUseInsecureRandom", false)) {
+ factory = new InsecureBouncyCastleRandom.Factory();
+ } else {
+ factory = new BouncyCastleRandom.Factory();
+ }
+ setRandomFactory(new SingletonRandomFactory(factory));
+ }
+
+ private static class InsecureBouncyCastleRandom implements Random {
+ private static class Factory implements NamedFactory<Random> {
+ @Override
+ public String getName() {
+ return "INSECURE_bouncycastle";
+ }
+
+ @Override
+ public Random create() {
+ return new InsecureBouncyCastleRandom();
+ }
+ }
+
+ private final RandomGenerator random;
+
+ private InsecureBouncyCastleRandom() {
+ random = new VMPCRandomGenerator();
+ random.addSeedMaterial(1234);
+ }
+
+ @Override
+ public void fill(byte[] bytes, int start, int len) {
+ random.nextBytes(bytes, start, len);
+ }
+
+ @Override
+ public int random(int n) {
+ if (n > 0) {
+ if ((n & -n) == n) {
+ return (int)((n * (long) next(31)) >> 31);
+ }
+ int bits, val;
+ do {
+ bits = next(31);
+ val = bits % n;
+ } while (bits - val + (n-1) < 0);
+ return val;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ final protected int next(int numBits) {
+ int bytes = (numBits+7)/8;
+ byte next[] = new byte[bytes];
+ int ret = 0;
+ random.nextBytes(next);
+ for (int i = 0; i < bytes; i++) {
+ ret = (next[i] & 0xFF) | (ret << 8);
+ }
+ return ret >>> (bytes*8 - numBits);
+ }
}
private void initProviderJce() {
@@ -441,8 +503,8 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
a.add(null);
a.add(new CipherNone.Factory());
- setCipherFactories(filter(cfg, "cipher", a.toArray(new NamedFactory[a
- .size()])));
+ setCipherFactories(filter(cfg, "cipher",
+ (NamedFactory<Cipher>[])a.toArray(new NamedFactory[a.size()])));
}
private void initMacs(final Config cfg) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index 2ec67a44d5..bacb167f91 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -90,6 +90,7 @@ public class SshKeyCacheImpl implements SshKeyCache {
}
}
+ @Override
public void evict(String username) {
if (username != null) {
cache.invalidate(username);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index 82394afbcf..439b8c8f13 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.audit.SshAuditEvent;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -25,7 +26,6 @@ import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.SystemLog;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -268,11 +268,11 @@ class SshLog implements LifecycleListener {
}
private String extractWhat(DispatchCommand dcmd) {
- String commandName = dcmd.getCommandName();
+ StringBuilder commandName = new StringBuilder(dcmd.getCommandName());
String[] args = dcmd.getArguments();
for (int i = 1; i < args.length; i++) {
- commandName = commandName + "." + args[i];
+ commandName.append(".").append(args[i]);
}
- return commandName;
+ return commandName.toString();
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshPluginStarterCallback.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
index 4f9fe3319d..f134d4836b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
@@ -17,6 +17,7 @@ package com.google.gerrit.sshd;
import com.google.gerrit.server.plugins.Plugin;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
+import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -25,8 +26,6 @@ import org.apache.sshd.server.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
-
@Singleton
class SshPluginStarterCallback
implements StartPluginListener, ReloadPluginListener {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
index f8b5ddb004..cd09cfab8d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -23,7 +24,6 @@ import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Key;
@@ -181,8 +181,10 @@ public class SshScope {
/** Returns exactly one instance per command executed. */
public static final Scope REQUEST = new Scope() {
+ @Override
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
return new Provider<T>() {
+ @Override
public T get() {
return requireContext().get(key, creator);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
index 62efaa020a..d4bb3539de 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
@@ -21,10 +21,10 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.sshd.SshScope.Context;
import org.apache.commons.codec.binary.Base64;
-import org.apache.sshd.common.future.CloseFuture;
-import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Constants;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
index fea16cdc06..59892a394f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
@@ -21,10 +21,10 @@ import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.FieldSetter;
import org.kohsuke.args4j.spi.OneArgumentOptionHandler;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Setter;
-import org.kohsuke.args4j.spi.FieldSetter;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
index 0e13fd6bc0..5eda57c411 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
@@ -57,6 +57,7 @@ abstract class BaseTestPrologCommand extends SshCommand {
protected abstract RestModifyView<RevisionResource, Input> createView();
+ @Override
protected final void run() throws UnloggedFailure {
try {
RevisionResource revision = revisions.parse(
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java
new file mode 100644
index 0000000000..af772f8e95
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2015 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.commands;
+
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+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.inject.Inject;
+
+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.server.session.ServerSession;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Close specified SSH connections */
+@AdminHighPriorityCommand
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "close-connection",
+ description = "Close the specified SSH connection", runsAt = MASTER_OR_SLAVE)
+final class CloseConnection extends SshCommand {
+
+ private static final Logger log = LoggerFactory.getLogger(CloseConnection.class);
+
+ @Inject
+ private SshDaemon sshDaemon;
+
+ @Argument(index = 0, multiValued = true, required = true,
+ metaVar = "SESSION_ID", usage = "List of SSH session IDs to be closed")
+ private final List<String> sessionIds = new ArrayList<>();
+
+ @Option(name = "--wait",
+ usage = "wait for connection to close before exiting")
+ private boolean wait;
+
+ @Override
+ protected void run() throws Failure {
+ 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()) {
+ ServerSession serverSession =
+ (ServerSession) ServerSession.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 (InterruptedException e) {
+ log.warn("Wait for connection to close interrupted: "
+ + e.getMessage());
+ }
+ }
+ break;
+ }
+ }
+ if (!connectionFound) {
+ stderr.print("close connection " + sessionId + ": no such connection\n");
+ }
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CommandUtils.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CommandUtils.java
new file mode 100644
index 0000000000..1e89986945
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CommandUtils.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2014 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.commands;
+
+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.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.BaseCommand.UnloggedFailure;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CommandUtils {
+ public static PatchSet parsePatchSet(final String patchIdentity, ReviewDb db,
+ ProjectControl projectControl, String branch)
+ throws UnloggedFailure, OrmException {
+ // By commit?
+ //
+ if (patchIdentity.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
+ final RevId id = new RevId(patchIdentity);
+ final ResultSet<PatchSet> patches;
+ if (id.isComplete()) {
+ patches = db.patchSets().byRevision(id);
+ } else {
+ patches = db.patchSets().byRevisionRange(id, id.max());
+ }
+
+ final Set<PatchSet> matches = new HashSet<>();
+ for (final PatchSet ps : patches) {
+ final Change change = db.changes().get(ps.getId().getParentKey());
+ if (inProject(change, projectControl) && inBranch(change, branch)) {
+ matches.add(ps);
+ }
+ }
+
+ switch (matches.size()) {
+ case 1:
+ return matches.iterator().next();
+ case 0:
+ throw error("\"" + patchIdentity + "\" no such patch set");
+ default:
+ throw error("\"" + patchIdentity + "\" matches multiple patch sets");
+ }
+ }
+
+ // By older style change,patchset?
+ //
+ if (patchIdentity.matches("^[1-9][0-9]*,[1-9][0-9]*$")) {
+ final PatchSet.Id patchSetId;
+ try {
+ patchSetId = PatchSet.Id.parse(patchIdentity);
+ } catch (IllegalArgumentException e) {
+ throw error("\"" + patchIdentity + "\" is not a valid patch set");
+ }
+ final PatchSet patchSet = db.patchSets().get(patchSetId);
+ if (patchSet == null) {
+ throw error("\"" + patchIdentity + "\" no such patch set");
+ }
+ if (projectControl != null || branch != null) {
+ final Change change = db.changes().get(patchSetId.getParentKey());
+ if (!inProject(change, projectControl)) {
+ throw error("change " + change.getId() + " not in project "
+ + projectControl.getProject().getName());
+ }
+ if (!inBranch(change, branch)) {
+ throw error("change " + change.getId() + " not in branch "
+ + change.getDest().get());
+ }
+ }
+ return patchSet;
+ }
+
+ throw error("\"" + patchIdentity + "\" is not a valid patch set");
+ }
+
+ private static boolean inProject(final Change change,
+ ProjectControl projectControl) {
+ if (projectControl == null) {
+ // No --project option, so they want every project.
+ return true;
+ }
+ return projectControl.getProject().getNameKey().equals(change.getProject());
+ }
+
+ private static boolean inBranch(final Change change, String branch) {
+ if (branch == null) {
+ // No --branch option, so they want every branch.
+ return true;
+ }
+ return change.getDest().get().equals(branch);
+ }
+
+ public static UnloggedFailure error(final String msg) {
+ return new UnloggedFailure(1, msg);
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index fb39dee52a..4e151b3055 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -23,8 +23,8 @@ import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.api.projects.ProjectInput.ConfigValue;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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;
@@ -33,7 +33,6 @@ import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SuggestParentCandidates;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import org.kohsuke.args4j.Argument;
@@ -87,26 +86,34 @@ final class CreateProjectCommand extends SshCommand {
@Option(name = "--change-id", usage = "if change-id is required")
private InheritableBoolean requireChangeID = InheritableBoolean.INHERIT;
+ @Option(name = "--new-change-for-all-not-in-target", usage = "if a new change will be created for every commit not in target branch")
+ private InheritableBoolean createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
+
@Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required")
- void setUseContributorArgreements(boolean on) {
+ void setUseContributorArgreements(@SuppressWarnings("unused") boolean on) {
contributorAgreements = InheritableBoolean.TRUE;
}
@Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
- void setUseSignedOffBy(boolean on) {
+ void setUseSignedOffBy(@SuppressWarnings("unused") boolean on) {
signedOffBy = InheritableBoolean.TRUE;
}
@Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
- void setUseContentMerge(boolean on) {
+ void setUseContentMerge(@SuppressWarnings("unused") boolean on) {
contentMerge = InheritableBoolean.TRUE;
}
@Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
- void setRequireChangeId(boolean on) {
+ void setRequireChangeId(@SuppressWarnings("unused") boolean on) {
requireChangeID = InheritableBoolean.TRUE;
}
+ @Option(name = "--create-new-change-for-all-not-in-target", aliases = {"--ncfa"}, usage = "if a new change will be created for every commit not in target branch")
+ void setNewChangeForAllNotInTarget(@SuppressWarnings("unused") boolean on) {
+ createNewChangeForAllNotInTarget = InheritableBoolean.TRUE;
+ }
+
@Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", usage = "initial branch name\n"
+ "(default: master)")
private List<String> branch;
@@ -166,6 +173,7 @@ final class CreateProjectCommand extends SshCommand {
input.useSignedOffBy = signedOffBy;
input.useContentMerge = contentMerge;
input.requireChangeId = requireChangeID;
+ input.createNewChangeForAllNotInTarget = createNewChangeForAllNotInTarget;
input.branches = branch;
input.createEmptyCommit = createEmptyCommit;
input.maxObjectSizeLimit = maxObjectSizeLimit;
@@ -182,7 +190,7 @@ final class CreateProjectCommand extends SshCommand {
stdout.print(parent + "\n");
}
}
- } catch (RestApiException | OrmException | NoSuchProjectException err) {
+ } catch (RestApiException | NoSuchProjectException err) {
throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index f905c5b5cd..f75eb2b191 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -31,12 +31,14 @@ public class DefaultCommandModule extends CommandModule {
protected void configure() {
final CommandName git = Commands.named("git");
final CommandName gerrit = Commands.named("gerrit");
+ final CommandName logging = Commands.named(gerrit, "logging");
final CommandName plugin = Commands.named(gerrit, "plugin");
final CommandName testSubmit = Commands.named(gerrit, "test-submit");
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
command(gerrit, AproposCommand.class);
command(gerrit, BanCommitCommand.class);
+ command(gerrit, CloseConnection.class);
command(gerrit, FlushCaches.class);
command(gerrit, ListProjectsCommand.class);
command(gerrit, ListMembersCommand.class);
@@ -98,5 +100,11 @@ public class DefaultCommandModule extends CommandModule {
command(gerrit, CreateAccountCommand.class);
command(testSubmit, TestSubmitRuleCommand.class);
command(testSubmit, TestSubmitTypeCommand.class);
+
+ command(logging).toProvider(new DispatchCommandProvider(logging));
+ command(logging, SetLoggingLevelCommand.class);
+ command(logging, ListLoggingLevelCommand.class);
+ alias(logging, "ls", ListLoggingLevelCommand.class);
+ alias(logging, "set", SetLoggingLevelCommand.class);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index a1099dc0bb..c533f4f4ed 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -24,16 +24,13 @@ 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.ProjectControl;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +38,7 @@ import java.util.List;
@RequiresCapability(GlobalCapability.RUN_GC)
@CommandMetaData(name = "gc", description = "Run Git garbage collection",
runsAt = MASTER_OR_SLAVE)
-public class GarbageCollectionCommand extends BaseCommand {
+public class GarbageCollectionCommand extends SshCommand {
@Option(name = "--all", usage = "runs the Git garbage collection for all projects")
private boolean all;
@@ -59,23 +56,10 @@ public class GarbageCollectionCommand extends BaseCommand {
@Inject
private GarbageCollection.Factory garbageCollectionFactory;
- private PrintWriter stdout;
-
@Override
- public void start(Environment env) throws IOException {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- stdout = toPrintWriter(out);
- try {
- parseCommandLine();
- verifyCommandLine();
- runGC();
- } finally {
- stdout.flush();
- }
- }
- });
+ public void run() throws Exception {
+ verifyCommandLine();
+ runGC();
}
private void verifyCommandLine() throws UnloggedFailure {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index f0169a8e33..82ad16ff6f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -16,7 +16,7 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -28,43 +28,36 @@ import com.google.gerrit.server.group.GroupJson;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gerrit.server.group.ListGroups;
import com.google.gerrit.server.ioutil.ColumnFormatter;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Option;
import java.io.PrintWriter;
@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller",
runsAt = MASTER_OR_SLAVE)
-public class ListGroupsCommand extends BaseCommand {
+public class ListGroupsCommand extends SshCommand {
@Inject
private MyListGroups impl;
@Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
- }
- final PrintWriter stdout = toPrintWriter(out);
- try {
- impl.display(stdout);
- } finally {
- stdout.flush();
- }
- }
- });
+ public void run() throws Exception {
+ if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
+ throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
+ }
+ impl.display(stdout);
+ }
+
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
- private static class MyListGroups extends ListGroups {
+ private static class MyListGroups extends ListGroups {
@Option(name = "--verbose", aliases = {"-v"},
usage = "verbose output format with tab-separated columns for the " +
"group name, UUID, description, owner group name, " +
@@ -86,7 +79,7 @@ public class ListGroupsCommand extends BaseCommand {
void display(final PrintWriter out) throws OrmException {
final ColumnFormatter formatter = new ColumnFormatter(out, '\t');
for (final GroupInfo info : get()) {
- formatter.addColumn(Objects.firstNonNull(info.name, "n/a"));
+ formatter.addColumn(MoreObjects.firstNonNull(info.name, "n/a"));
if (verboseOutput) {
AccountGroup o = info.ownerId != null
? groupCache.get(new AccountGroup.UUID(Url.decode(info.ownerId)))
@@ -96,7 +89,7 @@ public class ListGroupsCommand extends BaseCommand {
formatter.addColumn(Strings.nullToEmpty(info.description));
formatter.addColumn(o != null ? o.getName() : "n/a");
formatter.addColumn(o != null ? o.getGroupUUID().get() : "");
- formatter.addColumn(Boolean.toString(Objects.firstNonNull(
+ formatter.addColumn(Boolean.toString(MoreObjects.firstNonNull(
info.options.visibleToAll, Boolean.FALSE)));
}
formatter.nextLine();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListLoggingLevelCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListLoggingLevelCommand.java
new file mode 100644
index 0000000000..bc6bc17ba0
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListLoggingLevelCommand.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2014 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.commands;
+
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.kohsuke.args4j.Argument;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.TreeMap;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "ls-level", description = "list the level of loggers",
+ runsAt = MASTER_OR_SLAVE)
+public class ListLoggingLevelCommand extends SshCommand {
+
+ @Argument(index = 0, required = false, metaVar = "NAME", usage = "used to match loggers")
+ private String name;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void run() {
+ Map<String, String> logs = new TreeMap<>();
+ for (Enumeration<Logger> logger = LogManager.getCurrentLoggers(); logger
+ .hasMoreElements();) {
+ Logger log = logger.nextElement();
+ if (name == null || log.getName().contains(name)) {
+ logs.put(log.getName(), log.getEffectiveLevel().toString());
+ }
+ }
+ for (Map.Entry<String, String> e : logs.entrySet()) {
+ stdout.println(e.getKey() + ": " + e.getValue());
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index f8cf8ddc13..b24c4bfccf 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -16,51 +16,42 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupDetailFactory.Factory;
import com.google.gerrit.server.group.ListMembers;
import com.google.gerrit.server.ioutil.ColumnFormatter;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import java.io.PrintWriter;
import java.util.List;
-import javax.inject.Inject;
-
/**
* Implements a command that allows the user to see the members of a group.
*/
@CommandMetaData(name = "ls-members", description = "List the members of a given group",
runsAt = MASTER_OR_SLAVE)
-public class ListMembersCommand extends BaseCommand {
+public class ListMembersCommand extends SshCommand {
@Inject
ListMembersCommandImpl impl;
@Override
- public void start(Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- final PrintWriter stdout = toPrintWriter(out);
- try {
- impl.display(stdout);
- } finally {
- stdout.flush();
- }
- }
- });
+ public void run() throws Exception {
+ impl.display(stdout);
+ }
+
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
private static class ListMembersCommandImpl extends ListMembers {
@@ -72,13 +63,12 @@ public class ListMembersCommand extends BaseCommand {
@Inject
protected ListMembersCommandImpl(GroupCache groupCache,
Factory groupDetailFactory,
- AccountInfo.Loader.Factory accountLoaderFactory,
- AccountCache accountCache) {
+ AccountLoader.Factory accountLoaderFactory) {
super(groupCache, groupDetailFactory, accountLoaderFactory);
this.groupCache = groupCache;
}
- void display(PrintWriter writer) throws UnloggedFailure, OrmException {
+ void display(PrintWriter writer) throws OrmException {
AccountGroup group = groupCache.get(new AccountGroup.NameKey(name));
String errorText = "Group not found or not visible\n";
@@ -88,32 +78,28 @@ public class ListMembersCommand extends BaseCommand {
return;
}
- try {
- List<AccountInfo> members = apply(group.getGroupUUID());
- ColumnFormatter formatter = new ColumnFormatter(writer, '\t');
- formatter.addColumn("id");
- formatter.addColumn("username");
- formatter.addColumn("full name");
- formatter.addColumn("email");
- formatter.nextLine();
- for (AccountInfo member : members) {
- if (member == null) {
- continue;
- }
-
- formatter.addColumn(member._id.toString());
- formatter.addColumn(Objects.firstNonNull(member.username, "n/a"));
- formatter.addColumn(Objects.firstNonNull(
- Strings.emptyToNull(member.name), "n/a"));
- formatter.addColumn(Objects.firstNonNull(member.email, "n/a"));
- formatter.nextLine();
+ List<AccountInfo> members = apply(group.getGroupUUID());
+ ColumnFormatter formatter = new ColumnFormatter(writer, '\t');
+ formatter.addColumn("id");
+ formatter.addColumn("username");
+ formatter.addColumn("full name");
+ formatter.addColumn("email");
+ formatter.nextLine();
+ for (AccountInfo member : members) {
+ if (member == null) {
+ continue;
}
- formatter.finish();
- } catch (MethodNotAllowedException e) {
- writer.write(errorText);
- writer.flush();
+ formatter.addColumn(Integer.toString(member._accountId));
+ formatter.addColumn(MoreObjects.firstNonNull(
+ member.username, "n/a"));
+ formatter.addColumn(MoreObjects.firstNonNull(
+ Strings.emptyToNull(member.name), "n/a"));
+ formatter.addColumn(MoreObjects.firstNonNull(member.email, "n/a"));
+ formatter.nextLine();
}
+
+ formatter.finish();
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index 78034fccf6..134a719e87 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -17,37 +17,34 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.server.project.ListProjects;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
-
import java.util.List;
@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller",
runsAt = MASTER_OR_SLAVE)
-final class ListProjectsCommand extends BaseCommand {
+final class ListProjectsCommand extends SshCommand {
@Inject
private ListProjects impl;
@Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- if (!impl.getFormat().isJson()) {
- List<String> showBranch = impl.getShowBranch();
- if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
- }
- if (impl.isShowTree() && impl.isShowDescription()) {
- throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
- }
- }
- impl.display(out);
+ public void run() throws Exception {
+ if (!impl.getFormat().isJson()) {
+ List<String> showBranch = impl.getShowBranch();
+ if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {
+ throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
+ }
+ if (impl.isShowTree() && impl.isShowDescription()) {
+ throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
}
- });
+ }
+ impl.display(out);
+ }
+
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 49120f7f4b..799686d656 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -44,7 +44,7 @@ final class PluginInstallCommand extends SshCommand {
private String name;
@Option(name = "-")
- void useInput(boolean on) {
+ void useInput(@SuppressWarnings("unused") boolean on) {
source = "-";
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 9f6bb509df..d45d76e604 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -19,29 +19,24 @@ import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.ListPlugins;
-import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.apache.sshd.server.Environment;
-
-import java.io.IOException;
-
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.VIEW_PLUGINS)
@CommandMetaData(name = "ls", description = "List the installed plugins",
runsAt = MASTER_OR_SLAVE)
-final class PluginLsCommand extends BaseCommand {
+final class PluginLsCommand extends SshCommand {
@Inject
private ListPlugins impl;
@Override
- public void start(Environment env) throws IOException {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine(impl);
- impl.display(out);
- }
- });
+ public void run() throws Exception {
+ impl.display(stdout);
+ }
+
+ @Override
+ protected void parseCommandLine() throws UnloggedFailure {
+ parseCommandLine(impl);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index 63b7ab6596..f65a0c94c9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -14,7 +14,8 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.server.query.change.QueryProcessor;
+import com.google.gerrit.server.query.change.OutputStreamQuery;
+import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -27,10 +28,10 @@ import java.util.List;
@CommandMetaData(name = "query", description = "Query the change database")
class Query extends SshCommand {
@Inject
- private QueryProcessor processor;
+ private OutputStreamQuery processor;
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
- void setFormat(QueryProcessor.OutputFormat format) {
+ void setFormat(OutputFormat format) {
processor.setOutput(out, format);
}
@@ -97,7 +98,7 @@ class Query extends SshCommand {
@Override
protected void parseCommandLine() throws UnloggedFailure {
- processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
+ processor.setOutput(out, OutputFormat.TEXT);
super.parseCommandLine();
if (processor.getIncludeFiles() &&
!(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
index 39ec81f57a..8f26bbab42 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -14,9 +14,9 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.Version;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -224,20 +224,15 @@ public class QueryShell {
return;
}
- try {
- final String[] types = {"TABLE", "VIEW"};
- ResultSet rs = meta.getTables(null, null, null, types);
- try {
- if (outputFormat == OutputFormat.PRETTY) {
- println(" List of relations");
- }
- showResultSet(rs, false, 0,
- Identity.create(rs, "TABLE_SCHEM"),
- Identity.create(rs, "TABLE_NAME"),
- Identity.create(rs, "TABLE_TYPE"));
- } finally {
- rs.close();
+ final String[] types = {"TABLE", "VIEW"};
+ try (ResultSet rs = meta.getTables(null, null, null, types)) {
+ if (outputFormat == OutputFormat.PRETTY) {
+ println(" List of relations");
}
+ showResultSet(rs, false, 0,
+ Identity.create(rs, "TABLE_SCHEM"),
+ Identity.create(rs, "TABLE_NAME"),
+ Identity.create(rs, "TABLE_TYPE"));
} catch (SQLException e) {
error(e);
}
@@ -260,91 +255,81 @@ public class QueryShell {
return;
}
- try {
- ResultSet rs = meta.getColumns(null, null, tableName, null);
- try {
- if (!rs.next()) {
- throw new SQLException("Table " + tableName + " not found");
- }
+ try (ResultSet rs = meta.getColumns(null, null, tableName, null)) {
+ if (!rs.next()) {
+ throw new SQLException("Table " + tableName + " not found");
+ }
- if (outputFormat == OutputFormat.PRETTY) {
- println(" Table " + tableName);
- }
- showResultSet(rs, true, 0,
- Identity.create(rs, "COLUMN_NAME"),
- new Function("TYPE") {
- @Override
- String apply(final ResultSet rs) throws SQLException {
- String type = rs.getString("TYPE_NAME");
- switch (rs.getInt("DATA_TYPE")) {
- case java.sql.Types.CHAR:
- case java.sql.Types.VARCHAR:
- type += "(" + rs.getInt("COLUMN_SIZE") + ")";
- break;
- }
-
- String def = rs.getString("COLUMN_DEF");
- if (def != null && !def.isEmpty()) {
- type += " DEFAULT " + def;
- }
-
- int nullable = rs.getInt("NULLABLE");
- if (nullable == DatabaseMetaData.columnNoNulls) {
- type += " NOT NULL";
- }
- return type;
- }
- });
- } finally {
- rs.close();
+ if (outputFormat == OutputFormat.PRETTY) {
+ println(" Table " + tableName);
}
+ showResultSet(rs, true, 0,
+ Identity.create(rs, "COLUMN_NAME"),
+ new Function("TYPE") {
+ @Override
+ String apply(final ResultSet rs) throws SQLException {
+ String type = rs.getString("TYPE_NAME");
+ switch (rs.getInt("DATA_TYPE")) {
+ case java.sql.Types.CHAR:
+ case java.sql.Types.VARCHAR:
+ type += "(" + rs.getInt("COLUMN_SIZE") + ")";
+ break;
+ }
+
+ String def = rs.getString("COLUMN_DEF");
+ if (def != null && !def.isEmpty()) {
+ type += " DEFAULT " + def;
+ }
+
+ int nullable = rs.getInt("NULLABLE");
+ if (nullable == DatabaseMetaData.columnNoNulls) {
+ type += " NOT NULL";
+ }
+ return type;
+ }
+ });
} catch (SQLException e) {
error(e);
return;
}
- try {
- ResultSet rs = meta.getIndexInfo(null, null, tableName, false, true);
- try {
- Map<String, IndexInfo> indexes = new TreeMap<>();
- while (rs.next()) {
- final String indexName = rs.getString("INDEX_NAME");
- IndexInfo def = indexes.get(indexName);
- if (def == null) {
- def = new IndexInfo();
- def.name = indexName;
- indexes.put(indexName, def);
- }
+ try (ResultSet rs = meta.getIndexInfo(null, null, tableName, false, true)) {
+ Map<String, IndexInfo> indexes = new TreeMap<>();
+ while (rs.next()) {
+ final String indexName = rs.getString("INDEX_NAME");
+ IndexInfo def = indexes.get(indexName);
+ if (def == null) {
+ def = new IndexInfo();
+ def.name = indexName;
+ indexes.put(indexName, def);
+ }
- if (!rs.getBoolean("NON_UNIQUE")) {
- def.unique = true;
- }
+ if (!rs.getBoolean("NON_UNIQUE")) {
+ def.unique = true;
+ }
- final int pos = rs.getInt("ORDINAL_POSITION");
- final String col = rs.getString("COLUMN_NAME");
- String desc = rs.getString("ASC_OR_DESC");
- if ("D".equals(desc)) {
- desc = " DESC";
- } else {
- desc = "";
- }
- def.addColumn(pos, col + desc);
+ final int pos = rs.getInt("ORDINAL_POSITION");
+ final String col = rs.getString("COLUMN_NAME");
+ String desc = rs.getString("ASC_OR_DESC");
+ if ("D".equals(desc)) {
+ desc = " DESC";
+ } else {
+ desc = "";
+ }
+ def.addColumn(pos, col + desc);
- String filter = rs.getString("FILTER_CONDITION");
- if (filter != null && !filter.isEmpty()) {
- def.filter.append(filter);
- }
+ String filter = rs.getString("FILTER_CONDITION");
+ if (filter != null && !filter.isEmpty()) {
+ def.filter.append(filter);
}
+ }
- if (outputFormat == OutputFormat.PRETTY) {
- println("");
- println("Indexes on " + tableName + ":");
- for (IndexInfo def : indexes.values()) {
- println(" " + def);
- }
+ if (outputFormat == OutputFormat.PRETTY) {
+ println("");
+ println("Indexes on " + tableName + ":");
+ for (IndexInfo def : indexes.values()) {
+ println(" " + def);
}
- } finally {
- rs.close();
}
} catch (SQLException e) {
error(e);
@@ -366,11 +351,8 @@ public class QueryShell {
try {
if (hasResultSet) {
- final ResultSet rs = statement.getResultSet();
- try {
+ try (ResultSet rs = statement.getResultSet()) {
showResultSet(rs, false, start);
- } finally {
- rs.close();
}
} else {
@@ -727,7 +709,7 @@ public class QueryShell {
print(help.toString());
}
- private static abstract class Function {
+ private abstract static class Function {
final String name;
Function(final String name) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index f39cd36557..3ac72a7dc6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -16,6 +16,7 @@ package com.google.gerrit.sshd.commands;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
+import com.google.common.io.CharStreams;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.extensions.api.GerritApi;
@@ -26,12 +27,10 @@ import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RevisionApi;
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.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@@ -39,8 +38,8 @@ import com.google.gerrit.server.util.LabelVote;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.util.cli.CmdLineParser;
+import com.google.gson.JsonSyntaxException;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -50,6 +49,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -75,7 +76,9 @@ public class ReviewCommand extends SshCommand {
@Argument(index = 0, required = true, multiValued = true, metaVar = "{COMMIT | CHANGE,PATCHSET}", usage = "list of commits or patch sets to review")
void addPatchSetId(final String token) {
try {
- patchSets.add(parsePatchSet(token));
+ PatchSet ps = CommandUtils.parsePatchSet(token, db, projectControl,
+ branch);
+ patchSets.add(ps);
} catch (UnloggedFailure e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (OrmException e) {
@@ -101,6 +104,9 @@ public class ReviewCommand extends SshCommand {
@Option(name = "--restore", usage = "restore the specified abandoned change(s)")
private boolean restoreChange;
+ @Option(name = "--rebase", usage = "rebase the specified change(s)")
+ private boolean rebaseChange;
+
@Option(name = "--submit", aliases = "-s", usage = "submit the specified patch set(s)")
private boolean submitChange;
@@ -110,11 +116,14 @@ public class ReviewCommand extends SshCommand {
@Option(name = "--delete", usage = "delete the specified draft patch set(s)")
private boolean deleteDraftPatchSet;
+ @Option(name = "--json", aliases = "-j", usage = "read review input json from stdin")
+ private boolean json;
+
@Option(name = "--label", aliases = "-l", usage = "custom label(s) to assign", metaVar = "LABEL=VALUE")
void addLabel(final String token) {
LabelVote v = LabelVote.parseWithEquals(token);
- LabelType.checkName(v.getLabel()); // Disallow SUBM.
- customLabels.put(v.getLabel(), v.getValue());
+ LabelType.checkName(v.label()); // Disallow SUBM.
+ customLabels.put(v.label(), v.value());
}
@Inject
@@ -147,6 +156,9 @@ public class ReviewCommand extends SshCommand {
if (deleteDraftPatchSet) {
throw error("abandon and delete actions are mutually exclusive");
}
+ if (rebaseChange) {
+ throw error("abandon and rebase actions are mutually exclusive");
+ }
}
if (publishPatchSet) {
if (restoreChange) {
@@ -159,17 +171,55 @@ public class ReviewCommand extends SshCommand {
throw error("publish and delete actions are mutually exclusive");
}
}
- if (deleteDraftPatchSet) {
+ if (json) {
+ if (restoreChange) {
+ throw error("json and restore actions are mutually exclusive");
+ }
if (submitChange) {
- throw error("delete and submit actions are mutually exclusive");
+ throw error("json and submit actions are mutually exclusive");
+ }
+ if (deleteDraftPatchSet) {
+ throw error("json and delete actions are mutually exclusive");
+ }
+ if (publishPatchSet) {
+ throw error("json and publish actions are mutually exclusive");
+ }
+ if (abandonChange) {
+ throw error("json and abandon actions are mutually exclusive");
+ }
+ if (changeComment != null) {
+ throw error("json and message are mutually exclusive");
+ }
+ if (rebaseChange) {
+ throw error("json and rebase actions are mutually exclusive");
}
}
+ if (rebaseChange) {
+ if (deleteDraftPatchSet) {
+ throw error("rebase and delete actions are mutually exclusive");
+ }
+ if (submitChange) {
+ throw error("rebase and submit actions are mutually exclusive");
+ }
+ }
+ if (deleteDraftPatchSet && submitChange) {
+ throw error("delete and submit actions are mutually exclusive");
+ }
boolean ok = true;
+ ReviewInput input = null;
+ if (json) {
+ input = reviewFromJson();
+ }
+
for (final PatchSet patchSet : patchSets) {
try {
- reviewPatchSet(patchSet);
- } catch (UnloggedFailure e) {
+ if (input != null) {
+ applyReview(patchSet, input);
+ } else {
+ reviewPatchSet(patchSet);
+ }
+ } catch (RestApiException | UnloggedFailure e) {
ok = false;
writeError("error: " + e.getMessage() + "\n");
} catch (NoSuchChangeException e) {
@@ -184,21 +234,30 @@ public class ReviewCommand extends SshCommand {
}
if (!ok) {
- throw new UnloggedFailure(1, "one or more reviews failed;"
- + " review output above");
+ throw error("one or more reviews failed; review output above");
}
}
private void applyReview(PatchSet patchSet,
- final ReviewInput review) throws Exception {
+ final ReviewInput review) throws RestApiException {
gApi.get().changes()
.id(patchSet.getId().getParentKey().get())
.revision(patchSet.getRevision().get())
.review(review);
}
- private void reviewPatchSet(final PatchSet patchSet) throws Exception {
+ private ReviewInput reviewFromJson() throws UnloggedFailure {
+ try (InputStreamReader r =
+ new InputStreamReader(in, StandardCharsets.UTF_8)) {
+ return OutputFormat.JSON.newGson().
+ fromJson(CharStreams.toString(r), ReviewInput.class);
+ } catch (IOException | JsonSyntaxException e) {
+ writeError(e.getMessage() + '\n');
+ throw error("internal error while reading review input");
+ }
+ }
+ private void reviewPatchSet(final PatchSet patchSet) throws Exception {
if (changeComment == null) {
changeComment = "";
}
@@ -242,6 +301,10 @@ public class ReviewCommand extends SshCommand {
applyReview(patchSet, review);
}
+ if (rebaseChange){
+ revisionApi(patchSet).rebase();
+ }
+
if (submitChange) {
revisionApi(patchSet).submit();
}
@@ -251,8 +314,7 @@ public class ReviewCommand extends SshCommand {
} else if (deleteDraftPatchSet) {
revisionApi(patchSet).delete();
}
- } catch (IllegalStateException | InvalidChangeOperationException
- | RestApiException e) {
+ } catch (IllegalStateException | RestApiException e) {
throw error(e.getMessage());
}
}
@@ -265,83 +327,6 @@ public class ReviewCommand extends SshCommand {
return changeApi(patchSet).revision(patchSet.getRevision().get());
}
- private PatchSet parsePatchSet(final String patchIdentity)
- throws UnloggedFailure, OrmException {
- // By commit?
- //
- if (patchIdentity.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
- final RevId id = new RevId(patchIdentity);
- final ResultSet<PatchSet> patches;
- if (id.isComplete()) {
- patches = db.patchSets().byRevision(id);
- } else {
- patches = db.patchSets().byRevisionRange(id, id.max());
- }
-
- final Set<PatchSet> matches = new HashSet<>();
- for (final PatchSet ps : patches) {
- final Change change = db.changes().get(ps.getId().getParentKey());
- if (inProject(change) && inBranch(change)) {
- matches.add(ps);
- }
- }
-
- switch (matches.size()) {
- case 1:
- return matches.iterator().next();
- case 0:
- throw error("\"" + patchIdentity + "\" no such patch set");
- default:
- throw error("\"" + patchIdentity + "\" matches multiple patch sets");
- }
- }
-
- // By older style change,patchset?
- //
- if (patchIdentity.matches("^[1-9][0-9]*,[1-9][0-9]*$")) {
- final PatchSet.Id patchSetId;
- try {
- patchSetId = PatchSet.Id.parse(patchIdentity);
- } catch (IllegalArgumentException e) {
- throw error("\"" + patchIdentity + "\" is not a valid patch set");
- }
- final PatchSet patchSet = db.patchSets().get(patchSetId);
- if (patchSet == null) {
- throw error("\"" + patchIdentity + "\" no such patch set");
- }
- if (projectControl != null || branch != null) {
- final Change change = db.changes().get(patchSetId.getParentKey());
- if (!inProject(change)) {
- throw error("change " + change.getId() + " not in project "
- + projectControl.getProject().getName());
- }
- if (!inBranch(change)) {
- throw error("change " + change.getId() + " not in branch "
- + change.getDest().get());
- }
- }
- return patchSet;
- }
-
- throw error("\"" + patchIdentity + "\" is not a valid patch set");
- }
-
- private boolean inProject(final Change change) {
- if (projectControl == null) {
- // No --project option, so they want every project.
- return true;
- }
- return projectControl.getProject().getNameKey().equals(change.getProject());
- }
-
- private boolean inBranch(final Change change) {
- if (branch == null) {
- // No --branch option, so they want every branch.
- return true;
- }
- return change.getDest().get().equals(branch);
- }
-
@Override
protected void parseCommandLine() throws UnloggedFailure {
optionList = new ArrayList<>();
@@ -355,15 +340,16 @@ public class ReviewCommand extends SshCommand {
}
for (LabelType type : allProjectsControl.getLabelTypes().getLabelTypes()) {
- String usage;
- usage = "score for " + type.getName() + "\n";
+ StringBuilder usage = new StringBuilder("score for ")
+ .append(type.getName())
+ .append("\n");
for (LabelValue v : type.getValues()) {
- usage += v.format() + "\n";
+ usage.append(v.format()).append("\n");
}
final String name = "--" + type.getName().toLowerCase();
- optionList.add(new ApproveOption(name, usage, type));
+ optionList.add(new ApproveOption(name, usage.toString(), type));
}
super.parseCommandLine();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
index 65f876fe70..bdc4cef648 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
@@ -84,6 +84,7 @@ final class ScpCommand extends BaseCommand {
@Override
public void start(final Environment env) {
startThread(new Runnable() {
+ @Override
public void run() {
runImp();
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 5088424102..1cb442d4ed 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -14,7 +14,11 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -34,13 +38,12 @@ import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
import com.google.gerrit.server.account.PutActive;
import com.google.gerrit.server.account.PutHttpPassword;
import com.google.gerrit.server.account.PutName;
-import com.google.gerrit.sshd.BaseCommand;
+import com.google.gerrit.server.account.PutPreferred;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -56,7 +59,8 @@ import java.util.List;
/** Set a user's account settings. **/
@CommandMetaData(name = "set-account", description = "Change an account's settings")
-final class SetAccountCommand extends BaseCommand {
+@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
+final class SetAccountCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id")
private Account.Id id;
@@ -76,6 +80,9 @@ final class SetAccountCommand extends BaseCommand {
@Option(name = "--delete-email", metaVar = "EMAIL", usage = "email addresses to delete from the account")
private List<String> deleteEmails = new ArrayList<>();
+ @Option(name = "--preferred-email", metaVar = "EMAIL", usage = "a registered email address from the account")
+ private String preferredEmail;
+
@Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account")
private List<String> addSshKeys = new ArrayList<>();
@@ -85,8 +92,8 @@ final class SetAccountCommand extends BaseCommand {
@Option(name = "--http-password", metaVar = "PASSWORD", usage = "password for HTTP authentication for the account")
private String httpPassword;
- @Inject
- private IdentifiedUser currentUser;
+ @Option(name = "--clear-http-password", usage = "clear HTTP password for the account")
+ private boolean clearHttpPassword;
@Inject
private IdentifiedUser.GenericFactory genericUserFactory;
@@ -95,52 +102,42 @@ final class SetAccountCommand extends BaseCommand {
private CreateEmail.Factory createEmailFactory;
@Inject
- private Provider<GetEmails> getEmailsProvider;
+ private GetEmails getEmails;
@Inject
- private Provider<DeleteEmail> deleteEmailProvider;
+ private DeleteEmail deleteEmail;
@Inject
- private Provider<PutName> putNameProvider;
+ private PutPreferred putPreferred;
@Inject
- private Provider<PutHttpPassword> putHttpPasswordProvider;
+ private PutName putName;
@Inject
- private Provider<PutActive> putActiveProvider;
+ private PutHttpPassword putHttpPassword;
@Inject
- private Provider<DeleteActive> deleteActiveProvider;
+ private PutActive putActive;
@Inject
- private Provider<AddSshKey> addSshKeyProvider;
+ private DeleteActive deleteActive;
@Inject
- private Provider<GetSshKeys> getSshKeysProvider;
+ private AddSshKey addSshKey;
@Inject
- private Provider<DeleteSshKey> deleteSshKeyProvider;
+ private GetSshKeys getSshKeys;
+
+ @Inject
+ private DeleteSshKey deleteSshKey;
private IdentifiedUser user;
private AccountResource rsrc;
@Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- if (!currentUser.getCapabilities().canAdministrateServer()) {
- String msg =
- String.format(
- "fatal: %s does not have \"Administrator\" capability.",
- currentUser.getUserName());
- throw new UnloggedFailure(1, msg);
- }
- parseCommandLine();
- validate();
- setAccount();
- }
- });
+ public void run() throws Exception {
+ validate();
+ setAccount();
}
private void validate() throws UnloggedFailure {
@@ -148,6 +145,11 @@ final class SetAccountCommand extends BaseCommand {
throw new UnloggedFailure(1,
"--active and --inactive options are mutually exclusive.");
}
+ if (clearHttpPassword && !Strings.isNullOrEmpty(httpPassword)) {
+ throw new UnloggedFailure(1,
+ "--http-password and --clear-http-password options are mutually " +
+ "exclusive.");
+ }
if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) {
throw new UnloggedFailure(1, "Only one option may use the stdin");
}
@@ -157,6 +159,11 @@ final class SetAccountCommand extends BaseCommand {
if (deleteEmails.contains("ALL")) {
deleteEmails = Collections.singletonList("ALL");
}
+ if (deleteEmails.contains(preferredEmail)) {
+ throw new UnloggedFailure(1,
+ "--preferred-email and --delete-email options are mutually " +
+ "exclusive for the same email address.");
+ }
}
private void setAccount() throws OrmException, IOException, UnloggedFailure {
@@ -171,23 +178,27 @@ final class SetAccountCommand extends BaseCommand {
deleteEmail(email);
}
+ if (preferredEmail != null) {
+ putPreferred(preferredEmail);
+ }
+
if (fullName != null) {
PutName.Input in = new PutName.Input();
in.name = fullName;
- putNameProvider.get().apply(rsrc, in);
+ putName.apply(rsrc, in);
}
- if (httpPassword != null) {
+ if (httpPassword != null || clearHttpPassword) {
PutHttpPassword.Input in = new PutHttpPassword.Input();
in.httpPassword = httpPassword;
- putHttpPasswordProvider.get().apply(rsrc, in);
+ putHttpPassword.apply(rsrc, in);
}
if (active) {
- putActiveProvider.get().apply(rsrc, null);
+ putActive.apply(rsrc, null);
} else if (inactive) {
try {
- deleteActiveProvider.get().apply(rsrc, null);
+ deleteActive.apply(rsrc, null);
} catch (ResourceNotFoundException e) {
// user is already inactive
}
@@ -208,7 +219,7 @@ final class SetAccountCommand extends BaseCommand {
}
private void addSshKeys(List<String> sshKeys) throws RestApiException,
- UnloggedFailure, OrmException, IOException {
+ OrmException, IOException {
for (final String sshKey : sshKeys) {
AddSshKey.Input in = new AddSshKey.Input();
in.raw = new RawInput() {
@@ -227,13 +238,13 @@ final class SetAccountCommand extends BaseCommand {
return sshKey.length();
}
};
- addSshKeyProvider.get().apply(rsrc, in);
+ addSshKey.apply(rsrc, in);
}
}
private void deleteSshKeys(List<String> sshKeys) throws RestApiException,
OrmException {
- List<SshKeyInfo> infos = getSshKeysProvider.get().apply(rsrc);
+ List<SshKeyInfo> infos = getSshKeys.apply(rsrc);
if (sshKeys.contains("ALL")) {
for (SshKeyInfo i : infos) {
deleteSshKey(i);
@@ -250,10 +261,10 @@ final class SetAccountCommand extends BaseCommand {
}
}
- private void deleteSshKey(SshKeyInfo i) throws OrmException {
+ private void deleteSshKey(SshKeyInfo i) throws AuthException, OrmException {
AccountSshKey sshKey = new AccountSshKey(
new AccountSshKey.Id(user.getAccountId(), i.seq), i.sshPublicKey);
- deleteSshKeyProvider.get().apply(
+ deleteSshKey.apply(
new AccountResource.SshKey(user, sshKey), null);
}
@@ -269,35 +280,44 @@ final class SetAccountCommand extends BaseCommand {
}
}
- private void deleteEmail(String email) throws UnloggedFailure,
- RestApiException, OrmException {
+ private void deleteEmail(String email) throws RestApiException, OrmException {
if (email.equals("ALL")) {
- List<EmailInfo> emails = getEmailsProvider.get().apply(rsrc);
- DeleteEmail deleteEmail = deleteEmailProvider.get();
+ List<EmailInfo> emails = getEmails.apply(rsrc);
for (EmailInfo e : emails) {
deleteEmail.apply(new AccountResource.Email(user, e.email),
new DeleteEmail.Input());
}
} else {
- deleteEmailProvider.get().apply(new AccountResource.Email(user, email),
+ deleteEmail.apply(new AccountResource.Email(user, email),
new DeleteEmail.Input());
}
}
+ private void putPreferred(String email) throws RestApiException,
+ OrmException {
+ for (EmailInfo e : getEmails.apply(rsrc)) {
+ if (e.email.equals(email)) {
+ putPreferred.apply(new AccountResource.Email(user, email), null);
+ return;
+ }
+ }
+ stderr.println("preferred email not found: " + email);
+ }
+
private List<String> readSshKey(final List<String> sshKeys)
throws UnsupportedEncodingException, IOException {
if (!sshKeys.isEmpty()) {
- String sshKey;
int idx = sshKeys.indexOf("-");
if (idx >= 0) {
- sshKey = "";
+ StringBuilder sshKey = new StringBuilder();
BufferedReader br =
new BufferedReader(new InputStreamReader(in, "UTF-8"));
String line;
while ((line = br.readLine()) != null) {
- sshKey += line + "\n";
+ sshKey.append(line)
+ .append("\n");
}
- sshKeys.set(idx, sshKey);
+ sshKeys.set(idx, sshKey.toString());
}
}
return sshKeys;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetLoggingLevelCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetLoggingLevelCommand.java
new file mode 100644
index 0000000000..49edf145d7
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetLoggingLevelCommand.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2014 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.commands;
+
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.helpers.Loader;
+import org.kohsuke.args4j.Argument;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "set-level", description = "Change the level of loggers",
+ runsAt = MASTER_OR_SLAVE)
+public class SetLoggingLevelCommand extends SshCommand {
+ private static final String LOG_CONFIGURATION = "log4j.properties";
+ private static final String JAVA_OPTIONS_LOG_CONFIG = "log4j.configuration";
+
+ private static enum LevelOption {
+ ALL,
+ TRACE,
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR,
+ FATAL,
+ OFF,
+ RESET,
+ }
+
+ @Argument(index = 0, required = true, metaVar = "LEVEL", usage = "logging level to set to")
+ private LevelOption level;
+
+ @Argument(index = 1, required = false, metaVar = "NAME", usage = "used to match loggers")
+ private String name;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void run() throws MalformedURLException {
+ if (level == LevelOption.RESET) {
+ reset();
+ } else {
+ for (Enumeration<Logger> logger = LogManager.getCurrentLoggers(); logger
+ .hasMoreElements();) {
+ Logger log = logger.nextElement();
+ if (name == null || log.getName().contains(name)) {
+ log.setLevel(Level.toLevel(level.name()));
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void reset() throws MalformedURLException {
+ for (Enumeration<Logger> logger = LogManager.getCurrentLoggers();
+ logger.hasMoreElements();) {
+ logger.nextElement().setLevel(null);
+ }
+
+ String path = System.getProperty(JAVA_OPTIONS_LOG_CONFIG);
+ if (Strings.isNullOrEmpty(path)) {
+ PropertyConfigurator.configure(Loader.getResource(LOG_CONFIGURATION));
+ } else {
+ PropertyConfigurator.configure(new URL(path));
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
index cc9e6edd4b..923865a2a1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -16,7 +16,7 @@ package com.google.gerrit.sshd.commands;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.restapi.IdString;
@@ -119,7 +119,7 @@ public class SetMembersCommand extends SshCommand {
new Function<Account.Id, String>() {
@Override
public String apply(Account.Id accountId) {
- return Objects.firstNonNull(accountCache.get(accountId)
+ return MoreObjects.firstNonNull(accountCache.get(accountId)
.getAccount().getPreferredEmail(), "n/a");
}
}))).getBytes(ENC));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index cc72e968a9..3fd9b4d5c2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -16,9 +16,9 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.extensions.api.projects.ProjectState;
-import com.google.gerrit.extensions.common.InheritableBoolean;
-import com.google.gerrit.extensions.common.SubmitType;
+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.reviewdb.client.Project;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
@@ -66,42 +66,42 @@ final class SetProjectCommand extends SshCommand {
private InheritableBoolean requireChangeID;
@Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required")
- void setUseContributorArgreements(boolean on) {
+ void setUseContributorArgreements(@SuppressWarnings("unused") boolean on) {
contributorAgreements = InheritableBoolean.TRUE;
}
@Option(name = "--no-contributor-agreements", aliases = {"--nca"}, usage = "if contributor agreement is not required")
- void setNoContributorArgreements(boolean on) {
+ void setNoContributorArgreements(@SuppressWarnings("unused") boolean on) {
contributorAgreements = InheritableBoolean.FALSE;
}
@Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
- void setUseSignedOffBy(boolean on) {
+ void setUseSignedOffBy(@SuppressWarnings("unused") boolean on) {
signedOffBy = InheritableBoolean.TRUE;
}
@Option(name = "--no-signed-off-by", aliases = {"--nso"}, usage = "if signed-off-by is not required")
- void setNoSignedOffBy(boolean on) {
+ void setNoSignedOffBy(@SuppressWarnings("unused") boolean on) {
signedOffBy = InheritableBoolean.FALSE;
}
@Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
- void setUseContentMerge(boolean on) {
+ void setUseContentMerge(@SuppressWarnings("unused") boolean on) {
contentMerge = InheritableBoolean.TRUE;
}
@Option(name = "--no-content-merge", usage = "don't allow automatic conflict resolving within files")
- void setNoContentMerge(boolean on) {
+ void setNoContentMerge(@SuppressWarnings("unused") boolean on) {
contentMerge = InheritableBoolean.FALSE;
}
@Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
- void setRequireChangeId(boolean on) {
+ void setRequireChangeId(@SuppressWarnings("unused") boolean on) {
requireChangeID = InheritableBoolean.TRUE;
}
@Option(name = "--no-change-id", aliases = {"--nid"}, usage = "if change-id is not required")
- void setNoChangeId(boolean on) {
+ void setNoChangeId(@SuppressWarnings("unused") boolean on) {
requireChangeID = InheritableBoolean.FALSE;
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
index 8ce935c0bd..852105847a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -21,6 +21,7 @@ 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.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.DeleteReviewer;
@@ -29,6 +30,8 @@ import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
@@ -78,6 +81,9 @@ public class SetReviewersCommand extends SshCommand {
private ReviewDb db;
@Inject
+ private Provider<InternalChangeQuery> queryProvider;
+
+ @Inject
private ReviewerResource.Factory reviewerFactory;
@Inject
@@ -87,7 +93,10 @@ public class SetReviewersCommand extends SshCommand {
private Provider<DeleteReviewer> deleteReviewerProvider;
@Inject
- private ChangeControl.Factory changeControlFactory;
+ private Provider<CurrentUser> userProvider;
+
+ @Inject
+ private ChangeControl.GenericFactory changeControlFactory;
@Inject
private ChangesCollection changesCollection;
@@ -166,21 +175,10 @@ public class SetReviewersCommand extends SshCommand {
// By newer style changeKey?
//
- boolean changeKeyParses = false;
- if (idstr.matches("^I[0-9a-fA-F]*$")) {
- Change.Key key;
- try {
- key = Change.Key.parse(idstr);
- changeKeyParses = true;
- } catch (IllegalArgumentException e) {
- key = null;
- changeKeyParses = false;
- }
-
- if (changeKeyParses) {
- for (Change change : db.changes().byKeyRange(key, key.max())) {
- matchChange(matched, change);
- }
+ boolean changeKeyParses = idstr.matches("^I[0-9a-f]*$");
+ if (changeKeyParses) {
+ for (ChangeData cd : queryProvider.get().byKeyPrefix(idstr)) {
+ matchChange(matched, cd.change());
}
}
@@ -248,7 +246,8 @@ public class SetReviewersCommand extends SshCommand {
try {
if (change != null
&& inProject(change)
- && changeControlFactory.controlFor(change).isVisible(db)) {
+ && changeControlFactory.controlFor(change,
+ userProvider.get()).isVisible(db)) {
matched.add(change.getId());
}
} catch (NoSuchChangeException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 1f3a49a9b5..1c1fb60c7d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -17,6 +17,7 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.Strings;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -32,7 +33,6 @@ import com.google.gerrit.server.config.GetSummary.ThreadSummaryInfo;
import com.google.gerrit.server.config.ListCaches;
import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.config.ListCaches.CacheType;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 3d1ed3f074..108df9630f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -16,12 +16,12 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.util.IdGenerator;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 147d52a79c..b069cd491d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -16,14 +16,14 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.ListTasks;
import com.google.gerrit.server.config.ListTasks.TaskInfo;
import com.google.gerrit.server.git.WorkQueue.Task;
-import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -108,7 +108,7 @@ final class ShowQueue extends SshCommand {
stdout.print(String.format("%8s %-12s %-4s %s\n",
task.id, start, startTime(task.startTime),
- Objects.firstNonNull(remoteName, "n/a")));
+ MoreObjects.firstNonNull(remoteName, "n/a")));
}
}
stdout.print("----------------------------------------------"
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
index 6da858a867..889d3fb7fd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -16,12 +16,13 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.common.EventSource;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventTypes;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
import com.google.gerrit.sshd.BaseCommand;
@@ -51,27 +52,34 @@ final class StreamEvents extends BaseCommand {
private IdentifiedUser currentUser;
@Inject
- private ChangeHooks hooks;
+ private EventSource source;
@Inject
@StreamCommandExecutor
private WorkQueue.Executor pool;
/** Queue of events to stream to the connected user. */
- private final LinkedBlockingQueue<ChangeEvent> queue =
+ private final LinkedBlockingQueue<Event> queue =
new LinkedBlockingQueue<>(MAX_EVENTS);
private final Gson gson = new Gson();
/** Special event to notify clients they missed other events. */
- private final Object droppedOutputEvent = new Object() {
- @SuppressWarnings("unused")
- final String type = "dropped-output";
- };
+ private static final class DroppedOutputEvent extends Event {
+ public DroppedOutputEvent() {
+ super("dropped-output");
+ }
+ }
+
+ private static final DroppedOutputEvent droppedOutputEvent = new DroppedOutputEvent();
+
+ static {
+ EventTypes.registerClass(droppedOutputEvent);
+ }
- private final ChangeListener listener = new ChangeListener() {
+ private final EventListener listener = new EventListener() {
@Override
- public void onChangeEvent(final ChangeEvent event) {
+ public void onEvent(final Event event) {
offer(event);
}
};
@@ -124,12 +132,12 @@ final class StreamEvents extends BaseCommand {
}
stdout = toPrintWriter(out);
- hooks.addChangeListener(listener, currentUser);
+ source.addEventListener(listener, currentUser);
}
@Override
protected void onExit(final int rc) {
- hooks.removeChangeListener(listener);
+ source.removeEventListener(listener);
synchronized (taskLock) {
done = true;
@@ -140,7 +148,7 @@ final class StreamEvents extends BaseCommand {
@Override
public void destroy() {
- hooks.removeChangeListener(listener);
+ source.removeEventListener(listener);
final boolean exit;
synchronized (taskLock) {
@@ -157,7 +165,7 @@ final class StreamEvents extends BaseCommand {
}
}
- private void offer(final ChangeEvent event) {
+ private void offer(final Event event) {
synchronized (taskLock) {
if (!queue.offer(event)) {
dropped = true;
@@ -169,9 +177,9 @@ final class StreamEvents extends BaseCommand {
}
}
- private ChangeEvent poll() {
+ private Event poll() {
synchronized (taskLock) {
- ChangeEvent event = queue.poll();
+ Event event = queue.poll();
if (event == null) {
task = null;
}
@@ -188,7 +196,7 @@ final class StreamEvents extends BaseCommand {
// destroy() above, or it closed the stream and is no longer
// accepting output. Either way terminate this instance.
//
- hooks.removeChangeListener(listener);
+ source.removeEventListener(listener);
flush();
onExit(0);
return;
@@ -199,7 +207,7 @@ final class StreamEvents extends BaseCommand {
dropped = false;
}
- final ChangeEvent event = poll();
+ final Event event = poll();
if (event == null) {
break;
}
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
index e330834152..734b7045f2 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -50,9 +50,9 @@ import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.BooleanOptionHandler;
import org.kohsuke.args4j.spi.EnumOptionHandler;
+import org.kohsuke.args4j.spi.FieldSetter;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Setter;
-import org.kohsuke.args4j.spi.FieldSetter;
import java.io.StringWriter;
import java.io.Writer;
@@ -131,7 +131,7 @@ public class CmdLineParser {
char next = '?';
List<NamedOptionDef> booleans = new ArrayList<>();
- for (@SuppressWarnings("rawtypes") OptionHandler handler : parser.options) {
+ for (@SuppressWarnings("rawtypes") OptionHandler handler : parser.optionsList) {
if (handler.option instanceof NamedOptionDef) {
NamedOptionDef n = (NamedOptionDef) handler.option;
@@ -274,7 +274,7 @@ public class CmdLineParser {
@SuppressWarnings("rawtypes")
private OptionHandler findHandler(String name) {
if (options == null) {
- options = index(parser.options);
+ options = index(parser.optionsList);
}
return options.get(name);
}
@@ -318,7 +318,7 @@ public class CmdLineParser {
private class MyParser extends org.kohsuke.args4j.CmdLineParser {
@SuppressWarnings("rawtypes")
- private List<OptionHandler> options;
+ private List<OptionHandler> optionsList;
private HelpOption help;
MyParser(final Object bean) {
@@ -344,14 +344,14 @@ public class CmdLineParser {
@SuppressWarnings("rawtypes")
private OptionHandler add(OptionHandler handler) {
ensureOptionsInitialized();
- options.add(handler);
+ optionsList.add(handler);
return handler;
}
private void ensureOptionsInitialized() {
- if (options == null) {
+ if (optionsList == null) {
help = new HelpOption();
- options = Lists.newArrayList();
+ optionsList = Lists.newArrayList();
addOption(help, help);
}
}
diff --git a/gerrit-util-http/BUCK b/gerrit-util-http/BUCK
new file mode 100644
index 0000000000..7041c0aaf8
--- /dev/null
+++ b/gerrit-util-http/BUCK
@@ -0,0 +1,20 @@
+java_library(
+ name = 'http',
+ srcs = glob(['src/main/java/**/*.java']),
+ provided_deps = ['//lib:servlet-api-3_1'],
+ visibility = ['PUBLIC'],
+)
+
+java_test(
+ name = 'http_tests',
+ srcs = glob(['src/test/java/**/*.java']),
+ deps = [
+ ':http',
+ '//lib:junit',
+ '//lib:servlet-api-3_1',
+ '//lib/easymock:easymock',
+ ],
+ source_under_test = [':http'],
+ # TODO(sop) Remove after Buck supports Eclipse
+ visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-util-http/src/main/java/com/google/gerrit/util/http/RequestUtil.java b/gerrit-util-http/src/main/java/com/google/gerrit/util/http/RequestUtil.java
new file mode 100644
index 0000000000..922a8d5649
--- /dev/null
+++ b/gerrit-util-http/src/main/java/com/google/gerrit/util/http/RequestUtil.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2014 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.util.http;
+
+import javax.servlet.http.HttpServletRequest;
+
+/** Utilities for manipulating HTTP request objects. */
+public class RequestUtil {
+ /** HTTP request attribute for storing the Throwable that caused an error condition. */
+ private static final String ATTRIBUTE_ERROR_TRACE =
+ RequestUtil.class.getName() + "/ErrorTraceThrowable";
+
+ public static void setErrorTraceAttribute(HttpServletRequest req, Throwable t) {
+ req.setAttribute(ATTRIBUTE_ERROR_TRACE, t);
+ }
+
+ public static Throwable getErrorTraceAttribute(HttpServletRequest req) {
+ return (Throwable) req.getAttribute(ATTRIBUTE_ERROR_TRACE);
+ }
+
+ /**
+ * @return the same value as {@link HttpServletRequest#getPathInfo()}, but
+ * without decoding URL-encoded characters.
+ */
+ public static String getEncodedPathInfo(HttpServletRequest req) {
+ // Based on com.google.guice.ServletDefinition$1#getPathInfo() from:
+ // https://github.com/google/guice/blob/41c126f99d6309886a0ded2ac729033d755e1593/extensions/servlet/src/com/google/inject/servlet/ServletDefinition.java
+ String servletPath = req.getServletPath();
+ int servletPathLength = servletPath.length();
+ String requestUri = req.getRequestURI();
+ String pathInfo = requestUri.substring(req.getContextPath().length())
+ .replaceAll("[/]{2,}", "/");
+ if (pathInfo.startsWith(servletPath)) {
+ pathInfo = pathInfo.substring(servletPathLength);
+ // Corner case: when servlet path & request path match exactly (without
+ // trailing '/'), then pathinfo is null.
+ if (pathInfo.isEmpty() && servletPathLength > 0) {
+ pathInfo = null;
+ }
+ } else {
+ pathInfo = null;
+ }
+ return pathInfo;
+ }
+
+ private RequestUtil() {
+ }
+}
diff --git a/gerrit-util-http/src/test/java/com/google/gerrit/util/http/RequestUtilTest.java b/gerrit-util-http/src/test/java/com/google/gerrit/util/http/RequestUtilTest.java
new file mode 100644
index 0000000000..cfa0111ac2
--- /dev/null
+++ b/gerrit-util-http/src/test/java/com/google/gerrit/util/http/RequestUtilTest.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2014 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.util.http;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class RequestUtilTest {
+ private List<Object> mocks;
+
+ @Before
+ public void setUp() {
+ mocks = Collections.synchronizedList(new ArrayList<>());
+ }
+
+ @After
+ public void tearDown() {
+ for (Object mock : mocks) {
+ verify(mock);
+ }
+ }
+
+ @Test
+ public void emptyContextPath() {
+ assertEquals("/foo/bar", RequestUtil.getEncodedPathInfo(
+ mockRequest("/s/foo/bar", "", "/s")));
+ assertEquals("/foo%2Fbar", RequestUtil.getEncodedPathInfo(
+ mockRequest("/s/foo%2Fbar", "", "/s")));
+ }
+
+ @Test
+ public void emptyServletPath() {
+ assertEquals("/foo/bar", RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/foo/bar", "/c", "")));
+ assertEquals("/foo%2Fbar", RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/foo%2Fbar", "/c", "")));
+ }
+
+ @Test
+ public void trailingSlashes() {
+ assertEquals("/foo/bar/", RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/s/foo/bar/", "/c", "/s")));
+ assertEquals("/foo/bar/", RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/s/foo/bar///", "/c", "/s")));
+ assertEquals("/foo%2Fbar/", RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/s/foo%2Fbar/", "/c", "/s")));
+ assertEquals("/foo%2Fbar/", RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/s/foo%2Fbar///", "/c", "/s")));
+ }
+
+ @Test
+ public void servletPathMatchesRequestPath() {
+ assertEquals(null, RequestUtil.getEncodedPathInfo(
+ mockRequest("/c/s", "/c", "/s")));
+ }
+
+ private HttpServletRequest mockRequest(String uri, String contextPath, String servletPath) {
+ HttpServletRequest req = createMock(HttpServletRequest.class);
+ expect(req.getRequestURI()).andStubReturn(uri);
+ expect(req.getContextPath()).andStubReturn(contextPath);
+ expect(req.getServletPath()).andStubReturn(servletPath);
+ replay(req);
+ mocks.add(req);
+ return req;
+ }
+}
diff --git a/gerrit-util-ssl/src/main/java/com/google/gerrit/util/ssl/BlindSSLSocketFactory.java b/gerrit-util-ssl/src/main/java/com/google/gerrit/util/ssl/BlindSSLSocketFactory.java
index ed31379afb..b8af85e53e 100644
--- a/gerrit-util-ssl/src/main/java/com/google/gerrit/util/ssl/BlindSSLSocketFactory.java
+++ b/gerrit-util-ssl/src/main/java/com/google/gerrit/util/ssl/BlindSSLSocketFactory.java
@@ -34,13 +34,16 @@ public class BlindSSLSocketFactory extends SSLSocketFactory {
static {
final X509TrustManager dummyTrustManager = new X509TrustManager() {
+ @Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
+ @Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
+ @Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
};
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index f557edc799..35f6084dbd 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -10,8 +10,10 @@ java_library(
'//gerrit-lucene:lucene',
'//gerrit-oauth:oauth',
'//gerrit-openid:openid',
+ '//gerrit-pgm:http',
+ '//gerrit-pgm:init',
'//gerrit-pgm:init-api',
- '//gerrit-pgm:init-base',
+ '//gerrit-pgm:util',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-server/src/main/prolog:common',
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 39240f1935..0fb1c9a948 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-war</artifactId>
- <version>2.10.5</version>
+ <version>2.11.1</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
index 9665cdda45..6bbbd8fc8f 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd;
-import com.google.gerrit.pgm.BaseInit;
+import com.google.gerrit.pgm.init.BaseInit;
import com.google.gerrit.pgm.init.PluginsDistribution;
import org.slf4j.Logger;
@@ -54,8 +54,7 @@ public final class SiteInitializer {
return;
}
- Connection conn = connectToDb();
- try {
+ try (Connection conn = connectToDb()) {
File site = getSiteFromReviewDb(conn);
if (site == null && initPath != null) {
site = new File(initPath);
@@ -66,8 +65,6 @@ public final class SiteInitializer {
new BaseInit(site, new ReviewDbDataSourceProvider(), false, false,
pluginsDistribution, pluginsToInstall).run();
}
- } finally {
- conn.close();
}
} catch (Exception e) {
LOG.error("Site init failed", e);
@@ -80,19 +77,15 @@ public final class SiteInitializer {
}
private File getSiteFromReviewDb(Connection conn) {
- try {
- Statement stmt = conn.createStatement();
- try {
- ResultSet rs = stmt.executeQuery("SELECT site_path FROM system_config");
- if (rs.next()) {
- return new File(rs.getString(1));
- }
- } finally {
- stmt.close();
+ try (Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(
+ "SELECT site_path FROM system_config")) {
+ if (rs.next()) {
+ return new File(rs.getString(1));
}
- return null;
} catch (SQLException e) {
return null;
}
+ return null;
}
}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index f59401c47f..b365e76ff6 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -28,7 +28,6 @@ import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.account.InternalAccountDirectory;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
@@ -38,14 +37,17 @@ import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.config.RestCacheAdminModule;
import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.contact.ContactStoreModule;
import com.google.gerrit.server.contact.HttpContactStoreConnection;
-import com.google.gerrit.server.git.GarbageCollectionRunner;
+import com.google.gerrit.server.git.ChangeCacheImplModule;
+import com.google.gerrit.server.git.GarbageCollectionModule;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
+import com.google.gerrit.server.mime.MimeUtil2Module;
import com.google.gerrit.server.patch.DiffExecutorModule;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginRestApiModule;
@@ -55,6 +57,7 @@ import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.server.schema.DatabaseModule;
import com.google.gerrit.server.schema.SchemaModule;
import com.google.gerrit.server.schema.SchemaVersionCheck;
+import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.ssh.SshAddressesModule;
import com.google.gerrit.solr.SolrIndexModule;
@@ -73,6 +76,7 @@ import com.google.inject.name.Names;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.spi.Message;
+import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
@@ -167,7 +171,7 @@ public class WebAppInitializer extends GuiceServletContextListener
webInjector = createWebInjector();
PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
- env.setCfgInjector(cfgInjector);
+ env.setDbCfgInjector(dbInjector, cfgInjector);
if (sshInjector != null) {
env.setSshInjector(sshInjector);
}
@@ -205,6 +209,8 @@ public class WebAppInitializer extends GuiceServletContextListener
private Injector createDbInjector() {
final List<Module> modules = new ArrayList<>();
+ AbstractModule secureStore = createSecureStoreModule();
+ modules.add(secureStore);
if (sitePath != null) {
Module sitePathModule = new AbstractModule() {
@Override
@@ -217,13 +223,13 @@ public class WebAppInitializer extends GuiceServletContextListener
Module configModule = new GerritServerConfigModule();
modules.add(configModule);
- Injector cfgInjector = Guice.createInjector(sitePathModule, configModule);
+ Injector cfgInjector = Guice.createInjector(sitePathModule, configModule, secureStore);
Config cfg = cfgInjector.getInstance(Key.get(Config.class,
GerritServerConfig.class));
String dbType = cfg.getString("database", null, "type");
final DataSourceType dst = Guice.createInjector(new DataSourceModule(),
- configModule, sitePathModule).getInstance(
+ configModule, sitePathModule, secureStore).getInstance(
Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
modules.add(new LifecycleModule() {
@Override
@@ -279,9 +285,10 @@ public class WebAppInitializer extends GuiceServletContextListener
modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
- modules.add(new MergeabilityChecksExecutorModule());
modules.add(new DiffExecutorModule());
+ modules.add(new MimeUtil2Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
+ modules.add(new ChangeCacheImplModule(false));
modules.add(new InternalAccountDirectory.Module());
modules.add(new DefaultCacheFactory.Module());
modules.add(new SmtpEmailSender.Module());
@@ -311,10 +318,10 @@ public class WebAppInitializer extends GuiceServletContextListener
modules.add(new AbstractModule() {
@Override
protected void configure() {
- bind(GerritUiOptions.class).toInstance(new GerritUiOptions(false));
+ bind(GerritOptions.class).toInstance(new GerritOptions(false, false));
}
});
- modules.add(GarbageCollectionRunner.module());
+ modules.add(new GarbageCollectionModule());
return cfgInjector.createChildInjector(modules);
}
@@ -340,6 +347,7 @@ public class WebAppInitializer extends GuiceServletContextListener
modules.add(H2CacheBasedWebSession.module());
modules.add(HttpContactStoreConnection.module());
modules.add(new HttpPluginModule());
+ modules.add(new ContactStoreModule());
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
if (authConfig.getAuthType() == AuthType.OPENID) {
@@ -347,6 +355,7 @@ public class WebAppInitializer extends GuiceServletContextListener
} else if (authConfig.getAuthType() == AuthType.OAUTH) {
modules.add(new OAuthModule());
}
+ modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
return sysInjector.createChildInjector(modules);
}
@@ -372,4 +381,16 @@ public class WebAppInitializer extends GuiceServletContextListener
manager = null;
}
}
+
+ private AbstractModule createSecureStoreModule() {
+ return new AbstractModule() {
+ @Override
+ public void configure() {
+ String secureStoreClassName =
+ GerritServerConfigModule.getSecureStoreClassName(sitePath);
+ bind(String.class).annotatedWith(SecureStoreClassName.class).toProvider(
+ Providers.of(secureStoreClassName));
+ }
+ };
+ }
}
diff --git a/lib/BUCK b/lib/BUCK
index 42170eed3f..a880f069ef 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -26,18 +26,19 @@ define_license(name = 'DO_NOT_DISTRIBUTE')
maven_jar(
name = 'gwtorm',
- id = 'com.google.gerrit:gwtorm:1.14',
- bin_sha1 = '7e7562d2a8ae233ac9f23ec90dee1a01646483c0',
- src_sha1 = 'ae991fdefe5e92ee7ed754786b924dc1ec119a8b',
+ id = 'com.google.gerrit:gwtorm:1.14-14-gf54f1f1',
+ bin_sha1 = 'c02267e0245dd06930ea64a2d7c5ddc5ba6d9cfb',
+ src_sha1 = '3d17ae8a173eb34d89098c748f28cddd5080adbc',
license = 'Apache2.0',
deps = [':protobuf'],
+ repository = GERRIT,
)
maven_jar(
name = 'gwtjsonrpc',
- id = 'gwtjsonrpc:gwtjsonrpc:1.5',
- bin_sha1 = '8995287e2c3c866e826d06993904e2c8d7961e4b',
- src_sha1 = 'c9461f6c0490f26720e3ff15b5607320eab89d96',
+ id = 'gwtjsonrpc:gwtjsonrpc:1.7-2-g272ca32',
+ bin_sha1 = '91be25537f7e53e0b5ff5edb9a42ebfc56f764b6',
+ src_sha1 = '7e6d8892f2e3bf21a9854afcfd2534263636dcbc',
license = 'Apache2.0',
repository = GERRIT,
)
@@ -51,8 +52,8 @@ maven_jar(
maven_jar(
name = 'guava',
- id = 'com.google.guava:guava:17.0',
- sha1 = '9c6ef172e8de35fd8d4d8783e4821e57cdef7445',
+ id = 'com.google.guava:guava:18.0',
+ sha1 = 'cce0823396aa693798f8882e64213b1772032b09',
license = 'Apache2.0',
)
@@ -116,24 +117,24 @@ maven_jar(
maven_jar(
name = 'pegdown',
- id = 'org.pegdown:pegdown:1.2.1',
- sha1 = '47689e060d90f90431b5ab2df911452b93930d8c',
+ id = 'org.pegdown:pegdown:1.4.2',
+ sha1 = 'd96db502ed832df867ff5d918f05b51ba3879ea7',
license = 'Apache2.0',
deps = [':parboiled-java'],
)
maven_jar(
name = 'parboiled-core',
- id = 'org.parboiled:parboiled-core:1.1.6',
- sha1 = '11bd0c34fc6ac3c3cbf440ab8180cc6422c044e9',
+ id = 'org.parboiled:parboiled-core:1.1.7',
+ sha1 = 'a60ff9a54cbeb30ec44c89e16ac4c35913cbad5a',
license = 'Apache2.0',
attach_source = False,
)
maven_jar(
name = 'parboiled-java',
- id = 'org.parboiled:parboiled-java:1.1.6',
- sha1 = 'cb2ffa720f75b2fce8cfd1875599319e75ea9557',
+ id = 'org.parboiled:parboiled-java:1.1.7',
+ sha1 = '2298c64ce8ee8e2fb37e97e16d7be52f0c7cf61f',
license = 'Apache2.0',
deps = [
':parboiled-core',
@@ -142,7 +143,7 @@ maven_jar(
'//lib/ow2:ow2-asm-util',
],
attach_source = False,
- visibility = [],
+ visibility = ['//gerrit-server:server_tests'],
)
maven_jar(
@@ -171,8 +172,8 @@ maven_jar(
maven_jar(
name = 'junit',
- id = 'junit:junit:4.11',
- sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
+ id = 'junit:junit:4.10',
+ sha1 = 'e4f1766ce7404a08f45d859fb9c226fc9e41a861',
license = 'DO_NOT_DISTRIBUTE',
deps = [':hamcrest-core'],
)
@@ -186,6 +187,17 @@ maven_jar(
)
maven_jar(
+ name = 'truth',
+ id = 'com.google.truth:truth:0.25',
+ sha1 = '503ba892e8482976b81eb2b2df292858fbac3782',
+ license = 'DO_NOT_DISTRIBUTE',
+ deps = [
+ ':guava',
+ ':junit',
+ ],
+)
+
+maven_jar(
name = 'tukaani-xz',
id = 'org.tukaani:xz:1.4',
sha1 = '18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3',
diff --git a/lib/antlr/BUCK b/lib/antlr/BUCK
index 732b459c21..edf153c635 100644
--- a/lib/antlr/BUCK
+++ b/lib/antlr/BUCK
@@ -1,11 +1,11 @@
include_defs('//lib/maven.defs')
-VERSION = '3.2'
+VERSION = '3.5.2'
maven_jar(
name = 'java_runtime',
id = 'org.antlr:antlr-runtime:' + VERSION,
- sha1 = '31c746001016c6226bd7356c9f87a6a084ce3715',
+ sha1 = 'cd9cd41361c155f3af0f653009dcecb08d8b4afd',
license = 'antlr',
)
@@ -18,8 +18,8 @@ java_binary(
maven_jar(
name = 'stringtemplate',
- id = 'org.antlr:stringtemplate:' + VERSION,
- sha1 = '6fe2e3bb57daebd1555494818909f9664376dd6c',
+ id = 'org.antlr:stringtemplate:4.0.2',
+ sha1 = 'e28e09e2d44d60506a7bcb004d6c23ff35c6ac08',
license = 'antlr',
attach_source = False,
visibility = [],
@@ -28,7 +28,7 @@ maven_jar(
maven_jar(
name = 'tool',
id = 'org.antlr:antlr:' + VERSION,
- sha1 = '6b0acabea7bb3da058200a77178057e47e25cb69',
+ sha1 = 'c4a65c950bfc3e7d04309c515b2177c00baf7764',
license = 'antlr',
deps = [
':java_runtime',
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index b1d5933eb4..66a12c1c78 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -15,6 +15,7 @@ java_library(
':jruby',
'//lib:args4j',
'//lib:guava',
+ '//lib/log:api',
],
visibility = ['//tools/eclipse:classpath'],
)
@@ -42,8 +43,8 @@ java_library(
maven_jar(
name = 'asciidoctor',
- id = 'org.asciidoctor:asciidoctor-java-integration:0.1.4',
- sha1 = '3596c7142fd30d7b65a0e64ba294f3d9d4bd538f',
+ id = 'org.asciidoctor:asciidoctorj:1.5.0',
+ sha1 = '192df5660f72a0fb76966dcc64193b94fba65f99',
license = 'Apache2.0',
visibility = [],
attach_source = False,
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index 9e48641950..c7562df111 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -53,6 +53,9 @@ public class AsciiDoctor {
@Option(name = "--out-ext", usage = "extension for output files")
private String outExt = ".html";
+ @Option(name = "--base-dir", usage = "base directory")
+ private File basedir;
+
@Option(name = "--tmp", usage = "temporary output path")
private File tmpdir;
@@ -82,7 +85,7 @@ public class AsciiDoctor {
OptionsBuilder optionsBuilder = OptionsBuilder.options();
optionsBuilder.backend(backend).docType(DOCTYPE).eruby(ERUBY)
- .safe(SafeMode.UNSAFE);
+ .safe(SafeMode.UNSAFE).baseDir(basedir);
// XXX(fishywang): ideally we should just output to a string and add the
// content into zip. But asciidoctor will actually ignore all attributes if
// not output to a file. So we *have* to output to a file then read the
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 96f3eb5e9b..7eb70c1243 100644
--- a/lib/asciidoctor/java/DocIndexer.java
+++ b/lib/asciidoctor/java/DocIndexer.java
@@ -51,7 +51,8 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class DocIndexer {
- private static final Version LUCENE_VERSION = Version.LUCENE_48;
+ @SuppressWarnings("deprecation")
+ private static final Version LUCENE_VERSION = Version.LUCENE_4_10_1;
private static final Pattern SECTION_HEADER = Pattern.compile("^=+ (.*)");
@Option(name = "-o", usage = "output JAR file")
@@ -99,7 +100,7 @@ public class DocIndexer {
RAMDirectory directory = new RAMDirectory();
IndexWriterConfig config = new IndexWriterConfig(
LUCENE_VERSION,
- new StandardAnalyzer(LUCENE_VERSION, CharArraySet.EMPTY_SET));
+ new StandardAnalyzer(CharArraySet.EMPTY_SET));
config.setOpenMode(OpenMode.CREATE);
IndexWriter iwriter = new IndexWriter(directory, config);
for (String inputFile : inputFiles) {
diff --git a/lib/auto/BUCK b/lib/auto/BUCK
new file mode 100644
index 0000000000..a596420752
--- /dev/null
+++ b/lib/auto/BUCK
@@ -0,0 +1,13 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+ name = 'auto-value',
+ id = 'com.google.auto.value:auto-value:1.0',
+ sha1 = '5d13e60f5d190003176ca6ba4a410fae2e3f6315',
+ # Exclude un-relocated dependencies and replace with our own versions; see
+ # https://github.com/google/auto/blob/auto-value-1.0/value/pom.xml#L147
+ exclude = ['org/apache/*'],
+ deps = ['//lib:velocity'],
+ license = 'Apache2.0',
+ visibility = ['PUBLIC'],
+)
diff --git a/lib/auto/auto_value.defs b/lib/auto/auto_value.defs
new file mode 100644
index 0000000000..440574726a
--- /dev/null
+++ b/lib/auto/auto_value.defs
@@ -0,0 +1,21 @@
+# NOTE: Do not use this file in your build rules; automatically supported by
+# our implementation of java_library.
+
+AUTO_VALUE_DEP = '//lib/auto:auto-value'
+
+# Annotation processor classpath requires transitive dependencies.
+# TODO(dborowitz): Clean this up when buck issue is closed and there is a
+# better supported interface:
+# https://github.com/facebook/buck/issues/85
+AUTO_VALUE_PROCESSOR_DEPS = [
+ '//lib:velocity',
+ '//lib/auto:auto-value',
+ '//lib/commons:collections',
+ '//lib/commons:lang',
+ '//lib/commons:oro',
+]
+
+AUTO_VALUE_PROCESSORS = [
+ 'com.google.auto.value.processor.AutoAnnotationProcessor',
+ 'com.google.auto.value.processor.AutoValueProcessor',
+]
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index e8539c6f06..4c235e4b6f 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -1,13 +1,22 @@
include_defs('//lib/maven.defs')
-include_defs('//lib/codemirror/cm3.defs')
+include_defs('//lib/codemirror/cm.defs')
include_defs('//lib/codemirror/closure.defs')
-VERSION = '28a638a984'
-SHA1 = '68f8f136092a5965778186fb401a33be34cf73ed'
-URL = GERRIT + 'net/codemirror/codemirror-%s.zip' % VERSION
+REPO = MAVEN_CENTRAL
+VERSION = '5.0'
+SHA1 = '24982be364be130fd7b2930c41f7203b63dbd86c'
-ZIP = 'codemirror-%s.zip' % VERSION
-TOP = 'codemirror-%s' % VERSION
+if REPO == MAVEN_CENTRAL:
+ URL = REPO + 'org/webjars/codemirror/%s/codemirror-%s.jar' % (VERSION, VERSION)
+ TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION
+ ZIP = 'codemirror-%s.jar' % VERSION
+else:
+ URL = REPO + 'net/codemirror/codemirror-%s.zip' % VERSION
+ TOP = 'codemirror-%s' % VERSION
+ ZIP = 'codemirror-%s.zip' % VERSION
+
+
+CLOSURE_VERSION = 'v20141120'
CLOSURE_COMPILER_ARGS = [
'--compilation_level SIMPLE_OPTIMIZATIONS',
@@ -17,44 +26,75 @@ CLOSURE_COMPILER_ARGS = [
genrule(
name = 'css',
cmd = ';'.join([
- ':>$OUT',
- "echo '/** @license' >>$OUT",
+ "echo '/** @license' >$OUT",
'unzip -p $(location :zip) %s/LICENSE >>$OUT' % TOP,
"echo '*/' >>$OUT",
] +
['unzip -p $(location :zip) %s/%s >>$OUT' % (TOP, n)
- for n in CM3_CSS + CM3_THEMES]
+ for n in CM_CSS]
),
- deps = [':zip'],
- out = 'cm3.css',
+ out = 'cm.css',
)
+for n in CM_THEMES:
+ genrule(
+ name = 'theme_%s' % n,
+ cmd = ';'.join([
+ "echo '/** @license' >$OUT",
+ 'unzip -p $(location :zip) %s/LICENSE >>$OUT' % TOP,
+ "echo '*/' >>$OUT",
+ 'unzip -p $(location :zip) %s/theme/%s.css >>$OUT' % (TOP, n)
+ ]
+ ),
+ out = 'theme_%s.css' % n,
+ )
+
genrule(
- name = 'cm3-verbose',
+ name = 'cm-verbose',
cmd = ';'.join([
- ':>$OUT',
- "echo '/** @license' >>$OUT",
+ "echo '/** @license' >$OUT",
'unzip -p $(location :zip) %s/LICENSE >>$OUT' % TOP,
"echo '*/' >>$OUT",
] +
- ['unzip -p $(location :zip) %s/%s >>$OUT' % (TOP, n)
- for n in CM3_JS]
+ ['unzip -p $(location :zip) %s/%s >>$OUT' % (TOP, n) for n in CM_JS] +
+ ['unzip -p $(location :zip) %s/addon/%s >>$OUT' % (TOP, n)
+ for n in CM_ADDONS]
),
- deps = [':zip'],
- out = 'cm3-verbose.js',
+ out = 'cm-verbose.js',
)
js_minify(
name = 'js',
- generated = [':cm3-verbose'],
+ generated = [':cm-verbose'],
compiler_args = CLOSURE_COMPILER_ARGS,
- out = 'cm3.js'
+ out = 'cm.js'
)
+for n in CM_MODES:
+ genrule (
+ name = 'mode_%s_src' % n,
+ cmd = ';'.join([
+ "echo '/** @license' >$OUT",
+ 'unzip -p $(location :zip) %s/LICENSE >>$OUT' % TOP,
+ "echo '*/' >>$OUT",
+ 'unzip -p $(location :zip) %s/mode/%s/%s.js >>$OUT' % (TOP, n, n),
+ ]),
+ out = 'mode_%s_src.js' %n,
+ )
+ js_minify(
+ name = 'mode_%s_js' % n,
+ generated = [':mode_%s_src' % n],
+ compiler_args = CLOSURE_COMPILER_ARGS,
+ out = 'mode_%s.js' % n,
+ )
+
prebuilt_jar(
name = 'codemirror',
binary_jar = ':jar',
- deps = ['//lib:LICENSE-codemirror'],
+ deps = [
+ ':jar',
+ '//lib:LICENSE-codemirror',
+ ],
visibility = ['PUBLIC'],
)
@@ -62,20 +102,14 @@ genrule(
name = 'jar',
cmd = ';'.join([
'cd $TMP',
- 'unzip -q $(location :zip) %s' %
- ' '.join(['%s/mode/%s' % (TOP, n) for n in CM3_MODES]),
- ';'.join(['$(exe :js_minifier) ' +
- ' '.join(CLOSURE_COMPILER_ARGS) +
- ' --js_output_file %s/mode/%s.min --js %s/mode/%s'
- % (TOP, n, TOP, n) for n in CM3_MODES]),
- ';'.join(['mv %s/mode/%s.min %s/mode/%s' % (TOP, n, TOP, n) for n in CM3_MODES]),
- 'mkdir net',
- 'mv %s net/codemirror' % TOP,
- 'mkdir net/codemirror/lib',
+ 'mkdir -p net/codemirror/{lib,mode,theme}',
'cp $(location :css) net/codemirror/lib',
- 'cp $(location :js) net/codemirror/lib',
- 'zip -qr $OUT *'
- ]),
+ 'cp $(location :js) net/codemirror/lib']
+ + ['cp $(location :mode_%s_js) net/codemirror/mode/%s.js' % (n, n)
+ for n in CM_MODES]
+ + ['cp $(location :theme_%s) net/codemirror/theme/%s.css' % (n, n)
+ for n in CM_THEMES]
+ + ['zip -qr $OUT net/codemirror/{lib,mode,theme}']),
out = 'codemirror.jar',
)
@@ -87,3 +121,32 @@ genrule(
' -v ' + SHA1,
out = ZIP,
)
+
+java_binary(
+ name = 'js_minifier',
+ main_class = 'com.google.javascript.jscomp.CommandLineRunner',
+ deps = [':compiler-jar']
+)
+
+maven_jar(
+ name = 'compiler-jar',
+ id = 'com.google.javascript:closure-compiler:' + CLOSURE_VERSION,
+ sha1 = '369618bf5a96f73e32655dc48919c0f97558d3b1',
+ license = 'Apache2.0',
+ deps = [
+ ':closure-compiler-externs',
+ '//lib:args4j',
+ '//lib:gson',
+ '//lib:guava',
+ '//lib:protobuf',
+ ],
+ visibility = [],
+)
+
+maven_jar(
+ name = 'closure-compiler-externs',
+ id = 'com.google.javascript:closure-compiler-externs:' + CLOSURE_VERSION,
+ sha1 = '247eff337e2737de43c8d963aaaef15bd8cda132',
+ license = 'Apache2.0',
+ visibility = [],
+)
diff --git a/lib/codemirror/closure.defs b/lib/codemirror/closure.defs
index e602b9fc53..0da1501f34 100644
--- a/lib/codemirror/closure.defs
+++ b/lib/codemirror/closure.defs
@@ -1,9 +1,3 @@
-# https://code.google.com/p/closure-compiler/wiki/BinaryDownloads?tm=2
-CLOSURE_VERSION = '20140407'
-CLOSURE_COMPILER_URL = 'http://dl.google.com/closure-compiler/compiler-%s.zip' % CLOSURE_VERSION
-COMPILER = 'compiler.jar'
-CLOSURE_COMPILER_SHA1 = 'eeb02bfd45eb4a080b66dd423eaee4bdd1d674e9'
-
def js_minify(
name,
out,
@@ -21,30 +15,4 @@ def js_minify(
cmd = ' '.join(cmd),
srcs = srcs,
out = out,
-)
-
-java_binary(
- name = 'js_minifier',
- main_class = 'com.google.javascript.jscomp.CommandLineRunner',
- deps = [':compiler-jar']
-)
-
-prebuilt_jar(
- name = 'compiler-jar',
- binary_jar = ':compiler',
-)
-
-genrule(
- name = 'compiler',
- cmd = 'unzip -p $(location :closure-compiler-zip) %s >$OUT' % COMPILER,
- out = COMPILER,
-)
-
-genrule(
- name = 'closure-compiler-zip',
- cmd = '$(exe //tools:download_file)' +
- ' -o $OUT' +
- ' -u ' + CLOSURE_COMPILER_URL +
- ' -v ' + CLOSURE_COMPILER_SHA1,
- out = 'closure-compiler.zip',
-)
+ )
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
new file mode 100644
index 0000000000..db87b25eba
--- /dev/null
+++ b/lib/codemirror/cm.defs
@@ -0,0 +1,82 @@
+CM_CSS = [
+ 'lib/codemirror.css',
+ 'addon/dialog/dialog.css',
+ 'addon/scroll/simplescrollbars.css',
+ 'addon/search/matchesonscrollbar.css',
+]
+
+CM_JS = [
+ 'lib/codemirror.js',
+ 'mode/meta.js',
+ 'keymap/vim.js',
+]
+
+CM_ADDONS = [
+ 'dialog/dialog.js',
+ 'edit/trailingspace.js',
+ 'scroll/annotatescrollbar.js',
+ 'scroll/simplescrollbars.js',
+ 'search/matchesonscrollbar.js',
+ 'search/searchcursor.js',
+ 'search/search.js',
+ 'selection/mark-selection.js',
+ 'mode/overlay.js',
+ 'mode/simple.js',
+]
+
+# Available themes must be enumerated here,
+# in gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Theme.java,
+# in gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java
+CM_THEMES = [
+ 'eclipse',
+ 'elegant',
+ 'midnight',
+ 'neat',
+ 'night',
+ 'twilight',
+]
+
+# Available modes must be enumerated here,
+# in gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java,
+# and in CodeMirror's own mode/meta.js script.
+CM_MODES = [
+ 'clike',
+ 'clojure',
+ 'coffeescript',
+ 'commonlisp',
+ 'css',
+ 'd',
+ 'dart',
+ 'diff',
+ 'dockerfile',
+ 'dtd',
+ 'erlang',
+ 'gas',
+ 'gfm',
+ 'go',
+ 'groovy',
+ 'haskell',
+ 'htmlmixed',
+ 'javascript',
+ 'lua',
+ 'markdown',
+ 'perl',
+ 'php',
+ 'pig',
+ 'properties',
+ 'python',
+ 'r',
+ 'rst',
+ 'ruby',
+ 'scheme',
+ 'shell',
+ 'smalltalk',
+ 'soy',
+ 'sql',
+ 'stex',
+ 'tcl',
+ 'velocity',
+ 'verilog',
+ 'xml',
+ 'yaml',
+]
diff --git a/lib/codemirror/cm3.defs b/lib/codemirror/cm3.defs
deleted file mode 100644
index 84f09c3e58..0000000000
--- a/lib/codemirror/cm3.defs
+++ /dev/null
@@ -1,60 +0,0 @@
-CM3_CSS = [
- 'lib/codemirror.css',
- 'addon/dialog/dialog.css',
-]
-
-CM3_THEMES = [
- 'theme/eclipse.css',
- 'theme/elegant.css',
- 'theme/midnight.css',
- 'theme/neat.css',
- 'theme/night.css',
- 'theme/twilight.css',
-]
-
-CM3_JS = [
- 'lib/codemirror.js',
- 'keymap/vim.js',
- 'addon/dialog/dialog.js',
- 'addon/search/searchcursor.js',
- 'addon/search/search.js',
- 'addon/selection/mark-selection.js',
- 'addon/edit/trailingspace.js',
-]
-
-CM3_MODES = [
- 'clike/clike.js',
- 'clojure/clojure.js',
- 'coffeescript/coffeescript.js',
- 'commonlisp/commonlisp.js',
- 'css/css.js',
- 'd/d.js',
- 'diff/diff.js',
- 'dtd/dtd.js',
- 'erlang/erlang.js',
- 'gas/gas.js',
- 'gfm/gfm.js',
- 'go/go.js',
- 'groovy/groovy.js',
- 'haskell/haskell.js',
- 'htmlmixed/htmlmixed.js',
- 'javascript/javascript.js',
- 'lua/lua.js',
- 'markdown/markdown.js',
- 'perl/perl.js',
- 'php/php.js',
- 'pig/pig.js',
- 'properties/properties.js',
- 'python/python.js',
- 'r/r.js',
- 'ruby/ruby.js',
- 'scheme/scheme.js',
- 'shell/shell.js',
- 'smalltalk/smalltalk.js',
- 'sql/sql.js',
- 'tcl/tcl.js',
- 'velocity/velocity.js',
- 'verilog/verilog.js',
- 'xml/xml.js',
- 'yaml/yaml.js',
-]
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 85e404ff30..155baad15c 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -82,36 +82,8 @@ maven_jar(
maven_jar(
name = 'validator',
- id = 'commons-validator:commons-validator:1.4.0',
- sha1 = '42fa1046955ade59f5354a1876cfc523cea33815',
+ id = 'commons-validator:commons-validator:1.4.1',
+ sha1 = '2231238e391057a53f92bde5bbc588622c1956c3',
license = 'Apache2.0',
)
-maven_jar(
- name = 'httpclient',
- id = 'org.apache.httpcomponents:httpclient:4.3.4',
- bin_sha1 = 'a9a1fef2faefed639ee0d0fba5b3b8e4eb2ff2d8',
- src_sha1 = '7a14aafed8c5e2c4e360a2c1abd1602efa768b1f',
- license = 'Apache2.0',
- deps = [
- ':codec',
- ':httpcore',
- '//lib/log:jcl-over-slf4j',
- ],
-)
-
-maven_jar(
- name = 'httpcore',
- id = 'org.apache.httpcomponents:httpcore:4.3.2',
- bin_sha1 = '31fbbff1ddbf98f3aa7377c94d33b0447c646b6e',
- src_sha1 = '4809f38359edeea9487f747e09aa58ec8d3a54c5',
- license = 'Apache2.0',
-)
-
-maven_jar(
- name = 'httpmime',
- id = 'org.apache.httpcomponents:httpmime:4.3.4',
- bin_sha1 = '54ffde537682aea984c22fbcf0106f21397c5f9b',
- src_sha1 = '0651e21152b0963661068f948d84ed08c18094f8',
- license = 'Apache2.0',
-)
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 8d2b7184d1..bc80aa5a62 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -1,11 +1,11 @@
include_defs('//lib/maven.defs')
-VERSION = '2.6.1'
+VERSION = '2.7.0'
maven_jar(
name = 'user',
id = 'com.google.gwt:gwt-user:' + VERSION,
- sha1 = 'c078b1b8cc0281214b0eb458d2c283d039374fad',
+ sha1 = 'bdc7af42581745d3d79c2efe0b514f432b998a5b',
license = 'Apache2.0',
attach_source = False,
)
@@ -13,17 +13,26 @@ maven_jar(
maven_jar(
name = 'dev',
id = 'com.google.gwt:gwt-dev:' + VERSION,
- sha1 = 'db237e4be0aa1fe43425d2c51ab5485dba211ddd',
+ sha1 = 'c2c3dd5baf648a0bb199047a818be5e560f48982',
license = 'Apache2.0',
deps = [
':javax-validation',
':javax-validation_src',
+ ':json',
],
attach_source = False,
exclude = ['org/eclipse/jetty/*'],
)
maven_jar(
+ name = 'json',
+ id = 'org.json:json:20140107',
+ sha1 = 'd1ffca6e2482b002702c6a576166fd685e3370e3',
+ license = 'DO_NOT_DISTRIBUTE',
+ attach_source = False,
+)
+
+maven_jar(
name = 'javax-validation',
id = 'javax.validation:validation-api:1.0.0.GA',
bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e',
@@ -51,4 +60,3 @@ maven_jar(
license = 'Apache2.0',
visibility = [],
)
-
diff --git a/lib/httpcomponents/BUCK b/lib/httpcomponents/BUCK
new file mode 100644
index 0000000000..50e463d1ad
--- /dev/null
+++ b/lib/httpcomponents/BUCK
@@ -0,0 +1,31 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+ name = 'httpclient',
+ id = 'org.apache.httpcomponents:httpclient:4.3.4',
+ bin_sha1 = 'a9a1fef2faefed639ee0d0fba5b3b8e4eb2ff2d8',
+ src_sha1 = '7a14aafed8c5e2c4e360a2c1abd1602efa768b1f',
+ license = 'Apache2.0',
+ deps = [
+ '//lib/commons:codec',
+ ':httpcore',
+ '//lib/log:jcl-over-slf4j',
+ ],
+)
+
+maven_jar(
+ name = 'httpcore',
+ id = 'org.apache.httpcomponents:httpcore:4.3.2',
+ bin_sha1 = '31fbbff1ddbf98f3aa7377c94d33b0447c646b6e',
+ src_sha1 = '4809f38359edeea9487f747e09aa58ec8d3a54c5',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'httpmime',
+ id = 'org.apache.httpcomponents:httpmime:4.3.4',
+ bin_sha1 = '54ffde537682aea984c22fbcf0106f21397c5f9b',
+ src_sha1 = '0651e21152b0963661068f948d84ed08c18094f8',
+ license = 'Apache2.0',
+)
+
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
index 13e977457b..891fcec150 100644
--- a/lib/jetty/BUCK
+++ b/lib/jetty/BUCK
@@ -1,24 +1,21 @@
include_defs('//lib/maven.defs')
-VERSION = '9.2.1.v20140609'
+VERSION = '9.2.9.v20150224'
EXCLUDE = ['about.html']
maven_jar(
name = 'servlet',
id = 'org.eclipse.jetty:jetty-servlet:' + VERSION,
- sha1 = 'f2327faaf09a3f306babc209f9a7ae01b1528464',
+ sha1 = '1797875a3cc524d181733f323866a5f7bbca03a7',
license = 'Apache2.0',
- deps = [
- ':security',
- '//lib:servlet-api-3_1',
- ],
+ deps = [':security'],
exclude = EXCLUDE,
)
maven_jar(
name = 'security',
id = 'org.eclipse.jetty:jetty-security:' + VERSION,
- sha1 = '8ac8cc9e5c66eb6022cbe80f4e22d4e42dc5e643',
+ sha1 = '1747a52b01afbf96b58b0ae0f352185560768fc2',
license = 'Apache2.0',
deps = [':server'],
exclude = EXCLUDE,
@@ -26,11 +23,10 @@ maven_jar(
)
maven_jar(
- name = 'webapp',
- id = 'org.eclipse.jetty:jetty-webapp:' + VERSION,
- sha1 = '906e0f4ba7a0cebb8af61513c8511981ba2ccf6e',
+ name = 'servlets',
+ id = 'org.eclipse.jetty:jetty-servlets:' + VERSION,
+ sha1 = '9b04f638c23a4db7c8e2dbfe31ab7370ce972ade',
license = 'Apache2.0',
- deps = [':xml'],
exclude = EXCLUDE,
visibility = [
'//tools/eclipse:classpath',
@@ -39,18 +35,9 @@ maven_jar(
)
maven_jar(
- name = 'xml',
- id = 'org.eclipse.jetty:jetty-xml:' + VERSION,
- sha1 = '0d589789eb98d31160d11413b6323b9ea4569046',
- license = 'Apache2.0',
- exclude = EXCLUDE,
- visibility = [],
-)
-
-maven_jar(
name = 'server',
id = 'org.eclipse.jetty:jetty-server:' + VERSION,
- sha1 = 'd02c51c4f8eec3174b09b6e978feaaf05c3dc4ea',
+ sha1 = 'd30a52e992c3484569f58763f55097a1da3202ee',
license = 'Apache2.0',
exported_deps = [
':continuation',
@@ -62,7 +49,7 @@ maven_jar(
maven_jar(
name = 'jmx',
id = 'org.eclipse.jetty:jetty-jmx:' + VERSION,
- sha1 = '1258d5ac618b120026da8a82283e6cb8ff4638a6',
+ sha1 = 'e0a9df505fbcc7c0481209325a106b922097468d',
license = 'Apache2.0',
exported_deps = [
':continuation',
@@ -74,7 +61,7 @@ maven_jar(
maven_jar(
name = 'continuation',
id = 'org.eclipse.jetty:jetty-continuation:' + VERSION,
- sha1 = 'e5bf20cdcd9c2878677f3c0f43baea2725f8c59e',
+ sha1 = '476cae89c420170549b4851ed58dca25f349d16d',
license = 'Apache2.0',
exclude = EXCLUDE,
)
@@ -82,7 +69,7 @@ maven_jar(
maven_jar(
name = 'http',
id = 'org.eclipse.jetty:jetty-http:' + VERSION,
- sha1 = 'a132617cb898afc9d4ce5d586e11ad90b9831fff',
+ sha1 = '8b30ddc8304df24a36efbfa267acc24b7403b692',
license = 'Apache2.0',
exported_deps = [':io'],
exclude = EXCLUDE,
@@ -91,7 +78,7 @@ maven_jar(
maven_jar(
name = 'io',
id = 'org.eclipse.jetty:jetty-io:' + VERSION,
- sha1 = '8465fe92159632e9f0a1bfe6951dba8163ac0b12',
+ sha1 = '06a4a23ee9decf2762d052bc2ae0501c08cc9023',
license = 'Apache2.0',
exported_deps = [':util'],
exclude = EXCLUDE,
@@ -101,7 +88,7 @@ maven_jar(
maven_jar(
name = 'util',
id = 'org.eclipse.jetty:jetty-util:' + VERSION,
- sha1 = '4ae7ac5d3cfcb21bc288dd3f4ec3ba2823442f0d',
+ sha1 = 'b5fb774a02158e9f66fed949581159a8d0dfcbe1',
license = 'Apache2.0',
exclude = EXCLUDE,
visibility = [],
diff --git a/lib/local.defs b/lib/local.defs
new file mode 100644
index 0000000000..6eec581a0d
--- /dev/null
+++ b/lib/local.defs
@@ -0,0 +1,33 @@
+def local_jar(
+ name,
+ jar,
+ src = None,
+ deps = [],
+ visibility = ['PUBLIC']):
+ binjar = name + '.jar'
+ srcjar = name + '-src.jar'
+ genrule(
+ name = '%s__local_bin' % name,
+ cmd = 'ln -s %s $OUT' % jar,
+ out = binjar)
+ if src:
+ genrule(
+ name = '%s__local_src' % name,
+ cmd = 'ln -s %s $OUT' % src,
+ out = srcjar)
+ prebuilt_jar(
+ name = '%s_src' % name,
+ binary_jar = ':%s__local_src' % name,
+ visibility = visibility,
+ )
+ else:
+ srcjar = None
+
+ prebuilt_jar(
+ name = name,
+ deps = deps,
+ binary_jar = ':%s__local_bin' % name,
+ source_jar = ':%s__local_src' % name if srcjar else None,
+ visibility = visibility,
+ )
+
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index 9ccc5aa59b..9026f79605 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -1,11 +1,11 @@
include_defs('//lib/maven.defs')
-VERSION = '4.8.1'
+VERSION = '4.10.2'
maven_jar(
name = 'core',
id = 'org.apache.lucene:lucene-core:' + VERSION,
- sha1 = 'a549eef6316a2c38d4cda932be809107deeaf8a7',
+ sha1 = 'c01e3d675d277e0a93e7890d03cc3246b2cdecaa',
license = 'Apache2.0',
exclude = [
'META-INF/LICENSE.txt',
@@ -16,7 +16,7 @@ maven_jar(
maven_jar(
name = 'analyzers-common',
id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION,
- sha1 = '6e3731524351c83cd21022a23bee5e87f0575555',
+ sha1 = 'f977f8c443e8f4e9d1fd7fdfda80a6cf60b3e7c2',
license = 'Apache2.0',
exclude = [
'META-INF/LICENSE.txt',
@@ -27,6 +27,6 @@ maven_jar(
maven_jar(
name = 'query-parser',
id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
- sha1 = 'f3e105d74137906fdeb2c7bc4dd68c08564778f9',
+ sha1 = 'd70f54e1060d553ba7aeb4d49a71fd0c068499e8',
license = 'Apache2.0',
)
diff --git a/lib/maven.defs b/lib/maven.defs
index 23708ca249..4edba9c268 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-ATLASSIAN = 'ATLASSIAN:'
+include_defs('//lib/local.defs')
+
GERRIT = 'GERRIT:'
GERRIT_API = 'GERRIT_API:'
MAVEN_CENTRAL = 'MAVEN_CENTRAL:'
@@ -139,35 +140,3 @@ def maven_jar(
visibility = visibility,
)
-def local_jar(
- name,
- jar,
- src = None,
- deps = [],
- visibility = ['PUBLIC']):
- binjar = name + '.jar'
- srcjar = name + '-src.jar'
- genrule(
- name = '%s__local_bin' % name,
- cmd = 'ln -s %s $OUT' % jar,
- out = binjar)
- if src:
- genrule(
- name = '%s__local_src' % name,
- cmd = 'ln -s %s $OUT' % src,
- out = srcjar)
- prebuilt_jar(
- name = '%s_src' % name,
- binary_jar = ':%s__local_src' % name,
- visibility = visibility,
- )
- else:
- srcjar = None
-
- prebuilt_jar(
- name = name,
- deps = deps,
- binary_jar = ':%s__local_bin' % name,
- source_jar = ':%s__local_src' % name if srcjar else None,
- visibility = visibility,
- )
diff --git a/lib/openid/BUCK b/lib/openid/BUCK
index c6c8baf6ae..728698bb32 100644
--- a/lib/openid/BUCK
+++ b/lib/openid/BUCK
@@ -8,7 +8,7 @@ maven_jar(
deps = [
':nekohtml',
':xerces',
- '//lib/commons:httpclient',
+ '//lib/httpcomponents:httpclient',
'//lib/log:jcl-over-slf4j',
'//lib/guice:guice',
],
diff --git a/lib/ow2/BUCK b/lib/ow2/BUCK
index 81d1618f8f..61b4e1b18a 100644
--- a/lib/ow2/BUCK
+++ b/lib/ow2/BUCK
@@ -1,32 +1,32 @@
include_defs('//lib/maven.defs')
-VERSION = '4.1'
+VERSION = '5.0.3'
maven_jar(
name = 'ow2-asm',
id = 'org.ow2.asm:asm:' + VERSION,
- sha1 = 'ad568238ee36a820bd6c6806807e8a14ea34684d',
+ sha1 = 'dcc2193db20e19e1feca8b1240dbbc4e190824fa',
license = 'ow2',
)
maven_jar(
name = 'ow2-asm-analysis',
id = 'org.ow2.asm:asm-analysis:' + VERSION,
- sha1 = '73401033069e4714f57b60aeae02f97210aaa64e',
+ sha1 = 'c7126aded0e8e13fed5f913559a0dd7b770a10f3',
license = 'ow2',
)
maven_jar(
name = 'ow2-asm-tree',
id = 'org.ow2.asm:asm-tree:' + VERSION,
- sha1 = '51085abcc4cb6c6e1cb5551e6f999eb8e31c5b2d',
+ sha1 = '287749b48ba7162fb67c93a026d690b29f410bed',
license = 'ow2',
)
maven_jar(
name = 'ow2-asm-util',
id = 'org.ow2.asm:asm-util:' + VERSION,
- sha1 = '6344065cb0f94e2b930a95e6656e040ebc11df08',
+ sha1 = '1512e5571325854b05fb1efce1db75fcced54389',
license = 'ow2',
)
diff --git a/lib/solr/BUCK b/lib/solr/BUCK
index afaa948511..cd3974200b 100644
--- a/lib/solr/BUCK
+++ b/lib/solr/BUCK
@@ -9,8 +9,8 @@ maven_jar(
deps = [
':noggit',
':zookeeper',
- '//lib/commons:httpclient',
- '//lib/commons:httpmime',
+ '//lib/httpcomponents:httpclient',
+ '//lib/httpcomponents:httpmime',
'//lib/commons:io',
],
)
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
-Subproject a93b85656a68b6a71fe7cf0bb0cc4ed8143657b
+Subproject 7923b67392164dcc65ada85f723fa5111b26548
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
-Subproject 5ac8e3475cc284bd1c2159cfded076c95946284
+Subproject 17b63c160498d02fb1c511c5b43b02f538b2955
diff --git a/plugins/download-commands b/plugins/download-commands
-Subproject 6287d6a8941f68ba8a3a8c27f2a979c02ede489
+Subproject baa09c2e265a2b264a5fb4571e7eefda04def0c
diff --git a/plugins/replication b/plugins/replication
-Subproject 16db002a0bfe6559a6395dabf155b7a8bfadc96
+Subproject 53ee1b8ec4de5de4d710233eda2230b5380f139
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
-Subproject 0b5aab68206744f6a41abba63aaa3714f354fa8
+Subproject f6403a1ed35e908a4828fdc9168ed752fa178c6
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
-Subproject 73c2381c5768f216b3a4abb1c623f8d0134a960
+Subproject 082bf62238d4d815636a329cc1ef4d86b36f982
diff --git a/tools/BUCK b/tools/BUCK
index 08ced89546..ee26062cb1 100644
--- a/tools/BUCK
+++ b/tools/BUCK
@@ -21,7 +21,7 @@ python_library(
visibility = ['PUBLIC'],
)
-python_library(
+python_test(
name = 'util_test',
srcs = ['util_test.py'],
deps = [':util'],
@@ -44,13 +44,3 @@ genrule(
visibility = ['PUBLIC'],
)
-java_test(
- name = 'python_tests',
- srcs = glob(['PythonTestCaller.java']),
- deps = [
- '//lib:guava',
- '//lib:junit',
- ':util',
- ':util_test',
- ],
-)
diff --git a/tools/PythonTestCaller.java b/tools/PythonTestCaller.java
deleted file mode 100644
index deabeb4668..0000000000
--- a/tools/PythonTestCaller.java
+++ /dev/null
@@ -1,67 +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.
-
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.io.ByteStreams;
-import com.google.common.base.Splitter;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import org.junit.Test;
-
-public class PythonTestCaller {
-
- @Test
- public void resolveUrl() throws Exception {
- PythonTestCaller.pythonUnit("tools", "util_test");
- }
-
- private static void pythonUnit(String d, String sut) throws Exception {
- ProcessBuilder b =
- new ProcessBuilder(Splitter.on(' ').splitToList(
- "python -m unittest " + sut))
- .directory(new File(d))
- .redirectErrorStream(true);
- Process p = null;
- InputStream i = null;
- byte[] out;
- try {
- p = b.start();
- i = p.getInputStream();
- out = ByteStreams.toByteArray(i);
- } catch (IOException e) {
- throw new Exception(e);
- } finally {
- if (p != null) {
- p.getOutputStream().close();
- }
- if (i != null) {
- i.close();
- }
- }
- int value;
- try {
- value = p.waitFor();
- } catch (InterruptedException e) {
- throw new Exception("interrupted waiting for process");
- }
- String err = new String(out, "UTF-8");
- if (value != 0) {
- System.err.print(err);
- }
- assertTrue(err, value == 0);
- }
-}
diff --git a/tools/checkstyle.xml b/tools/checkstyle.xml
new file mode 100644
index 0000000000..a9f8cfef83
--- /dev/null
+++ b/tools/checkstyle.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+ This configuration file was written by the eclipse-cs plugin configuration editor
+-->
+<!--
+ Checkstyle-Configuration: Google Checks for Gerrit
+ Description:
+Checkstyle configuration based on the Google coding conventions (https://google-styleguide.googlecode.com/svn-history/r130/trunk/javaguide.html),
+edited to remove noisy warnings.
+-->
+<module name="Checker">
+ <property name="severity" value="warning"/>
+ <property name="charset" value="UTF-8"/>
+ <module name="TreeWalker">
+ <module name="OuterTypeFilename"/>
+ <module name="LineLength">
+ <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
+ <property name="max" value="100"/>
+ </module>
+ <module name="OneTopLevelClass"/>
+ <module name="NoLineWrap"/>
+ <module name="EmptyBlock">
+ <property name="option" value="TEXT"/>
+ <property name="tokens" value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
+ </module>
+ <module name="NeedBraces"/>
+ <module name="LeftCurly">
+ <property name="maxLineLength" value="100"/>
+ </module>
+ <module name="RightCurly"/>
+ <module name="RightCurly">
+ <property name="option" value="alone"/>
+ <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
+ </module>
+ <module name="WhitespaceAround">
+ <property name="severity" value="ignore"/>
+ <property name="allowEmptyConstructors" value="true"/>
+ <property name="allowEmptyMethods" value="true"/>
+ <property name="allowEmptyTypes" value="true"/>
+ <property name="allowEmptyLoops" value="true"/>
+ <message key="ws.notFollowed" value="WhitespaceAround: ''{0}'' is not followed by whitespace."/>
+ <message key="ws.notPreceded" value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="OneStatementPerLine"/>
+ <module name="MultipleVariableDeclarations"/>
+ <module name="ArrayTypeStyle"/>
+ <module name="MissingSwitchDefault"/>
+ <module name="FallThrough"/>
+ <module name="UpperEll"/>
+ <module name="ModifierOrder"/>
+ <module name="EmptyLineSeparator">
+ <property name="severity" value="ignore"/>
+ <property name="allowNoEmptyLineBetweenFields" value="true"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="SeparatorWrap">
+ <property name="severity" value="ignore"/>
+ <property name="option" value="nl"/>
+ <property name="tokens" value="DOT"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="SeparatorWrap">
+ <property name="severity" value="ignore"/>
+ <property name="option" value="EOL"/>
+ <property name="tokens" value="COMMA"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="NoFinalizer"/>
+ <module name="GenericWhitespace">
+ <property name="severity" value="ignore"/>
+ <message key="ws.followed" value="GenericWhitespace ''{0}'' is followed by whitespace."/>
+ <message key="ws.illegalFollow" value="GenericWhitespace ''{0}'' should followed by whitespace."/>
+ <message key="ws.preceded" value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
+ <message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="Indentation">
+ <property name="severity" value="ignore"/>
+ <property name="basicOffset" value="2"/>
+ <property name="caseIndent" value="2"/>
+ <property name="arrayInitIndent" value="2"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="MethodParamPad">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ <module name="OperatorWrap">
+ <property name="severity" value="ignore"/>
+ <property name="option" value="NL"/>
+ <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+ </module>
+ <module name="FileTabCharacter">
+ <property name="severity" value="ignore"/>
+ <property name="eachLine" value="true"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+</module>
diff --git a/tools/default.defs b/tools/default.defs
index 27efa11a1f..30518c4f99 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -14,9 +14,60 @@
# Rule definitions loaded by default into every BUCK file.
+include_defs('//lib/auto/auto_value.defs')
include_defs('//tools/gwt-constants.defs')
+include_defs('//tools/java_doc.defs')
+include_defs('//tools/java_sources.defs')
import copy
+# Set defaults on java rules:
+# - Add AutoValue annotation processing support.
+# - Treat source files as UTF-8.
+
+_buck_java_library = java_library
+def java_library(*args, **kwargs):
+ _munge_args(kwargs)
+ _buck_java_library(*args, **kwargs)
+
+_buck_java_test = java_test
+def java_test(*args, **kwargs):
+ _munge_args(kwargs)
+ _buck_java_test(*args, **kwargs)
+
+
+# Munge kwargs to set Gerrit-specific defaults.
+def _munge_args(kwargs):
+ _set_auto_value(kwargs)
+ _set_extra_arguments(kwargs)
+
+def _set_extra_arguments(kwargs):
+ ext = 'extra_arguments'
+ if ext not in kwargs:
+ kwargs[ext] = []
+ extra_args = kwargs[ext]
+
+ for arg in extra_args:
+ if arg.startswith('-encoding'):
+ return
+
+ extra_args.extend(['-encoding', 'UTF-8'])
+
+def _set_auto_value(kwargs):
+ apk = 'annotation_processors'
+ if apk not in kwargs:
+ kwargs[apk] = []
+ aps = kwargs.get(apk, [])
+
+ apdk = 'annotation_processor_deps'
+ if apdk not in kwargs:
+ kwargs[apdk] = []
+ apds = kwargs.get(apdk, [])
+
+ if AUTO_VALUE_DEP in kwargs.get('deps', []):
+ aps.extend(AUTO_VALUE_PROCESSORS)
+ apds.extend(AUTO_VALUE_PROCESSOR_DEPS)
+
+
def genantlr(
name,
srcs,
@@ -38,6 +89,11 @@ def gwt_module(gwt_xml=None, **kwargs):
kw['resources'] += [gwt_xml]
if 'srcs' in kw:
kw['resources'] += kw['srcs']
+
+ # Buck does not accept duplicate resources. Callers may have
+ # included gwt_xml or srcs as part of resources, so de-dupe.
+ kw['resources'] = list(set(kw['resources']))
+
java_library(**kw)
def gerrit_extension(
@@ -73,7 +129,7 @@ def gerrit_plugin(
type = 'plugin',
visibility = ['PUBLIC']):
from multiprocessing import cpu_count
- mf_cmd = 'v=$(git describe HEAD);'
+ mf_cmd = 'v=\$(git describe HEAD);'
if manifest_file:
mf_src = [manifest_file]
mf_cmd += 'sed "s:@VERSION@:$v:g" $SRCS >$OUT'
@@ -92,20 +148,29 @@ def gerrit_plugin(
srcs = mf_src,
out = 'MANIFEST.MF',
)
- gwt_deps = []
static_jars = []
if gwt_module:
- gwt_deps = GWT_PLUGIN_DEPS
static_jars = [':%s-static-jar' % name]
java_library(
name = name + '__plugin',
srcs = srcs,
resources = resources,
deps = deps,
- provided_deps = ['//gerrit-%s-api:lib' % type] + provided_deps + gwt_deps,
+ provided_deps = ['//gerrit-%s-api:lib' % type] +
+ provided_deps +
+ GWT_PLUGIN_DEPS,
visibility = ['PUBLIC'],
)
if gwt_module:
+ java_library(
+ name = name + '__gwt_module',
+ srcs = [],
+ resources = list(set(srcs + resources)),
+ deps = deps,
+ provided_deps = ['//lib/gwt:dev'] +
+ GWT_PLUGIN_DEPS,
+ visibility = ['PUBLIC'],
+ )
prebuilt_jar(
name = '%s-static-jar' % name,
binary_jar = ':%s-static' % name,
@@ -122,8 +187,8 @@ def gerrit_plugin(
gwt_binary(
name = name + '__gwt_application',
modules = [gwt_module],
- deps = gwt_deps,
- module_deps = [':%s__plugin' % name],
+ deps = GWT_PLUGIN_DEPS,
+ module_deps = [':%s__gwt_module' % name],
local_workers = cpu_count(),
strict = True,
experimental_args = GWT_COMPILER_ARGS,
@@ -139,52 +204,3 @@ def gerrit_plugin(
] + static_jars,
visibility = visibility,
)
-
-def java_sources(
- name,
- srcs,
- visibility = []
- ):
- java_library(
- name = name,
- resources = srcs,
- visibility = visibility,
- )
-
-def java_doc(
- name,
- title,
- pkg,
- paths,
- srcs = [],
- deps = [],
- visibility = [],
- do_it_wrong = False,
- ):
- if do_it_wrong:
- sourcepath = paths
- else:
- sourcepath = ['$SRCDIR/' + n for n in paths]
- genrule(
- name = name,
- cmd = ' '.join([
- 'while ! test -f .buckconfig; do cd ..; done;',
- 'javadoc',
- '-quiet',
- '-protected',
- '-encoding UTF-8',
- '-charset UTF-8',
- '-notimestamp',
- '-windowtitle "' + title + '"',
- '-link http://docs.oracle.com/javase/7/docs/api',
- '-subpackages ' + pkg,
- '-sourcepath ',
- ':'.join(sourcepath),
- ' -classpath ',
- ':'.join(['$(location %s)' % n for n in deps]),
- '-d $TMP',
- ]) + ';jar cf $OUT -C $TMP .',
- srcs = srcs,
- out = name + '.jar',
- visibility = visibility,
-)
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index 4e76b02941..865c9d72c0 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -11,14 +11,16 @@ java_library(
'//gerrit-main:main_lib',
'//gerrit-patch-jgit:jgit_patch_tests',
'//gerrit-plugin-gwtui:gwtui-api-lib',
+ '//gerrit-reviewdb:client_tests',
'//gerrit-server:server',
'//gerrit-server:server_tests',
'//lib/asciidoctor:asciidoc_lib',
'//lib/asciidoctor:doc_indexer_lib',
+ '//lib/auto:auto-value',
'//lib/bouncycastle:bcprov',
'//lib/bouncycastle:bcpg',
'//lib/bouncycastle:bcpkix',
- '//lib/jetty:webapp',
+ '//lib/jetty:servlets',
'//lib/prolog:compiler_lib',
'//Documentation:index_lib',
] + scan_plugins(),
diff --git a/tools/eclipse/gerrit_gwt_debug.launch b/tools/eclipse/gerrit_gwt_debug.launch
index c09997fe88..35332111ba 100644
--- a/tools/eclipse/gerrit_gwt_debug.launch
+++ b/tools/eclipse/gerrit_gwt_debug.launch
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/buck-out/gen/lib/gwt/dev/gwt-dev-2.6.1.jar"/>
+<listEntry value="/gerrit/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritGwtDebugLauncher.java"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
@@ -10,8 +10,13 @@
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /&#10;-war ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/war&#10;-server com.google.gerrit.gwtdebug.GerritDebugLauncher&#10;com.google.gerrit.GerritGwtUI"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7&quot; javaProject=&quot;gerrit&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;gerrit&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gerrit.gwtdebug.GerritGwtDebugLauncher"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noprecompile -src ${resource_loc:/gerrit} -workDir ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui com.google.gerrit.GerritGwtUI -- --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../gerrit_testsite"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;-XX:MaxPermSize=128M&#10;-Dgwt.persistentunitcachedir=${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/unit_cache&#10;-Dgerrit.source_root=${resource_loc:/gerrit}&#10;-Dgerrit.site_path=${resource_loc:/gerrit}/../gerrit_testsite&#10;-da:com.google.gwtexpui.globalkey.client.KeyCommandSet"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1024M&#10;-XX:MaxPermSize=256M&#10;-Dgerrit.disable-gwtui-recompile=true"/>
</launchConfiguration>
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 200831689f..dd6f2483a9 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -37,15 +37,28 @@ while not path.exists(path.join(ROOT, '.buckconfig')):
opts = OptionParser()
opts.add_option('--src', action='store_true')
+opts.add_option('--plugins', help='create eclipse projects for plugins',
+ action='store_true')
args, _ = opts.parse_args()
-def gen_project():
- p = path.join(ROOT, '.project')
+def _query_classpath(targets):
+ deps = []
+ p = Popen(['buck', 'audit', 'classpath'] + targets, stdout=PIPE)
+ for line in p.stdout:
+ deps.append(line.strip())
+ s = p.wait()
+ if s != 0:
+ exit(s)
+ return deps
+
+
+def gen_project(name='gerrit', root=ROOT):
+ p = path.join(root, '.project')
with open(p, 'w') as fd:
print("""\
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
- <name>gerrit</name>
+ <name>""" + name + """</name>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
@@ -57,22 +70,30 @@ def gen_project():
</projectDescription>\
""", file=fd)
-def gen_classpath():
- def query_classpath(targets):
- deps = []
- p = Popen(['buck', 'audit', 'classpath'] + targets, stdout=PIPE)
- for line in p.stdout:
- deps.append(line.strip())
- s = p.wait()
- if s != 0:
- exit(s)
- return deps
+def gen_plugin_classpath(root):
+ p = path.join(root, '.classpath')
+ with open(p, 'w') as fd:
+ if path.exists(path.join(root, 'src', 'test', 'java')):
+ testpath = """
+ <classpathentry kind="src" path="src/test/java"\
+ out="buck-out/eclipse/test"/>"""
+ else:
+ testpath = ""
+ print("""\
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>%(testpath)s
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/gerrit"/>
+ <classpathentry kind="output" path="buck-out/eclipse/classes"/>
+</classpath>""" % {"testpath": testpath}, file=fd)
+def gen_classpath():
def make_classpath():
impl = minidom.getDOMImplementation()
return impl.createDocument(None, 'classpath', None)
- def classpathentry(kind, path, src=None, out=None):
+ def classpathentry(kind, path, src=None, out=None, exported=None):
e = doc.createElement('classpathentry')
e.setAttribute('kind', kind)
e.setAttribute('path', path)
@@ -80,6 +101,8 @@ def gen_classpath():
e.setAttribute('sourcepath', src)
if out:
e.setAttribute('output', out)
+ if exported:
+ e.setAttribute('exported', 'true')
doc.documentElement.appendChild(e)
doc = make_classpath()
@@ -87,9 +110,10 @@ def gen_classpath():
lib = set()
gwt_src = set()
gwt_lib = set()
+ plugins = set()
java_library = re.compile(r'[^/]+/gen/(.*)/lib__[^/]+__output/[^/]+[.]jar$')
- for p in query_classpath(MAIN):
+ for p in _query_classpath(MAIN):
if p.endswith('-src.jar'):
# gwt_module() depends on -src.jar for Java to JavaScript compiles.
gwt_lib.add(p)
@@ -108,7 +132,7 @@ def gen_classpath():
else:
lib.add(p)
- for p in query_classpath(GWT):
+ for p in _query_classpath(GWT):
m = java_library.match(p)
if m:
gwt_src.add(m.group(1))
@@ -119,6 +143,9 @@ def gen_classpath():
if s.startswith('lib/'):
out = 'buck-out/eclipse/lib'
elif s.startswith('plugins/'):
+ if args.plugins:
+ plugins.add(s)
+ continue
out = 'buck-out/eclipse/' + s
p = path.join(s, 'java')
@@ -138,15 +165,17 @@ def gen_classpath():
if path.exists(p):
classpathentry('src', p, out=o)
- for libs in [lib, gwt_lib]:
+ for libs in [gwt_lib, lib]:
for j in sorted(libs):
s = None
if j.endswith('.jar'):
s = j[:-4] + '-src.jar'
if not path.exists(s):
s = None
- classpathentry('lib', j, s)
-
+ if args.plugins:
+ classpathentry('lib', j, s, exported=True)
+ else:
+ classpathentry('lib', j, s)
for s in sorted(gwt_src):
p = path.join(ROOT, s, 'src', 'main', 'java')
classpathentry('lib', p, out='buck-out/eclipse/gwtsrc')
@@ -158,6 +187,30 @@ def gen_classpath():
with open(p, 'w') as fd:
doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')
+ if args.plugins:
+ for plugin in plugins:
+ plugindir = path.join(ROOT, plugin)
+ try:
+ gen_project(plugin.replace('plugins/', ""), plugindir)
+ gen_plugin_classpath(plugindir)
+ except (IOError, OSError) as err:
+ print('error generating project for %s: %s' % (plugin, err),
+ file=sys.stderr)
+
+def gen_factorypath():
+ doc = minidom.getDOMImplementation().createDocument(None, 'factorypath', None)
+ for jar in _query_classpath(['//lib/auto:auto-value']):
+ e = doc.createElement('factorypathentry')
+ e.setAttribute('kind', 'EXTJAR')
+ e.setAttribute('id', path.join(ROOT, jar))
+ e.setAttribute('enabled', 'true')
+ e.setAttribute('runInBatchMode', 'false')
+ doc.documentElement.appendChild(e)
+
+ p = path.join(ROOT, '.factorypath')
+ with open(p, 'w') as fd:
+ doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')
+
try:
if args.src:
try:
@@ -167,6 +220,7 @@ try:
gen_project()
gen_classpath()
+ gen_factorypath()
try:
targets = ['//tools:buck.properties'] + MAIN + GWT
diff --git a/tools/gerrit.importorder b/tools/gerrit.importorder
new file mode 100644
index 0000000000..831c5fe163
--- /dev/null
+++ b/tools/gerrit.importorder
@@ -0,0 +1,9 @@
+#Organize Import Order
+#Wed Jan 14 10:19:45 JST 2015
+6=javax
+5=java
+4=org
+3=net
+2=junit
+1=com
+0=com.google
diff --git a/tools/gwt-constants.defs b/tools/gwt-constants.defs
index cc09d3e9cd..a406aa8070 100644
--- a/tools/gwt-constants.defs
+++ b/tools/gwt-constants.defs
@@ -5,7 +5,14 @@ GWT_COMPILER_ARGS = [
'-XdisableCastChecking',
]
-GWT_PLUGIN_DEPS = [
+GWT_COMMON_DEPS = [
+ '//lib/ow2:ow2-asm',
+ '//lib/ow2:ow2-asm-analysis',
+ '//lib/ow2:ow2-asm-util',
+ '//lib/ow2:ow2-asm-tree',
+]
+
+GWT_PLUGIN_DEPS = GWT_COMMON_DEPS + [
'//gerrit-plugin-gwtui:gwtui-api-lib',
'//lib/gwt:user',
]
diff --git a/tools/java_doc.defs b/tools/java_doc.defs
new file mode 100644
index 0000000000..514a730a85
--- /dev/null
+++ b/tools/java_doc.defs
@@ -0,0 +1,38 @@
+def java_doc(
+ name,
+ title,
+ pkgs,
+ paths,
+ srcs = [],
+ deps = [],
+ visibility = [],
+ do_it_wrong = False,
+ ):
+ if do_it_wrong:
+ sourcepath = paths
+ else:
+ sourcepath = ['$SRCDIR/' + n for n in paths]
+ genrule(
+ name = name,
+ cmd = ' '.join([
+ 'while ! test -f .buckconfig; do cd ..; done;',
+ 'javadoc',
+ '-quiet',
+ '-protected',
+ '-encoding UTF-8',
+ '-charset UTF-8',
+ '-notimestamp',
+ '-windowtitle "' + title + '"',
+ '-link http://docs.oracle.com/javase/7/docs/api',
+ '-subpackages ',
+ ':'.join(pkgs),
+ '-sourcepath ',
+ ':'.join(sourcepath),
+ ' -classpath ',
+ ':'.join(['$(location %s)' % n for n in deps]),
+ '-d $TMP',
+ ]) + ';jar cf $OUT -C $TMP .',
+ srcs = srcs,
+ out = name + '.jar',
+ visibility = visibility,
+)
diff --git a/tools/java_sources.defs b/tools/java_sources.defs
new file mode 100644
index 0000000000..0b3974ec7a
--- /dev/null
+++ b/tools/java_sources.defs
@@ -0,0 +1,10 @@
+def java_sources(
+ name,
+ srcs,
+ visibility = []
+ ):
+ java_library(
+ name = name,
+ resources = srcs,
+ visibility = visibility,
+ )
diff --git a/tools/pack_war.py b/tools/pack_war.py
index ba39856ebf..7e7d89552a 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -15,7 +15,7 @@
from __future__ import print_function
from optparse import OptionParser
-from os import getcwd, chdir, makedirs, path, symlink
+from os import chdir, makedirs, path, symlink
from subprocess import check_call, check_output
import sys
diff --git a/tools/util.py b/tools/util.py
index f3cc8ce62d..ec895dddfa 100644
--- a/tools/util.py
+++ b/tools/util.py
@@ -15,7 +15,6 @@
from os import path
REPO_ROOTS = {
- 'ATLASSIAN': 'https://maven.atlassian.com/content/repositories/atlassian-3rdparty',
'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
'GERRIT_API': 'https://gerrit-api.commondatastorage.googleapis.com/release',
'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2',
diff --git a/tools/version.py b/tools/version.py
index a994bd8539..28f6b65ec3 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -19,6 +19,12 @@ import os.path
import re
import sys
+version_text = """# Maven style API version (e.g. '2.x-SNAPSHOT').
+# Used by :api_install and :api_deploy targets
+# when talking to the destination repository.
+#
+GERRIT_VERSION = '%s'
+"""
parser = OptionParser()
opts, args = parser.parse_args()
@@ -49,3 +55,9 @@ for project in ['gerrit-extension-api', 'gerrit-plugin-api',
outfile.write(outxml)
except IOError as err:
print('error updating %s: %s' % (pom, err), file=sys.stderr)
+
+try:
+ with open('VERSION', "w") as version_file:
+ version_file.write(version_text % new_version)
+except IOError as err:
+ print('error updating VERSION: %s' % err, file=sys.stderr)
diff --git a/website/releases/index.html b/website/releases/index.html
index b8d7905591..8aedc6b009 100644
--- a/website/releases/index.html
+++ b/website/releases/index.html
@@ -39,7 +39,7 @@
<script>
$.getJSON(
-'https://www.googleapis.com/storage/v1beta2/b/gerrit-releases/o?projection=noAcl&fields=items(name%2Csize)&callback=?',
+'https://www.googleapis.com/storage/v1/b/gerrit-releases/o?projection=noAcl&fields=items(name%2Csize)&callback=?',
function(data) {
var doc = document;
var frg = doc.createDocumentFragment();