summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/Makefile6
-rw-r--r--Documentation/access-control.txt16
-rw-r--r--Documentation/cmd-index.txt3
-rw-r--r--Documentation/cmd-query.txt110
-rw-r--r--Documentation/cmd-receive-pack.txt5
-rw-r--r--Documentation/cmd-stream-events.txt64
-rw-r--r--Documentation/config-gerrit.txt9
-rw-r--r--Documentation/index.txt1
-rw-r--r--Documentation/json.txt118
-rw-r--r--Documentation/licenses.txt39
-rw-r--r--Documentation/user-search.txt362
-rw-r--r--Documentation/user-upload.txt10
-rw-r--r--ReleaseNotes/ReleaseNotes-2.1.4.txt209
-rw-r--r--ReleaseNotes/index.txt1
-rwxr-xr-xcontrib/check-valid-commit.py104
-rw-r--r--gerrit-common/pom.xml2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java21
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java4
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java10
-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/ChangeListService.java47
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java3
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java42
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScriptSettings.java80
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java24
-rw-r--r--gerrit-gwtdebug/pom.xml6
-rw-r--r--gerrit-gwtui/pom.xml3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java29
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java77
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java146
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java22
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java19
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java12
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties14
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java8
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java12
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java32
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java14
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties18
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountSettings.java181
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java22
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AgreementPanel.java)21
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyContactInformationScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/MineDraftsScreen.java)29
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsPanel.java)24
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdPanel.java)24
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java176
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/PreferencePanel.java)55
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java78
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MySshKeysScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/MineStarredScreen.java)29
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ProjectWatchPanel.java)155
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java34
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java43
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java19
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java183
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java297
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ValidateEmailScreen.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java)57
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java119
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java)85
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java)57
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java39
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java31
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllAbandonedChangesScreen.java44
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllMergedChangesScreen.java44
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllOpenChangesScreen.java42
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectAbandonedChangesScreen.java47
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectMergedChangesScreen.java47
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectOpenChangesScreen.java44
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties11
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java21
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java25
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java65
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java54
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllSingleListScreen.java)4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java)110
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java235
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java160
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java (renamed from gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeQueryResultsScreen.java)39
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css39
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java105
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java165
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java185
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml62
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/BranchLink.java84
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java12
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java61
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java22
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLink.java19
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java9
-rw-r--r--gerrit-httpd/pom.xml2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java112
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java3
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java23
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java20
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java26
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java6
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java409
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java10
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java65
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java11
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java6
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java29
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java18
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java42
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java9
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java12
-rw-r--r--gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java6
-rw-r--r--gerrit-launcher/pom.xml2
-rw-r--r--gerrit-main/pom.xml2
-rw-r--r--gerrit-patch-commonsnet/pom.xml2
-rw-r--r--gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java45
-rw-r--r--gerrit-patch-jgit/pom.xml2
-rw-r--r--gerrit-pgm/pom.xml2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java8
-rw-r--r--gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java6
-rw-r--r--gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java6
-rw-r--r--gerrit-prettify/pom.xml8
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/PrettyFormatter.gwt.xml12
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/ClientSideFormatter.java43
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImpl.java67
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImplIE6.java46
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java34
-rw-r--r--gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettySettings.java106
-rw-r--r--gerrit-reviewdb/pom.xml2
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java4
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java188
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreferenceAccess.java26
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGeneralPreferences.java37
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java40
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatchAccess.java13
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java12
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java3
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java3
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java3
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java8
-rw-r--r--gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql14
-rw-r--r--gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql17
-rw-r--r--gerrit-server/.settings/org.eclipse.jdt.core.prefs12
-rw-r--r--gerrit-server/pom.xml12
-rw-r--r--gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g32
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java165
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java46
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java99
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java209
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java63
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java206
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java316
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java409
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java26
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java40
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/PopulatingCache.java (renamed from gerrit-server/src/main/java/com/google/gerrit/server/cache/SelfPopulatingCache.java)60
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java4
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java16
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ApprovalAttribute.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java23
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java39
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java194
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java26
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/TrackingIdAttribute.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java140
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java79
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java445
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java56
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java75
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java39
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java486
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java30
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java36
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java757
-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/PublishComments.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java84
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java215
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java76
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/ChangeQueryBuilder.java79
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java50
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java14
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java76
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java70
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java172
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java518
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java68
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java96
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java59
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java149
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java47
-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.java189
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java130
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java69
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java453
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java601
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java126
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java77
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java81
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java68
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java73
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java121
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java172
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java68
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java78
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java48
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java24
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java51
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java330
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java55
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java52
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java55
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java47
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java44
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java77
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java51
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java67
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java70
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java138
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/ChangeQueryBuilderTest.java210
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java36
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java64
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java138
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java8
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java8
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java17
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java (renamed from gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java)10
-rw-r--r--gerrit-sshd/pom.xml2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java116
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java9
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java13
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java8
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java123
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java3
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java1
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java71
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java6
-rwxr-xr-xgerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java2
-rw-r--r--gerrit-util-cli/pom.xml2
-rw-r--r--gerrit-util-ssl/pom.xml2
-rw-r--r--gerrit-war/pom.xml3
-rw-r--r--pom.xml44
-rw-r--r--tools/gwtui_dbg.launch11
302 files changed, 13032 insertions, 4975 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 18a92b4dbf..72e3f8689c 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -32,6 +32,7 @@ LOCAL_ROOT := .published
SCRIPTSDIR := $(shell pwd)/javascript
COMMIT := $(shell git describe HEAD | sed s/^v//)
PUB_DIR := $(PUB_ROOT)/$(VERSION)
+PRIOR = PRIOR
ifeq ($(VERSION),)
REVISION = $(COMMIT)
@@ -52,9 +53,8 @@ endif
@-rm -rf $(LOCAL_ROOT)
@echo "Checking out current $(VERSION)"
@if ! $(SVN) checkout $(PUB_DIR) $(LOCAL_ROOT) 2>/dev/null ; then \
- p=$$(git describe HEAD^ | perl -lne 'print $$1 if /^v(\d+\.\d+(?:\.\d+)?)/') && \
- echo "Copying $$p to $(VERSION) ..." && \
- $(SVN) cp -m "Create $(VERSION) documentation" $(PUB_ROOT)/$$p $(PUB_DIR) && \
+ echo "Copying $(PRIOR) to $(VERSION) ..." && \
+ $(SVN) cp -m "Create $(VERSION) documentation" $(PUB_ROOT)/$(PRIOR) $(PUB_DIR) && \
$(SVN) checkout $(PUB_DIR) $(LOCAL_ROOT) ; \
fi
@rm -f $(LOCAL_ROOT)/*.html
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 66b4082baa..b45853d696 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -135,10 +135,22 @@ 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
+by prefixing the reference name with `\^`. For example
+`\^refs/heads/[a-z]\{1,8\}` matches all lower case branch names
+between 1 and 8 characters long. Within a regular expression `.`
+is a wildcard matching any character, but may be escaped as `\.`.
+
+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
+the user 'joe' to use 'refs/heads/sandbox/joe/foo'.
+
When evaluating a reference-level access right, Gerrit will use
the full set of access rights to determine if the user
is allowed to perform a given action. For example, if a user is a
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 7fbf31b402..e213222048 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -66,6 +66,9 @@ link:cmd-review.html[gerrit approve]::
link:cmd-ls-projects.html[gerrit ls-projects]::
List projects visible to the caller.
+link:cmd-query.html[gerrit query]::
+ Query the change database.
+
link:cmd-review.html[gerrit review]::
Verify, approve and/or submit a patch set from the command line.
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
new file mode 100644
index 0000000000..76a9b7dc34
--- /dev/null
+++ b/Documentation/cmd-query.txt
@@ -0,0 +1,110 @@
+gerrit query
+============
+
+NAME
+----
+gerrit query - Query the change database
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit query' \
+[\--format {TEXT | JSON}] \
+[\--current-patch-set] \
+[\--patch-sets] \
+[\--] \
+<query> \
+[limit:<n>] \
+[resume\_sortkey:<sortKey>]
+
+DESCRIPTION
+-----------
+
+Queries the change database and returns results describing changes
+that match the input query. More recently updated changes appear
+before older changes, which is the same order presented in the
+web interface.
+
+A query may be limited on the number of results it returns with the
+'limit:' operator. If no limit is supplied an internal default
+limit is used to prevent explosion of the result set. To obtain
+results beyond the limit, the 'resume_sortkey:' operator can be used
+to resume the query at the change that follows the last change of
+the prior result set.
+
+Non-option arguments to this command are joined with spaces and then
+parsed as a query. This simplifies calling conventions over SSH
+by permitting operators to appear in different arguments without
+multiple levels of quoting required.
+
+OPTIONS
+-------
+\--current-patch-set::
+ Include information about the current patch set in the results.
+
+\--patch-sets::
+ Include information about all patch sets. If combined with
+ the \--current-patch-set flag then the current patch set
+ information will be output twice, once in each field.
+
+limit:<n>::
+ Maximum number of results to return. This is actually a
+ query operator, and not a command line option. If more
+ than one limit: operator is provided, the smallest limit
+ will be used to cut the result set.
+
+resume\_sortkey:<sortKey>::
+ Resume results from this sort key. Callers should pass
+ the sortKey of the last change of the prior result set to
+ resume a prior query. This is actually a query operator,
+ and not a command line option.
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+EXAMPLES
+--------
+
+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", ...}
+ {"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
+-----
+
+Resume the same query and obtain the final results:
+-----
+ $ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2 resume_sortkey:000e6aee00003e26
+ {"project":"tools/gerrit", ...}
+ {"project":"tools/gerrit", ...}
+ {"type":"stats","rowCount":1,"runningTimeMilliseconds:15}
+-----
+
+
+SCHEMA
+------
+The JSON messages consist of nested objects referencing the
+link:json.html#change[change],
+link:json.html#patchset[patchset],
+link:json.html#[account]
+involved, and other attributes as appropriate.
+
+Note that any field may be missing in the JSON messages, so consumers
+of this JSON stream should deal with that appropriately.
+
+SEE ALSO
+--------
+
+* link:user-search.html[Query Operators]
+* link:json.html[JSON Data Formats]
+* link:access-control.html[Access Controls]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-receive-pack.txt b/Documentation/cmd-receive-pack.txt
index 5a5ed6da06..ca965508dd 100644
--- a/Documentation/cmd-receive-pack.txt
+++ b/Documentation/cmd-receive-pack.txt
@@ -64,6 +64,11 @@ Send a review for a change on the master branch to charlie@example.com:
git push --receive-pack='git receive-pack --reviewer charlie@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master
=====
+Send reviews, but tagging them with the topic name 'bug42':
+=====
+ git push --receive-pack='git receive-pack --reviewer charlie@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master/bug42
+=====
+
Also CC two other parties:
=====
git push --receive-pack='git receive-pack --reviewer charlie@example.com --cc alice@example.com --cc bob@example.com' ssh://review.example.com:29418/project HEAD:refs/for/master
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 2d68d7295f..0b536fc476 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -54,86 +54,48 @@ Patchset Added
^^^^^^^^^^^^^^
type:: "patchset-added"
-change:: <<change,change attribute>>
+change:: link:json.html#change[change attribute]
-patchset:: <<patchset,patchset attribute>>
+patchset:: link:json.html#patchset[patchset attribute]
-uploader:: <<account,account attribute>>
+uploader:: link:json.html#account[account attribute]
Change Abandoned
^^^^^^^^^^^^^^^^
type:: "change-abandoned"
-change:: <<change,change attribute>>
+change:: link:json.html#change[change attribute]
-patchset:: <<patchset,patchset attribute>>
+patchset:: link:json.html#patchset[patchset attribute]
-abandoner:: <<account,account attribute>>
+abandoner:: link:json.html#account[account attribute]
Change Merged
^^^^^^^^^^^^^
type:: "change-merged"
-change:: <<change,change attribute>>
+change:: link:json.html#change[change attribute]
-patchset:: <<patchset,patchset attribute>>
+patchset:: link:json.html#patchset[patchset attribute]
-submitter:: <<account,account attribute>>
+submitter:: link:json.html#account[account attribute]
Comment Added
^^^^^^^^^^^^^
type:: "comment-added"
-change:: <<change,change attribute>>
+change:: link:json.html#change[change attribute]
-patchset:: <<patchset,patchset attribute>>
+patchset:: link:json.html#patchset[patchset attribute]
-author:: <<account,account attribute>>
+author:: link:json.html#account[account attribute]
comment:: Comment text author had written
-Attributes
-~~~~~~~~~~
-Attributes are part events to give context related to the event.
-
-[[change]]
-change:: The Gerrit change the event is related to
-
- project;; Project path in Gerrit
-
- branch;; Branch name within project
-
- id;; Change identifier
-
- number;; Change number (deprecated)
-
- subject;; Description of change
-
- owner;; Owner in account attribute
-
- url;; Canonical URL to reach this change
-
-[[account]]
-account:: An account that is related to an event or attribute
-
- name;; Account user's full name
-
- email;; Account user's preferred email
-
-[[patchset]]
-patchset:: Refers to a specific patchset within a change
-
- number;; The patchset number
-
- revision;; Git commit-ish for this patchset
-
- ref;; Git reference pointing at revision
-
- uploader;; Uploader of patch set in account attribute
-
SEE ALSO
--------
+* link:json.html[JSON Data Formats]
* link:access-control.html[Access Controls]
GERRIT
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 814d32ec20..7384a87495 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -250,7 +250,6 @@ except when the cache is full.
Default is `90 days` for most caches, except:
+
* `"ldap_groups"`: default is `1 hour`
-* `"openid"`: default is `5 minutes`
* `"web_sessions"`: default is `12 hours`
[[cache.name.memoryLimit]]cache.<name>.memoryLimit::
@@ -261,7 +260,6 @@ this is total number of items, not bytes of heap used.
Default is 1024 for most caches, except:
+
* `"diff"`: default is `128`
-* `"openid"`: default is `64`
[[cache.name.diskLimit]]cache.<name>.diskLimit::
+
@@ -345,13 +343,6 @@ Caches a mapping of LDAP username to Gerrit account identity. The
cache automatically updates when a user first creates their account
within Gerrit, so the cache expire time is largely irrelevant.
-cache `"openid"`::
-+
-If OpenID authentication is enabled, caches the OpenID discovery
-response by URL, for up to 5 minutes. This can reduce the time
-required for OpenID authentication through very common providers,
-such as Google Accounts.
-
cache `"projects"`::
+
Caches the project description records, from the `projects` table
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 856c974eef..1e13e8d8f5 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -5,6 +5,7 @@ User Guide
----------
* link:http://source.android.com/submit-patches/workflow[Default Workflow]
+* link:user-search.html[Searching Changes]
* link:cmd-index.html[Command Line Tools]
* link:pgm-index.html[Server Programs]
* link:user-upload.html[Uploading Changes]
diff --git a/Documentation/json.txt b/Documentation/json.txt
new file mode 100644
index 0000000000..1c9a808dbe
--- /dev/null
+++ b/Documentation/json.txt
@@ -0,0 +1,118 @@
+Gerrit Code Review - JSON Data
+==============================
+
+Some commands produce JSON data streams intended for other
+applications to consume. The structures are documented below.
+Note that any field may be missing in the JSON messages, so consumers
+of this JSON stream should deal with that appropriately.
+
+[[change]]
+change
+------
+The Gerrit change being reviewed, or that was already reviewed.
+
+project:: Project path in Gerrit
+
+branch:: Branch name within project
+
+topic:: Topic name specified by the uploader for this change series
+
+id:: Change identifier, as scraped out of the Change-Id field in
+the commit message, or as assigned by the server if it was missing.
+
+number:: Change number (deprecated)
+
+subject:: Description of change
+
+owner:: Owner in <<account,account attribute>>
+
+url:: Canonical URL to reach this change
+
+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.
+
+ NEW;; Change is still being reviewed.
+
+ SUBMITTED;; Change has been submitted and is in the merge queue.
+ It may be waiting for one or more dependencies.
+
+ MERGED;; Change has been merged to its branch.
+
+ ABANDONED;; Change was abandoned by its owner or administrator.
+
+trackingIds:: Issue tracking system links in
+<<trackingid,trackingid attribute>>, scraped out of the commit
+message based on the server's
+link:config-gerrit.html#trackingid[trackingid] sections.
+
+currentPatchSet:: Current <<patchset,patchset attribute>>.
+
+patchSets:: All <<patchset,patchset attribute>> for this change.
+
+[[trackingid]]
+trackingid
+----------
+A link to an issue tracking system.
+
+system:: Name of the system. This comes straight from the
+gerrit.config file.
+
+id:: Id number as scraped out of the commit message.
+
+[[account]]
+account
+-------
+A user account.
+
+name:: User's full name, if configured.
+
+email:: User's preferred email address.
+
+[[patchset]]
+patchset
+--------
+Refers to a specific patchset within a <<change,change>>.
+
+number:: The patchset number.
+
+revision:: Git commit for this patchset.
+
+ref:: Git reference pointing at the revision. This reference is
+available through the Gerrit Code Review server's Git interface
+for the containing change.
+
+uploader:: Uploader of the patch set in <<account,account attribute>>.
+
+approvals:: The <<approval,approval attribute>> granted.
+
+[[approval]]
+approval
+--------
+Records the code review approval granted to a patch set.
+
+type:: Internal name of the approval given.
+
+description:: Human readable category of the approval.
+
+value:: Value assigned by the approval, usually a numerical score.
+
+grantedOn:: Time in seconds since the UNIX epoch when this approval
+was added or last updated.
+
+by:: Reviewer of the patch set in <<account,account attribute>>.
+
+SEE ALSO
+--------
+
+* link:cmd-stream-events.html[gerrit stream-events]
+* link:cmd-query.html[gerrit query]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 4427f87cdc..2c123f8841 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -22,6 +22,7 @@ Guice <<apache2,Apache License 2.0>>
Apache Commons Codec <<apache2,Apache License 2.0>>
Apache Commons DBCP <<apache2,Apache License 2.0>>
Apache Commons Http Client <<apache2,Apache License 2.0>>
+Apache Commons Lang <<apache2,Apache License 2.0>>
Apache Commons Logging <<apache2,Apache License 2.0>>
Apache Commons Net <<apache2,Apache License 2.0>>
Apache Commons Pool <<apache2,Apache License 2.0>>
@@ -48,6 +49,7 @@ Clippy <<clippy,MIT License>>
juniversalchardet <<mpl1_1,MPL 1.1>>
AOP Alliance Public Domain
JSR 305 <<jsr305,New-Style BSD>>
+dk.brics.automaton <<automaton,New-Style BSD>>
-----------------------------------------------------------
Cryptography Notice
@@ -560,6 +562,43 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
----
+[[automaton]]
+dk.brics.automaton - New Style BSD
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* link:http://www.brics.dk/automaton/index.html
+
+----
+Copyright (c) 2007-2009, dk.brics.automaton
+All rights reserved.
+
+http://www.opensource.org/licenses/bsd-license.php
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the JSR305 expert group nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+----
+
[[args4j]]
args4j - MIT License
~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
new file mode 100644
index 0000000000..c62e894017
--- /dev/null
+++ b/Documentation/user-search.txt
@@ -0,0 +1,362 @@
+Gerrit Code Review - Searching Changes
+======================================
+
+Default Searches
+----------------
+
+Most basic searches can be viewed by clicking on a link along the top
+menu bar. The link will prefill the search box with a common search
+query, execute it, and present the results. If exactly one change
+matches the search, the change will be presented instead of a list.
+
+
+[grid="all"]
+`---------------------------`------------------------------
+Description Default Query
+-----------------------------------------------------------
+All > Open status:open '(or is:open)'
+All > Merged status:merged
+All > Abandoned status:abandoned
+My > Dafts has:draft
+My > Watched Changes status:open is:watched
+My > Starred Changes is:starred
+Open changes in Foo status:open project:Foo
+-----------------------------------------------------------
+
+Basic Change Search
+-------------------
+
+Similar to many popular search engines on the web, just enter some
+text and let Gerrit figure out the meaning:
+
+[grid="all"]
+`---------------------------------`------------------------------
+Description Examples
+-----------------------------------------------------------------
+Legacy numerical id 15183
+Full or abbreviated Change-Id Ic0ff33
+Full or abbreviated commit SHA-1 d81b32ef
+Email address user@example.com
+Approval requirement CodeReview>=+2, Verified=1
+-----------------------------------------------------------------
+
+
+Search Operators
+----------------
+
+Operators act as restrictions on the search. As more operators
+are added to the same query string, they further restrict the
+returned results.
+
+[[age]]
+age:'AGE'::
++
+Amount of time that has expired since the change was last updated
+with a review comment or new patch set. The age must be specified
+to include a unit suffix, for example `age:2d`:
++
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+* d, day, days
+* w, week, weeks (`1 week` is treated as `7 days`)
+* mon, month, months (`1 month` is treated as `30 days`)
+* y, year, years (`1 year` is treated as `365 days`)
+
+[[change]]
+change:'ID'::
++
+Either a legacy numerical 'ID' such as 15183, or a newer style
+Change-Id that was scraped out of the commit message.
+
+[[owner]]
+owner:'USER'::
++
+Changes originally submitted by 'USER'.
+
+[[reviewer]]
+reviewer:'USER'::
++
+Changes that have been, or need to be, reviewed by 'USER'.
+
+[[commit]]
+commit:'SHA1'::
++
+Changes where 'SHA1' is one of the patch sets of the change.
+
+[[project]]
+project:'PROJECT'::
++
+Changes occuring in 'PROJECT'.
+
+[[branch]]
+branch:'BRANCH'::
++
+Changes for 'BRANCH'. The branch name is the short name shown
+in the web interface, without the traditional 'refs/heads/'
+prefix. This operator is a shorthand for 'refs:'. Searching for
+'branch:master' really means 'ref:refs/heads/master', and searching
+for 'branch:refs/heads/master' is the same as searching for
+'ref:refs/heads/refs/heads/master'.
+
+[[topic]]
+topic:'TOPIC'::
++
+Changes whose designated topic at upload was 'TOPIC'. This is
+often combined with 'branch:' and 'project:' operators to select
+all related changes in a series.
+
+[[ref]]
+ref:'REF'::
++
+Changes where the destination branch is exactly the given 'REF'
+name. Since 'REF' is absolute from the top of the repository it
+must start with 'refs/'.
+
+[[tr]][[bug]]
+tr:'ID', bug:'ID'::
++
+Search for changes whose commit message contains 'ID' and matched
+one or more of the
+link:config-gerrit.html#trackingid[trackingid sections]
+in the server's configuration file. This is typically used to
+search for changes that fix a bug or defect by the issue tracking
+system's issue identifier.
+
+[[label]]
+label:'VALUE'::
++
+Matches changes where the approval score 'VALUE' has been set during
+a review. See <<labels,labels>> below for more detail on the format
+of the argument.
+
+[[file]]
+file:\^'REGEX'::
++
+Matches any change where REGEX matches a file that was affected
+by the change. The regular expression pattern must start with
+'\^'. For example, to match all XML files use `file:^.*\.xml$`.
+Currently this operator is only available on a watched project
+and may not be used in the search bar.
+
+[[has]]
+has:draft::
++
+True if there is a draft comment saved by the current user.
+
+has:star::
++
+Same as 'is:starred', true if the change has been starred by the
+current user.
+
+[[is]]
+is:starred::
++
+Same as 'has:star', true if the change has been starred by the
+current user.
+
+is:watched::
++
+True if this change matches one of the current user's watch filters,
+and thus is likely to notify the user when it updates.
+
+is:reviewed::
++
+True if there is at least one non-zero score on the change, in any
+approval category, by any user.
+
+is:open::
++
+True if the change is other open or submitted, merge pending.
+
+is:closed::
++
+True if the change is either merged or abandoned.
+
+is:submitted, is:merged, is:abandoned::
++
+Same as <<status,status:'STATE'>>.
+
+[[status]]
+status:open::
++
+True if the change state is other 'review in progress' or 'submitted,
+merge pending'.
+
+status:reviewed::
++
+Same as 'is:reviewed', matches if there is at least one non-zero
+score on the change, in any approval category, by any user.
+
+status:submitted::
++
+Change has been submitted, but is waiting for a dependency.
+
+status:closed::
++
+True if the change is either 'merged' or 'abandoned'.
+
+status:merged::
++
+Change has been merged into the branch.
+
+status:abandoned::
++
+Change has been abandoned by the change owner, or administrator.
+
+
+Boolean Operators
+-----------------
+
+Unless otherwise specified, operators are joined using the `AND`
+boolean operator, thereby restricting the search results.
+
+Parentheses can be used to force a particular precendence on complex
+operator expressions, otherwise OR has higher precendence than AND.
+
+Negation
+~~~~~~~~
+Any operator can be negated by prefixing it with `-`, for example
+`-is:starred` is the exact opposite of `is:starred` and will
+therefore return changes that are *not* starred by the current user.
+
+The operator `NOT` (in all caps) is a synonym.
+
+AND
+~~~
+The boolean operator `AND` (in all caps) can be used to join two
+other operators together. This results in a restriction of the
+results, returning only changes that match both operators.
+
+OR
+~~
+The boolean operator `OR` (in all caps) can be used to find changes
+that match either operator. This increases the nubmer of results
+that are returned, as more changes are considered.
+
+
+[[labels]]
+Labels
+------
+Label operators can be used to match approval score given during
+a code review. The specific set of supported labels depends on
+the server configuration, however `CodeReview` and `Verified`
+are the default labels provided out of the box.
+
+A label name is any of the following:
+
+* The category name. If the category name contains spaces,
+ it must be wrapped in double quotes. Example: `label:"Code Review"`.
+
+* The name, without spaces. This avoids needing to use double quotes
+ for the common category Code Review. Example: `label:CodeReview`.
+
+* The internal short name. Example: `label:CRVW`, or `label:VRIF`.
+
+* The one or two character abbreviation shown in the column header
+ of change list pages. Example: `label:R` or `label:V`.
+
+A label name must be followed by a score, or an operator and a score.
+The easiest way to explain these are by example.
+
+`label:CodeReview=2`::
+`label:CodeReview=+2`::
+`label:CodeReview+2`::
++
+Matches changes where there is at least one \+2 score for Code Review.
+The \+ prefix is optional for positive score values. If the + is used,
+the = operator is optional.
+
+`label:CodeReview=-2`::
+`label:CodeReview-2`::
++
+Matches changes where there is at least one -2 score for Code Review.
+Because the negative sign is required, the = operator is optional.
+
+`label:CodeReview=1`::
++
+Matches changes where there is at least one +1 score for Code Review.
+Scores of +2 are not matched, even though they are higher.
+
+`label:CodeReview>=1`::
++
+Matches changes with either a +1, +2, or any higher score.
+
+`label:CodeReview<=-1`::
++
+Matches changes with either a -1, -2, or any lower score.
+
+`is:open CodeReview+2 Verified+1 -Verified-1 -CodeReview-2`::
++
+Matches changes that are ready to be submitted.
+
+`is:open (Verified-1 OR CodeReview-2)`::
++
+Changes that are blocked from submission due to a blocking score.
+
+
+Magical Operators
+-----------------
+
+Most of these operators exist to support features of Gerrit Code
+Review, and are not meant to be accessed by the average end-user.
+However, they are recognized by the query parser, and may prove
+useful in limited contexts to administrators or power-users.
+
+visibleto:'USER-or-GROUP'::
++
+Matches changes that are visible to 'USER' or to anyone who is a
+member of 'GROUP'. Here group names may be specified as either
+an internal group name, or if LDAP is being used, an external LDAP
+group name. The value may be wrapped in double quotes to include
+spaces or other special characters. For example, to match an LDAP
+group: `visibleto:"CN=Developers, DC=example, DC=com"`.
++
+This operator may be useful to test access control rules, however a
+change can only be matched if both the current user and the supplied
+user or group can see it. This is due to the implicit 'is:visible'
+clause that is always added by the server.
+
+is:visible::
++
+Magical internal flag to prove the current user has access to read
+the change. This flag is always added to any query.
+
+starredby:'USER'::
++
+Matches changes that have been started by 'USER'.
+
+watchedby:'USER'::
++
+Matches changes that 'USER' has configured watch filters for.
+
+draftby:'USER'::
++
+Matches changes that 'USER' has left unpublished drafts on.
+Since the drafts are unpublished, it is not possible to see the
+draft text, or even how many drafts there are.
+
+limit:'CNT'::
++
+Limit the returned results to no more than 'CNT' records. This is
+automatically set to the page size configured in the current user's
+preferences. Including it in a web query may lead to unpredictable
+results with regards to pagination.
+
+resume\_sortkey:'KEY'::
++
+Positions the low level scan routine to start from 'KEY' and
+continue through changes from this point. This is most often used
+for paginating result sets. Including this in a web query may lead
+to unpredictable results.
+
+sortkey\_after:'KEY', sortkey\_before:'KEY'::
++
+Restart the low level scan routine from 'KEY'. This is automatically
+set by the pagination system as the user navigates through results
+of a query. Including either value in a web query may lead to
+unpredictable results.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 8034637159..10fddd3c1f 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -116,6 +116,16 @@ 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.
+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
+'driver/i42' will be saved on each change this push creates or
+updates:
+
+====
+ git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/experimental/driver/i42
+====
+
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
diff --git a/ReleaseNotes/ReleaseNotes-2.1.4.txt b/ReleaseNotes/ReleaseNotes-2.1.4.txt
new file mode 100644
index 0000000000..d882d56fe1
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.1.4.txt
@@ -0,0 +1,209 @@
+Release notes for Gerrit 2.1.4
+==============================
+
+Gerrit 2.1.4 is now available in the usual location:
+
+link:http://code.google.com/p/gerrit/downloads/list[http://code.google.com/p/gerrit/downloads/list]
+
+Schema Change
+-------------
+
+*WARNING* This release contains multiple schema changes. To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+New Features
+------------
+
+Change Management
+~~~~~~~~~~~~~~~~~
+
+* issue 504 Implement full query operators
++
+The search box now implments a wide range of operators and boolean
+expressions, permitting complex queries such as `is:open CodeReview>=1
+(has:draft OR is:starred)` to locate open changes that have been code
+reviewed, but still have unpublished drafts or were starred by the
+current user. The full range of supported operators is documented
+in the user guide.
+
+* Change lists now use query operators
++
+All current change lists have been reimplemented using query
+operators, so selecting 'All open changes' actually performs the query
+'is:open'. This is to help end-users learn the different operators
+that are supported, and simplifies the internal implementation
+considerably by removing redundant code.
+
+* issue 51 Tag changes with topic branches
++
+Changes can be tagged with a topic name during upload. To add the tag
+'query' when pushing to branch 'master', use `git push URL
+HEAD:refs/for/master/query`. To add a topic name with `repo upload`
+use the `-t` command line flag. Topic names are displayed next to the
+branch name in the web UI, and can be searched for with the `topic:`
+query operator.
+
+* Filter the list of open changes by watched projects
++
+The query operator `is:watched` matches changes matching the user's
+watched project list, and a new menu item was added under the My menu
+to select open changes matching these watched projects.
+
+Web UI
+~~~~~~
+
+* issue 579 Remember diff formatting preferences
++
+Formatting options at the top of a side-by-side or unified diff page
+are now remembered by saving the current preferences into the user's
+account whenever 'Update' is clicked.
+
+* issue 680 Show commit message on the per-file review pages
+
+* issue 498 Improved keyboard navigation
++
+More keyboard bindings have been added, reducing the need to switch to
+the mouse while navigating through a change and performing a review.
+
+* issue 395 Open new window/new tab for all files in a change
++
+New buttons permit opening all modified files of a change into
+new windows or tabs.
+
+* issue 440 Add copy to clipboard button for change-id
++
+The Change-Id field in the upper left side of a change now support to
+copy "Change-Id: I...." onto the clipboard, making it easier to paste
+into a commit message.
+
+* issue 559 Allow copying user public ssh key to clipboard
+
+Email Notifications
+~~~~~~~~~~~~~~~~~~~
+
+* issue 311 No longer CC a user by default
++
+The user who causes a notification to be sent is no longer CC'd on the
+email when it is sent. This reduces the number of messages sent to a
+user, but can be re-enabled through a checkbox in the Settings >
+Preferences panel.
+
+* issue 535 Enable watching of all projects
++
+Adding the magic `\-- All Projects \--` to the watched project list
+permits the user to be notified of any change occurring in any
+project. Project specific entries override the notification settings
+for all projects.
+
+* issue 492 Allow watching specific branches or any other search query
++
+In addition to watching a project, users can register a query string
+to match specific changes, reducing notifications to be a smaller
+subset of the changes that occur in a project.
+
+* issue 70 Allow file:^regex to match affected files
++
+The file:^path operator can be used in a watch filter to receive
+notifications only when files matching the regular expression are
+modified by the change.
+
+* issue 623 Include Gerrit-Owner, Gerrit-Reviewer in email footers
++
+New fields in the email footer provide additional detail, enabling
+better filtering and classification of messages.
+
+Access Control
+~~~~~~~~~~~~~~
+
+* Support regular expressions for ref access rules
++
+References in an access rule can now be specified by regular
+expression by prefixing the reference name with ^.
+
+* issue 577 Support $\{username\} in access rules
++
+Adding `$\{username\}` into a reference causes the current username to
+be inserted at that position. When combined with the Push Branch
+permission this creates a per-user branch namespace feature, giving
+each user their own "sandbox" to push changes to.
+
+Authentication
+~~~~~~~~~~~~~~
+
+* Remove password authentication over SSH
++
+Adding password authentication over SSH turned out to be a major
+mistake. Users primarily use SSH public keys, and the password
+prompt just got in the way or confused them. Password support has
+been removed from the SSH server.
+
+* Username cannot be changed once assigned
++
+Once a username has been selected for a user account, it
+cannot be modified by the user.
+
+* issue 555 Make LDAP sessions persistent for the session age
++
+Web sessions are now persistent for the cache.web_sessions.maxAge
+setting, rather than expiring when the browser closes. (Previously
+sessions expired when the browser exited.)
+
+Misc.
+~~~~~
+
+* Add topic, lastUpdated, sortKey to ChangeAttribute
++
+Additional change fields are now exported as part of the
+stream-events output.
+
+* issue 504 gerrit query SSH command
++
+Queries to lookup change information can be executed over SSH through
+the `gerrit query` command, with results output in either human
+readable text or machine readable JSON. Change queries can also be
+run over HTTP with the `/query?q=<query>&format=JSON` URL. Both
+interfaces are intended for automated tools.
+
+* Remove git diff-tree dependency
++
+Gerrit no longer requires `git` in the PATH; differences are now
+constructed in pure Java code. Remote repository initialization over
+SSH still requires `git` on the remote host's PATH.
+
+* Internal dependencies updated
++
+Updated JGit to 0.8.4.87-g395d236, log4j to 1.2.16, GWT to 2.0.4,
+sfl4j to 1.6.1, easymock to 3.0, JUnit to 4.8.1.
+
+Bug Fixes
+---------
+
+Web UI
+~~~~~~
+
+* issue 352 Confirm branch deletion in web UI
++
+Deleting a branch now presents a confirmation dialog to give the user
+a second chance to abort the destructive operation.
+
+* Fix some JavaScript errors under Chrome
++
+The GWT compiler started to define symbols in the same namespace as
+the prettify syntax highlighting library. We moved the prettify
+library into its own iframe so it has a different JavaScript namespace
+in the browser.
+
+Misc.
+~~~~~
+
+* issue 614 Fix 503 error when Jetty cancels a request
++
+A bug was introduced in 2.1.3 that caused a server 503 error
+when a fetch/pull/clone or push request timed out. Fixed.
+
+Version
+-------
+
+df89f998d5c0fa5802a70482d4582b6313a018e4
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index bc9feb8725..bd98667f57 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,6 +4,7 @@ Gerrit Code Review - Release Notes
[[2_1]]
Version 2.1.x
-------------
+* link:ReleaseNotes-2.1.4.html[2.1.4]
* link:ReleaseNotes-2.1.3.html[2.1.3]
* link:ReleaseNotes-2.1.2.5.html[2.1.2.5]
* link:ReleaseNotes-2.1.2.4.html[2.1.2.4]
diff --git a/contrib/check-valid-commit.py b/contrib/check-valid-commit.py
new file mode 100755
index 0000000000..ca1785ecdb
--- /dev/null
+++ b/contrib/check-valid-commit.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+import commands
+import getopt
+import sys
+
+
+SSH_USER = 'bot'
+SSH_HOST = 'localhost'
+SSH_PORT = 29418
+SSH_COMMAND = 'ssh %s@%s -p %d gerrit approve ' % (SSH_USER, SSH_HOST, SSH_PORT)
+FAILURE_SCORE = '--code-review=-2'
+FAILURE_MESSAGE = 'This commit message does not match the standard.' \
+ + ' Please correct the commit message and upload a replacement patch.'
+PASS_SCORE = '--code-review=0'
+PASS_MESSAGE = ''
+
+def main():
+ change = None
+ project = None
+ branch = None
+ commit = None
+ patchset = None
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], '', \
+ ['change=', 'project=', 'branch=', 'commit=', 'patchset='])
+ except getopt.GetoptError, err:
+ print 'Error: %s' % (err)
+ usage()
+ sys.exit(-1)
+
+ for arg, value in opts:
+ if arg == '--change':
+ change = value
+ elif arg == '--project':
+ project = value
+ elif arg == '--branch':
+ branch = value
+ elif arg == '--commit':
+ commit = value
+ elif arg == '--patchset':
+ patchset = value
+ else:
+ print 'Error: option %s not recognized' % (arg)
+ usage()
+ sys.exit(-1)
+
+ if change == None or project == None or branch == None \
+ or commit == None or patchset == None:
+ usage()
+ sys.exit(-1)
+
+ command = 'git cat-file commit %s' % (commit)
+ status, output = commands.getstatusoutput(command)
+
+ if status != 0:
+ print 'Error running \'%s\'. status: %s, output:\n\n%s' % \
+ (command, status, output)
+ sys.exit(-1)
+
+ commitMessage = output[(output.find('\n\n')+2):]
+ commitLines = commitMessage.split('\n')
+
+ if len(commitLines) > 1 and len(commitLines[1]) != 0:
+ fail(commit, 'Invalid commit summary. The summary must be ' \
+ + 'one line followed by a blank line.')
+
+ i = 0
+ for line in commitLines:
+ i = i + 1
+ if len(line) > 80:
+ fail(commit, 'Line %d is over 80 characters.' % i)
+
+ passes(commit)
+
+def usage():
+ print 'Usage:\n'
+ print sys.argv[0] + ' --change <change id> --project <project name> ' \
+ + '--branch <branch> --commit <sha1> --patchset <patchset id>'
+
+def fail( commit, message ):
+ command = SSH_COMMAND + FAILURE_SCORE + ' -m \\\"' \
+ + _shell_escape( FAILURE_MESSAGE + '\n\n' + message) \
+ + '\\\" ' + commit
+ commands.getstatusoutput(command)
+ sys.exit(1)
+
+def passes( commit ):
+ command = SSH_COMMAND + PASS_SCORE + ' -m \\\"' \
+ + _shell_escape(PASS_MESSAGE) + ' \\\" ' + commit
+ commands.getstatusoutput(command)
+
+def _shell_escape(x):
+ s = ''
+ for c in x:
+ if c in '\n':
+ s = s + '\\\"$\'\\n\'\\\"'
+ else:
+ s = s + c
+ return s
+
+if __name__ == '__main__':
+ main()
+
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index b441b82fca..adac3a8058 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-common</artifactId>
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 5a85ef5560..43541b7bb3 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
@@ -24,7 +24,9 @@ import com.google.gwtorm.client.KeyUtil;
public class PageLinks {
public static final String SETTINGS = "settings";
+ public static final String SETTINGS_PREFERENCES = "settings,preferences";
public static final String SETTINGS_SSHKEYS = "settings,ssh-keys";
+ public static final String SETTINGS_HTTP_PASSWORD = "settings,http-password";
public static final String SETTINGS_WEBIDENT = "settings,web-identities";
public static final String SETTINGS_MYGROUPS = "settings,group-memberships";
public static final String SETTINGS_AGREEMENTS = "settings,agreements";
@@ -33,11 +35,13 @@ public class PageLinks {
public static final String SETTINGS_NEW_AGREEMENT = "settings,new-agreement";
public static final String REGISTER = "register";
+ public static final String TOP = "n,z";
+
public static final String MINE = "mine";
public static final String MINE_STARRED = "mine,starred";
public static final String MINE_DRAFTS = "mine,drafts";
+ public static final String MINE_WATCHED = "mine,watched," + TOP;
- public static final String TOP = "n,z";
public static final String ALL_ABANDONED = "all,abandoned," + TOP;
public static final String ALL_MERGED = "all,merged," + TOP;
public static final String ALL_OPEN = "all,open," + TOP;
@@ -65,19 +69,26 @@ public class PageLinks {
return "q," + KeyUtil.encode(query) + "," + TOP;
}
- public static String toProject(final Project.NameKey proj, Status status) {
+ public static String projectQuery(Project.NameKey proj, Status status) {
switch (status) {
case ABANDONED:
- return "project,abandoned," + proj.toString() + ",n,z";
+ return "status:abandoned " + op("project", proj.get());
case MERGED:
- return "project,merged," + proj.toString() + ",n,z";
+ return "status:merged " + op("project", proj.get());
case NEW:
case SUBMITTED:
default:
- return "project,open," + proj.toString() + ",n,z";
+ return "status:open " + op("project", proj.get());
+ }
+ }
+
+ public static String op(String name, String value) {
+ if (value.indexOf(' ') >= 0) {
+ return name + ":\"" + value + "\"";
}
+ return name + ":" + value;
}
protected PageLinks() {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
index 83d31a06ba..1117455ede 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -50,6 +50,10 @@ public interface AccountSecurity extends RemoteJsonService {
AsyncCallback<AccountExternalId> callback);
@SignInRequired
+ void clearPassword(AccountExternalId.Key key,
+ AsyncCallback<AccountExternalId> gerritCallback);
+
+ @SignInRequired
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
@SignInRequired
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 2fad82ee3d..e219074451 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
@@ -16,6 +16,7 @@ package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -33,14 +34,21 @@ public interface AccountService extends RemoteJsonService {
void myAccount(AsyncCallback<Account> callback);
@SignInRequired
+ void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback);
+
+ @SignInRequired
void changePreferences(AccountGeneralPreferences pref,
AsyncCallback<VoidResult> gerritCallback);
@SignInRequired
+ void changeDiffPreferences(AccountDiffPreference diffPref,
+ AsyncCallback<VoidResult> callback);
+
+ @SignInRequired
void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
@SignInRequired
- void addProjectWatch(String projectName,
+ void addProjectWatch(String projectName, String filter,
AsyncCallback<AccountProjectWatchInfo> callback);
@SignInRequired
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 3de4ee090f..b5271ec539 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
@@ -27,6 +27,7 @@ public class ChangeInfo {
protected Change.Status status;
protected ProjectInfo project;
protected String branch;
+ protected String topic;
protected boolean starred;
protected Timestamp lastUpdatedOn;
protected String sortKey;
@@ -42,6 +43,7 @@ public class ChangeInfo {
status = c.getStatus();
project = new ProjectInfo(c.getProject());
branch = c.getDest().getShortName();
+ topic = c.getTopic();
lastUpdatedOn = c.getLastUpdatedOn();
sortKey = c.getSortKey();
}
@@ -74,6 +76,10 @@ public class ChangeInfo {
return branch;
}
+ public String getTopic() {
+ return topic;
+ }
+
public boolean isStarred() {
return starred;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
index 3473373d24..5ff85e3a2a 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
@@ -17,7 +17,6 @@ package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.Project;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.client.RpcImpl;
@@ -28,44 +27,6 @@ import java.util.Set;
@RpcImpl(version = Version.V2_0)
public interface ChangeListService extends RemoteJsonService {
- /** Get all open changes more recent than pos, fetching at most limit rows. */
- void allOpenPrev(String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get all open changes older than pos, fetching at most limit rows. */
- void allOpenNext(String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get all open changes more recent than pos, fetching at most limit rows. */
- void byProjectOpenPrev(Project.NameKey project, String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get all open changes older than pos, fetching at most limit rows. */
- void byProjectOpenNext(Project.NameKey project, String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /**
- * Get all closed changes with same status, more recent than pos, fetching at
- * most limit rows.
- */
- void byProjectClosedPrev(Project.NameKey project, Change.Status status,
- String pos, int limit, AsyncCallback<SingleListChangeInfo> callback);
-
- /**
- * Get all closed changes with same status, older than pos, fetching at most
- * limit rows.
- */
- void byProjectClosedNext(Project.NameKey project, Change.Status status,
- String pos, int limit, AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get all closed changes more recent than pos, fetching at most limit rows. */
- void allClosedPrev(Change.Status status, String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get all closed changes older than pos, fetching at most limit rows. */
- void allClosedNext(Change.Status status, String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
/** Get all changes which match an arbitrary query string. */
void allQueryPrev(String query, String pos, int limit,
AsyncCallback<SingleListChangeInfo> callback);
@@ -77,14 +38,6 @@ public interface ChangeListService extends RemoteJsonService {
/** Get the data to show AccountDashboardScreen for an account. */
void forAccount(Account.Id id, AsyncCallback<AccountDashboardInfo> callback);
- /** Get the changes starred by the caller. */
- @SignInRequired
- void myStarredChanges(AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get the changes with unpublished drafts by the caller. */
- @SignInRequired
- void myDraftChanges(AsyncCallback<SingleListChangeInfo> callback);
-
/** Get the ids of all changes starred by the caller. */
@SignInRequired
void myStarredChangeIds(AsyncCallback<Set<Change.Id>> callback);
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 e2dc1a7c66..717a49287f 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
@@ -15,9 +15,11 @@
package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
/** Data sent as part of the host page, to bootstrap the UI. */
public class HostPageData {
public Account account;
+ public AccountDiffPreference accountDiffPref;
public GerritConfig config;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
index 287656690e..5aec0c766a 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
@@ -16,6 +16,7 @@ package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
@@ -34,7 +35,7 @@ import java.util.Set;
@RpcImpl(version = Version.V2_0)
public interface PatchDetailService extends RemoteJsonService {
void patchScript(Patch.Key key, PatchSet.Id a, PatchSet.Id b,
- PatchScriptSettings settings, AsyncCallback<PatchScript> callback);
+ AccountDiffPreference diffPrefs, AsyncCallback<PatchScript> callback);
@SignInRequired
void saveDraft(PatchLineComment comment,
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 2c1fd721aa..048d4408cb 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
@@ -14,17 +14,15 @@
package com.google.gerrit.common.data;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.WHOLE_FILE_CONTEXT;
-
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.prettify.client.ClientSideFormatter;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.PrettyFormatter;
-import com.google.gerrit.prettify.common.PrettySettings;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.prettify.common.SparseHtmlFile;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Patch.ChangeType;
import org.eclipse.jgit.diff.Edit;
@@ -41,7 +39,7 @@ public class PatchScript {
protected String oldName;
protected String newName;
protected List<String> header;
- protected PatchScriptSettings settings;
+ protected AccountDiffPreference diffPrefs;
protected SparseFileContent a;
protected SparseFileContent b;
protected List<Edit> edits;
@@ -53,7 +51,7 @@ public class PatchScript {
protected boolean intralineDifference;
public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
- final String nn, final List<String> h, final PatchScriptSettings s,
+ final String nn, final List<String> h, final AccountDiffPreference dp,
final SparseFileContent ca, final SparseFileContent cb,
final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
final CommentDetail cd, final List<Patch> hist, final boolean hf,
@@ -63,7 +61,7 @@ public class PatchScript {
oldName = on;
newName = nn;
header = h;
- settings = s;
+ diffPrefs = dp;
a = ca;
b = cb;
edits = e;
@@ -114,12 +112,12 @@ public class PatchScript {
return history;
}
- public PatchScriptSettings getSettings() {
- return settings;
+ public AccountDiffPreference getDiffPrefs() {
+ return diffPrefs;
}
- public void setSettings(PatchScriptSettings s) {
- settings = s;
+ public void setDiffPrefs(AccountDiffPreference dp) {
+ diffPrefs = dp;
}
public boolean isHugeFile() {
@@ -127,7 +125,7 @@ public class PatchScript {
}
public boolean isIgnoreWhitespace() {
- return settings.getWhitespace() != Whitespace.IGNORE_NONE;
+ return diffPrefs.getIgnoreWhitespace() != Whitespace.IGNORE_NONE;
}
public boolean hasIntralineDifference() {
@@ -143,12 +141,12 @@ public class PatchScript {
}
public SparseHtmlFile getSparseHtmlFileA() {
- PrettySettings s = new PrettySettings(settings.getPrettySettings());
- s.setFileName(a.getPath());
- s.setShowWhiteSpaceErrors(false);
+ AccountDiffPreference dp = new AccountDiffPreference(diffPrefs);
+ dp.setShowWhitespaceErrors(false);
PrettyFormatter f = ClientSideFormatter.FACTORY.get();
- f.setPrettySettings(s);
+ f.setDiffPrefs(dp);
+ f.setFileName(a.getPath());
f.setEditFilter(PrettyFormatter.A);
f.setEditList(edits);
f.format(a);
@@ -156,15 +154,15 @@ public class PatchScript {
}
public SparseHtmlFile getSparseHtmlFileB() {
- PrettySettings s = new PrettySettings(settings.getPrettySettings());
- s.setFileName(b.getPath());
+ AccountDiffPreference dp = new AccountDiffPreference(diffPrefs);
PrettyFormatter f = ClientSideFormatter.FACTORY.get();
- f.setPrettySettings(s);
+ f.setDiffPrefs(dp);
+ f.setFileName(b.getPath());
f.setEditFilter(PrettyFormatter.B);
f.setEditList(edits);
- if (s.isSyntaxHighlighting() && a.isWholeFile() && !b.isWholeFile()) {
+ if (dp.isSyntaxHighlighting() && a.isWholeFile() && !b.isWholeFile()) {
f.format(b.apply(a, edits));
} else {
f.format(b);
@@ -177,8 +175,8 @@ public class PatchScript {
}
public Iterable<EditList.Hunk> getHunks() {
- int ctx = settings.getContext();
- if (ctx == WHOLE_FILE_CONTEXT) {
+ int ctx = diffPrefs.getContext();
+ if (ctx == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
ctx = Math.max(a.size(), b.size());
}
return new EditList(edits, ctx, a.size(), b.size()).getHunks();
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScriptSettings.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScriptSettings.java
deleted file mode 100644
index cd43ba41e6..0000000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScriptSettings.java
+++ /dev/null
@@ -1,80 +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.prettify.common.PrettySettings;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.CodedEnum;
-
-public class PatchScriptSettings {
- public static enum Whitespace implements CodedEnum {
- IGNORE_NONE('N'), //
- IGNORE_SPACE_AT_EOL('E'), //
- IGNORE_SPACE_CHANGE('S'), //
- IGNORE_ALL_SPACE('A');
-
- private final char code;
-
- private Whitespace(final char c) {
- code = c;
- }
-
- public char getCode() {
- return code;
- }
- }
-
- protected int context;
- protected Whitespace whitespace;
- protected PrettySettings pretty;
-
- public PatchScriptSettings() {
- context = AccountGeneralPreferences.DEFAULT_CONTEXT;
- whitespace = Whitespace.IGNORE_NONE;
- pretty = new PrettySettings();
- }
-
- public PatchScriptSettings(final PatchScriptSettings s) {
- context = s.context;
- whitespace = s.whitespace;
- pretty = new PrettySettings(s.pretty);
- }
-
- public PrettySettings getPrettySettings() {
- return pretty;
- }
-
- public void setPrettySettings(PrettySettings s) {
- pretty = s;
- }
-
- public int getContext() {
- return context;
- }
-
- public void setContext(final int ctx) {
- assert 0 <= ctx || ctx == AccountGeneralPreferences.WHOLE_FILE_CONTEXT;
- context = ctx;
- }
-
- public Whitespace getWhitespace() {
- return whitespace;
- }
-
- public void setWhitespace(final Whitespace ws) {
- assert ws != null;
- whitespace = ws;
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java
new file mode 100644
index 0000000000..4a66a416a7
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java
@@ -0,0 +1,24 @@
+// 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.errors;
+
+/** Error indicating the query cannot be executed. */
+public class InvalidQueryException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidQueryException(String message, String query) {
+ super("Invalid query: " + query + "\n\n" + message);
+ }
+}
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 04f1e41fde..c3a07a4178 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,10 +22,10 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
- <artifactId>gerrit-gwtdbug</artifactId>
+ <artifactId>gerrit-gwtdebug</artifactId>
<name>Gerrit Code Review - GWT UI Debugging Support</name>
<description>
@@ -42,12 +42,14 @@ limitations under the License.
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-gwtui</artifactId>
<version>${project.version}</version>
+ <classifier>classes</classifier>
</dependency>
<dependency>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-war</artifactId>
<version>${project.version}</version>
+ <classifier>classes</classifier>
<exclusions>
<exclusion>
<groupId>com.google.gerrit</groupId>
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index 5b4d192c32..29c5b01cfd 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtui</artifactId>
@@ -244,6 +244,7 @@ limitations under the License.
<artifactId>maven-war-plugin</artifactId>
<configuration>
<packagingExcludes>WEB-INF/classes/**,WEB-INF/lib/**</packagingExcludes>
+ <attachClasses>true</attachClasses>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java
new file mode 100644
index 0000000000..e3b7525ee2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java
@@ -0,0 +1,29 @@
+// 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;
+
+/**
+ * Interface that a caller must implement to react on the result of a
+ * {@link ConfirmationDialog}.
+ */
+public interface ConfirmationCallback {
+
+ /**
+ * Called when the {@link ConfirmationDialog} is finished with OK.
+ * To be overwritten by subclasses.
+ */
+ public void onOk();
+
+}
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
new file mode 100644
index 0000000000..c4cb770bce
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
@@ -0,0 +1,77 @@
+// 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;
+
+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.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.AutoCenterDialogBox;
+
+public class ConfirmationDialog extends AutoCenterDialogBox {
+
+
+ private Button cancelButton;
+
+ public ConfirmationDialog(final String dialogTitle, final HTML message,
+ final ConfirmationCallback callback) {
+ super(/* auto hide */false, /* modal */true);
+ setGlassEnabled(true);
+ setText(dialogTitle);
+
+ final FlowPanel buttons = new FlowPanel();
+
+ final Button okButton = new Button();
+ okButton.setText(Gerrit.C.confirmationDialogOk());
+ okButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ hide();
+ callback.onOk();
+ }
+ });
+ buttons.add(okButton);
+
+ cancelButton = new Button();
+ DOM.setStyleAttribute(cancelButton.getElement(), "marginLeft", "300px");
+ cancelButton.setText(Gerrit.C.confirmationDialogCancel());
+ cancelButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ hide();
+ }
+ });
+ buttons.add(cancelButton);
+
+ final FlowPanel center = new FlowPanel();
+ center.add(message);
+ center.add(buttons);
+ add(center);
+
+ message.setWidth("400px");
+
+ setWidget(center);
+ }
+
+ @Override
+ public void center() {
+ super.center();
+ GlobalKey.dialog(this);
+ cancelButton.setFocus(true);
+ }
+}
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 7db92ad179..f93fd5eba2 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
@@ -21,37 +21,47 @@ import static com.google.gerrit.common.PageLinks.MINE_DRAFTS;
import static com.google.gerrit.common.PageLinks.MINE_STARRED;
import static com.google.gerrit.common.PageLinks.REGISTER;
import static com.google.gerrit.common.PageLinks.SETTINGS;
+import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
+import static com.google.gerrit.common.PageLinks.SETTINGS_CONTACT;
+import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
+import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS;
import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT;
+import static com.google.gerrit.common.PageLinks.SETTINGS_PREFERENCES;
+import static com.google.gerrit.common.PageLinks.SETTINGS_PROJECTS;
+import static com.google.gerrit.common.PageLinks.SETTINGS_SSHKEYS;
import static com.google.gerrit.common.PageLinks.SETTINGS_WEBIDENT;
-import static com.google.gerrit.common.PageLinks.TOP;
-
-import com.google.gerrit.client.account.AccountSettings;
+import static com.google.gerrit.common.PageLinks.op;
+
+import com.google.gerrit.client.account.MyAgreementsScreen;
+import com.google.gerrit.client.account.MyContactInformationScreen;
+import com.google.gerrit.client.account.MyGroupsScreen;
+import com.google.gerrit.client.account.MyIdentitiesScreen;
+import com.google.gerrit.client.account.MyPasswordScreen;
+import com.google.gerrit.client.account.MyPreferencesScreen;
+import com.google.gerrit.client.account.MyProfileScreen;
+import com.google.gerrit.client.account.MySshKeysScreen;
+import com.google.gerrit.client.account.MyWatchedProjectsScreen;
import com.google.gerrit.client.account.NewAgreementScreen;
import com.google.gerrit.client.account.RegisterScreen;
import com.google.gerrit.client.account.ValidateEmailScreen;
import com.google.gerrit.client.admin.AccountGroupScreen;
import com.google.gerrit.client.admin.GroupListScreen;
-import com.google.gerrit.client.admin.ProjectAdminScreen;
+import com.google.gerrit.client.admin.ProjectAccessScreen;
+import com.google.gerrit.client.admin.ProjectBranchesScreen;
+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.auth.openid.OpenIdSignInDialog;
import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
import com.google.gerrit.client.changes.AccountDashboardScreen;
-import com.google.gerrit.client.changes.AllAbandonedChangesScreen;
-import com.google.gerrit.client.changes.AllMergedChangesScreen;
-import com.google.gerrit.client.changes.AllOpenChangesScreen;
-import com.google.gerrit.client.changes.ByProjectAbandonedChangesScreen;
-import com.google.gerrit.client.changes.ByProjectMergedChangesScreen;
-import com.google.gerrit.client.changes.ByProjectOpenChangesScreen;
-import com.google.gerrit.client.changes.ChangeQueryResultsScreen;
import com.google.gerrit.client.changes.ChangeScreen;
-import com.google.gerrit.client.changes.MineDraftsScreen;
-import com.google.gerrit.client.changes.MineStarredScreen;
import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.PublishCommentScreen;
+import com.google.gerrit.client.changes.QueryScreen;
import com.google.gerrit.client.patches.PatchScreen;
import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.SignInMode;
+import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Change;
@@ -106,7 +116,7 @@ public class Dispatcher {
private static void select(final String token) {
if (token.startsWith("patch,")) {
- patch(token, null, 0, null);
+ patch(token, null, 0, null, null);
} else if (token.startsWith("change,publish,")) {
publish(token);
@@ -148,12 +158,17 @@ public class Dispatcher {
}
} else if (MINE_STARRED.equals(token)) {
- return new MineStarredScreen();
+ return QueryScreen.forQuery("is:starred");
} else if (MINE_DRAFTS.equals(token)) {
- return new MineDraftsScreen();
+ return QueryScreen.forQuery("has:draft");
} else {
+ String p = "mine,watched,";
+ if (token.startsWith(p)) {
+ return QueryScreen.forQuery("is:watched status:open", skip(p, token));
+ }
+
return new NotFoundScreen();
}
}
@@ -163,18 +178,19 @@ public class Dispatcher {
p = "all,abandoned,";
if (token.startsWith(p)) {
- return new AllAbandonedChangesScreen(skip(p, token));
+ return QueryScreen.forQuery("status:abandoned", skip(p, token));
}
p = "all,merged,";
if (token.startsWith(p)) {
- return new AllMergedChangesScreen(skip(p, token));
+ return QueryScreen.forQuery("status:merged", skip(p, token));
}
p = "all,open,";
if (token.startsWith(p)) {
- return new AllOpenChangesScreen(skip(p, token));
+ return QueryScreen.forQuery("status:open", skip(p, token));
}
+
return new NotFoundScreen();
}
@@ -185,25 +201,32 @@ public class Dispatcher {
if (token.startsWith(p)) {
final String s = skip(p, token);
final int c = s.indexOf(',');
- return new ByProjectOpenChangesScreen(Project.NameKey.parse(s.substring(
- 0, c)), s.substring(c + 1));
+ Project.NameKey proj = Project.NameKey.parse(s.substring(0, c));
+ return QueryScreen.forQuery( //
+ "status:open " + op("project", proj.get()), //
+ s.substring(c + 1));
}
p = "project,merged,";
if (token.startsWith(p)) {
final String s = skip(p, token);
final int c = s.indexOf(',');
- return new ByProjectMergedChangesScreen(Project.NameKey.parse(s
- .substring(0, c)), s.substring(c + 1));
+ Project.NameKey proj = Project.NameKey.parse(s.substring(0, c));
+ return QueryScreen.forQuery( //
+ "status:merged " + op("project", proj.get()), //
+ s.substring(c + 1));
}
p = "project,abandoned,";
if (token.startsWith(p)) {
final String s = skip(p, token);
final int c = s.indexOf(',');
- return new ByProjectAbandonedChangesScreen(Project.NameKey.parse(s
- .substring(0, c)), s.substring(c + 1));
+ Project.NameKey proj = Project.NameKey.parse(s.substring(0, c));
+ return QueryScreen.forQuery( //
+ "status:abandoned " + op("project", proj.get()), //
+ s.substring(c + 1));
}
+
return new NotFoundScreen();
}
@@ -222,7 +245,7 @@ public class Dispatcher {
if (token.startsWith(p)) {
final String s = skip(p, token);
final int c = s.indexOf(',');
- return new ChangeQueryResultsScreen(s.substring(0, c), s.substring(c + 1));
+ return new QueryScreen(s.substring(0, c), s.substring(c + 1));
}
return new NotFoundScreen();
@@ -244,7 +267,8 @@ public class Dispatcher {
}
public static void patch(String token, final Patch.Key id,
- final int patchIndex, final PatchTable patchTable) {
+ final int patchIndex, final PatchSetDetail patchSetDetail,
+ final PatchTable patchTable) {
GWT.runAsync(new AsyncSplit(token) {
public void onSuccess() {
Gerrit.display(token, select());
@@ -258,6 +282,7 @@ public class Dispatcher {
return new PatchScreen.SideBySide( //
id != null ? id : Patch.Key.parse(skip(p, token)), //
patchIndex, //
+ patchSetDetail, //
patchTable //
);
}
@@ -267,6 +292,7 @@ public class Dispatcher {
return new PatchScreen.Unified( //
id != null ? id : Patch.Key.parse(skip(p, token)), //
patchIndex, //
+ patchSetDetail, //
patchTable //
);
}
@@ -285,6 +311,43 @@ public class Dispatcher {
private Screen select() {
String p;
+ if (token.equals(SETTINGS)) {
+ return new MyProfileScreen();
+ }
+
+ if (token.equals(SETTINGS_PREFERENCES)) {
+ return new MyPreferencesScreen();
+ }
+
+ if (token.equals(SETTINGS_PROJECTS)) {
+ return new MyWatchedProjectsScreen();
+ }
+
+ if (token.equals(SETTINGS_CONTACT)) {
+ return new MyContactInformationScreen();
+ }
+
+ if (token.equals(SETTINGS_SSHKEYS)) {
+ return new MySshKeysScreen();
+ }
+
+ if (token.equals(SETTINGS_WEBIDENT)) {
+ return new MyIdentitiesScreen();
+ }
+
+ if (token.equals(SETTINGS_HTTP_PASSWORD)) {
+ return new MyPasswordScreen();
+ }
+
+ if (token.equals(SETTINGS_MYGROUPS)) {
+ return new MyGroupsScreen();
+ }
+
+ if (token.equals(SETTINGS_AGREEMENTS)
+ && Gerrit.getConfig().isUseContributorAgreements()) {
+ return new MyAgreementsScreen();
+ }
+
p = "register,";
if (token.startsWith(p)) {
return new RegisterScreen(skip(p, token));
@@ -301,7 +364,7 @@ public class Dispatcher {
final String[] args = skip(p, token).split(",");
final SignInMode mode = SignInMode.valueOf(args[0]);
final String msg = KeyUtil.decode(args[1]);
- final String to = PageLinks.MINE;
+ final String to = MINE;
switch (Gerrit.getConfig().getAuthType()) {
case OPENID:
new OpenIdSignInDialog(mode, to, msg).center();
@@ -315,9 +378,9 @@ public class Dispatcher {
}
switch (mode) {
case SIGN_IN:
- return new AllOpenChangesScreen(TOP);
+ return QueryScreen.forQuery("status:open");
case LINK_IDENTIY:
- return new AccountSettings(SETTINGS_WEBIDENT);
+ return new MyIdentitiesScreen();
}
}
@@ -329,7 +392,7 @@ public class Dispatcher {
return new NewAgreementScreen(skip(p, token));
}
- return new AccountSettings(token);
+ return new NotFoundScreen();
}
});
}
@@ -351,8 +414,23 @@ public class Dispatcher {
if (token.startsWith(p)) {
p = skip(p, token);
final int c = p.indexOf(',');
- final String idstr = p.substring(0, c);
- return new ProjectAdminScreen(Project.NameKey.parse(idstr), token);
+ final Project.NameKey k = Project.NameKey.parse(p.substring(0, c));
+ final boolean isWild = k.equals(Gerrit.getConfig().getWildProject());
+ p = p.substring(c + 1);
+
+ if (ProjectScreen.INFO.equals(p)) {
+ return new ProjectInfoScreen(k);
+ }
+
+ if (!isWild && ProjectScreen.BRANCH.equals(p)) {
+ return new ProjectBranchesScreen(k);
+ }
+
+ if (ProjectScreen.ACCESS.equals(p)) {
+ return new ProjectAccessScreen(k);
+ }
+
+ return new NotFoundScreen();
}
if (ADMIN_GROUPS.equals(token)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
index 178126dd28..74a2678555 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
@@ -17,6 +17,9 @@ package com.google.gerrit.client;
import com.google.gerrit.client.rpc.RpcConstants;
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.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Button;
@@ -30,6 +33,7 @@ import com.google.gwtjsonrpc.client.RemoteJsonException;
public class ErrorDialog extends PluginSafePopupPanel {
private final Label text;
private final FlowPanel body;
+ private final Button closey;
protected ErrorDialog() {
super(/* auto hide */false, /* modal */true);
@@ -44,7 +48,7 @@ public class ErrorDialog extends PluginSafePopupPanel {
final FlowPanel buttons = new FlowPanel();
buttons.setStyleName(Gerrit.RESOURCES.css().errorDialogButtons());
- final Button closey = new Button();
+ closey = new Button();
closey.setText(Gerrit.C.errorDialogContinue());
closey.addClickHandler(new ClickHandler() {
@Override
@@ -52,6 +56,16 @@ public class ErrorDialog extends PluginSafePopupPanel {
hide();
}
});
+ closey.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ // if the close button is triggered by a key we need to consume the key
+ // event, otherwise the key event would be propagated to the parent
+ // screen and eventually trigger some unwanted action there after the
+ // error dialog was closed
+ event.stopPropagation();
+ }
+ });
buttons.add(closey);
final FlowPanel center = new FlowPanel();
@@ -108,7 +122,10 @@ public class ErrorDialog extends PluginSafePopupPanel {
final Label r = new Label(cn);
r.setStyleName(Gerrit.RESOURCES.css().errorDialogErrorType());
body.add(r);
- body.add(new Label(what.getMessage()));
+
+ final Label m = new Label(what.getMessage());
+ DOM.setStyleAttribute(m.getElement(),"whiteSpace","pre");
+ body.add(m);
}
public void setText(final String t) {
@@ -118,5 +135,6 @@ public class ErrorDialog extends PluginSafePopupPanel {
@Override
public void center() {
show();
+ closey.setFocus(true);
}
}
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 641db80b3a..ecb4cd619b 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
@@ -27,6 +27,7 @@ import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.common.data.SystemInfoService;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
@@ -70,6 +71,7 @@ public class Gerrit implements EntryPoint {
private static String myHost;
private static GerritConfig myConfig;
private static Account myAccount;
+ private static AccountDiffPreference myAccountDiffPref;
private static TabPanel menuLeft;
private static LinkMenuBar menuRight;
@@ -172,6 +174,15 @@ public class Gerrit implements EntryPoint {
return myAccount;
}
+ /** @return the currently signed in users's diff preferences; null if no diff preferences defined for the account */
+ public static AccountDiffPreference getAccountDiffPreference() {
+ return myAccountDiffPref;
+ }
+
+ public static void setAccountDiffPreference(AccountDiffPreference accountDiffPref) {
+ myAccountDiffPref = accountDiffPref;
+ }
+
/** @return true if the user is currently authenticated */
public static boolean isSignedIn() {
return getUserAccount() != null;
@@ -203,6 +214,7 @@ public class Gerrit implements EntryPoint {
static void deleteSessionCookie() {
Cookies.removeCookie(SESSION_COOKIE);
myAccount = null;
+ myAccountDiffPref = null;
refreshMenuBar();
}
@@ -234,6 +246,9 @@ public class Gerrit implements EntryPoint {
if (result.account != null) {
myAccount = result.account;
}
+ if (result.accountDiffPref != null) {
+ myAccountDiffPref = result.accountDiffPref;
+ }
onModuleLoad2();
}
});
@@ -409,7 +424,8 @@ public class Gerrit implements EntryPoint {
if (signedIn) {
m = new LinkMenuBar();
addLink(m, C.menuMyChanges(), PageLinks.MINE);
- addLink(m, C.menyMyDrafts(), PageLinks.MINE_DRAFTS);
+ addLink(m, C.menuMyDrafts(), PageLinks.MINE_DRAFTS);
+ addLink(m, C.menuMyWatchedChanges(), PageLinks.MINE_WATCHED);
addLink(m, C.menuMyStarredChanges(), PageLinks.MINE_STARRED);
menuLeft.add(m, C.menuMine());
menuLeft.selectTab(1);
@@ -427,6 +443,7 @@ public class Gerrit implements EntryPoint {
if (getConfig().isDocumentationAvailable()) {
m = new LinkMenuBar();
addDocLink(m, C.menuDocumentationIndex(), "index.html");
+ addDocLink(m, C.menuDocumentationSearch(), "user-search.html");
addDocLink(m, C.menuDocumentationUpload(), "user-upload.html");
addDocLink(m, C.menuDocumentationAccess(), "access-control.html");
menuLeft.add(m, C.menuDocumentation());
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 b4ffe34894..3f263d23fd 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
@@ -32,6 +32,12 @@ public interface GerritConstants extends Constants {
String errorDialogTitle();
String errorDialogContinue();
+ String confirmationDialogOk();
+ String confirmationDialogCancel();
+
+ String branchDeletionDialogTitle();
+ String branchDeletionConfirmationMessage();
+
String notSignedInTitle();
String notSignedInBody();
@@ -47,7 +53,8 @@ public interface GerritConstants extends Constants {
String menuMine();
String menuMyChanges();
- String menyMyDrafts();
+ String menuMyDrafts();
+ String menuMyWatchedChanges();
String menuMyStarredChanges();
String menuAdmin();
@@ -57,6 +64,7 @@ public interface GerritConstants extends Constants {
String menuDocumentation();
String menuDocumentationIndex();
+ String menuDocumentationSearch();
String menuDocumentationUpload();
String menuDocumentationAccess();
@@ -74,7 +82,9 @@ public interface GerritConstants extends Constants {
String sectionJumping();
String jumpAllOpen();
String jumpAllMerged();
+ String jumpAllAbandoned();
String jumpMine();
String jumpMineDrafts();
+ String jumpMineWatched();
String jumpMineStarred();
}
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 4acbe17e30..83a06e47de 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
@@ -13,6 +13,12 @@ loginTypeUnsupported = Sign in is not available.
errorDialogTitle = Application Error
errorDialogContinue = Continue
+confirmationDialogOk = OK
+confirmationDialogCancel = Cancel
+
+branchDeletionDialogTitle = Branch Deletion
+branchDeletionConfirmationMessage = Do you really want to delete the following branches?
+
notSignedInTitle = Code Review - Session Expired
notSignedInBody = <b>Session Expired</b>\
<p>You are no longer signed in to Gerrit Code Review.</p>\
@@ -30,8 +36,9 @@ menuAllAbandoned = Abandoned
menuMine = My
menuMyChanges = Changes
-menyMyDrafts = Drafts
+menuMyDrafts = Drafts
menuMyStarredChanges = Starred Changes
+menuMyWatchedChanges = Watched Changes
menuAdmin = Admin
menuPeople = People
@@ -40,7 +47,8 @@ menuProjects = Projects
menuDocumentation = Documentation
menuDocumentationIndex = Index
-menuDocumentationUpload = Uploading Changes
+menuDocumentationSearch = Searching
+menuDocumentationUpload = Uploading
menuDocumentationAccess = Access Controls
searchHint = Change #, SHA-1, tr:id, owner:email or reviewer:email
@@ -57,6 +65,8 @@ keyHelp = Press '?' to view keyboard shortcuts
sectionJumping = Jumping
jumpAllOpen = Go to all open changes
jumpAllMerged = Go to all merged changes
+jumpAllAbandoned = Go to all abandoned changes
jumpMine = Go to my dashboard
+jumpMineWatched = Go to watched changes
jumpMineDrafts = Go to drafts
jumpMineStarred = Go to starred changes
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 2d631ef9fb..c55677063f 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
@@ -25,11 +25,14 @@ public interface GerritCss extends CssResource {
String accountDashboard();
String accountInfoBlock();
String accountName();
+ String accountUsername();
+ String accountPassword();
String activeRow();
String addReviewer();
String removeReviewer();
String removeReviewerCell();
String addSshKeyPanel();
+ String addWatchPanel();
String approvalCategoryList();
String approvalTable();
String approvalhint();
@@ -123,6 +126,7 @@ public interface GerritCss extends CssResource {
String linkMenuItemNotLast();
String menuBarUserName();
String menuItem();
+ String menuScreenMenuBar();
String missingApproval();
String missingApprovalList();
String needsReview();
@@ -168,8 +172,6 @@ public interface GerritCss extends CssResource {
String sshHostKeyPanelKnownHostEntry();
String sshKeyPanelEncodedKey();
String sshKeyPanelInvalid();
- String sshPanelUsername();
- String sshPanelPassword();
String topmenu();
String topmenuMenuLeft();
String topmenuMenuRight();
@@ -177,5 +179,7 @@ public interface GerritCss extends CssResource {
String topmenuTDmenu();
String topmost();
String useridentity();
+ String usernameField();
String version();
+ String watchedProjectFilter();
}
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 6c789bf3f3..3ec53727da 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
@@ -38,6 +38,12 @@ class JumpKeys {
Gerrit.display(PageLinks.ALL_MERGED);
}
});
+ jumps.add(new KeyCommand(0, 'a', Gerrit.C.jumpAllAbandoned()) {
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ Gerrit.display(PageLinks.ALL_ABANDONED);
+ }
+ });
if (Gerrit.isSignedIn()) {
jumps.add(new KeyCommand(0, 'i', Gerrit.C.jumpMine()) {
@@ -52,6 +58,12 @@ class JumpKeys {
Gerrit.display(PageLinks.MINE_DRAFTS);
}
});
+ jumps.add(new KeyCommand(0, 'w', Gerrit.C.jumpMineWatched()) {
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ Gerrit.display(PageLinks.MINE_WATCHED);
+ }
+ });
jumps.add(new KeyCommand(0, 's', Gerrit.C.jumpMineStarred()) {
@Override
public void onKeyPress(final KeyPressEvent event) {
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 af86a21501..f3544964fe 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
@@ -16,26 +16,35 @@ package com.google.gerrit.client;
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.gwt.user.client.DOM;
import com.google.gwt.user.client.History;
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.PopupPanel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.user.client.AutoCenterDialogBox;
/** A dialog box telling the user they are not signed in. */
-public class NotSignedInDialog extends AutoCenterDialogBox {
+public class NotSignedInDialog extends AutoCenterDialogBox implements CloseHandler<PopupPanel> {
+
+ private Button signin;
+ private boolean buttonClicked = false;
+
public NotSignedInDialog() {
super(/* auto hide */false, /* modal */true);
setGlassEnabled(true);
setText(Gerrit.C.notSignedInTitle());
final FlowPanel buttons = new FlowPanel();
- final Button signin = new Button();
+ signin = new Button();
signin.setText(Gerrit.C.menuSignIn());
signin.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
+ buttonClicked = true;
hide();
Gerrit.doSignIn(History.getToken());
}
@@ -48,6 +57,7 @@ public class NotSignedInDialog extends AutoCenterDialogBox {
close.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
+ buttonClicked = true;
Gerrit.deleteSessionCookie();
hide();
}
@@ -60,5 +70,23 @@ public class NotSignedInDialog extends AutoCenterDialogBox {
add(center);
center.setWidth("400px");
+
+ addCloseHandler(this);
+ }
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (!buttonClicked) {
+ // the dialog was closed without one of the buttons being pressed
+ // e.g. the user pressed ESC to close the dialog
+ Gerrit.deleteSessionCookie();
+ }
+ }
+
+ @Override
+ public void center() {
+ super.center();
+ 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 db9ff59b3f..2d3d2e8cfb 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
@@ -14,6 +14,7 @@
package com.google.gerrit.client;
+import com.google.gerrit.client.changes.QueryScreen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.Change;
import com.google.gwt.event.dom.client.BlurEvent;
@@ -43,7 +44,7 @@ class SearchPanel extends Composite {
setStyleName(Gerrit.RESOURCES.css().searchPanel());
searchBox = new NpTextBox();
- searchBox.setVisibleLength(46);
+ searchBox.setVisibleLength(70);
searchBox.setText(Gerrit.C.searchHint());
searchBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
searchBox.addFocusHandler(new FocusHandler() {
@@ -137,7 +138,7 @@ class SearchPanel extends Composite {
if (query.matches("^[1-9][0-9]*$")) {
Gerrit.display(PageLinks.toChange(Change.Id.parse(query)));
} else {
- Gerrit.display(PageLinks.toChangeQuery(query));
+ Gerrit.display(PageLinks.toChangeQuery(query), QueryScreen.forQuery(query));
}
}
}
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 24439c7055..3756bedf73 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
@@ -17,23 +17,26 @@ package com.google.gerrit.client.account;
import com.google.gwt.i18n.client.Constants;
public interface AccountConstants extends Constants {
- String accountSettingsHeading();
+ String settingsHeading();
String fullName();
String preferredEmail();
String registeredOn();
String accountId();
- String defaultContextFieldLabel();
String maximumPageSizeFieldLabel();
String contextWholeFile();
String showSiteHeader();
String useFlashClipboard();
+ String copySelfOnEmails();
String buttonSaveChanges();
+ String tabAccountSummary();
String tabPreferences();
+ String tabWatchedProjects();
String tabContactInformation();
String tabSshKeys();
+ String tabHttpAccess();
String tabWebIdentities();
String tabMyGroups();
String tabAgreements();
@@ -47,7 +50,9 @@ public interface AccountConstants extends Constants {
String userName();
String password();
+ String buttonSetUserName();
String buttonChangeUserName();
+ String buttonClearPassword();
String buttonGeneratePassword();
String invalidUserName();
@@ -73,9 +78,11 @@ public interface AccountConstants extends Constants {
String buttonDeleteIdentity();
String buttonLinkIdentity();
- String watchedProjects();
String buttonWatchProject();
String defaultProjectName();
+ String defaultFilter();
+ String watchedProjectName();
+ String watchedProjectFilter();
String watchedProjectColumnEmailNotifications();
String watchedProjectColumnNewChanges();
String watchedProjectColumnAllComments();
@@ -116,6 +123,7 @@ public interface AccountConstants extends Constants {
String welcomeToGerritCodeReview();
String welcomeReviewContact();
String welcomeContactFrom();
+ String welcomeUsernameHeading();
String welcomeSshKeyHeading();
String welcomeSshKeyText();
String welcomeAgreementHeading();
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 09ae95a038..29ee14af9a 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
@@ -1,4 +1,4 @@
-accountSettingsHeading = Account Settings
+settingsHeading = Settings
fullName = Full Name
preferredEmail = Email Address
@@ -6,14 +6,18 @@ registeredOn = Registered
accountId = Account ID
showSiteHeader = Show Site Header
useFlashClipboard = Use Flash Clipboard Widget
+copySelfOnEmails = CC Me On Comments I Write
defaultContextFieldLabel = Default Context:
maximumPageSizeFieldLabel = Maximum Page Size:
contextWholeFile = Whole File
buttonSaveChanges = Save Changes
+tabAccountSummary = Profile
tabPreferences = Preferences
+tabWatchedProjects = Watched Projects
tabContactInformation = Contact Information
-tabSshKeys = SSH Keys
+tabSshKeys = SSH Public Keys
+tabHttpAccess = HTTP Password
tabWebIdentities = Identities
tabMyGroups = Groups
tabAgreements = Agreements
@@ -27,8 +31,10 @@ buttonAddSshKey = Add
userName = Username
password = Password
+buttonSetUserName = Select Username
buttonChangeUserName = Change Username
-buttonGeneratePassword = Regenerate
+buttonClearPassword = Clear Password
+buttonGeneratePassword = Generate Password
invalidUserName = Username must contain only letters, numbers, _, - or .
sshKeyInvalid = Invalid Key
@@ -53,9 +59,11 @@ addSshKeyHelp = (<a href="http://help.github.com/key-setup-redirect" target="_bl
invalidSshKeyError = Invalid SSH Key
sshJavaAppletNotAvailable = Open Key Unavailable: Java not enabled
-watchedProjects = Watched Projects
buttonWatchProject = Watch
defaultProjectName = Project Name
+defaultFilter = branch:name, or other search expression
+watchedProjectName = Project Name
+watchedProjectFilter = Only If
watchedProjectColumnEmailNotifications = Email Notifications
watchedProjectColumnNewChanges = New Changes
watchedProjectColumnAllComments = All Comments
@@ -111,6 +119,8 @@ welcomeContactFrom = \
you are to others, and to send updates to code reviews you have either \
started or subscribed to.</p>
+welcomeUsernameHeading = Select a unique username:
+
welcomeSshKeyHeading = Register an SSH public key:
welcomeSshKeyText = \
<p>Gerrit Code Review uses \
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountSettings.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountSettings.java
deleted file mode 100644
index 59a754729c..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountSettings.java
+++ /dev/null
@@ -1,181 +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.account;
-
-import static com.google.gerrit.client.FormatUtil.mediumFormat;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.ui.AccountScreen;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.Account;
-import com.google.gwt.event.logical.shared.SelectionEvent;
-import com.google.gwt.event.logical.shared.SelectionHandler;
-import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.LazyPanel;
-import com.google.gwt.user.client.ui.TabPanel;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AccountSettings extends AccountScreen {
- private final String initialTabToken;
- private int labelIdx, fieldIdx;
- private Grid info;
-
- private List<String> tabTokens;
- private TabPanel tabs;
-
- public AccountSettings(final String tabToken) {
- initialTabToken = tabToken;
- }
-
- @Override
- public boolean displayToken(String token) {
- final int tabIdx = tabTokens.indexOf(token);
- if (0 <= tabIdx) {
- tabs.selectTab(tabIdx);
- setToken(token);
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
-
- final int idx = tabTokens.indexOf(initialTabToken);
- tabs.selectTab(0 <= idx ? idx : 0);
- display(Gerrit.getUserAccount());
- display();
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setPageTitle(Util.C.accountSettingsHeading());
-
- if (LocaleInfo.getCurrentLocale().isRTL()) {
- labelIdx = 1;
- fieldIdx = 0;
- } else {
- labelIdx = 0;
- fieldIdx = 1;
- }
-
- info = new Grid(4, 2);
- info.setStyleName(Gerrit.RESOURCES.css().infoBlock());
- info.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
- add(info);
-
- infoRow(0, Util.C.fullName());
- infoRow(1, Util.C.preferredEmail());
- infoRow(2, Util.C.registeredOn());
- infoRow(3, Util.C.accountId());
-
- final CellFormatter fmt = info.getCellFormatter();
- fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
- fmt.addStyleName(3, 0, Gerrit.RESOURCES.css().bottomheader());
-
- tabTokens = new ArrayList<String>();
- tabs = new TabPanel();
- tabs.setWidth("98%");
- add(tabs);
-
- tabs.add(new LazyPanel() {
- @Override
- protected PreferencePanel createWidget() {
- return new PreferencePanel();
- }
- }, Util.C.tabPreferences());
- tabTokens.add(PageLinks.SETTINGS);
-
- tabs.add(new LazyPanel() {
- @Override
- protected ProjectWatchPanel createWidget() {
- return new ProjectWatchPanel();
- }
- }, Util.C.watchedProjects());
- tabTokens.add(PageLinks.SETTINGS_PROJECTS);
-
- tabs.add(new LazyPanel() {
- @Override
- protected ContactPanelFull createWidget() {
- final ContactPanelFull p = new ContactPanelFull();
- p.accountSettings = AccountSettings.this;
- return p;
- }
- }, Util.C.tabContactInformation());
- tabTokens.add(PageLinks.SETTINGS_CONTACT);
-
- tabs.add(new LazyPanel() {
- @Override
- protected SshPanel createWidget() {
- return new SshPanel();
- }
- }, Util.C.tabSshKeys());
- tabTokens.add(PageLinks.SETTINGS_SSHKEYS);
-
- tabs.add(new LazyPanel() {
- @Override
- protected ExternalIdPanel createWidget() {
- return new ExternalIdPanel();
- }
- }, Util.C.tabWebIdentities());
- tabTokens.add(PageLinks.SETTINGS_WEBIDENT);
-
- tabs.add(new LazyPanel() {
- @Override
- protected MyGroupsPanel createWidget() {
- return new MyGroupsPanel();
- }
- }, Util.C.tabMyGroups());
- tabTokens.add(PageLinks.SETTINGS_MYGROUPS);
-
- if (Gerrit.getConfig().isUseContributorAgreements()) {
- tabs.add(new LazyPanel() {
- @Override
- protected AgreementPanel createWidget() {
- return new AgreementPanel();
- }
- }, Util.C.tabAgreements());
- tabTokens.add(PageLinks.SETTINGS_AGREEMENTS);
- }
-
- tabs.addSelectionHandler(new SelectionHandler<Integer>() {
- @Override
- public void onSelection(final SelectionEvent<Integer> event) {
- setToken(tabTokens.get(event.getSelectedItem()));
- }
- });
- }
-
- private void infoRow(final int row, final String name) {
- info.setText(row, labelIdx, name);
- info.getCellFormatter().addStyleName(row, 0,
- Gerrit.RESOURCES.css().header());
- }
-
- void display(final Account account) {
- info.setText(0, fieldIdx, account.getFullName());
- info.setText(1, fieldIdx, account.getPreferredEmail());
- info.setText(2, fieldIdx, mediumFormat(account.getRegisteredOn()));
- info.setText(3, fieldIdx, account.getId().toString());
- }
-}
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 923a86aa63..21c9163f69 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
@@ -20,6 +20,7 @@ import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ContactInformation;
+import com.google.gerrit.reviewdb.Account.FieldName;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -46,8 +47,6 @@ import java.util.List;
import java.util.Set;
class ContactPanelShort extends Composite {
- AccountSettings accountSettings;
-
protected final FlowPanel body;
protected int labelIdx, fieldIdx;
protected Button save;
@@ -100,12 +99,18 @@ class ContactPanelShort extends Composite {
emailLine.add(registerNewEmail);
}
- row(infoPlainText, 0, Util.C.contactFieldFullName(), nameTxt);
- row(infoPlainText, 1, Util.C.contactFieldEmail(), emailLine);
+ int row = 0;
+ if (!Gerrit.getConfig().canEdit(FieldName.USER_NAME)) {
+ infoPlainText.resizeRows(infoPlainText.getRowCount() + 1);
+ row(infoPlainText, row++, Util.C.userName(), new UsernameField());
+ }
+
+ row(infoPlainText, row++, Util.C.contactFieldFullName(), nameTxt);
+ row(infoPlainText, row++, Util.C.contactFieldEmail(), emailLine);
infoPlainText.getCellFormatter().addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
infoPlainText.getCellFormatter().addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
- infoPlainText.getCellFormatter().addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
+ infoPlainText.getCellFormatter().addStyleName(row - 1, 0, Gerrit.RESOURCES.css().bottomheader());
save = new Button(Util.C.buttonSaveChanges());
save.setEnabled(false);
@@ -227,6 +232,10 @@ class ContactPanelShort extends Composite {
}
registerNewEmail.setEnabled(true);
}
+ display();
+ }
+
+ void display() {
}
protected void row(final Grid info, final int row, final String name,
@@ -354,9 +363,6 @@ class ContactPanelShort extends Composite {
me.setFullName(result.getFullName());
me.setPreferredEmail(result.getPreferredEmail());
Gerrit.refreshMenuBar();
- if (accountSettings != null) {
- accountSettings.display(me);
- }
}
ContactInformation toContactInformation() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AgreementPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
index 326a1dfc80..54de4723c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AgreementPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
@@ -16,7 +16,7 @@ package com.google.gerrit.client.account;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.common.PageLinks;
@@ -26,30 +26,27 @@ import com.google.gerrit.reviewdb.AccountAgreement;
import com.google.gerrit.reviewdb.AccountGroupAgreement;
import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-class AgreementPanel extends Composite {
+public class MyAgreementsScreen extends SettingsScreen {
private AgreementTable agreements;
- AgreementPanel() {
- final FlowPanel body = new FlowPanel();
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
agreements = new AgreementTable();
- body.add(agreements);
- body.add(new Hyperlink(Util.C.newAgreement(), PageLinks.SETTINGS_NEW_AGREEMENT));
-
- initWidget(body);
+ add(agreements);
+ add(new Hyperlink(Util.C.newAgreement(), PageLinks.SETTINGS_NEW_AGREEMENT));
}
@Override
protected void onLoad() {
super.onLoad();
- Util.ACCOUNT_SVC.myAgreements(new GerritCallback<AgreementInfo>() {
- public void onSuccess(final AgreementInfo result) {
+ Util.ACCOUNT_SVC.myAgreements(new ScreenLoadCallback<AgreementInfo>(this) {
+ public void preDisplay(final AgreementInfo result) {
agreements.display(result);
}
});
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/MineDraftsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyContactInformationScreen.java
index 2b935ba4b5..c542511df4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/MineDraftsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyContactInformationScreen.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2008 The Android Open Source Project
+// 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.
@@ -12,27 +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.account;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.common.PageLinks;
-
-
-public class MineDraftsScreen extends MineSingleListScreen {
- public MineDraftsScreen() {
- super(PageLinks.MINE_DRAFTS);
- }
+public class MyContactInformationScreen extends SettingsScreen {
+ private ContactPanelFull panel;
@Override
protected void onInitUI() {
super.onInitUI();
- setWindowTitle(Gerrit.C.menyMyDrafts());
- setPageTitle(Util.C.draftsHeading());
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- Util.LIST_SVC.myDraftChanges(loadCallback());
+ panel = new ContactPanelFull() {
+ @Override
+ void display() {
+ MyContactInformationScreen.this.display();
+ }
+ };
+ add(panel);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
index 9ba20e82bf..b9fff040d4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
@@ -15,34 +15,26 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.admin.GroupTable;
-import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
import java.util.List;
-class MyGroupsPanel extends Composite {
+public class MyGroupsScreen extends SettingsScreen {
private GroupTable groups;
- MyGroupsPanel() {
- final FlowPanel body = new FlowPanel();
-
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
groups = new GroupTable(false /* do not hyperlink to admin */);
- body.add(groups);
-
- initWidget(body);
+ add(groups);
}
@Override
protected void onLoad() {
super.onLoad();
- refresh();
- }
-
- private void refresh() {
- Util.ACCOUNT_SEC.myGroups(new GerritCallback<List<AccountGroup>>() {
- public void onSuccess(final List<AccountGroup> result) {
+ Util.ACCOUNT_SEC.myGroups(new ScreenLoadCallback<List<AccountGroup>>(this) {
+ public void preDisplay(final List<AccountGroup> result) {
groups.display(result);
}
});
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
index 84d8e8a2c9..f3816f2fc4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
@@ -18,6 +18,7 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
import com.google.gerrit.client.auth.openid.OpenIdUtil;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.common.auth.SignInMode;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
@@ -29,8 +30,6 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Button;
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.FlexTable.FlexCellFormatter;
import java.util.Collections;
@@ -39,16 +38,16 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-class ExternalIdPanel extends Composite {
+public class MyIdentitiesScreen extends SettingsScreen {
private IdTable identites;
private Button deleteIdentity;
- ExternalIdPanel() {
- final FlowPanel body = new FlowPanel();
- body.add(new UsernamePanel());
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
identites = new IdTable();
- body.add(identites);
+ add(identites);
deleteIdentity = new Button(Util.C.buttonDeleteIdentity());
deleteIdentity.setEnabled(false);
@@ -58,7 +57,7 @@ class ExternalIdPanel extends Composite {
identites.deleteChecked();
}
});
- body.add(deleteIdentity);
+ add(deleteIdentity);
switch (Gerrit.getConfig().getAuthType()) {
case OPENID: {
@@ -70,21 +69,18 @@ class ExternalIdPanel extends Composite {
new OpenIdSignInDialog(SignInMode.LINK_IDENTIY, to, null).center();
}
});
- body.add(linkIdentity);
+ add(linkIdentity);
break;
}
}
-
- initWidget(body);
}
@Override
protected void onLoad() {
super.onLoad();
-
Util.ACCOUNT_SEC
- .myExternalIds(new GerritCallback<List<AccountExternalId>>() {
- public void onSuccess(final List<AccountExternalId> result) {
+ .myExternalIds(new ScreenLoadCallback<List<AccountExternalId>>(this) {
+ public void preDisplay(final List<AccountExternalId> result) {
identites.display(result);
}
});
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
new file mode 100644
index 0000000000..2202ac88c0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
@@ -0,0 +1,176 @@
+// 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.account;
+
+import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.reviewdb.AccountExternalId;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+
+import java.util.List;
+
+public class MyPasswordScreen extends SettingsScreen {
+ private CopyableLabel password;
+ private Button generatePassword;
+ private Button clearPassword;
+ private AccountExternalId id;
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ password = new CopyableLabel("");
+ password.addStyleName(Gerrit.RESOURCES.css().accountPassword());
+
+ generatePassword = new Button(Util.C.buttonGeneratePassword());
+ generatePassword.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ doGeneratePassword();
+ }
+ });
+
+ clearPassword = new Button(Util.C.buttonClearPassword());
+ clearPassword.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ doClearPassword();
+ }
+ });
+
+ final Grid userInfo = new Grid(2, 2);
+ final CellFormatter fmt = userInfo.getCellFormatter();
+ userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
+ userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
+ add(userInfo);
+
+ row(userInfo, 0, Util.C.userName(), new UsernameField());
+ row(userInfo, 1, Util.C.password(), password);
+
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
+
+ final FlowPanel buttons = new FlowPanel();
+ buttons.add(generatePassword);
+ buttons.add(clearPassword);
+ add(buttons);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+
+ enableUI(false);
+ Util.ACCOUNT_SEC
+ .myExternalIds(new ScreenLoadCallback<List<AccountExternalId>>(this) {
+ public void preDisplay(final List<AccountExternalId> result) {
+ AccountExternalId id = null;
+ for (AccountExternalId i : result) {
+ if (i.isScheme(SCHEME_USERNAME)) {
+ id = i;
+ break;
+ }
+ }
+ display(id);
+ }
+ });
+ }
+
+ private void display(AccountExternalId id) {
+ String user, pass;
+ if (id != null) {
+ user = id.getSchemeRest();
+ pass = id.getPassword();
+ } else {
+ user = null;
+ pass = null;
+ }
+ this.id = id;
+
+ Gerrit.getUserAccount().setUserName(user);
+
+ password.setText(pass != null ? pass : "");
+ password.setVisible(pass != null);
+
+ enableUI(true);
+ }
+
+ private void row(final Grid info, final int row, final String name,
+ final Widget field) {
+ final CellFormatter fmt = info.getCellFormatter();
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ info.setText(row, 1, name);
+ info.setWidget(row, 0, field);
+ fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().header());
+ } else {
+ info.setText(row, 0, name);
+ info.setWidget(row, 1, field);
+ fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().header());
+ }
+ }
+
+ private void doGeneratePassword() {
+ if (id != null) {
+ enableUI(false);
+ Util.ACCOUNT_SEC.generatePassword(id.getKey(),
+ new GerritCallback<AccountExternalId>() {
+ public void onSuccess(final AccountExternalId result) {
+ display(result);
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ enableUI(true);
+ }
+ });
+ }
+ }
+
+ private void doClearPassword() {
+ if (id != null) {
+ enableUI(false);
+ Util.ACCOUNT_SEC.clearPassword(id.getKey(),
+ new GerritCallback<AccountExternalId>() {
+ public void onSuccess(final AccountExternalId result) {
+ display(result);
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ enableUI(true);
+ }
+ });
+ }
+ }
+
+ private void enableUI(boolean on) {
+ on &= id != null;
+
+ generatePassword.setEnabled(on);
+ clearPassword.setVisible(on && id.getPassword() != null);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/PreferencePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index 583aa2a8a2..bb81f3f85f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/PreferencePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -14,14 +14,12 @@
package com.google.gerrit.client.account;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.CONTEXT_CHOICES;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.DEFAULT_CONTEXT;
import static com.google.gerrit.reviewdb.AccountGeneralPreferences.DEFAULT_PAGESIZE;
import static com.google.gerrit.reviewdb.AccountGeneralPreferences.PAGESIZE_CHOICES;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.WHOLE_FILE_CONTEXT;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -31,21 +29,20 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.Button;
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.Grid;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwtjsonrpc.client.VoidResult;
-class PreferencePanel extends Composite {
+public class MyPreferencesScreen extends SettingsScreen {
private CheckBox showSiteHeader;
private CheckBox useFlashClipboard;
- private ListBox defaultContext;
+ private CheckBox copySelfOnEmails;
private ListBox maximumPageSize;
private Button save;
- PreferencePanel() {
- final FlowPanel body = new FlowPanel();
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
final ClickHandler onClickSave = new ClickHandler() {
@Override
@@ -66,24 +63,15 @@ class PreferencePanel extends Composite {
useFlashClipboard = new CheckBox(Util.C.useFlashClipboard());
useFlashClipboard.addClickHandler(onClickSave);
+ copySelfOnEmails = new CheckBox(Util.C.copySelfOnEmails());
+ copySelfOnEmails.addClickHandler(onClickSave);
+
maximumPageSize = new ListBox();
for (final short v : PAGESIZE_CHOICES) {
maximumPageSize.addItem(Util.M.rowsPerPage(v), String.valueOf(v));
}
maximumPageSize.addChangeHandler(onChangeSave);
- defaultContext = new ListBox();
- for (final short v : CONTEXT_CHOICES) {
- final String label;
- if (v == WHOLE_FILE_CONTEXT) {
- label = Util.C.contextWholeFile();
- } else {
- label = Util.M.lines(v);
- }
- defaultContext.addItem(label, String.valueOf(v));
- }
- defaultContext.addChangeHandler(onChangeSave);
-
final int labelIdx, fieldIdx;
if (LocaleInfo.getCurrentLocale().isRTL()) {
labelIdx = 1;
@@ -103,15 +91,15 @@ class PreferencePanel extends Composite {
formGrid.setWidget(row, fieldIdx, useFlashClipboard);
row++;
- formGrid.setText(row, labelIdx, Util.C.maximumPageSizeFieldLabel());
- formGrid.setWidget(row, fieldIdx, maximumPageSize);
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, copySelfOnEmails);
row++;
- formGrid.setText(row, labelIdx, Util.C.defaultContextFieldLabel());
- formGrid.setWidget(row, fieldIdx, defaultContext);
+ formGrid.setText(row, labelIdx, Util.C.maximumPageSizeFieldLabel());
+ formGrid.setWidget(row, fieldIdx, maximumPageSize);
row++;
- body.add(formGrid);
+ add(formGrid);
save = new Button(Util.C.buttonSaveChanges());
save.setEnabled(false);
@@ -121,18 +109,15 @@ class PreferencePanel extends Composite {
doSave();
}
});
- body.add(save);
-
- initWidget(body);
+ add(save);
}
@Override
protected void onLoad() {
super.onLoad();
- Util.ACCOUNT_SVC.myAccount(new GerritCallback<Account>() {
- public void onSuccess(final Account result) {
+ Util.ACCOUNT_SVC.myAccount(new ScreenLoadCallback<Account>(this) {
+ public void preDisplay(final Account result) {
display(result.getGeneralPreferences());
- enable(true);
}
});
}
@@ -140,15 +125,15 @@ class PreferencePanel extends Composite {
private void enable(final boolean on) {
showSiteHeader.setEnabled(on);
useFlashClipboard.setEnabled(on);
+ copySelfOnEmails.setEnabled(on);
maximumPageSize.setEnabled(on);
- defaultContext.setEnabled(on);
}
private void display(final AccountGeneralPreferences p) {
showSiteHeader.setValue(p.isShowSiteHeader());
useFlashClipboard.setValue(p.isUseFlashClipboard());
+ copySelfOnEmails.setValue(p.isCopySelfOnEmails());
setListBox(maximumPageSize, DEFAULT_PAGESIZE, p.getMaximumPageSize());
- setListBox(defaultContext, DEFAULT_CONTEXT, p.getDefaultContext());
}
private void setListBox(final ListBox f, final short defaultValue,
@@ -177,8 +162,8 @@ class PreferencePanel extends Composite {
final AccountGeneralPreferences p = new AccountGeneralPreferences();
p.setShowSiteHeader(showSiteHeader.getValue());
p.setUseFlashClipboard(useFlashClipboard.getValue());
+ p.setCopySelfOnEmails(copySelfOnEmails.getValue());
p.setMaximumPageSize(getListBox(maximumPageSize, DEFAULT_PAGESIZE));
- p.setDefaultContext(getListBox(defaultContext, DEFAULT_CONTEXT));
enable(false);
save.setEnabled(false);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
new file mode 100644
index 0000000000..65c2590b0e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
@@ -0,0 +1,78 @@
+// 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.account;
+
+import static com.google.gerrit.client.FormatUtil.mediumFormat;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+
+public class MyProfileScreen extends SettingsScreen {
+ private int labelIdx, fieldIdx;
+ private Grid info;
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ labelIdx = 1;
+ fieldIdx = 0;
+ } else {
+ labelIdx = 0;
+ fieldIdx = 1;
+ }
+
+ info = new Grid(5, 2);
+ info.setStyleName(Gerrit.RESOURCES.css().infoBlock());
+ info.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
+ add(info);
+
+ infoRow(0, Util.C.userName());
+ infoRow(1, Util.C.fullName());
+ infoRow(2, Util.C.preferredEmail());
+ infoRow(3, Util.C.registeredOn());
+ infoRow(4, Util.C.accountId());
+
+ final CellFormatter fmt = info.getCellFormatter();
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(4, 0, Gerrit.RESOURCES.css().bottomheader());
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ display(Gerrit.getUserAccount());
+ display();
+ }
+
+ private void infoRow(final int row, final String name) {
+ info.setText(row, labelIdx, name);
+ info.getCellFormatter().addStyleName(row, 0,
+ Gerrit.RESOURCES.css().header());
+ }
+
+ void display(final Account account) {
+ info.setWidget(0, fieldIdx, new UsernameField());
+ info.setText(1, fieldIdx, account.getFullName());
+ info.setText(2, fieldIdx, account.getPreferredEmail());
+ info.setText(3, fieldIdx, mediumFormat(account.getRegisteredOn()));
+ info.setText(4, fieldIdx, account.getId().toString());
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/MineStarredScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MySshKeysScreen.java
index df4d30d40a..b1d6d82889 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/MineStarredScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MySshKeysScreen.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2008 The Android Open Source Project
+// 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.
@@ -12,27 +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.account;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.common.PageLinks;
-
-
-public class MineStarredScreen extends MineSingleListScreen {
- public MineStarredScreen() {
- super(PageLinks.MINE_STARRED);
- }
+public class MySshKeysScreen extends SettingsScreen {
+ private SshPanel panel;
@Override
protected void onInitUI() {
super.onInitUI();
- setWindowTitle(Gerrit.C.menuMyStarredChanges());
- setPageTitle(Util.C.starredHeading());
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- Util.LIST_SVC.myStarredChanges(loadCallback());
+ panel = new SshPanel() {
+ @Override
+ void display() {
+ MySshKeysScreen.this.display();
+ }
+ };
+ add(panel);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ProjectWatchPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
index 9fb3748355..28099bf08f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ProjectWatchPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
@@ -16,6 +16,7 @@ package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.ProjectLink;
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
@@ -36,10 +37,12 @@ import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button;
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.Grid;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -47,44 +50,45 @@ import com.google.gwtjsonrpc.client.VoidResult;
import java.util.HashSet;
import java.util.List;
-class ProjectWatchPanel extends Composite {
+public class MyWatchedProjectsScreen extends SettingsScreen {
private WatchTable watches;
private Button addNew;
+ private NpTextBox nameBox;
private SuggestBox nameTxt;
+ private NpTextBox filterTxt;
private Button delSel;
private boolean submitOnSelection;
- ProjectWatchPanel() {
- final FlowPanel body = new FlowPanel();
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
{
- final FlowPanel fp = new FlowPanel();
-
- final NpTextBox box = new NpTextBox();
- nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), box);
- box.setVisibleLength(50);
- box.setText(Util.C.defaultProjectName());
- box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- box.addFocusHandler(new FocusHandler() {
+ nameBox = new NpTextBox();
+ nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), nameBox);
+ nameBox.setVisibleLength(50);
+ nameBox.setText(Util.C.defaultProjectName());
+ nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ nameBox.addFocusHandler(new FocusHandler() {
@Override
public void onFocus(FocusEvent event) {
- if (Util.C.defaultProjectName().equals(box.getText())) {
- box.setText("");
- box.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ if (Util.C.defaultProjectName().equals(nameBox.getText())) {
+ nameBox.setText("");
+ nameBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
}
}
});
- box.addBlurHandler(new BlurHandler() {
+ nameBox.addBlurHandler(new BlurHandler() {
@Override
public void onBlur(BlurEvent event) {
- if ("".equals(box.getText())) {
- box.setText(Util.C.defaultProjectName());
- box.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ if ("".equals(nameBox.getText())) {
+ nameBox.setText(Util.C.defaultProjectName());
+ nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
}
}
});
- box.addKeyPressHandler(new KeyPressHandler() {
+ nameBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
submitOnSelection = false;
@@ -107,7 +111,37 @@ class ProjectWatchPanel extends Composite {
}
}
});
- fp.add(nameTxt);
+
+ filterTxt = new NpTextBox();
+ filterTxt.setVisibleLength(50);
+ filterTxt.setText(Util.C.defaultFilter());
+ filterTxt.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ filterTxt.addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ if (Util.C.defaultFilter().equals(filterTxt.getText())) {
+ filterTxt.setText("");
+ filterTxt.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ }
+ }
+ });
+ filterTxt.addBlurHandler(new BlurHandler() {
+ @Override
+ public void onBlur(BlurEvent event) {
+ if ("".equals(filterTxt.getText())) {
+ filterTxt.setText(Util.C.defaultFilter());
+ filterTxt.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
+ }
+ }
+ });
+ filterTxt.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ doAddNew();
+ }
+ }
+ });
addNew = new Button(Util.C.buttonWatchProject());
addNew.addClickHandler(new ClickHandler() {
@@ -116,26 +150,40 @@ class ProjectWatchPanel extends Composite {
doAddNew();
}
});
+
+ final Grid grid = new Grid(2, 2);
+ grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
+ grid.setText(0, 0, Util.C.watchedProjectName());
+ grid.setWidget(0, 1, nameTxt);
+
+ grid.setText(1, 0, Util.C.watchedProjectFilter());
+ grid.setWidget(1, 1, filterTxt);
+
+ final CellFormatter fmt = grid.getCellFormatter();
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().header());
+ fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().header());
+ fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
+
+ final FlowPanel fp = new FlowPanel();
+ fp.setStyleName(Gerrit.RESOURCES.css().addWatchPanel());
+ fp.add(grid);
fp.add(addNew);
- body.add(fp);
+ add(fp);
}
watches = new WatchTable();
- body.add(watches);
- {
- final FlowPanel fp = new FlowPanel();
- delSel = new Button(Util.C.buttonDeleteSshKey());
- delSel.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- watches.deleteChecked();
- }
- });
- fp.add(delSel);
- body.add(fp);
- }
+ add(watches);
- initWidget(body);
+ delSel = new Button(Util.C.buttonDeleteSshKey());
+ delSel.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ watches.deleteChecked();
+ }
+ });
+ add(delSel);
}
void doAddNew() {
@@ -145,11 +193,23 @@ class ProjectWatchPanel extends Composite {
return;
}
+ String filter = filterTxt.getText();
+ if (filter == null || filter.isEmpty()
+ || filter.equals(Util.C.defaultFilter())) {
+ filter = null;
+ }
+
addNew.setEnabled(false);
- Util.ACCOUNT_SVC.addProjectWatch(projectName,
+ nameBox.setEnabled(false);
+ filterTxt.setEnabled(false);
+
+ Util.ACCOUNT_SVC.addProjectWatch(projectName, filter,
new GerritCallback<AccountProjectWatchInfo>() {
public void onSuccess(final AccountProjectWatchInfo result) {
addNew.setEnabled(true);
+ nameBox.setEnabled(true);
+ filterTxt.setEnabled(true);
+
nameTxt.setText("");
watches.insertWatch(result);
}
@@ -157,6 +217,9 @@ class ProjectWatchPanel extends Composite {
@Override
public void onFailure(final Throwable caught) {
addNew.setEnabled(true);
+ nameBox.setEnabled(true);
+ filterTxt.setEnabled(true);
+
super.onFailure(caught);
}
});
@@ -166,8 +229,9 @@ class ProjectWatchPanel extends Composite {
protected void onLoad() {
super.onLoad();
Util.ACCOUNT_SVC
- .myProjectWatch(new GerritCallback<List<AccountProjectWatchInfo>>() {
- public void onSuccess(final List<AccountProjectWatchInfo> result) {
+ .myProjectWatch(new ScreenLoadCallback<List<AccountProjectWatchInfo>>(
+ this) {
+ public void preDisplay(final List<AccountProjectWatchInfo> result) {
watches.display(result);
}
});
@@ -177,8 +241,7 @@ class ProjectWatchPanel extends Composite {
WatchTable() {
table.setWidth("");
table.insertRow(1);
- table.setText(0, 2, com.google.gerrit.client.changes.Util.C
- .changeTableColumnProject());
+ table.setText(0, 2, Util.C.watchedProjectName());
table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications());
final FlexCellFormatter fmt = table.getFlexCellFormatter();
@@ -253,8 +316,16 @@ class ProjectWatchPanel extends Composite {
}
void populate(final int row, final AccountProjectWatchInfo k) {
+ final FlowPanel fp = new FlowPanel();
+ fp.add(new ProjectLink(k.getProject().getNameKey(), Status.NEW));
+ if (k.getWatch().getFilter() != null) {
+ Label filter = new Label(k.getWatch().getFilter());
+ filter.setStyleName(Gerrit.RESOURCES.css().watchedProjectFilter());
+ fp.add(filter);
+ }
+
table.setWidget(row, 1, new CheckBox());
- table.setWidget(row, 2, new ProjectLink(k.getProject().getNameKey(), Status.NEW));
+ table.setWidget(row, 2, fp);
{
final CheckBox notifyNewChanges = new CheckBox();
notifyNewChanges.addClickHandler(new ClickHandler() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
index f43d1ac2a2..8c99d45389 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
@@ -20,9 +20,13 @@ import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Account.FieldName;
+import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
public class RegisterScreen extends AccountScreen {
private final String nextToken;
@@ -65,6 +69,36 @@ public class RegisterScreen extends AccountScreen {
});
formBody.add(contactGroup);
+ if (Gerrit.getUserAccount().getUserName() == null
+ && Gerrit.getConfig().canEdit(FieldName.USER_NAME)) {
+ final FlowPanel fp = new FlowPanel();
+ fp.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
+ fp.add(new SmallHeading(Util.C.welcomeUsernameHeading()));
+
+ final Grid userInfo = new Grid(1, 2);
+ final CellFormatter fmt = userInfo.getCellFormatter();
+ userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
+ userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
+ fp.add(userInfo);
+
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().bottomheader());
+
+ UsernameField field = new UsernameField();
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ userInfo.setText(0, 1, Util.C.userName());
+ userInfo.setWidget(0, 0, field);
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().header());
+ } else {
+ userInfo.setText(0, 0, Util.C.userName());
+ userInfo.setWidget(0, 1, field);
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().header());
+ }
+
+ formBody.add(fp);
+ }
+
final FlowPanel sshKeyGroup = new FlowPanel();
sshKeyGroup.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
sshKeyGroup.add(new SmallHeading(Util.C.welcomeSshKeyHeading()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
new file mode 100644
index 0000000000..93560182ef
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -0,0 +1,43 @@
+// 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.account;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.ui.MenuScreen;
+import com.google.gerrit.common.PageLinks;
+
+public abstract class SettingsScreen extends MenuScreen {
+ public SettingsScreen() {
+ setRequiresSignIn(true);
+
+ link(Util.C.tabAccountSummary(), PageLinks.SETTINGS);
+ link(Util.C.tabPreferences(), PageLinks.SETTINGS_PREFERENCES);
+ link(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
+ link(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
+ link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
+ link(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
+ link(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
+ link(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);
+ if (Gerrit.getConfig().isUseContributorAgreements()) {
+ link(Util.C.tabAgreements(), PageLinks.SETTINGS_AGREEMENTS);
+ }
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ setPageTitle(Util.C.settingsHeading());
+ }
+}
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 a320818a86..fba55b3c00 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
@@ -41,6 +41,7 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtjsonrpc.client.RemoteJsonException;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -69,9 +70,10 @@ class SshPanel extends Composite {
private Panel serverKeys;
+ private int loadCount;
+
SshPanel() {
final FlowPanel body = new FlowPanel();
- body.add(new UsernamePanel());
showAddKeyBlock = new Button(Util.C.buttonShowAddSshKey());
showAddKeyBlock.addClickHandler(new ClickHandler() {
@@ -319,6 +321,9 @@ class SshPanel extends Composite {
if (result.isEmpty() && keys.isVisible()) {
showAddKeyBlock(true);
}
+ if (++loadCount == 2) {
+ display();
+ }
}
});
@@ -328,10 +333,16 @@ class SshPanel extends Composite {
for (final SshHostKey keyInfo : result) {
serverKeys.add(new SshHostKeyPanel(keyInfo));
}
+ if (++loadCount == 2) {
+ display();
+ }
}
});
}
+ void display() {
+ }
+
@Override
protected void onUnload() {
if (appletLoadTimer != null) {
@@ -437,7 +448,11 @@ class SshPanel extends Composite {
fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().sshKeyPanelInvalid());
}
table.setText(row, 3, k.getAlgorithm());
- table.setText(row, 4, elide(k.getEncodedKey(), 40));
+
+ CopyableLabel keyLabel = new CopyableLabel(k.getSshPublicKey());
+ keyLabel.setPreviewText(elide(k.getEncodedKey(), 40));
+ table.setWidget(row, 4, keyLabel);
+
table.setText(row, 5, k.getComment());
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
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
new file mode 100644
index 0000000000..93ccb7431a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -0,0 +1,183 @@
+// 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.account;
+
+import com.google.gerrit.client.ErrorDialog;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.TextSaveButtonListener;
+import com.google.gerrit.common.errors.InvalidUserNameException;
+import com.google.gerrit.reviewdb.Account;
+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.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+import com.google.gwtjsonrpc.client.VoidResult;
+
+class UsernameField extends Composite {
+ private CopyableLabel userNameLbl;
+ private NpTextBox userNameTxt;
+ private Button setUserName;
+
+ UsernameField() {
+ String user = Gerrit.getUserAccount().getUserName();
+ userNameLbl = new CopyableLabel(user != null ? user : "");
+ userNameLbl.setStyleName(Gerrit.RESOURCES.css().accountUsername());
+
+ if (user != null || !canEditUserName()) {
+ initWidget(userNameLbl);
+
+ } else {
+ final FlowPanel body = new FlowPanel();
+ initWidget(body);
+ setStyleName(Gerrit.RESOURCES.css().usernameField());
+
+ userNameTxt = new NpTextBox();
+ userNameTxt.addKeyPressHandler(new UserNameValidator());
+ userNameTxt.addStyleName(Gerrit.RESOURCES.css().accountUsername());
+ userNameTxt.setVisibleLength(16);
+ userNameTxt.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ doSetUserName();
+ }
+ }
+ });
+
+ setUserName = new Button(Util.C.buttonSetUserName());
+ setUserName.setVisible(canEditUserName());
+ setUserName.setEnabled(false);
+ setUserName.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ doSetUserName();
+ }
+ });
+ new TextSaveButtonListener(userNameTxt, setUserName);
+
+ userNameLbl.setVisible(false);
+ body.add(userNameLbl);
+ body.add(userNameTxt);
+ body.add(setUserName);
+ }
+ }
+
+ private boolean canEditUserName() {
+ return Gerrit.getConfig().canEdit(Account.FieldName.USER_NAME);
+ }
+
+ private void doSetUserName() {
+ if (!canEditUserName()) {
+ return;
+ }
+
+ 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>() {
+ public void onSuccess(final VoidResult result) {
+ Gerrit.getUserAccount().setUserName(newUserName);
+ userNameLbl.setText(newUserName);
+ userNameLbl.setVisible(true);
+ userNameTxt.setVisible(false);
+ setUserName.setVisible(false);
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ enableUI(true);
+ if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
+ invalidUserName();
+ } else {
+ super.onFailure(caught);
+ }
+ }
+ });
+ }
+
+ private void invalidUserName() {
+ new ErrorDialog(Util.C.invalidUserName()).center();
+ }
+
+ private void enableUI(final boolean on) {
+ userNameTxt.setEnabled(on);
+ setUserName.setEnabled(on);
+ }
+
+ private final class UserNameValidator implements KeyPressHandler {
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ final char code = event.getCharCode();
+ switch (code) {
+ case KeyCodes.KEY_ALT:
+ case KeyCodes.KEY_BACKSPACE:
+ case KeyCodes.KEY_CTRL:
+ case KeyCodes.KEY_DELETE:
+ case KeyCodes.KEY_DOWN:
+ case KeyCodes.KEY_END:
+ case KeyCodes.KEY_ENTER:
+ case KeyCodes.KEY_ESCAPE:
+ case KeyCodes.KEY_HOME:
+ case KeyCodes.KEY_LEFT:
+ case KeyCodes.KEY_PAGEDOWN:
+ case KeyCodes.KEY_PAGEUP:
+ case KeyCodes.KEY_RIGHT:
+ case KeyCodes.KEY_SHIFT:
+ case KeyCodes.KEY_TAB:
+ case KeyCodes.KEY_UP:
+ // Allow these, even if one of their assigned codes is
+ // identical to an ASCII character we do not want to
+ // allow in the box.
+ //
+ // We still want to let the user move around the input box
+ // with their arrow keys, or to move between fields using tab.
+ // Invalid characters introduced will be caught through the
+ // server's own validation of the input data.
+ //
+ break;
+
+ default:
+ final TextBox box = (TextBox) event.getSource();
+ final String re;
+ if (box.getCursorPos() == 0)
+ re = Account.USER_NAME_PATTERN_FIRST;
+ else
+ re = Account.USER_NAME_PATTERN_REST;
+ if (!String.valueOf(code).matches("^" + re + "$")) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java
deleted file mode 100644
index 2ec20d0f5c..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java
+++ /dev/null
@@ -1,297 +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.account;
-
-import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
-
-import com.google.gerrit.client.ErrorDialog;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.TextSaveButtonListener;
-import com.google.gerrit.common.errors.InvalidUserNameException;
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountExternalId;
-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.i18n.client.LocaleInfo;
-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.Grid;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwtexpui.clippy.client.CopyableLabel;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
-import com.google.gwtjsonrpc.client.VoidResult;
-
-import java.util.List;
-
-public class UsernamePanel extends Composite {
- private NpTextBox userNameTxt;
- private Button changeUserName;
-
- private CopyableLabel password;
- private Button generatePassword;
-
- private AccountExternalId.Key idKey;
-
- UsernamePanel() {
- final FlowPanel body = new FlowPanel();
- initWidget(body);
-
- userNameTxt = new NpTextBox();
- userNameTxt.addKeyPressHandler(new UserNameValidator());
- userNameTxt.addStyleName(Gerrit.RESOURCES.css().sshPanelUsername());
- userNameTxt.setVisibleLength(16);
- userNameTxt.setReadOnly(!canEditUserName());
- userNameTxt.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (event.getCharCode() == KeyCodes.KEY_ENTER) {
- doChangeUserName();
- }
- }
- });
-
- changeUserName = new Button(Util.C.buttonChangeUserName());
- changeUserName.setVisible(canEditUserName());
- changeUserName.setEnabled(false);
- changeUserName.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- doChangeUserName();
- }
- });
- new TextSaveButtonListener(userNameTxt, changeUserName);
-
- password = new CopyableLabel("");
- password.addStyleName(Gerrit.RESOURCES.css().sshPanelPassword());
- password.setVisible(false);
-
- generatePassword = new Button(Util.C.buttonGeneratePassword());
- generatePassword.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- doGeneratePassword();
- }
- });
-
- final Grid userInfo = new Grid(2, 3);
- final CellFormatter fmt = userInfo.getCellFormatter();
- userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
- userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
- body.add(userInfo);
-
- row(userInfo, 0, Util.C.userName(), userNameTxt, changeUserName);
- row(userInfo, 1, Util.C.password(), password, generatePassword);
-
- fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
- fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().topmost());
-
- fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
-
- enableUI(false);
- Util.ACCOUNT_SEC
- .myExternalIds(new GerritCallback<List<AccountExternalId>>() {
- public void onSuccess(final List<AccountExternalId> result) {
- AccountExternalId id = null;
- for (AccountExternalId i : result) {
- if (i.isScheme(SCHEME_USERNAME)) {
- id = i;
- break;
- }
- }
- display(id);
- }
- });
- }
-
- private void display(AccountExternalId id) {
- String user, pass;
- if (id != null) {
- idKey = id.getKey();
- user = id.getSchemeRest();
- pass = id.getPassword();
- } else {
- idKey = null;
- user = null;
- pass = null;
- }
-
- Gerrit.getUserAccount().setUserName(user);
- userNameTxt.setText(user);
- userNameTxt.setEnabled(true);
- generatePassword.setEnabled(idKey != null);
-
- if (pass != null) {
- password.setText(pass);
- password.setVisible(true);
- } else {
- password.setVisible(false);
- }
- }
-
- private void row(final Grid info, final int row, final String name,
- final Widget field1, final Widget field2) {
- final CellFormatter fmt = info.getCellFormatter();
- if (LocaleInfo.getCurrentLocale().isRTL()) {
- info.setText(row, 2, name);
- info.setWidget(row, 1, field1);
- info.setWidget(row, 0, field2);
- fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
- fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().header());
- } else {
- info.setText(row, 0, name);
- info.setWidget(row, 1, field1);
- info.setWidget(row, 2, field2);
- fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
- fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().header());
- }
- }
-
- private boolean canEditUserName() {
- return Gerrit.getConfig().canEdit(Account.FieldName.USER_NAME);
- }
-
- void doChangeUserName() {
- if (!canEditUserName()) {
- return;
- }
-
- 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>() {
- public void onSuccess(final VoidResult result) {
- Gerrit.getUserAccount().setUserName(newUserName);
- enableUI(true);
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- enableUI(true);
- if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
- invalidUserName();
- } else {
- super.onFailure(caught);
- }
- }
- });
- }
-
- void invalidUserName() {
- userNameTxt.setFocus(true);
- new ErrorDialog(Util.C.invalidUserName()).center();
- }
-
- void doGeneratePassword() {
- if (idKey == null) {
- return;
- }
-
- enableUI(false);
-
- Util.ACCOUNT_SEC.generatePassword(idKey,
- new GerritCallback<AccountExternalId>() {
- public void onSuccess(final AccountExternalId result) {
- enableUI(true);
- display(result);
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- enableUI(true);
- if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
- invalidUserName();
- } else {
- super.onFailure(caught);
- }
- }
- });
- }
-
- private void enableUI(final boolean on) {
- userNameTxt.setEnabled(on);
- changeUserName.setEnabled(on);
- generatePassword.setEnabled(on && idKey != null);
- }
-
- private final class UserNameValidator implements KeyPressHandler {
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- final char code = event.getCharCode();
- switch (code) {
- case KeyCodes.KEY_ALT:
- case KeyCodes.KEY_BACKSPACE:
- case KeyCodes.KEY_CTRL:
- case KeyCodes.KEY_DELETE:
- case KeyCodes.KEY_DOWN:
- case KeyCodes.KEY_END:
- case KeyCodes.KEY_ENTER:
- case KeyCodes.KEY_ESCAPE:
- case KeyCodes.KEY_HOME:
- case KeyCodes.KEY_LEFT:
- case KeyCodes.KEY_PAGEDOWN:
- case KeyCodes.KEY_PAGEUP:
- case KeyCodes.KEY_RIGHT:
- case KeyCodes.KEY_SHIFT:
- case KeyCodes.KEY_TAB:
- case KeyCodes.KEY_UP:
- // Allow these, even if one of their assigned codes is
- // identical to an ASCII character we do not want to
- // allow in the box.
- //
- // We still want to let the user move around the input box
- // with their arrow keys, or to move between fields using tab.
- // Invalid characters introduced will be caught through the
- // server's own validation of the input data.
- //
- break;
-
- default:
- final TextBox box = (TextBox) event.getSource();
- final String re;
- if (box.getCursorPos() == 0)
- re = Account.USER_NAME_PATTERN_FIRST;
- else
- re = Account.USER_NAME_PATTERN_REST;
- if (!String.valueOf(code).matches("^" + re + "$")) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ValidateEmailScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ValidateEmailScreen.java
index 79f67f3755..4e54e85d72 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ValidateEmailScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ValidateEmailScreen.java
@@ -30,7 +30,7 @@ public class ValidateEmailScreen extends AccountScreen {
@Override
protected void onInitUI() {
super.onInitUI();
- setPageTitle(Util.C.accountSettingsHeading());
+ setPageTitle(Util.C.settingsHeading());
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 75d6931f4b..14449f9cb8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -17,6 +17,7 @@ package com.google.gerrit.client.admin;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.Hyperlink;
@@ -43,7 +44,6 @@ import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.ui.Button;
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.Grid;
import com.google.gwt.user.client.ui.ListBox;
@@ -59,9 +59,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
-public class ProjectRightsPanel extends Composite {
- private Project.NameKey projectName;
-
+public class ProjectAccessScreen extends ProjectScreen {
private Panel parentPanel;
private Hyperlink parentName;
@@ -74,26 +72,25 @@ public class ProjectRightsPanel extends Composite {
private NpTextBox nameTxtBox;
private SuggestBox nameTxt;
private NpTextBox referenceTxt;
+ private FlowPanel addPanel;
- private final FlowPanel addPanel = new FlowPanel();
-
- public ProjectRightsPanel(final Project.NameKey toShow) {
- projectName = toShow;
+ public ProjectAccessScreen(final Project.NameKey toShow) {
+ super(toShow);
+ }
- final FlowPanel body = new FlowPanel();
- initParent(body);
- initRights(body);
- initWidget(body);
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ initParent();
+ initRights();
}
@Override
protected void onLoad() {
- enableForm(false);
super.onLoad();
-
- Util.PROJECT_SVC.projectDetail(projectName,
- new GerritCallback<ProjectDetail>() {
- public void onSuccess(final ProjectDetail result) {
+ Util.PROJECT_SVC.projectDetail(getProjectKey(),
+ new ScreenLoadCallback<ProjectDetail>(this) {
+ public void preDisplay(final ProjectDetail result) {
enableForm(true);
display(result);
}
@@ -112,16 +109,17 @@ public class ProjectRightsPanel extends Composite {
rangeMaxBox.setEnabled(canAdd);
}
- private void initParent(final Panel body) {
+ private void initParent() {
parentPanel = new VerticalPanel();
parentPanel.add(new SmallHeading(Util.C.headingParentProjectName()));
parentName = new Hyperlink("", "");
parentPanel.add(parentName);
- body.add(parentPanel);
+ add(parentPanel);
}
- private void initRights(final Panel body) {
+ private void initRights() {
+ addPanel = new FlowPanel();
addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
final Grid addGrid = new Grid(5, 2);
@@ -144,7 +142,7 @@ public class ProjectRightsPanel extends Composite {
for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
.getActionTypes()) {
final ApprovalCategory c = at.getCategory();
- if (Gerrit.getConfig().getWildProject().equals(projectName)
+ if (Gerrit.getConfig().getWildProject().equals(getProjectKey())
&& ApprovalCategory.OWN.equals(c.getId())) {
// Giving out control of the WILD_PROJECT to other groups beyond
// Administrators is dangerous. Having control over WILD_PROJECT
@@ -228,10 +226,10 @@ public class ProjectRightsPanel extends Composite {
}
});
- body.add(new SmallHeading(Util.C.headingAccessRights()));
- body.add(rights);
- body.add(delRight);
- body.add(addPanel);
+ add(new SmallHeading(Util.C.headingAccessRights()));
+ add(rights);
+ add(delRight);
+ add(addPanel);
if (catBox.getItemCount() > 0) {
catBox.setSelectedIndex(0);
@@ -250,8 +248,7 @@ public class ProjectRightsPanel extends Composite {
}
parentPanel.setVisible(!isWild);
- parentName.setTargetHistoryToken(Dispatcher.toProjectAdmin(parent,
- ProjectAdminScreen.ACCESS_TAB));
+ parentName.setTargetHistoryToken(Dispatcher.toProjectAdmin(parent, ACCESS));
parentName.setText(parent.get());
rights.display(result.groups, result.rights);
@@ -262,7 +259,7 @@ public class ProjectRightsPanel extends Composite {
private void doDeleteRefRights(final HashSet<RefRight.Key> refRightIds) {
if (!refRightIds.isEmpty()) {
- Util.PROJECT_SVC.deleteRight(projectName, refRightIds,
+ Util.PROJECT_SVC.deleteRight(getProjectKey(), refRightIds,
new GerritCallback<ProjectDetail>() {
@Override
public void onSuccess(final ProjectDetail result) {
@@ -338,8 +335,8 @@ public class ProjectRightsPanel extends Composite {
}
addRight.setEnabled(false);
- Util.PROJECT_SVC.addRight(projectName, at.getCategory().getId(), groupName,
- refPattern, min.getValue(), max.getValue(),
+ Util.PROJECT_SVC.addRight(getProjectKey(), at.getCategory().getId(),
+ groupName, refPattern, min.getValue(), max.getValue(),
new GerritCallback<ProjectDetail>() {
public void onSuccess(final ProjectDetail result) {
addRight.setEnabled(true);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java
deleted file mode 100644
index 6be86572e4..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java
+++ /dev/null
@@ -1,119 +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.admin;
-
-import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.common.data.ProjectDetail;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gwt.event.logical.shared.SelectionEvent;
-import com.google.gwt.event.logical.shared.SelectionHandler;
-import com.google.gwt.user.client.ui.LazyPanel;
-import com.google.gwt.user.client.ui.TabPanel;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ProjectAdminScreen extends Screen {
- static final String INFO_TAB = "info";
- static final String BRANCH_TAB = "branches";
- static final String ACCESS_TAB = "access";
-
- private final Project.NameKey projectName;
- private final String initialTabToken;
-
- private List<String> tabTokens;
- private TabPanel tabs;
-
- public ProjectAdminScreen(final Project.NameKey toShow, final String token) {
- projectName = toShow;
- initialTabToken = token;
- }
-
- @Override
- public boolean displayToken(String token) {
- final int tabIdx = tabTokens.indexOf(token);
- if (0 <= tabIdx) {
- tabs.selectTab(tabIdx);
- setToken(token);
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- protected void onLoad() {
- super.onLoad();
- Util.PROJECT_SVC.projectDetail(projectName,
- new ScreenLoadCallback<ProjectDetail>(this) {
- @Override
- protected void preDisplay(final ProjectDetail result) {
- display(result);
- tabs.selectTab(tabTokens.indexOf(initialTabToken));
- }
- });
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- tabTokens = new ArrayList<String>();
- tabs = new TabPanel();
- tabs.setWidth("98%");
- add(tabs);
-
- tabs.add(new LazyPanel() {
- @Override
- protected ProjectInfoPanel createWidget() {
- return new ProjectInfoPanel(projectName);
- }
- }, Util.C.projectAdminTabGeneral());
- tabTokens.add(Dispatcher.toProjectAdmin(projectName, INFO_TAB));
-
- if (!Gerrit.getConfig().getWildProject().equals(projectName)) {
- tabs.add(new LazyPanel() {
- @Override
- protected ProjectBranchesPanel createWidget() {
- return new ProjectBranchesPanel(projectName);
- }
- }, Util.C.projectAdminTabBranches());
- tabTokens.add(Dispatcher.toProjectAdmin(projectName, BRANCH_TAB));
- }
-
- tabs.add(new LazyPanel() {
- @Override
- protected ProjectRightsPanel createWidget() {
- return new ProjectRightsPanel(projectName);
- }
- }, Util.C.projectAdminTabAccess());
- tabTokens.add(Dispatcher.toProjectAdmin(projectName, ACCESS_TAB));
-
- tabs.addSelectionHandler(new SelectionHandler<Integer>() {
- @Override
- public void onSelection(final SelectionEvent<Integer> event) {
- setToken(tabTokens.get(event.getSelectedItem()));
- }
- });
- }
-
-
- private void display(final ProjectDetail result) {
- final Project project = result.project;
- setPageTitle(Util.M.project(project.getName()));
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 090b000867..8bff3e5ff5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -14,11 +14,14 @@
package com.google.gerrit.client.admin;
+import com.google.gerrit.client.ConfirmationCallback;
+import com.google.gerrit.client.ConfirmationDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.FancyFlexTable;
-import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.common.data.GitwebLink;
+import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.InvalidRevisionException;
import com.google.gerrit.reviewdb.Branch;
@@ -37,10 +40,9 @@ import com.google.gwt.user.client.DeferredCommand;
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.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.RemoteJsonException;
@@ -49,33 +51,24 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-public class ProjectBranchesPanel extends Composite {
- private Project.NameKey projectName;
-
+public class ProjectBranchesScreen extends ProjectScreen {
private BranchesTable branches;
private Button delBranch;
private Button addBranch;
private NpTextBox nameTxtBox;
private NpTextBox irevTxtBox;
+ private FlowPanel addPanel;
- private final FlowPanel addPanel = new FlowPanel();
-
- public ProjectBranchesPanel(final Project.NameKey toShow) {
- final FlowPanel body = new FlowPanel();
- initBranches(body);
- initWidget(body);
-
- projectName = toShow;
+ public ProjectBranchesScreen(final Project.NameKey toShow) {
+ super(toShow);
}
@Override
protected void onLoad() {
- enableForm(false);
super.onLoad();
-
- Util.PROJECT_SVC.listBranches(projectName,
- new GerritCallback<ListBranchesResult>() {
- public void onSuccess(final ListBranchesResult result) {
+ Util.PROJECT_SVC.listBranches(getProjectKey(),
+ new ScreenLoadCallback<ListBranchesResult>(this) {
+ public void preDisplay(final ListBranchesResult result) {
enableForm(true);
display(result.getBranches());
addPanel.setVisible(result.getCanAdd());
@@ -95,7 +88,11 @@ public class ProjectBranchesPanel extends Composite {
irevTxtBox.setEnabled(on);
}
- private void initBranches(final Panel body) {
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ addPanel = new FlowPanel();
addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
final Grid addGrid = new Grid(2, 2);
@@ -186,9 +183,9 @@ public class ProjectBranchesPanel extends Composite {
}
});
- body.add(branches);
- body.add(delBranch);
- body.add(addPanel);
+ add(branches);
+ add(delBranch);
+ add(addPanel);
}
private void doAddNewBranch() {
@@ -216,7 +213,7 @@ public class ProjectBranchesPanel extends Composite {
}
addBranch.setEnabled(false);
- Util.PROJECT_SVC.addBranch(projectName, branchName, rev,
+ Util.PROJECT_SVC.addBranch(getProjectKey(), branchName, rev,
new GerritCallback<ListBranchesResult>() {
public void onSuccess(final ListBranchesResult result) {
addBranch.setEnabled(true);
@@ -264,31 +261,47 @@ public class ProjectBranchesPanel extends Composite {
}
void deleteChecked() {
+ final StringBuilder message = new StringBuilder();
+ message.append("<b>").append(Gerrit.C.branchDeletionConfirmationMessage()).append("</b>");
+ message.append("<p>");
final HashSet<Branch.NameKey> ids = new HashSet<Branch.NameKey>();
for (int row = 1; row < table.getRowCount(); row++) {
final Branch k = getRowItem(row);
if (k != null && table.getWidget(row, 1) instanceof CheckBox
&& ((CheckBox) table.getWidget(row, 1)).getValue()) {
+ if (!ids.isEmpty()) {
+ message.append(", <br>");
+ }
+ message.append(k.getName());
ids.add(k.getNameKey());
}
}
+ message.append("</p>");
if (ids.isEmpty()) {
return;
}
- Util.PROJECT_SVC.deleteBranch(projectName, ids,
- new GerritCallback<Set<Branch.NameKey>>() {
- public void onSuccess(final Set<Branch.NameKey> deleted) {
- for (int row = 1; row < table.getRowCount();) {
- final Branch k = getRowItem(row);
- if (k != null && deleted.contains(k.getNameKey())) {
- table.removeRow(row);
- } else {
- row++;
+ ConfirmationDialog confirmationDialog =
+ new ConfirmationDialog(Gerrit.C.branchDeletionDialogTitle(),
+ new HTML(message.toString()), new ConfirmationCallback() {
+ @Override
+ public void onOk() {
+ Util.PROJECT_SVC.deleteBranch(getProjectKey(), ids,
+ new GerritCallback<Set<Branch.NameKey>>() {
+ public void onSuccess(final Set<Branch.NameKey> deleted) {
+ for (int row = 1; row < table.getRowCount();) {
+ final Branch k = getRowItem(row);
+ if (k != null && deleted.contains(k.getNameKey())) {
+ table.removeRow(row);
+ } else {
+ row++;
+ }
+ }
}
- }
- }
- });
+ });
+ }
+ });
+ confirmationDialog.center();
}
void display(final List<Branch> result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 49566badd0..d308fa6c1a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -16,6 +16,7 @@ package com.google.gerrit.client.admin;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.data.ProjectDetail;
@@ -28,15 +29,12 @@ import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Button;
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.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextArea;
-public class ProjectInfoPanel extends Composite {
- private Project.NameKey projectName;
+public class ProjectInfoScreen extends ProjectScreen {
private Project project;
private Panel submitTypePanel;
@@ -49,7 +47,14 @@ public class ProjectInfoPanel extends Composite {
private NpTextArea descTxt;
private Button saveProject;
- public ProjectInfoPanel(final Project.NameKey toShow) {
+ public ProjectInfoScreen(final Project.NameKey toShow) {
+ super(toShow);
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
saveProject = new Button(Util.C.buttonSaveChanges());
saveProject.addClickHandler(new ClickHandler() {
@Override
@@ -58,28 +63,18 @@ public class ProjectInfoPanel extends Composite {
}
});
- final FlowPanel body = new FlowPanel();
- initDescription(body);
- initSubmitType(body);
- initAgreements(body);
- body.add(saveProject);
-
- initWidget(body);
- projectName = toShow;
+ initDescription();
+ initSubmitType();
+ initAgreements();
+ add(saveProject);
}
@Override
protected void onLoad() {
- enableForm(false, false, false);
- saveProject.setEnabled(false);
super.onLoad();
- refresh();
- }
-
- private void refresh() {
- Util.PROJECT_SVC.projectDetail(projectName,
- new GerritCallback<ProjectDetail>() {
- public void onSuccess(final ProjectDetail result) {
+ Util.PROJECT_SVC.projectDetail(getProjectKey(),
+ new ScreenLoadCallback<ProjectDetail>(this) {
+ public void preDisplay(final ProjectDetail result) {
enableForm(result.canModifyAgreements,
result.canModifyDescription, result.canModifyMergeType);
saveProject.setVisible(
@@ -102,7 +97,7 @@ public class ProjectInfoPanel extends Composite {
canModifyAgreements || canModifyDescription || canModifyMergeType);
}
- private void initDescription(final Panel body) {
+ private void initDescription() {
final VerticalPanel vp = new VerticalPanel();
vp.add(new SmallHeading(Util.C.headingDescription()));
@@ -111,11 +106,11 @@ public class ProjectInfoPanel extends Composite {
descTxt.setCharacterWidth(60);
vp.add(descTxt);
- body.add(vp);
+ add(vp);
new TextSaveButtonListener(descTxt, saveProject);
}
- private void initSubmitType(final Panel body) {
+ private void initSubmitType() {
submitTypePanel = new VerticalPanel();
submitTypePanel.add(new SmallHeading(Util.C.headingSubmitType()));
@@ -130,10 +125,10 @@ public class ProjectInfoPanel extends Composite {
}
});
submitTypePanel.add(submitType);
- body.add(submitTypePanel);
+ add(submitTypePanel);
}
- private void initAgreements(final Panel body) {
+ private void initAgreements() {
final ValueChangeHandler<Boolean> onChangeSave =
new ValueChangeHandler<Boolean>() {
@Override
@@ -153,7 +148,7 @@ public class ProjectInfoPanel extends Composite {
useSignedOffBy.addValueChangeHandler(onChangeSave);
agreementsPanel.add(useSignedOffBy);
- body.add(agreementsPanel);
+ add(agreementsPanel);
}
private void setSubmitType(final Project.SubmitType newSubmitType) {
@@ -203,12 +198,6 @@ public class ProjectInfoPanel extends Composite {
result.canModifyDescription, result.canModifyMergeType);
display(result);
}
-
- @Override
- public void onFailure(final Throwable caught) {
- refresh();
- super.onFailure(caught);
- }
});
}
}
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 642712af75..e676718ee3 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
@@ -105,7 +105,7 @@ public class ProjectListScreen extends Screen {
}
private String link(final Project item) {
- return Dispatcher.toProjectAdmin(item.getNameKey(), ProjectAdminScreen.INFO_TAB);
+ return Dispatcher.toProjectAdmin(item.getNameKey(), ProjectScreen.INFO);
}
void display(final List<Project> result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
new file mode 100644
index 0000000000..2df1ba39ae
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
@@ -0,0 +1,39 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.google.gerrit.client.admin;
+
+import static com.google.gerrit.client.Dispatcher.toProjectAdmin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.ui.MenuScreen;
+import com.google.gerrit.reviewdb.Project;
+
+public abstract class ProjectScreen extends MenuScreen {
+ public static final String INFO = "info";
+ public static final String BRANCH = "branches";
+ public static final String ACCESS = "access";
+
+ private final Project.NameKey name;
+
+ public ProjectScreen(final Project.NameKey toShow) {
+ name = toShow;
+
+ final boolean isWild = toShow.equals(Gerrit.getConfig().getWildProject());
+
+ link(Util.C.projectAdminTabGeneral(), toProjectAdmin(name, INFO));
+ if (!isWild) {
+ link(Util.C.projectAdminTabBranches(), toProjectAdmin(name, BRANCH));
+ }
+ link(Util.C.projectAdminTabAccess(), toProjectAdmin(name, ACCESS));
+ }
+
+ protected Project.NameKey getProjectKey() {
+ return name;
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ setPageTitle(Util.M.project(name.get()));
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
index 75a0c0d82d..2a83ed14bb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
@@ -21,19 +21,26 @@ import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.reviewdb.PatchSet;
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.gwt.user.client.DOM;
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.PopupPanel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtexpui.user.client.AutoCenterDialogBox;
-public class AbandonChangeDialog extends AutoCenterDialogBox {
+public class AbandonChangeDialog extends AutoCenterDialogBox implements CloseHandler<PopupPanel>{
private final FlowPanel panel;
private final NpTextArea message;
private final Button sendButton;
private final Button cancelButton;
private final PatchSet.Id psid;
+ private final AsyncCallback<ChangeDetail> callback;
+
+ private boolean buttonClicked = false;
public AbandonChangeDialog(final PatchSet.Id psi,
final AsyncCallback<ChangeDetail> callback) {
@@ -41,6 +48,7 @@ public class AbandonChangeDialog extends AutoCenterDialogBox {
setGlassEnabled(true);
psid = psi;
+ this.callback = callback;
addStyleName(Gerrit.RESOURCES.css().abandonChangeDialog());
setText(Util.C.abandonChangeTitle());
@@ -70,6 +78,7 @@ public class AbandonChangeDialog extends AutoCenterDialogBox {
Util.MANAGE_SVC.abandonChange(psid, message.getText().trim(),
new GerritCallback<ChangeDetail>() {
public void onSuccess(ChangeDetail result) {
+ buttonClicked = true;
if (callback != null) {
callback.onSuccess(result);
}
@@ -87,9 +96,11 @@ public class AbandonChangeDialog extends AutoCenterDialogBox {
buttonPanel.add(sendButton);
cancelButton = new Button(Util.C.buttonAbandonChangeCancel());
+ DOM.setStyleAttribute(cancelButton.getElement(), "marginLeft", "300px");
cancelButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
+ buttonClicked = true;
if (callback != null) {
callback.onFailure(null);
}
@@ -97,11 +108,25 @@ public class AbandonChangeDialog extends AutoCenterDialogBox {
}
});
buttonPanel.add(cancelButton);
+
+ addCloseHandler(this);
}
@Override
- protected void onLoad() {
- super.onLoad();
+ public void center() {
+ super.center();
+ GlobalKey.dialog(this);
message.setFocus(true);
}
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (!buttonClicked) {
+ // the dialog was closed without one of the buttons being pressed
+ // e.g. the user pressed ESC to close the dialog
+ if (callback != null) {
+ callback.onFailure(null);
+ }
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllAbandonedChangesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllAbandonedChangesScreen.java
deleted file mode 100644
index 39b6162c8e..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllAbandonedChangesScreen.java
+++ /dev/null
@@ -1,44 +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.Gerrit;
-import com.google.gerrit.reviewdb.Change;
-
-
-public class AllAbandonedChangesScreen extends AllSingleListScreen {
- public AllAbandonedChangesScreen(final String positionToken) {
- super("all,abandoned", positionToken);
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setWindowTitle(Gerrit.C.menuAllAbandoned());
- setPageTitle(Util.C.allAbandonedChanges());
- }
-
- @Override
- protected void loadPrev() {
- Util.LIST_SVC.allClosedPrev(Change.Status.ABANDONED, pos, pageSize,
- loadCallback());
- }
-
- @Override
- protected void loadNext() {
- Util.LIST_SVC.allClosedNext(Change.Status.ABANDONED, pos, pageSize,
- loadCallback());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllMergedChangesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllMergedChangesScreen.java
deleted file mode 100644
index 9c4931a35a..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllMergedChangesScreen.java
+++ /dev/null
@@ -1,44 +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.Gerrit;
-import com.google.gerrit.reviewdb.Change;
-
-
-public class AllMergedChangesScreen extends AllSingleListScreen {
- public AllMergedChangesScreen(final String positionToken) {
- super("all,merged", positionToken);
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setWindowTitle(Gerrit.C.menuAllMerged());
- setPageTitle(Util.C.allMergedChanges());
- }
-
- @Override
- protected void loadPrev() {
- Util.LIST_SVC.allClosedPrev(Change.Status.MERGED, pos, pageSize,
- loadCallback());
- }
-
- @Override
- protected void loadNext() {
- Util.LIST_SVC.allClosedNext(Change.Status.MERGED, pos, pageSize,
- loadCallback());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllOpenChangesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllOpenChangesScreen.java
deleted file mode 100644
index 94050666b4..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllOpenChangesScreen.java
+++ /dev/null
@@ -1,42 +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.Gerrit;
-
-
-
-public class AllOpenChangesScreen extends AllSingleListScreen {
- public AllOpenChangesScreen(final String positionToken) {
- super("all,open", positionToken);
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setWindowTitle(Gerrit.C.menuAllOpen());
- setPageTitle(Util.C.allOpenChanges());
- }
-
- @Override
- protected void loadPrev() {
- Util.LIST_SVC.allOpenPrev(pos, pageSize, loadCallback());
- }
-
- @Override
- protected void loadNext() {
- Util.LIST_SVC.allOpenNext(pos, pageSize, loadCallback());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectAbandonedChangesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectAbandonedChangesScreen.java
deleted file mode 100644
index 79d00ef9e7..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectAbandonedChangesScreen.java
+++ /dev/null
@@ -1,47 +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.reviewdb.Change;
-import com.google.gerrit.reviewdb.Project;
-
-
-public class ByProjectAbandonedChangesScreen extends AllSingleListScreen {
- private final Project.NameKey projectKey;
-
- public ByProjectAbandonedChangesScreen(final Project.NameKey proj,
- final String positionToken) {
- super("project,abandoned," + proj.toString(), positionToken);
- projectKey = proj;
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setPageTitle(Util.M.changesAbandonedInProject(projectKey.get()));
- }
-
- @Override
- protected void loadPrev() {
- Util.LIST_SVC.byProjectClosedPrev(projectKey, Change.Status.ABANDONED, pos,
- pageSize, loadCallback());
- }
-
- @Override
- protected void loadNext() {
- Util.LIST_SVC.byProjectClosedNext(projectKey, Change.Status.ABANDONED, pos,
- pageSize, loadCallback());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectMergedChangesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectMergedChangesScreen.java
deleted file mode 100644
index 55caf90501..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectMergedChangesScreen.java
+++ /dev/null
@@ -1,47 +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.reviewdb.Change;
-import com.google.gerrit.reviewdb.Project;
-
-
-public class ByProjectMergedChangesScreen extends AllSingleListScreen {
- private final Project.NameKey projectKey;
-
- public ByProjectMergedChangesScreen(final Project.NameKey proj,
- final String positionToken) {
- super("project,merged," + proj.toString(), positionToken);
- projectKey = proj;
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setPageTitle(Util.M.changesMergedInProject(projectKey.get()));
- }
-
- @Override
- protected void loadPrev() {
- Util.LIST_SVC.byProjectClosedPrev(projectKey, Change.Status.MERGED, pos,
- pageSize, loadCallback());
- }
-
- @Override
- protected void loadNext() {
- Util.LIST_SVC.byProjectClosedNext(projectKey, Change.Status.MERGED, pos,
- pageSize, loadCallback());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectOpenChangesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectOpenChangesScreen.java
deleted file mode 100644
index f9525a47c9..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ByProjectOpenChangesScreen.java
+++ /dev/null
@@ -1,44 +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.reviewdb.Project;
-
-
-public class ByProjectOpenChangesScreen extends AllSingleListScreen {
- private final Project.NameKey projectKey;
-
- public ByProjectOpenChangesScreen(final Project.NameKey proj,
- final String positionToken) {
- super("project,open," + proj.toString(), positionToken);
- projectKey = proj;
- }
-
- @Override
- protected void onInitUI() {
- super.onInitUI();
- setPageTitle(Util.M.changesOpenInProject(projectKey.get()));
- }
-
- @Override
- protected void loadPrev() {
- Util.LIST_SVC.byProjectOpenPrev(projectKey, pos, pageSize, loadCallback());
- }
-
- @Override
- protected void loadNext() {
- Util.LIST_SVC.byProjectOpenNext(projectKey, pos, pageSize, loadCallback());
- }
-}
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 4e34d25f13..9a08932687 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 changesRecentlyClosed();
String starredHeading();
+ String watchedHeading();
String draftsHeading();
String allOpenChanges();
String allAbandonedChanges();
@@ -46,6 +47,9 @@ public interface ChangeConstants extends Constants {
String changeTablePagePrev();
String changeTablePageNext();
String upToDashboard();
+ String expandCollapseDependencies();
+ String previousPatchSet();
+ String nextPatchSet();
String keyPublishComments();
String patchTableColumnName();
@@ -58,7 +62,8 @@ public interface ChangeConstants extends Constants {
String patchTablePrev();
String patchTableNext();
- String patchTableOpen();
+ String patchTableOpenDiff();
+ String patchTableOpenUnifiedDiff();
String upToChangeIconLink();
String prevPatchLinkIcon();
String nextPatchLinkIcon();
@@ -75,6 +80,7 @@ public interface ChangeConstants extends Constants {
String changeInfoBlockOwner();
String changeInfoBlockProject();
String changeInfoBlockBranch();
+ String changeInfoBlockTopic();
String changeInfoBlockUploaded();
String changeInfoBlockUpdated();
String changeInfoBlockStatus();
@@ -111,4 +117,7 @@ public interface ChangeConstants extends Constants {
String reviewed();
String submitFailed();
String buttonClose();
+
+ String buttonDiffAllSideBySide();
+ String buttonDiffAllUnified();
}
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 7211c9498f..59d0bd52c1 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
@@ -4,6 +4,7 @@ statusLongMerged = Merged
statusLongAbandoned = Abandoned
starredHeading = Starred Changes
+watchedHeading = Open Changes of Watched Projects
draftsHeading = Changes with unpublished drafts
changesRecentlyClosed = Recently closed
allOpenChanges = All open changes
@@ -26,6 +27,9 @@ changeTableStar = Star (or unstar) change
changeTablePagePrev = Previous page of changes
changeTablePageNext = Next page of changes
upToDashboard = Up to dashboard
+expandCollapseDependencies = Expands / Collapses dependencies section
+previousPatchSet = Previous patch set
+nextPatchSet = Next patch set
keyPublishComments = Review and publish comments
patchTableColumnName = File Path
@@ -38,7 +42,8 @@ patchTableDownloadPostImage = new
patchTablePrev = Previous file
patchTableNext = Next file
-patchTableOpen = Open file
+patchTableOpenDiff = Open diff
+patchTableOpenUnifiedDiff = Open unified diff
changeScreenIncludedIn = Included in
changeScreenDependencies = Dependencies
@@ -52,6 +57,7 @@ approvalTableAddReviewer = Add Reviewer
changeInfoBlockOwner = Owner
changeInfoBlockProject = Project
changeInfoBlockBranch = Branch
+changeInfoBlockTopic = Topic
changeInfoBlockUploaded = Uploaded
changeInfoBlockUpdated = Updated
changeInfoBlockStatus = Status
@@ -92,3 +98,6 @@ nextPatchLinkIcon = &#x21e8;
reviewed = Reviewed
submitFailed = Submit Failed
buttonClose = Close
+
+buttonDiffAllSideBySide = Diff All Side-by-Side
+buttonDiffAllUnified = Diff All Unified
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
index 4ca965e94b..05323263fd 100644
--- 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
@@ -14,42 +14,29 @@
package com.google.gerrit.client.changes;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
public class ChangeDescriptionBlock extends Composite {
private final ChangeInfoBlock infoBlock;
- private final HTML description;
+ private final CommitMessageBlock messageBlock;
public ChangeDescriptionBlock() {
infoBlock = new ChangeInfoBlock();
- description = new HTML();
- description.setStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
+ messageBlock = new CommitMessageBlock();
final HorizontalPanel hp = new HorizontalPanel();
hp.add(infoBlock);
- hp.add(description);
+ hp.add(messageBlock);
initWidget(hp);
}
public void display(final Change chg, final PatchSetInfo info,
final AccountInfoCache acc) {
infoBlock.display(chg, acc);
-
- SafeHtml msg = new SafeHtmlBuilder().append(info.getMessage());
- msg = msg.linkify();
- msg = CommentLinkProcessor.apply(msg);
- msg = new SafeHtmlBuilder().openElement("p").append(msg).closeElement("p");
- msg = msg.replaceAll("\n\n", "</p><p>");
- msg = msg.replaceAll("\n", "<br />");
- SafeHtml.set(description, msg);
+ messageBlock.display(info.getMessage());
}
}
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
index 02165f7573..0c7106eefa 100644
--- 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
@@ -18,6 +18,7 @@ import static com.google.gerrit.client.FormatUtil.mediumFormat;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.AccountDashboardLink;
+import com.google.gerrit.client.ui.BranchLink;
import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.ProjectLink;
import com.google.gerrit.common.data.AccountInfoCache;
@@ -34,11 +35,12 @@ public class ChangeInfoBlock extends Composite {
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_UPLOADED = 4;
- private static final int R_UPDATED = 5;
- private static final int R_STATUS = 6;
- private static final int R_PERMALINK = 7;
- private static final int R_CNT = 8;
+ 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_STATUS = 7;
+ private static final int R_PERMALINK = 8;
+ private static final int R_CNT = 9;
private final Grid table;
@@ -51,6 +53,7 @@ public class ChangeInfoBlock extends Composite {
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());
@@ -73,10 +76,18 @@ public class ChangeInfoBlock extends Composite {
public void display(final Change chg, final AccountInfoCache acc) {
final Branch.NameKey dst = chg.getDest();
- table.setText(R_CHANGE_ID, 1, chg.getKey().get());
+
+ 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, AccountDashboardLink.link(acc, chg.getOwner()));
table.setWidget(R_PROJECT, 1, new ProjectLink(chg.getProject(), chg.getStatus()));
- table.setText(R_BRANCH, 1, dst.getShortName());
+ table.setWidget(R_BRANCH, 1, new BranchLink(dst.getShortName(), chg
+ .getProject(), chg.getStatus(), dst.get(), null));
+ table.setWidget(R_TOPIC, 1, new BranchLink(chg.getTopic(),
+ chg.getProject(), chg.getStatus(), dst.get(), chg.getTopic()));
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()));
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
index a700dd8ea2..ae2dd11914 100644
--- 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
@@ -18,7 +18,6 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
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;
@@ -28,7 +27,6 @@ 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.ChangeInfo;
-import com.google.gerrit.common.data.GitwebLink;
import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
@@ -40,11 +38,9 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwtexpui.globalkey.client.GlobalKey;
@@ -61,7 +57,6 @@ public class ChangeScreen extends Screen {
private Image starChange;
private boolean starred;
- private PatchSet.Id currentPatchSet;
private ChangeDescriptionBlock descriptionBlock;
private ApprovalTable approvals;
@@ -72,7 +67,7 @@ public class ChangeScreen extends Screen {
private ChangeTable.Section dependsOn;
private ChangeTable.Section neededBy;
- private FlowPanel patchSetPanels;
+ private PatchSetsBlock patchSetsBlock;
private Panel comments;
@@ -121,6 +116,7 @@ public class ChangeScreen extends Screen {
super.registerKeys();
regNavigation = GlobalKey.add(this, keysNavigation);
regAction = GlobalKey.add(this, keysAction);
+ patchSetsBlock.setRegisterKeys(true);
}
public void refresh() {
@@ -150,6 +146,7 @@ public class ChangeScreen extends Screen {
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
keysNavigation.add(new DashboardKeyCommand(0, 'u', Util.C.upToDashboard()));
+ keysNavigation.add(new ExpandCollapseDependencySectionKeyCommand(0, 'd', Util.C.expandCollapseDependencies()));
if (Gerrit.isSignedIn()) {
keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar()));
@@ -195,8 +192,8 @@ public class ChangeScreen extends Screen {
dependenciesPanel.setWidth("95%");
add(dependenciesPanel);
- patchSetPanels = new FlowPanel();
- add(patchSetPanels);
+ patchSetsBlock = new PatchSetsBlock(this);
+ add(patchSetsBlock);
comments = new FlowPanel();
comments.setStyleName(Gerrit.RESOURCES.css().changeComments());
@@ -245,7 +242,7 @@ public class ChangeScreen extends Screen {
approvals.display(detail.getChange(), detail.getMissingApprovals(), detail
.getApprovals());
- addPatchSets(detail);
+ patchSetsBlock.display(detail);
addComments(detail);
// If any dependency change is still open, show our dependency list.
@@ -264,40 +261,6 @@ public class ChangeScreen extends Screen {
dependenciesPanel.setOpen(depsOpen);
}
- private void addPatchSets(final ChangeDetail detail) {
- patchSetPanels.clear();
-
- final PatchSet currps = detail.getCurrentPatchSet();
- final GitwebLink gw = Gerrit.getConfig().getGitwebLink();
- for (final PatchSet ps : detail.getPatchSets()) {
- final ComplexDisclosurePanel panel =
- new ComplexDisclosurePanel(Util.M.patchSetHeader(ps.getPatchSetId()),
- ps == currps);
- final PatchSetPanel psp = new PatchSetPanel(this, detail, ps);
- panel.setContent(psp);
-
- final InlineLabel revtxt = new InlineLabel(ps.getRevision().get() + " ");
- revtxt.addStyleName(Gerrit.RESOURCES.css().patchSetRevision());
- panel.getHeader().add(revtxt);
- if (gw != null) {
- final Anchor revlink =
- new Anchor("(gitweb)", false, gw.toRevision(detail.getChange()
- .getProject(), ps));
- revlink.addStyleName(Gerrit.RESOURCES.css().patchSetLink());
- panel.getHeader().add(revlink);
- }
-
- if (ps == currps) {
- psp.ensureLoaded(detail.getCurrentPatchSetDetail());
- } else {
- panel.addOpenHandler(psp);
- }
- add(panel);
- patchSetPanels.add(panel);
- }
- currentPatchSet = currps.getId();
- }
-
private void addComments(final ChangeDetail detail) {
comments.clear();
@@ -398,6 +361,17 @@ public class ChangeScreen extends Screen {
}
}
+ 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 StarKeyCommand extends NeedsSignInKeyCommand {
public StarKeyCommand(int mask, char key, String help) {
super(mask, key, help);
@@ -416,8 +390,9 @@ public class ChangeScreen extends Screen {
@Override
public void onKeyPress(final KeyPressEvent event) {
- Gerrit.display("change,publish," + currentPatchSet.toString(),
- new PublishCommentScreen(currentPatchSet));
+ PatchSet.Id currentPatchSetId = patchSetsBlock.getCurrentPatchSet().getId();
+ Gerrit.display("change,publish," + currentPatchSetId.toString(),
+ new PublishCommentScreen(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 ed45a64e2d..76b9f49e10 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
@@ -21,6 +21,7 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountDashboardLink;
+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;
@@ -226,7 +227,8 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
table.setWidget(row, C_OWNER, link(c.getOwner()));
table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey(), c
.getStatus()));
- table.setText(row, C_BRANCH, c.getBranch());
+ 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()));
setRowItem(row, c);
}
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/changes/CommitMessageBlock.java
new file mode 100644
index 0000000000..48e257de34
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
@@ -0,0 +1,54 @@
+// 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.ui.CommentLinkProcessor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+public class CommitMessageBlock extends Composite {
+ private final HTML description;
+
+ public CommitMessageBlock() {
+ this(null);
+ }
+
+ public CommitMessageBlock(String height) {
+ description = new HTML();
+ description.setStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
+ if (height != null) {
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setHeight(height);
+ scrollPanel.add(description);
+ initWidget(scrollPanel);
+ } else {
+ initWidget(description);
+ }
+ }
+
+ public void display(final String commitMessage) {
+ SafeHtml msg = new SafeHtmlBuilder().append(commitMessage);
+ msg = msg.linkify();
+ msg = CommentLinkProcessor.apply(msg);
+ msg = new SafeHtmlBuilder().openElement("p").append(msg).closeElement("p");
+ msg = msg.replaceAll("\n\n", "</p><p>");
+ msg = msg.replaceAll("\n", "<br />");
+ SafeHtml.set(description, msg);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllSingleListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
index 941d762879..6ca102f33d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AllSingleListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
@@ -31,7 +31,7 @@ import com.google.gwtexpui.globalkey.client.KeyCommand;
import java.util.List;
-public abstract class AllSingleListScreen extends Screen {
+public abstract class PagedSingleListScreen extends Screen {
protected static final String MIN_SORTKEY = "";
protected static final String MAX_SORTKEY = "z";
@@ -46,7 +46,7 @@ public abstract class AllSingleListScreen extends Screen {
protected boolean useLoadPrev;
protected String pos;
- protected AllSingleListScreen(final String anchorToken,
+ protected PagedSingleListScreen(final String anchorToken,
final String positionToken) {
anchorPrefix = anchorToken;
useLoadPrev = positionToken.startsWith("p,");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 56b440e304..da4b78b8d1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -18,7 +18,12 @@ import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountDashboardLink;
+import com.google.gerrit.client.ui.ComplexDisclosurePanel;
+import com.google.gerrit.client.ui.PatchLink;
+import com.google.gerrit.client.ui.PatchLink.SideBySide;
+import com.google.gerrit.client.ui.PatchLink.Unified;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.data.GitwebLink;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
@@ -26,6 +31,7 @@ import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
+import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.Project;
@@ -39,8 +45,8 @@ 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.Composite;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
@@ -50,9 +56,10 @@ import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Set;
-class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
+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_DOWNLOAD = 2;
@@ -66,14 +73,48 @@ class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
private Grid infoTable;
private Panel actionsPanel;
private PatchTable patchTable;
+ private final Set<ClickHandler> registeredClickHandler = new HashSet<ClickHandler>();
- PatchSetPanel(final ChangeScreen parent, final ChangeDetail detail,
+ /**
+ * Creates a closed complex disclosure panel for a patch set.
+ * The patch set details are loaded when the complex disclosure panel is opened.
+ */
+ PatchSetComplexDisclosurePanel(final ChangeScreen parent, final ChangeDetail detail,
final PatchSet ps) {
+ this(parent, detail, ps, false);
+ addOpenHandler(this);
+ }
+
+ /**
+ * Creates an open complex disclosure panel for a patch set.
+ */
+ PatchSetComplexDisclosurePanel(final ChangeScreen parent, final ChangeDetail detail,
+ final PatchSetDetail psd) {
+ this(parent, detail, psd.getPatchSet(), true);
+ ensureLoaded(psd);
+ }
+
+ private PatchSetComplexDisclosurePanel(final ChangeScreen parent, final ChangeDetail detail,
+ final PatchSet ps, boolean isOpen) {
+ super(Util.M.patchSetHeader(ps.getPatchSetId()), isOpen);
changeScreen = parent;
changeDetail = detail;
patchSet = ps;
body = new FlowPanel();
- initWidget(body);
+ setContent(body);
+
+ final GitwebLink gw = Gerrit.getConfig().getGitwebLink();
+
+ final InlineLabel revtxt = new InlineLabel(ps.getRevision().get() + " ");
+ revtxt.addStyleName(Gerrit.RESOURCES.css().patchSetRevision());
+ getHeader().add(revtxt);
+ if (gw != null) {
+ final Anchor revlink =
+ new Anchor("(gitweb)", false, gw.toRevision(detail.getChange()
+ .getProject(), ps));
+ revlink.addStyleName(Gerrit.RESOURCES.css().patchSetLink());
+ getHeader().add(revlink);
+ }
}
/**
@@ -106,7 +147,7 @@ class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
patchTable = new PatchTable();
patchTable.setSavePointerId("PatchTable " + patchSet.getId());
- patchTable.display(info.getKey(), detail.getPatches());
+ patchTable.display(detail);
body.add(infoTable);
@@ -119,7 +160,12 @@ class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
populateActions(detail);
}
}
+ populateDiffAllActions(detail);
body.add(patchTable);
+
+ for(ClickHandler clickHandler : registeredClickHandler) {
+ patchTable.addClickHandler(clickHandler);
+ }
}
private void displayDownload() {
@@ -366,6 +412,36 @@ class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
}
}
+ private void populateDiffAllActions(final PatchSetDetail detail) {
+ final Button diffAllSideBySide = new Button(Util.C.buttonDiffAllSideBySide());
+ diffAllSideBySide.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ for (Patch p : detail.getPatches()) {
+ SideBySide link = new PatchLink.SideBySide(p.getFileName(), p.getKey(), 0, null, null);
+ Window.open(Window.Location.getPath() + "#"
+ + link.getTargetHistoryToken(), "_blank", null);
+ }
+ }
+ });
+ actionsPanel.add(diffAllSideBySide);
+
+ final Button diffAllUnified = new Button(Util.C.buttonDiffAllUnified());
+ diffAllUnified.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ for (Patch p : detail.getPatches()) {
+ Unified link = new PatchLink.Unified(p.getFileName(), p.getKey(), 0, null, null);
+ Window.open(Window.Location.getPath() + "#"
+ + link.getTargetHistoryToken(), "_blank", null);
+ }
+ }
+ });
+ actionsPanel.add(diffAllUnified);
+ }
+
private void populateReviewAction() {
final Button b = new Button(Util.C.buttonReview());
b.addClickHandler(new ClickHandler() {
@@ -385,6 +461,7 @@ class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
new GerritCallback<PatchSetDetail>() {
public void onSuccess(final PatchSetDetail result) {
ensureLoaded(result);
+ patchTable.setRegisterKeys(true);
}
});
}
@@ -417,4 +494,27 @@ class PatchSetPanel extends Composite implements OpenHandler<DisclosurePanel> {
}
changeScreen.display(result);
}
+
+ 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
new file mode 100644
index 0000000000..b0f06a4e3e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java
@@ -0,0 +1,235 @@
+// 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.data.ChangeDetail;
+import com.google.gerrit.reviewdb.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.HashMap;
+import java.util.List;
+
+/**
+ * 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 HashMap<PatchSet.Id, PatchSetComplexDisclosurePanel> patchSetPanels =
+ new HashMap<PatchSet.Id, PatchSetComplexDisclosurePanel>();
+
+ private final ChangeScreen parent;
+ private final FlowPanel body;
+ private HandlerRegistration regNavigation;
+
+ /**
+ * 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(final ChangeScreen parent) {
+ this.parent = parent;
+ 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) {
+ clear();
+
+ final PatchSet currps = detail.getCurrentPatchSet();
+ currentPatchSetId = currps.getId();
+ patchSets = detail.getPatchSets();
+
+ for (final PatchSet ps : patchSets) {
+ if (ps == currps) {
+ add(new PatchSetComplexDisclosurePanel(parent, detail, detail
+ .getCurrentPatchSetDetail()));
+ } else {
+ add(new PatchSetComplexDisclosurePanel(parent, detail, ps));
+ }
+ }
+ }
+
+ private void clear() {
+ setRegisterKeys(false);
+ body.clear();
+ patchSetPanels.clear();
+ }
+
+ /**
+ * 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.
+ */
+ private void activate(final PatchSet.Id patchSetId) {
+ if (!patchSetId.equals(activePatchSetId)) {
+ deactivate();
+ PatchSetComplexDisclosurePanel patchSetPanel =
+ patchSetPanels.get(patchSetId);
+ patchSetPanel.setOpen(true);
+ patchSetPanel.setActive(true);
+ activePatchSetId = patchSetId;
+ }
+ }
+
+ /** 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
+ 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/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
index bc14bdbf9e..f448b67daf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
@@ -19,13 +19,14 @@ import com.google.gerrit.client.patches.PatchScreen;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.PatchLink;
+import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.Patch;
-import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Patch.Key;
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.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.IncrementalCommand;
@@ -35,32 +36,47 @@ import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.progress.client.ProgressBar;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import com.google.gwtorm.client.KeyUtil;
+import java.util.ArrayList;
import java.util.List;
public class PatchTable extends Composite {
private final FlowPanel myBody;
- private PatchSet.Id psid;
+ private PatchSetDetail detail;
private Command onLoadCommand;
private MyTable myTable;
private String savePointerId;
private List<Patch> patchList;
+ private List<ClickHandler> clickHandlers;
+ private boolean active;
+ private boolean registerKeys;
+
public PatchTable() {
myBody = new FlowPanel();
initWidget(myBody);
}
- public void display(final PatchSet.Id id, final List<Patch> list) {
- psid = id;
+ public int indexOf(Patch.Key patch) {
+ for (int i = 0; i < patchList.size(); i++) {
+ if (patchList.get(i).getKey().equals(patch)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void display(PatchSetDetail detail) {
+ this.detail = detail;
+ this.patchList = detail.getPatches();
myTable = null;
- patchList = list;
- final DisplayCommand cmd = new DisplayCommand(list);
+ final DisplayCommand cmd = new DisplayCommand(patchList);
if (cmd.execute()) {
cmd.initMeter();
DeferredCommand.addCommand(cmd);
@@ -85,20 +101,66 @@ public class PatchTable extends Composite {
}
}
+ public void addClickHandler(final ClickHandler clickHandler) {
+ if (myTable != null) {
+ myTable.addClickHandler(clickHandler);
+ } else {
+ if (clickHandlers == null) {
+ clickHandlers = new ArrayList<ClickHandler>(2);
+ }
+ clickHandlers.add(clickHandler);
+ }
+ }
+
public void setRegisterKeys(final boolean on) {
- myTable.setRegisterKeys(on);
+ registerKeys = on;
+ if (myTable != null) {
+ myTable.setRegisterKeys(on);
+ }
}
public void movePointerTo(final Patch.Key k) {
myTable.movePointerTo(k);
}
+ public void setActive(boolean active) {
+ this.active = active;
+ if (myTable != null) {
+ myTable.setActive(active);
+ }
+ }
+
public void notifyDraftDelta(final Patch.Key k, final int delta) {
if (myTable != null) {
myTable.notifyDraftDelta(k, delta);
}
}
+ private void setMyTable(MyTable table) {
+ myBody.clear();
+ myBody.add(table);
+ myTable = table;
+
+ if (clickHandlers != null) {
+ for (ClickHandler ch : clickHandlers) {
+ myTable.addClickHandler(ch);
+ }
+ clickHandlers = null;
+ }
+
+ if (active) {
+ myTable.setActive(true);
+ active = false;
+ }
+
+ if (registerKeys) {
+ myTable.setRegisterKeys(registerKeys);
+ registerKeys = false;
+ }
+
+ myTable.finishDisplay();
+ }
+
/**
* @return a link to the previous file in this patch set, or null.
*/
@@ -133,9 +195,9 @@ public class PatchTable extends Composite {
PatchLink link;
if (patchType == PatchScreen.Type.SIDE_BY_SIDE
&& patch.getPatchType() == Patch.PatchType.UNIFIED) {
- link = new PatchLink.SideBySide("", thisKey, index, this);
+ link = new PatchLink.SideBySide("", thisKey, index, detail, this);
} else {
- link = new PatchLink.Unified("", thisKey, index, this);
+ link = new PatchLink.Unified("", thisKey, index, detail, this);
}
SafeHtmlBuilder text = new SafeHtmlBuilder();
text.append(before);
@@ -170,13 +232,16 @@ public class PatchTable extends Composite {
private static final int C_PATH = 2;
private static final int C_DRAFT = 3;
private static final int C_SIDEBYSIDE = 4;
+ private int activeRow = -1;
MyTable() {
keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.patchTablePrev()));
keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.patchTableNext()));
- keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.patchTableOpen()));
+ keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.patchTableOpenDiff()));
keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER, Util.C
- .patchTableOpen()));
+ .patchTableOpenDiff()));
+ keysNavigation.add(new OpenUnifiedDiffKeyCommand(0, 'O', Util.C
+ .patchTableOpenUnifiedDiff()));
table.addClickHandler(new ClickHandler() {
@Override
@@ -190,6 +255,10 @@ public class PatchTable extends Composite {
setSavePointerId(PatchTable.this.savePointerId);
}
+ public void addClickHandler(final ClickHandler clickHandler) {
+ table.addClickHandler(clickHandler);
+ }
+
void updateReviewedStatus(final Patch.Key patchKey, boolean reviewed) {
final int row = findRow(patchKey);
if (0 <= row) {
@@ -234,17 +303,35 @@ public class PatchTable extends Composite {
super.movePointerTo(oldId);
}
+ /** Activates / Deactivates the key navigation and the highlighting of the current row for this table */
+ public void setActive(boolean active) {
+ if (active) {
+ if(activeRow > 0 && getCurrentRow() != activeRow) {
+ super.movePointerTo(activeRow);
+ activeRow = -1;
+ }
+ } else {
+ if(getCurrentRow() > 0) {
+ activeRow = getCurrentRow();
+ super.movePointerTo(-1);
+ }
+ }
+ setRegisterKeys(active);
+ }
+
void initializeRow(int row) {
Patch patch = PatchTable.this.patchList.get(row - 1);
setRowItem(row, patch);
Widget nameCol;
if (patch.getPatchType() == Patch.PatchType.UNIFIED) {
- nameCol = new PatchLink.SideBySide(patch.getFileName(), patch.getKey(), row - 1,
- PatchTable.this);
+ nameCol =
+ new PatchLink.SideBySide(patch.getFileName(), patch.getKey(),
+ row - 1, detail, PatchTable.this);
} else {
- nameCol = new PatchLink.Unified(patch.getFileName(), patch.getKey(), row - 1,
- PatchTable.this);
+ nameCol =
+ new PatchLink.Unified(patch.getFileName(), patch.getKey(), row - 1,
+ detail, PatchTable.this);
}
if (patch.getSourceFileName() != null) {
final String text;
@@ -266,16 +353,16 @@ public class PatchTable extends Composite {
int C_UNIFIED = C_SIDEBYSIDE + 1;
if (patch.getPatchType() == Patch.PatchType.UNIFIED) {
- table.setWidget(row, C_SIDEBYSIDE,
- new PatchLink.SideBySide(Util.C.patchTableDiffSideBySide(), patch.getKey(), row - 1,
- PatchTable.this));
+ table.setWidget(row, C_SIDEBYSIDE, new PatchLink.SideBySide(Util.C
+ .patchTableDiffSideBySide(), patch.getKey(), row - 1, detail,
+ PatchTable.this));
} else if (patch.getPatchType() == Patch.PatchType.BINARY) {
C_UNIFIED = C_SIDEBYSIDE + 2;
}
- table.setWidget(row, C_UNIFIED,
- new PatchLink.Unified(Util.C.patchTableDiffUnified(), patch.getKey(), row - 1,
- PatchTable.this));
+ table.setWidget(row, C_UNIFIED, new PatchLink.Unified(Util.C
+ .patchTableDiffUnified(), patch.getKey(), row - 1, detail,
+ PatchTable.this));
}
void appendHeader(final SafeHtmlBuilder m) {
@@ -462,6 +549,29 @@ public class PatchTable extends Composite {
((InlineHyperlink) link).go();
}
}
+
+ private final class OpenUnifiedDiffKeyCommand extends KeyCommand {
+
+ public OpenUnifiedDiffKeyCommand(int mask, char key, String help) {
+ super(mask, key, help);
+ }
+
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ Widget link = table.getWidget(getCurrentRow(), C_PATH);
+ if (link instanceof FlowPanel) {
+ link = ((FlowPanel) link).getWidget(0);
+ }
+ if (link instanceof PatchLink.Unified) {
+ ((InlineHyperlink) link).go();
+ } else {
+ link = table.getWidget(getCurrentRow(), C_SIDEBYSIDE + 1);
+ if (link instanceof PatchLink.Unified) {
+ ((InlineHyperlink) link).go();
+ }
+ }
+ }
+ }
}
private final class DisplayCommand implements IncrementalCommand {
@@ -530,10 +640,8 @@ public class PatchTable extends Composite {
}
void showTable() {
- PatchTable.this.myBody.clear();
- PatchTable.this.myBody.add(table);
- PatchTable.this.myTable = table;
- table.finishDisplay();
+ setMyTable(table);
+
if (PatchTable.this.onLoadCommand != null) {
PatchTable.this.onLoadCommand.execute();
PatchTable.this.onLoadCommand = null;
@@ -542,7 +650,7 @@ public class PatchTable extends Composite {
void initMeter() {
if (meter == null) {
- meter = new ProgressBar(Util.M.loadingPatchSet(psid.get()));
+ meter = new ProgressBar(Util.M.loadingPatchSet(detail.getPatchSet().getId().get()));
PatchTable.this.myBody.clear();
PatchTable.this.myBody.add(meter);
}
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
index d0d148eb03..0335178391 100644
--- 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
@@ -281,10 +281,7 @@ public class PublishCommentScreen extends AccountScreen implements
draftsPanel.add(panel);
// Parent table can be null here since we are not showing any
// next/previous links
- panel.add(new PatchLink.SideBySide(fn, patchKey, 0, null /*
- * parent
- * table
- */));
+ panel.add(new PatchLink.SideBySide(fn, patchKey, 0, null, null));
priorFile = fn;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeQueryResultsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
index 001a06f3dc..bcd2ed22ce 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeQueryResultsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
@@ -19,16 +19,23 @@ import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.SingleListChangeInfo;
+import com.google.gerrit.reviewdb.RevId;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
+public class QueryScreen extends PagedSingleListScreen {
+ public static QueryScreen forQuery(String query) {
+ return forQuery(query, PageLinks.TOP);
+ }
+
+ public static QueryScreen forQuery(String query, String position) {
+ return new QueryScreen(KeyUtil.encode(query), position);
+ }
-public class ChangeQueryResultsScreen extends AllSingleListScreen {
private final String query;
- public ChangeQueryResultsScreen(final String encQuery,
- final String positionToken) {
+ public QueryScreen(final String encQuery, final String positionToken) {
super("q," + encQuery, positionToken);
query = KeyUtil.decode(encQuery);
}
@@ -45,13 +52,13 @@ public class ChangeQueryResultsScreen extends AllSingleListScreen {
return new GerritCallback<SingleListChangeInfo>() {
public final void onSuccess(final SingleListChangeInfo result) {
if (isAttached()) {
- if (result.getChanges().size() == 1) {
+ if (result.getChanges().size() == 1 && isSingleQuery(query)) {
final ChangeInfo c = result.getChanges().get(0);
Gerrit.display(PageLinks.toChange(c), new ChangeScreen(c));
} else {
Gerrit.setQueryString(query);
display(result);
- ChangeQueryResultsScreen.this.display();
+ QueryScreen.this.display();
}
}
}
@@ -67,4 +74,26 @@ public class ChangeQueryResultsScreen extends AllSingleListScreen {
protected void loadNext() {
Util.LIST_SVC.allQueryNext(query, pos, pageSize, loadCallback());
}
+
+ private static boolean isSingleQuery(String query) {
+ if (query.matches("^[1-9][0-9]*$")) {
+ // Legacy numeric identifier.
+ //
+ return true;
+ }
+
+ if (query.matches("^[iI][0-9a-f]{4,}$")) {
+ // Newer style Change-Id.
+ //
+ return true;
+ }
+
+ if (query.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
+ // Commit SHA-1 of any change.
+ //
+ return true;
+ }
+
+ return false;
+ }
}
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 a68fc15367..d44aa78e7f 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
@@ -113,6 +113,29 @@
}
+/** MenuScreen **/
+.menuScreenMenuBar {
+ background: topMenuColor;
+ padding-top: 0.5em;
+ padding-bottom: 10em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ border-right: 1px solid black;
+ margin-right: 0.5em;
+}
+
+.menuScreenMenuBar .menuItem {
+ white-space: nowrap;
+ display: block;
+ border-right: none;
+ padding: 0.2em;
+}
+
+.menuScreenMenuBar .menuItem.activeRow {
+ background: selectionColor;
+}
+
+
/** CommentPanel **/
.commentPanelBorder {
border-top: 1px solid lightgray;
@@ -961,11 +984,14 @@ a:hover.downloadLink {
/** AccountSettings **/
-.sshPanelUsername {
+.usernameField {
+ white-space: nowrap;
+}
+.accountUsername {
font-family: mono-font;
font-size: small;
}
-.sshPanelPassword {
+.accountPassword {
font-family: mono-font;
font-size: small;
}
@@ -1003,6 +1029,15 @@ a:hover.downloadLink {
font-weight: bold;
}
+.addWatchPanel {
+ margin-top: 10px;
+ padding: 5px 5px 5px 5px;
+}
+.watchedProjectFilter {
+ margin-left: 1em;
+ color: grey;
+}
+
.addSshKeyPanel {
margin-top: 10px;
background-color: trimColor;
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
new file mode 100644
index 0000000000..6aae855f55
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
@@ -0,0 +1,105 @@
+// 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.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.client.ui.ChangeLink;
+import com.google.gerrit.client.ui.InlineHyperlink;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+
+class NavLinks extends Composite {
+ private final KeyCommandSet keys;
+ private final Grid table;
+
+ private InlineHyperlink prev;
+ private InlineHyperlink next;
+
+ private KeyCommand prevKey;
+ private KeyCommand nextKey;
+
+ NavLinks(KeyCommandSet kcs, Change.Id forChange) {
+ keys = kcs;
+ table = new Grid(1, 3);
+ initWidget(table);
+
+ final CellFormatter fmt = table.getCellFormatter();
+ table.setStyleName(Gerrit.RESOURCES.css().sideBySideScreenLinkTable());
+ fmt.setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_LEFT);
+ fmt.setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_CENTER);
+ fmt.setHorizontalAlignment(0, 2, HasHorizontalAlignment.ALIGN_RIGHT);
+
+ final ChangeLink up = new ChangeLink("", forChange);
+ SafeHtml.set(up, SafeHtml.asis(Util.C.upToChangeIconLink()));
+ table.setWidget(0, 1, up);
+ }
+
+ void display(int patchIndex, PatchScreen.Type type, PatchTable fileList) {
+ if (fileList != null) {
+ prev = fileList.getPreviousPatchLink(patchIndex, type);
+ next = fileList.getNextPatchLink(patchIndex, type);
+ } else {
+ prev = null;
+ next = null;
+ }
+
+ if (prev != null) {
+ if (keys != null && prevKey == null) {
+ prevKey = new KeyCommand(0, '[', PatchUtil.C.previousFileHelp()) {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ prev.go();
+ }
+ };
+ keys.add(prevKey);
+ }
+ table.setWidget(0, 0, prev);
+ } else {
+ if (keys != null && prevKey != null) {
+ keys.remove(prevKey);
+ prevKey = null;
+ }
+ table.clearCell(0, 0);
+ }
+
+ if (next != null) {
+ if (keys != null && nextKey == null) {
+ nextKey = new KeyCommand(0, ']', PatchUtil.C.nextFileHelp()) {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ next.go();
+ }
+ };
+ keys.add(nextKey);
+ }
+ table.setWidget(0, 2, next);
+ } else {
+ if (keys != null && nextKey != null) {
+ keys.remove(nextKey);
+ nextKey = null;
+ }
+ table.clearCell(0, 2);
+ }
+ }
+}
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/PatchScreen.java
index dfcbb7e362..6c45138b6c 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/PatchScreen.java
@@ -14,25 +14,23 @@
package com.google.gerrit.client.patches;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.WHOLE_FILE_CONTEXT;
-
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.changes.ChangeScreen;
+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.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.prettify.client.ClientSideFormatter;
import com.google.gerrit.prettify.common.PrettyFactory;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
@@ -49,15 +47,12 @@ import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.rpc.AsyncCallback;
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.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.VerticalPanel;
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.SafeHtml;
import com.google.gwtjsonrpc.client.VoidResult;
public abstract class PatchScreen extends Screen implements
@@ -66,8 +61,8 @@ public abstract class PatchScreen extends Screen implements
public static class SideBySide extends PatchScreen {
public SideBySide(final Patch.Key id, final int patchIndex,
- final PatchTable patchTable) {
- super(id, patchIndex, patchTable);
+ final PatchSetDetail patchSetDetail, final PatchTable patchTable) {
+ super(id, patchIndex, patchSetDetail, patchTable);
}
@Override
@@ -83,11 +78,11 @@ public abstract class PatchScreen extends Screen implements
public static class Unified extends PatchScreen {
public Unified(final Patch.Key id, final int patchIndex,
- final PatchTable patchTable) {
- super(id, patchIndex, patchTable);
- final PatchScriptSettings s = settingsPanel.getValue();
- s.getPrettySettings().setSyntaxHighlighting(false);
- settingsPanel.setValue(s);
+ final PatchSetDetail patchSetDetail, final PatchTable patchTable) {
+ super(id, patchIndex, patchSetDetail, patchTable);
+ final AccountDiffPreference dp = settingsPanel.getValue();
+ dp.setSyntaxHighlighting(false);
+ settingsPanel.setValue(dp);
}
@Override
@@ -104,6 +99,7 @@ public abstract class PatchScreen extends Screen implements
// Which patch set id's are being diff'ed
private static PatchSet.Id diffSideA = null;
private static PatchSet.Id diffSideB = null;
+
private static Boolean historyOpen = null;
private static final OpenHandler<DisclosurePanel> cacheOpenState =
new OpenHandler<DisclosurePanel>() {
@@ -124,6 +120,7 @@ public abstract class PatchScreen extends Screen implements
private static Change.Id currentChangeId = null;
protected final Patch.Key patchKey;
+ protected PatchSetDetail patchSetDetail;
protected PatchTable fileList;
protected PatchSet.Id idSideA;
protected PatchSet.Id idSideB;
@@ -134,6 +131,9 @@ public abstract class PatchScreen extends Screen implements
private FlowPanel contentPanel;
private Label noDifference;
private AbstractPatchContentTable contentTable;
+ private CommitMessageBlock commitMessageBlock;
+ private NavLinks topNav;
+ private NavLinks bottomNav;
private int rpcSequence;
private PatchScript lastScript;
@@ -151,9 +151,6 @@ public abstract class PatchScreen extends Screen implements
/** Link to the screen for the next file, null if not applicable */
private InlineHyperlink nextFileLink;
- private static final char SHORTCUT_PREVIOUS_FILE = '[';
- private static final char SHORTCUT_NEXT_FILE = ']';
-
/**
* How this patch should be displayed in the patch screen.
*/
@@ -162,8 +159,9 @@ public abstract class PatchScreen extends Screen implements
}
protected PatchScreen(final Patch.Key id, final int patchIndex,
- final PatchTable patchTable) {
+ final PatchSetDetail detail, final PatchTable patchTable) {
patchKey = id;
+ patchSetDetail = detail;
fileList = patchTable;
// If we have any diff side stored, make sure they are applicable to the
@@ -182,9 +180,9 @@ public abstract class PatchScreen extends Screen implements
settingsPanel = new PatchScriptSettingsPanel();
settingsPanel
- .addValueChangeHandler(new ValueChangeHandler<PatchScriptSettings>() {
+ .addValueChangeHandler(new ValueChangeHandler<AccountDiffPreference>() {
@Override
- public void onValueChange(ValueChangeEvent<PatchScriptSettings> event) {
+ public void onValueChange(ValueChangeEvent<AccountDiffPreference> event) {
update(event.getValue());
}
});
@@ -207,9 +205,9 @@ public abstract class PatchScreen extends Screen implements
lastScript = null;
}
- private void update(PatchScriptSettings s) {
- if (lastScript != null && canReuse(s, lastScript)) {
- lastScript.setSettings(s);
+ private void update(AccountDiffPreference dp) {
+ if (lastScript != null && canReuse(dp, lastScript)) {
+ lastScript.setDiffPrefs(dp);
RpcStatus.INSTANCE.onRpcStart(null);
settingsPanel.setEnabled(false);
DeferredCommand.addCommand(new Command() {
@@ -227,24 +225,24 @@ public abstract class PatchScreen extends Screen implements
}
}
- private boolean canReuse(PatchScriptSettings s, PatchScript last) {
- if (last.getSettings().getWhitespace() != s.getWhitespace()) {
+ private boolean canReuse(AccountDiffPreference dp, PatchScript last) {
+ if (last.getDiffPrefs().getIgnoreWhitespace() != dp.getIgnoreWhitespace()) {
// Whitespace ignore setting requires server computation.
return false;
}
- final int ctx = s.getContext();
- if (ctx == WHOLE_FILE_CONTEXT && !last.getA().isWholeFile()) {
+ final int ctx = dp.getContext();
+ if (ctx == AccountDiffPreference.WHOLE_FILE_CONTEXT && !last.getA().isWholeFile()) {
// We don't have the entire file here, so we can't render it.
return false;
}
- if (last.getSettings().getContext() < ctx && !last.getA().isWholeFile()) {
+ if (last.getDiffPrefs().getContext() < ctx && !last.getA().isWholeFile()) {
// We don't have sufficient context.
return false;
}
- if (s.getPrettySettings().isSyntaxHighlighting()
+ if (dp.isSyntaxHighlighting()
&& !last.getA().isWholeFile()) {
// We need the whole file to syntax highlight accurately.
return false;
@@ -271,8 +269,17 @@ public abstract class PatchScreen extends Screen implements
|| (historyOpen != null && historyOpen));
historyPanel.addOpenHandler(cacheOpenState);
historyPanel.addCloseHandler(cacheCloseState);
- add(historyPanel);
- add(settingsPanel);
+
+
+ VerticalPanel vp = new VerticalPanel();
+ vp.add(historyPanel);
+ vp.add(settingsPanel);
+ commitMessageBlock = new CommitMessageBlock("6em");
+ HorizontalPanel hp = new HorizontalPanel();
+ hp.setWidth("100%");
+ hp.add(vp);
+ hp.add(commitMessageBlock);
+ add(hp);
noDifference = new Label(PatchUtil.C.noDifference());
noDifference.setStyleName(Gerrit.RESOURCES.css().patchNoDifference());
@@ -281,37 +288,25 @@ public abstract class PatchScreen extends Screen implements
contentTable = createContentTable();
contentTable.fileList = fileList;
- add(createNextPrevLinks());
+ topNav =
+ new NavLinks(keysNavigation, patchKey.getParentKey().getParentKey());
+ bottomNav = new NavLinks(null, patchKey.getParentKey().getParentKey());
+
+ add(topNav);
contentPanel = new FlowPanel();
contentPanel.setStyleName(Gerrit.RESOURCES.css()
.sideBySideScreenSideBySideTable());
contentPanel.add(noDifference);
contentPanel.add(contentTable);
add(contentPanel);
- add(createNextPrevLinks());
+ add(bottomNav);
- // This must be done after calling createNextPrevLinks(), which initializes
- // these fields
- if (previousFileLink != null) {
- installLinkShortCut(previousFileLink, SHORTCUT_PREVIOUS_FILE, PatchUtil.C
- .previousFileHelp());
- }
- if (nextFileLink != null) {
- installLinkShortCut(nextFileLink, SHORTCUT_NEXT_FILE, PatchUtil.C
- .nextFileHelp());
+ if (fileList != null) {
+ topNav.display(patchIndex, getPatchScreenType(), fileList);
+ bottomNav.display(patchIndex, getPatchScreenType(), fileList);
}
}
- private void installLinkShortCut(final InlineHyperlink link, char shortcut,
- String help) {
- keysNavigation.add(new KeyCommand(0, shortcut, help) {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- link.go();
- }
- });
- }
-
void setReviewedByCurrentUser(boolean reviewed) {
if (fileList != null) {
fileList.updateReviewedStatus(patchKey, reviewed);
@@ -331,36 +326,28 @@ public abstract class PatchScreen extends Screen implements
});
}
- private Widget createNextPrevLinks() {
- final Grid table = new Grid(1, 3);
- final CellFormatter fmt = table.getCellFormatter();
- table.setStyleName(Gerrit.RESOURCES.css().sideBySideScreenLinkTable());
- fmt.setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_LEFT);
- fmt.setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_CENTER);
- fmt.setHorizontalAlignment(0, 2, HasHorizontalAlignment.ALIGN_RIGHT);
-
- if (fileList != null) {
- previousFileLink =
- fileList.getPreviousPatchLink(patchIndex, getPatchScreenType());
- table.setWidget(0, 0, previousFileLink);
-
- nextFileLink =
- fileList.getNextPatchLink(patchIndex, getPatchScreenType());
- table.setWidget(0, 2, nextFileLink);
- }
-
- final ChangeLink up =
- new ChangeLink("", patchKey.getParentKey().getParentKey());
- SafeHtml.set(up, SafeHtml.asis(Util.C.upToChangeIconLink()));
- table.setWidget(0, 1, up);
-
- return table;
- }
-
@Override
protected void onLoad() {
super.onLoad();
- refresh(true);
+ if (patchSetDetail == null) {
+ Util.DETAIL_SVC.patchSetDetail(idSideB,
+ new GerritCallback<PatchSetDetail>() {
+ @Override
+ public void onSuccess(PatchSetDetail result) {
+ patchSetDetail = result;
+ if (fileList == null) {
+ fileList = new PatchTable();
+ fileList.display(result);
+ patchIndex = fileList.indexOf(patchKey);
+ topNav.display(patchIndex, getPatchScreenType(), fileList);
+ bottomNav.display(patchIndex, getPatchScreenType(), fileList);
+ }
+ refresh(true);
+ }
+ });
+ } else {
+ refresh(true);
+ }
}
@Override
@@ -418,6 +405,20 @@ public abstract class PatchScreen extends Screen implements
setWindowTitle(PatchUtil.M.patchWindowTitle(cid.abbreviate(), fileName));
setPageTitle(PatchUtil.M.patchPageTitle(cid.abbreviate(), path));
+ if (idSideB.equals(patchSetDetail.getPatchSet().getId())) {
+ commitMessageBlock.setVisible(true);
+ commitMessageBlock.display(patchSetDetail.getInfo().getMessage());
+ } else {
+ commitMessageBlock.setVisible(false);
+ Util.DETAIL_SVC.patchSetDetail(idSideB,
+ new GerritCallback<PatchSetDetail>() {
+ @Override
+ public void onSuccess(PatchSetDetail result) {
+ commitMessageBlock.display(result.getInfo().getMessage());
+ }
+ });
+ }
+
historyTable.display(script.getHistory());
historyPanel.setVisible(true);
@@ -501,7 +502,7 @@ public abstract class PatchScreen extends Screen implements
Util.DETAIL_SVC.patchSetDetail(psid,
new GerritCallback<PatchSetDetail>() {
public void onSuccess(final PatchSetDetail result) {
- fileList.display(psid, result.getPatches());
+ fileList.display(result);
}
});
}
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 5a461edc6b..758b0f0ad0 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
@@ -14,16 +14,12 @@
package com.google.gerrit.client.patches;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.DEFAULT_CONTEXT;
-import static com.google.gerrit.reviewdb.AccountGeneralPreferences.WHOLE_FILE_CONTEXT;
-
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.Util;
+import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.NpIntTextBox;
-import com.google.gerrit.common.data.PatchScriptSettings;
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
-import com.google.gerrit.prettify.common.PrettySettings;
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -43,15 +39,16 @@ import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtjsonrpc.client.VoidResult;
public class PatchScriptSettingsPanel extends Composite implements
- HasValueChangeHandlers<PatchScriptSettings> {
+ HasValueChangeHandlers<AccountDiffPreference> {
private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
interface MyUiBinder extends UiBinder<Widget, PatchScriptSettingsPanel> {
}
- private PatchScriptSettings value;
+ private AccountDiffPreference value;
private boolean enableIntralineDifference = true;
private boolean enableSmallFileFeatures = true;
@@ -71,7 +68,7 @@ public class PatchScriptSettingsPanel extends Composite implements
CheckBox intralineDifference;
@UiField
- CheckBox showFullFile;
+ ListBox context;
@UiField
CheckBox whitespaceErrors;
@@ -85,9 +82,24 @@ public class PatchScriptSettingsPanel extends Composite implements
@UiField
Button update;
+ /**
+ * Counts +1 for every setEnabled(true) and -1 for every setEnabled(false)
+ *
+ * The purpose is to prevent enabling widgets too early. It might happen that
+ * setEnabled(false) is called from this class and from an event handler
+ * of ValueChangeEvent in another class. The first setEnabled(true) would then
+ * enable widgets too early i.e. before the second setEnabled(true) is called.
+ *
+ * With this counter the setEnabled(true) will enable widgets only when
+ * setEnabledCounter == 0. Until it is less than zero setEnabled(true) will
+ * not enable the widgets.
+ */
+ private int setEnabledCounter;
+
public PatchScriptSettingsPanel() {
initWidget(uiBinder.createAndBindUi(this));
initIgnoreWhitespace(ignoreWhitespace);
+ initContext(context);
if (!Gerrit.isSignedIn()) {
reviewed.setVisible(false);
}
@@ -103,42 +115,41 @@ public class PatchScriptSettingsPanel extends Composite implements
tabWidth.addKeyPressHandler(onEnter);
colWidth.addKeyPressHandler(onEnter);
- final PatchScriptSettings s = new PatchScriptSettings();
- if (Gerrit.isSignedIn()) {
- final Account u = Gerrit.getUserAccount();
- final AccountGeneralPreferences pref = u.getGeneralPreferences();
- s.setContext(pref.getDefaultContext());
+ if (Gerrit.isSignedIn() && Gerrit.getAccountDiffPreference() != null) {
+ setValue(Gerrit.getAccountDiffPreference());
} else {
- s.setContext(DEFAULT_CONTEXT);
+ setValue(AccountDiffPreference.createDefault(null));
}
- setValue(s);
}
@Override
public HandlerRegistration addValueChangeHandler(
- ValueChangeHandler<PatchScriptSettings> handler) {
+ ValueChangeHandler<AccountDiffPreference> handler) {
return super.addHandler(handler, ValueChangeEvent.getType());
}
public void setEnabled(final boolean on) {
- for (Widget w : (HasWidgets) getWidget()) {
- if (w instanceof FocusWidget) {
- ((FocusWidget) w).setEnabled(on);
- }
+ if (on) {
+ setEnabledCounter++;
+ } else {
+ setEnabledCounter--;
}
- toggleEnabledStatus(on);
+ if (on && setEnabledCounter == 0 || !on) {
+ for (Widget w : (HasWidgets) getWidget()) {
+ if (w instanceof FocusWidget) {
+ ((FocusWidget) w).setEnabled(on);
+ }
+ }
+ toggleEnabledStatus(on);
+ };
}
public void setEnableSmallFileFeatures(final boolean on) {
enableSmallFileFeatures = on;
if (enableSmallFileFeatures) {
- final PrettySettings p = getValue().getPrettySettings();
-
- syntaxHighlighting.setValue(p.isSyntaxHighlighting());
- showFullFile.setValue(getValue().getContext() == WHOLE_FILE_CONTEXT);
+ syntaxHighlighting.setValue(value.isSyntaxHighlighting());
} else {
syntaxHighlighting.setValue(false);
- showFullFile.setValue(false);
}
toggleEnabledStatus(update.isEnabled());
}
@@ -146,8 +157,7 @@ public class PatchScriptSettingsPanel extends Composite implements
public void setEnableIntralineDifference(final boolean on) {
enableIntralineDifference = on;
if (enableIntralineDifference) {
- final PrettySettings p = getValue().getPrettySettings();
- intralineDifference.setValue(p.isIntralineDifference());
+ intralineDifference.setValue(value.isIntralineDifference());
} else {
intralineDifference.setValue(false);
}
@@ -157,41 +167,36 @@ public class PatchScriptSettingsPanel extends Composite implements
private void toggleEnabledStatus(final boolean on) {
intralineDifference.setEnabled(on & enableIntralineDifference);
syntaxHighlighting.setEnabled(on & enableSmallFileFeatures);
- showFullFile.setEnabled(on & enableSmallFileFeatures);
final String title =
enableSmallFileFeatures ? null : PatchUtil.C.disabledOnLargeFiles();
syntaxHighlighting.setTitle(title);
- showFullFile.setTitle(title);
}
public CheckBox getReviewedCheckBox() {
return reviewed;
}
- public PatchScriptSettings getValue() {
+ public AccountDiffPreference getValue() {
return value;
}
- public void setValue(final PatchScriptSettings s) {
- final PrettySettings p = s.getPrettySettings();
-
- setIgnoreWhitespace(s.getWhitespace());
+ public void setValue(final AccountDiffPreference dp) {
+ setIgnoreWhitespace(dp.getIgnoreWhitespace());
if (enableSmallFileFeatures) {
- showFullFile.setValue(s.getContext() == WHOLE_FILE_CONTEXT);
- syntaxHighlighting.setValue(p.isSyntaxHighlighting());
+ syntaxHighlighting.setValue(dp.isSyntaxHighlighting());
} else {
- showFullFile.setValue(false);
syntaxHighlighting.setValue(false);
}
+ setContext(dp.getContext());
- tabWidth.setIntValue(p.getTabSize());
- colWidth.setIntValue(p.getLineLength());
- intralineDifference.setValue(p.isIntralineDifference());
- whitespaceErrors.setValue(p.isShowWhiteSpaceErrors());
- showTabs.setValue(p.isShowTabs());
+ tabWidth.setIntValue(dp.getTabSize());
+ colWidth.setIntValue(dp.getLineLength());
+ intralineDifference.setValue(dp.isIntralineDifference());
+ whitespaceErrors.setValue(dp.isShowWhitespaceErrors());
+ showTabs.setValue(dp.isShowTabs());
- value = s;
+ value = dp;
}
@UiHandler("update")
@@ -200,33 +205,38 @@ public class PatchScriptSettingsPanel extends Composite implements
}
private void update() {
- PatchScriptSettings s = new PatchScriptSettings(getValue());
- PrettySettings p = s.getPrettySettings();
-
- s.setWhitespace(getIgnoreWhitespace());
- if (showFullFile.getValue()) {
- s.setContext(WHOLE_FILE_CONTEXT);
- } else if (Gerrit.isSignedIn()) {
- final Account u = Gerrit.getUserAccount();
- final AccountGeneralPreferences pref = u.getGeneralPreferences();
- if (pref.getDefaultContext() == WHOLE_FILE_CONTEXT) {
- s.setContext(DEFAULT_CONTEXT);
- } else {
- s.setContext(pref.getDefaultContext());
- }
- } else {
- s.setContext(DEFAULT_CONTEXT);
+ AccountDiffPreference dp = new AccountDiffPreference(value);
+ dp.setIgnoreWhitespace(getIgnoreWhitespace());
+ dp.setContext(getContext());
+ dp.setTabSize(tabWidth.getIntValue());
+ dp.setLineLength(colWidth.getIntValue());
+ dp.setSyntaxHighlighting(syntaxHighlighting.getValue());
+ dp.setIntralineDifference(intralineDifference.getValue());
+ dp.setShowWhitespaceErrors(whitespaceErrors.getValue());
+ dp.setShowTabs(showTabs.getValue());
+
+ value = dp;
+ fireEvent(new ValueChangeEvent<AccountDiffPreference>(dp) {});
+
+ if (Gerrit.isSignedIn()) {
+ persistDiffPreferences();
}
+ }
- p.setTabSize(tabWidth.getIntValue());
- p.setLineLength(colWidth.getIntValue());
- p.setSyntaxHighlighting(syntaxHighlighting.getValue());
- p.setIntralineDifference(intralineDifference.getValue());
- p.setShowWhiteSpaceErrors(whitespaceErrors.getValue());
- p.setShowTabs(showTabs.getValue());
+ private void persistDiffPreferences() {
+ setEnabled(false);
+ Util.ACCOUNT_SVC.changeDiffPreferences(value, new GerritCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult result) {
+ Gerrit.setAccountDiffPreference(value);
+ setEnabled(true);
+ }
- value = s;
- fireEvent(new ValueChangeEvent<PatchScriptSettings>(s) {});
+ @Override
+ public void onFailure(Throwable caught) {
+ setEnabled(true);
+ }
+ });
}
private void initIgnoreWhitespace(ListBox ws) {
@@ -240,12 +250,24 @@ public class PatchScriptSettingsPanel extends Composite implements
Whitespace.IGNORE_ALL_SPACE.name());
}
+ private void initContext(ListBox context) {
+ for (final short v : AccountDiffPreference.CONTEXT_CHOICES) {
+ final String label;
+ if (v == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
+ label = Util.C.contextWholeFile();
+ } else {
+ label = Util.M.lines(v);
+ }
+ context.addItem(label, String.valueOf(v));
+ }
+ }
+
private Whitespace getIgnoreWhitespace() {
final int sel = ignoreWhitespace.getSelectedIndex();
if (0 <= sel) {
return Whitespace.valueOf(ignoreWhitespace.getValue(sel));
}
- return value.getWhitespace();
+ return value.getIgnoreWhitespace();
}
private void setIgnoreWhitespace(Whitespace s) {
@@ -257,4 +279,23 @@ public class PatchScriptSettingsPanel extends Composite implements
}
ignoreWhitespace.setSelectedIndex(0);
}
+
+ private short getContext() {
+ final int sel = context.getSelectedIndex();
+ if (0 <= sel) {
+ return Short.parseShort(context.getValue(sel));
+ }
+ return (short) value.getContext();
+ }
+
+ private void setContext(int ctx) {
+ String v = String.valueOf(ctx);
+ for (int i = 0; i < context.getItemCount(); i++) {
+ if (context.getValue(i).equals(v)) {
+ context.setSelectedIndex(i);
+ return;
+ }
+ }
+ context.setSelectedIndex(0);
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
index 9d7303dfa1..7bbc8fe3ab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
@@ -49,6 +49,7 @@ limitations under the License.
.controls .gwt-ListBox {
font-size: fontSize;
padding: 0;
+ margin-right: 1em;
}
.updateButton {
@@ -61,13 +62,24 @@ limitations under the License.
<g:HTMLPanel>
<table class='{style.controls}'>
<tr valign='top'>
- <td colspan='2'>
- <ui:msg>
- Ignore Whitespace:
- <g:ListBox
- ui:field='ignoreWhitespace'
- visibleItemCount='1'
- tabIndex='1'/>
+ <ui:msg>
+ <td align='right'>Ignore Whitespace:</td>
+ <td align='right'>
+ <g:ListBox
+ ui:field='ignoreWhitespace'
+ visibleItemCount='1'
+ tabIndex='1'/>
+ </td>
+ </ui:msg>
+
+ <td align='right'>
+ <ui:msg>Tab Width:
+ <my:NpIntTextBox
+ ui:field='tabWidth'
+ width='2em'
+ visibleLength='2'
+ maxLength='2'
+ tabIndex='3'/>
</ui:msg>
</td>
@@ -75,20 +87,13 @@ limitations under the License.
<g:CheckBox
ui:field='syntaxHighlighting'
text='Syntax Coloring'
- tabIndex='4'>
+ tabIndex='5'>
<ui:attribute name='text'/>
</g:CheckBox>
<br/>
<g:CheckBox
ui:field='intralineDifference'
text='Intraline Difference'
- tabIndex='5'>
- <ui:attribute name='text'/>
- </g:CheckBox>
- <br/>
- <g:CheckBox
- ui:field='showFullFile'
- text='Show Full File'
tabIndex='6'>
<ui:attribute name='text'/>
</g:CheckBox>
@@ -131,28 +136,27 @@ limitations under the License.
</tr>
<tr valign='top'>
- <td>
- <ui:msg>Tab Width:
- <my:NpIntTextBox
- ui:field='tabWidth'
- width='2em'
- visibleLength='2'
- maxLength='2'
- tabIndex='2'/>
- </ui:msg>
- </td>
-
- <td>
+ <ui:msg>
+ <td align='right'>Context:</td>
+ <td align='right'>
+ <g:ListBox
+ ui:field='context'
+ visibleItemCount='1'
+ tabIndex='2'/>
+ </td>
+ </ui:msg>
+
+ <td align='right'>
<ui:msg>Columns:
<my:NpIntTextBox
ui:field='colWidth'
width='2.5em'
visibleLength='3'
maxLength='3'
- tabIndex='3'/>
+ tabIndex='4'/>
</ui:msg>
</td>
- </tr>
+ </tr>
</table>
</g:HTMLPanel>
</ui:UiBinder>
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
index 9b42573cb3..1bc4dd5c14 100644
--- 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
@@ -75,7 +75,7 @@ public class SideBySideTable extends AbstractPatchContentTable {
final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
final SafeHtmlBuilder nc = new SafeHtmlBuilder();
final boolean intraline =
- script.getSettings().getPrettySettings().isIntralineDifference()
+ script.getDiffPrefs().isIntralineDifference()
&& script.hasIntralineDifference();
appendHeader(script, nc);
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 b2be30a1b6..508e508282 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
@@ -133,7 +133,7 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
}
final boolean syntaxHighlighting =
- script.getSettings().getPrettySettings().isSyntaxHighlighting();
+ script.getDiffPrefs().isSyntaxHighlighting();
final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
for (final EditList.Hunk hunk : script.getHunks()) {
appendHunkHeader(nc, hunk);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/BranchLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/BranchLink.java
new file mode 100644
index 0000000000..01d7d8beb4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/BranchLink.java
@@ -0,0 +1,84 @@
+// 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.ui;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.QueryScreen;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.Branch;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project;
+
+/** Link to the open changes of a project. */
+public class BranchLink extends InlineHyperlink {
+ private final String query;
+
+ public BranchLink(Project.NameKey project, Change.Status status,
+ String branch, String topic) {
+ this(text(branch, topic), query(project, status, branch, topic));
+ }
+
+ public BranchLink(String text, Project.NameKey project, Change.Status status,
+ String branch, String topic) {
+ this(text, query(project, status, branch, topic));
+ }
+
+ private BranchLink(String text, String query) {
+ super(text, PageLinks.toChangeQuery(query));
+ this.query = query;
+ }
+
+ @Override
+ public void go() {
+ Gerrit.display(getTargetHistoryToken(), createScreen());
+ }
+
+ private Screen createScreen() {
+ return QueryScreen.forQuery(query);
+ }
+
+ private static String text(String branch, String topic) {
+ if (topic != null && !topic.isEmpty()) {
+ return branch + " (" + topic + ")";
+ } else {
+ return branch;
+ }
+ }
+
+ private static String query(Project.NameKey project, Change.Status status,
+ String branch, String topic) {
+ String query = PageLinks.projectQuery(project, status);
+
+ if (branch.startsWith(Branch.R_REFS)) {
+ if (branch.startsWith(Branch.R_HEADS)) {
+ query += " " + PageLinks.op("branch", //
+ branch.substring(Branch.R_HEADS.length()));
+ } else {
+ query += " " + PageLinks.op("ref", branch);
+ }
+ } else {
+ // Assume it was clipped already by the caller. This
+ // happens for example inside of the ChangeInfo object.
+ //
+ query += " " + PageLinks.op("branch", branch);
+ }
+
+ if (topic != null && !topic.isEmpty()) {
+ query += " " + PageLinks.op("topic", topic);
+ }
+
+ return query;
+ }
+}
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 fa3deba308..035328159a 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
@@ -47,6 +47,18 @@ public class LinkMenuBar extends Composite {
body.clear();
}
+ public LinkMenuItem find(String targetToken) {
+ for (Widget w : body) {
+ if (w instanceof LinkMenuItem) {
+ LinkMenuItem m = (LinkMenuItem) w;
+ if (targetToken.equals(m.getTargetHistoryToken())) {
+ return m;
+ }
+ }
+ }
+ return null;
+ }
+
public void add(final Widget i) {
if (body.getWidgetCount() > 0) {
final Widget p = body.getWidget(body.getWidgetCount() - 1);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java
new file mode 100644
index 0000000000..704185cb4a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java
@@ -0,0 +1,61 @@
+// 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.gerrit.client.Gerrit;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class MenuScreen extends Screen {
+ private final LinkMenuBar menu;
+ private final FlowPanel body;
+
+ public MenuScreen() {
+ menu = new LinkMenuBar();
+ menu.setStyleName(Gerrit.RESOURCES.css().menuScreenMenuBar());
+ body = new FlowPanel();
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ HorizontalPanel hp = new HorizontalPanel();
+ hp.add(menu);
+ hp.add(body);
+ super.add(hp);
+ }
+
+ @Override
+ public void setToken(String token) {
+ LinkMenuItem self = menu.find(token);
+ if (self != null) {
+ self.addStyleName(Gerrit.RESOURCES.css().activeRow());
+ }
+ super.setToken(token);
+ }
+
+ @Override
+ protected void add(final Widget w) {
+ body.add(w);
+ }
+
+ protected void link(String text, String target) {
+ final LinkMenuItem item = new LinkMenuItem(text, target);
+ item.setStyleName(Gerrit.RESOURCES.css().menuItem());
+ menu.add(item);
+ }
+}
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 1beae9dd92..e5cc0ea9e1 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
@@ -148,6 +148,7 @@ public abstract class NavigationTable<RowItem> extends FancyFlexTable<RowItem> {
}
} else if (clear) {
table.setWidget(currentRow, C_ARROW, null);
+ pointer.removeFromParent();
}
currentRow = newRow;
}
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 6a34826235..9979edf659 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
@@ -16,11 +16,13 @@ package com.google.gerrit.client.ui;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.changes.PatchTable;
+import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.Patch;
public abstract class PatchLink extends InlineHyperlink {
protected Patch.Key patchKey;
protected int patchIndex;
+ protected PatchSetDetail patchSetDetail;
protected PatchTable parentPatchTable;
/**
@@ -28,14 +30,17 @@ public abstract class PatchLink extends InlineHyperlink {
* @param patchKey The key for this patch
* @param patchIndex The index of the current patch in the patch set
* @param historyToken The history token
+ * @parma patchSetDetail Detailed information about the patch set.
* @param parentPatchTable The table used to display this link
*/
public PatchLink(final String text, final Patch.Key patchKey,
final int patchIndex, final String historyToken,
- PatchTable parentPatchTable) {
+ final PatchSetDetail patchSetDetail,
+ final PatchTable parentPatchTable) {
super(text, historyToken);
this.patchKey = patchKey;
this.patchIndex = patchIndex;
+ this.patchSetDetail = patchSetDetail;
this.parentPatchTable = parentPatchTable;
}
@@ -45,23 +50,26 @@ public abstract class PatchLink extends InlineHyperlink {
getTargetHistoryToken(), //
patchKey, //
patchIndex, //
+ patchSetDetail, //
parentPatchTable //
);
}
public static class SideBySide extends PatchLink {
public SideBySide(final String text, final Patch.Key patchKey,
- final int patchIndex, PatchTable parentPatchTable) {
- super(text, patchKey, patchIndex, Dispatcher
- .toPatchSideBySide(patchKey), parentPatchTable);
+ final int patchIndex, PatchSetDetail patchSetDetail,
+ PatchTable parentPatchTable) {
+ super(text, patchKey, patchIndex, Dispatcher.toPatchSideBySide(patchKey),
+ patchSetDetail, parentPatchTable);
}
}
public static class Unified extends PatchLink {
public Unified(final String text, final Patch.Key patchKey,
- final int patchIndex, PatchTable parentPatchTable) {
- super(text, patchKey, patchIndex,
- Dispatcher.toPatchUnified(patchKey), parentPatchTable);
+ final int patchIndex, PatchSetDetail patchSetDetail,
+ PatchTable parentPatchTable) {
+ super(text, patchKey, patchIndex, Dispatcher.toPatchUnified(patchKey),
+ patchSetDetail, parentPatchTable);
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLink.java
index 8ed7bf1486..b2c4c3e96f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLink.java
@@ -15,9 +15,7 @@
package com.google.gerrit.client.ui;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ByProjectAbandonedChangesScreen;
-import com.google.gerrit.client.changes.ByProjectMergedChangesScreen;
-import com.google.gerrit.client.changes.ByProjectOpenChangesScreen;
+import com.google.gerrit.client.changes.QueryScreen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
@@ -34,7 +32,7 @@ public class ProjectLink extends InlineHyperlink {
public ProjectLink(final String text, final Project.NameKey proj,
Change.Status stat) {
- super(text, PageLinks.toProject(proj, stat));
+ super(text, PageLinks.toChangeQuery(PageLinks.projectQuery(proj, stat)));
status = stat;
project = proj;
}
@@ -45,17 +43,6 @@ public class ProjectLink extends InlineHyperlink {
}
private Screen createScreen() {
- switch (status) {
- case ABANDONED:
- return new ByProjectAbandonedChangesScreen(project, "n,z");
-
- case MERGED:
- return new ByProjectMergedChangesScreen(project, "n,z");
-
- case NEW:
- case SUBMITTED:
- default:
- return new ByProjectOpenChangesScreen(project, "n,z");
- }
+ return QueryScreen.forQuery(PageLinks.projectQuery(project, status));
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
index a68ab062d7..a52b4ba655 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
@@ -62,7 +62,12 @@ public abstract class Screen extends View {
protected void setPageTitle(final String text) {
final String old = headerText.getText();
- headerText.setText(text);
+ if (text.isEmpty()) {
+ header.setVisible(false);
+ } else {
+ headerText.setText(text);
+ header.setVisible(true);
+ }
if (windowTitle == null || windowTitle == old) {
setWindowTitle(text);
}
@@ -72,7 +77,7 @@ public abstract class Screen extends View {
header.insert(w, 0);
}
- protected final void add(final Widget w) {
+ protected void add(final Widget w) {
body.add(w);
}
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index 7ee59a25aa..b02b142562 100644
--- a/gerrit-httpd/pom.xml
+++ b/gerrit-httpd/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-httpd</artifactId>
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
new file mode 100644
index 0000000000..b79971c03a
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
@@ -0,0 +1,112 @@
+// 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;
+
+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 ChangeQueryServlet extends HttpServlet {
+ private final Provider<QueryProcessor> processor;
+
+ @Inject
+ ChangeQueryServlet(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.setIncludeCurrentPatchSet(get(req, "current-patch-set", false));
+ p.setIncludePatchSets(get(req, "patch-sets", 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/ProjectServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
index 336d2fec2b..3f7f68d527 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
@@ -115,7 +115,8 @@ public class ProjectServlet extends GitServlet {
StringBuilder r = new StringBuilder();
r.append(urlProvider.get());
r.append('#');
- r.append(PageLinks.toProject(dst, Change.Status.NEW));
+ r.append(PageLinks.toChangeQuery(PageLinks.projectQuery(dst,
+ Change.Status.NEW)));
rsp.sendRedirect(r.toString());
}
});
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 0c254043a9..a55c5a7534 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
@@ -23,7 +23,6 @@ import com.google.gerrit.httpd.raw.LegacyGerritServlet;
import com.google.gerrit.httpd.raw.SshInfoServlet;
import com.google.gerrit.httpd.raw.StaticServlet;
import com.google.gerrit.httpd.raw.ToolServlet;
-import com.google.gerrit.reviewdb.RevId;
import com.google.gwtexpui.server.CacheControlFilter;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -47,6 +46,7 @@ class UrlModule extends ServletModule {
serve("/Gerrit/*").with(legacyGerritScreen());
serve("/cat/*").with(CatServlet.class);
serve("/logout").with(HttpLogoutServlet.class);
+ serve("/query").with(ChangeQueryServlet.class);
serve("/signout").with(HttpLogoutServlet.class);
serve("/ssh_info").with(SshInfoServlet.class);
serve("/static/*").with(StaticServlet.class);
@@ -60,15 +60,16 @@ class UrlModule extends ServletModule {
serve("/com/google/gerrit/launcher/*").with(notFound());
serve("/servlet/*").with(notFound());
- serve("/all").with(screen(PageLinks.ALL_MERGED));
+ serve("/all").with(query("status:merged"));
serve("/mine").with(screen(PageLinks.MINE));
- serve("/open").with(screen(PageLinks.ALL_OPEN));
+ serve("/open").with(query("status:open"));
serve("/settings").with(screen(PageLinks.SETTINGS));
- serve("/starred").with(screen(PageLinks.MINE_STARRED));
+ serve("/watched").with(query("is:watched status:open"));
+ serve("/starred").with(query("is:starred"));
serveRegex( //
"^/([1-9][0-9]*)/?$", //
- "^/r/(I?[0-9a-fA-F]{4," + RevId.LEN + "})/?$" //
+ "^/r/(.+)/?$" //
).with(changeQuery());
}
@@ -121,6 +122,18 @@ class UrlModule extends ServletModule {
});
}
+ private Key<HttpServlet> query(final String query) {
+ return key(new HttpServlet() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ toGerrit(PageLinks.toChangeQuery(query), req, rsp);
+ }
+ });
+ }
+
private Key<HttpServlet> key(final HttpServlet servlet) {
final Key<HttpServlet> srv =
Key.get(HttpServlet.class, UniqueAnnotations.create());
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 17a7ba5eeb..93b6d09a0d 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
@@ -29,6 +29,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.ChangeUserName;
+import com.google.gerrit.server.account.ClearPassword;
import com.google.gerrit.server.account.GeneratePassword;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -139,6 +140,7 @@ public class WebModule extends FactoryModule {
bind(AccountManager.class);
bind(ChangeUserName.CurrentUser.class);
factory(ChangeUserName.Factory.class);
+ factory(ClearPassword.Factory.class);
factory(GeneratePassword.Factory.class);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
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 ff81620afe..58f589ae0b 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
@@ -136,7 +136,7 @@ class HttpLoginServlet extends HttpServlet {
}
rdr.append(token);
- webSession.get().login(arsp, false);
+ webSession.get().login(arsp, true /* persistent cookie */);
rsp.sendRedirect(rdr.toString());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
index 08fcaa8090..a59d013188 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
@@ -63,7 +63,7 @@ class UserPassAuthServiceImpl implements UserPassAuthService {
result.success = true;
result.isNew = res.isNew();
- webSession.get().login(res, false);
+ webSession.get().login(res, true /* persistent cookie */);
callback.onSuccess(result);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java
index 6f2caa3d52..a928cb8536 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java
@@ -14,33 +14,13 @@
package com.google.gerrit.httpd.auth.openid;
-import static java.util.concurrent.TimeUnit.MINUTES;
-
import com.google.gerrit.httpd.rpc.RpcServletModule;
-import com.google.gerrit.server.cache.Cache;
-import com.google.gerrit.server.cache.CacheModule;
-import com.google.inject.TypeLiteral;
import com.google.inject.servlet.ServletModule;
-import java.util.List;
-
/** Servlets and RPC support related to OpenID authentication. */
public class OpenIdModule extends ServletModule {
@Override
protected void configureServlets() {
- install(new CacheModule() {
- @SuppressWarnings("unchecked")
- @Override
- protected void configure() {
- final TypeLiteral<Cache<String, List>> type =
- new TypeLiteral<Cache<String, List>>() {};
- core(type, "openid") //
- .maxAge(5, MINUTES) // don't cache too long, might be stale
- .memoryLimit(64) // short TTL means we won't have many entries
- ;
- }
- });
-
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class);
filter("/").through(XrdsFilter.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 3bef30f1dd..068855fd7e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -26,8 +26,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.UrlEncoded;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
-import com.google.gerrit.server.cache.Cache;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.ConfigUtil;
@@ -37,7 +35,6 @@ import com.google.gwtorm.client.KeyUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.google.inject.name.Named;
import org.eclipse.jgit.lib.Config;
import org.openid4java.consumer.ConsumerException;
@@ -104,7 +101,6 @@ class OpenIdServiceImpl implements OpenIdService {
private final AccountManager accountManager;
private final ConsumerManager manager;
private final List<OpenIdProviderPattern> allowedOpenIDs;
- private final SelfPopulatingCache<String, List> discoveryCache;
/** Maximum age, in seconds, before forcing re-authentication of account. */
private final int papeMaxAuthAge;
@@ -113,7 +109,6 @@ class OpenIdServiceImpl implements OpenIdService {
OpenIdServiceImpl(final Provider<WebSession> cf,
final Provider<IdentifiedUser> iu,
@CanonicalWebUrl @Nullable final Provider<String> up,
- @Named("openid") final Cache<String, List> openidCache,
@GerritServerConfig final Config config, final AuthConfig ac,
final AccountManager am) throws ConsumerException, MalformedURLException {
@@ -149,19 +144,6 @@ class OpenIdServiceImpl implements OpenIdService {
allowedOpenIDs = ac.getAllowedOpenIDs();
papeMaxAuthAge = (int) ConfigUtil.getTimeUnit(config, //
"auth", null, "maxOpenIdSessionAge", -1, TimeUnit.SECONDS);
-
- discoveryCache = new SelfPopulatingCache<String, List>(openidCache) {
- @Override
- protected List createEntry(final String url) throws Exception {
- try {
- final List<?> list = manager.discover(url);
- return list != null && !list.isEmpty() ? list : null;
- } catch (DiscoveryException e) {
- log.error("Cannot discover OpenID " + url, e);
- return null;
- }
- }
- };
}
public void discover(final String openidIdentifier, final SignInMode mode,
@@ -522,7 +504,13 @@ class OpenIdServiceImpl implements OpenIdService {
private State init(final String openidIdentifier, final SignInMode mode,
final boolean remember, final String returnToken) {
- final List<?> list = discoveryCache.get(openidIdentifier);
+ final List<?> list;
+ try {
+ list = manager.discover(openidIdentifier);
+ } catch (DiscoveryException e) {
+ log.error("Cannot discover OpenID " + openidIdentifier, e);
+ return null;
+ }
if (list == null || list.isEmpty()) {
return null;
}
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 d3a9059c2e..45213487da 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
@@ -158,6 +158,10 @@ public class HostPageServlet extends HttpServlet {
w.write(HPD_ID + ".account=");
json(((IdentifiedUser) user).getAccount(), w);
w.write(";");
+ w.write(HPD_ID + ".accountDiffPref=");
+ json(((IdentifiedUser) user).getAccountDiffPreference(), w);
+ w.write(";");
+
final byte[] userData = w.toString().getBytes("UTF-8");
raw = concat(page.part1, userData, page.part2);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
index b428c9a0ad..120de30573 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc;
import com.google.gerrit.common.errors.CorruptEntityException;
+import com.google.gerrit.common.errors.InvalidQueryException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ReviewDb;
@@ -64,6 +65,8 @@ public class BaseServiceImplementation {
if (r != null) {
callback.onSuccess(r);
}
+ } catch (InvalidQueryException e) {
+ callback.onFailure(e);
} catch (NoSuchProjectException e) {
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchGroupException e) {
@@ -116,8 +119,9 @@ public class BaseServiceImplementation {
* {@link AsyncCallback#onFailure(Throwable)}.
* @throws NoSuchProjectException
* @throws NoSuchGroupException
+ * @throws InvalidQueryException
*/
T run(ReviewDb db) throws OrmException, Failure, NoSuchProjectException,
- NoSuchGroupException;
+ NoSuchGroupException, InvalidQueryException;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
index d71a7811c0..332e262572 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
@@ -14,30 +14,29 @@
package com.google.gerrit.httpd.rpc;
-import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
-
import com.google.gerrit.common.data.AccountDashboardInfo;
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.ChangeListService;
import com.google.gerrit.common.data.SingleListChangeInfo;
import com.google.gerrit.common.data.ToggleStarRequest;
+import com.google.gerrit.common.errors.InvalidQueryException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeAccess;
-import com.google.gerrit.reviewdb.PatchLineComment;
-import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
-import com.google.gerrit.reviewdb.TrackingId;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
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.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.ChangeQueryRewriter;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
@@ -90,15 +89,22 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
+ private final ChangeQueryBuilder.Factory queryBuilder;
+ private final Provider<ChangeQueryRewriter> queryRewriter;
+
@Inject
ChangeListServiceImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser,
final ChangeControl.Factory changeControlFactory,
- final AccountInfoCacheFactory.Factory accountInfoCacheFactory) {
+ final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
+ final ChangeQueryBuilder.Factory queryBuilder,
+ final Provider<ChangeQueryRewriter> queryRewriter) {
super(schema, currentUser);
this.currentUser = currentUser;
this.changeControlFactory = changeControlFactory;
this.accountInfoCacheFactory = accountInfoCacheFactory;
+ this.queryBuilder = queryBuilder;
+ this.queryRewriter = queryRewriter;
}
private boolean canRead(final Change c) {
@@ -109,107 +115,13 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
}
}
- public void allOpenPrev(final String pos, final int pageSize,
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryPrev(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
- throws OrmException {
- return db.changes().allOpenPrev(sortKey, slim);
- }
- });
- }
-
- public void allOpenNext(final String pos, final int pageSize,
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryNext(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
- throws OrmException {
- return db.changes().allOpenNext(sortKey, slim);
- }
- });
- }
-
- public void byProjectOpenPrev(final Project.NameKey project,
- final String pos, final int pageSize,
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryPrev(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
- throws OrmException {
- return db.changes().byProjectOpenPrev(project, sortKey, slim);
- }
- });
- }
-
- public void byProjectOpenNext(final Project.NameKey project,
- final String pos, final int pageSize,
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryNext(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
- throws OrmException {
- return db.changes().byProjectOpenNext(project, sortKey, slim);
- }
- });
- }
-
- public void byProjectClosedPrev(final Project.NameKey project,
- final Change.Status s, final String pos, final int pageSize,
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryPrev(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
- throws OrmException {
- return db.changes().byProjectClosedPrev(s.getCode(), project, sortKey,
- slim);
- }
- });
- }
-
- public void byProjectClosedNext(final Project.NameKey project,
- final Change.Status s, final String pos, final int pageSize,
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryNext(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
- throws OrmException {
- return db.changes().byProjectClosedNext(s.getCode(), project, sortKey,
- slim);
- }
- });
- }
-
- public void allClosedPrev(final Change.Status s, final String pos,
- final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryPrev(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int lim, String key)
- throws OrmException {
- return db.changes().allClosedPrev(s.getCode(), key, lim);
- }
- });
- }
-
- public void allClosedNext(final Change.Status s, final String pos,
- final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new QueryNext(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int lim, String key)
- throws OrmException {
- return db.changes().allClosedNext(s.getCode(), key, lim);
- }
- });
- }
-
@Override
public void allQueryPrev(final String query, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
- throws OrmException {
+ throws OrmException, InvalidQueryException {
return searchQuery(db, query, lim, key, QUERY_PREV);
}
});
@@ -221,88 +133,70 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
- throws OrmException {
+ throws OrmException, InvalidQueryException {
return searchQuery(db, query, lim, key, QUERY_NEXT);
}
});
}
+ @SuppressWarnings("unchecked")
private ResultSet<Change> searchQuery(final ReviewDb db, String query,
final int limit, final String key, final Comparator<Change> cmp)
- throws OrmException {
- List<Change> result = new ArrayList<Change>();
- final HashSet<Change.Id> want = new HashSet<Change.Id>();
- query = query.trim();
-
- if (query.matches("^[1-9][0-9]*$")) {
- want.add(Change.Id.parse(query));
-
- } else if (query.matches("^[iI][0-9a-f]{4,}.*$")) {
- if (query.startsWith("i")) {
- query = "I" + query.substring(1);
- }
- final Change.Key a = new Change.Key(query);
- final Change.Key b = a.max();
- filterBySortKey(result, db.changes().byKeyRange(a, b), cmp, key);
- Collections.sort(result, cmp);
- if (limit < result.size()) {
- result = result.subList(0, limit);
- }
-
- } else if (query.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
- final RevId id = new RevId(query);
- final ResultSet<PatchSet> patches;
- if (id.isComplete()) {
- patches = db.patchSets().byRevision(id);
- } else {
- patches = db.patchSets().byRevisionRange(id, id.max());
- }
- for (PatchSet p : patches) {
- want.add(p.getId().getParentKey());
- }
- } else if (query.contains("owner:")) {
- String[] parsedQuery = query.split(":");
- if (parsedQuery.length > 1) {
- filterBySortKey(result, changesCreatedBy(db, parsedQuery[1]), cmp, key);
- }
- } else if (query.contains("reviewer:")) {
- String[] parsedQuery = query.split(":");
- if (parsedQuery.length > 1) {
- want.addAll(changesReviewedBy(db, parsedQuery[1]));
- }
- } else if (query.contains("bug:") || query.contains("tr:")) {
- String[] parsedQuery = query.split(":");
- if (parsedQuery.length > 1) {
- want.addAll(changesReferencingTr(db, parsedQuery[1]));
+ throws OrmException, InvalidQueryException {
+ try {
+ final ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
+ final Predicate<ChangeData> visibleToMe = builder.is_visible();
+ Predicate<ChangeData> q = builder.parse(query);
+ q = Predicate.and(q, //
+ cmp == QUERY_PREV //
+ ? builder.sortkey_after(key) //
+ : builder.sortkey_before(key), //
+ builder.limit(limit), //
+ visibleToMe //
+ );
+
+ ChangeQueryRewriter rewriter = queryRewriter.get();
+ Predicate<ChangeData> s = rewriter.rewrite(q);
+ if (!(s instanceof ChangeDataSource)) {
+ s = rewriter.rewrite(Predicate.and(builder.status_open(), q));
}
- }
-
- if (result.isEmpty() && want.isEmpty()) {
- return new ListResultSet<Change>(Collections.<Change> emptyList());
- }
-
- filterBySortKey(result, db.changes().get(want), cmp, key);
- Collections.sort(result, cmp);
- if (limit < result.size()) {
- result = result.subList(0, limit);
- }
- return new ListResultSet<Change>(result);
- }
- private static void filterBySortKey(final List<Change> dst,
- final Iterable<Change> src, final Comparator<Change> cmp, final String key) {
- if (cmp == QUERY_PREV) {
- for (Change c : src) {
- if (c.getSortKey().compareTo(key) > 0) {
- dst.add(c);
+ if (s instanceof ChangeDataSource) {
+ ArrayList<Change> r = new ArrayList();
+ HashSet<Change.Id> want = new HashSet<Change.Id>();
+ for (ChangeData d : ((ChangeDataSource) s).read()) {
+ if (d.hasChange()) {
+ // Checking visibleToMe here should be unnecessary, the
+ // query should have already performed it. But we don't
+ // want to trust the query rewriter that much yet.
+ //
+ if (visibleToMe.match(d)) {
+ r.add(d.getChange());
+ }
+ } else {
+ want.add(d.getId());
+ }
}
- }
- } else /* cmp == QUERY_NEXT */{
- for (Change c : src) {
- if (c.getSortKey().compareTo(key) < 0) {
- dst.add(c);
+
+ // Here we have to check canRead. Its impossible to
+ // do that test without the change object, and it being
+ // missing above means we have to compute it ourselves.
+ //
+ if (!want.isEmpty()) {
+ for (Change c : db.changes().get(want)) {
+ if (canRead(c)) {
+ r.add(c);
+ }
+ }
}
+
+ Collections.sort(r, cmp);
+ return new ListResultSet<Change>(r);
+ } else {
+ throw new InvalidQueryException("Not Supported", s.toString());
}
+ } catch (QueryParseException e) {
+ throw new InvalidQueryException(e.getMessage(), query);
}
}
@@ -362,45 +256,6 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
});
}
- public void myStarredChanges(
- final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new Action<SingleListChangeInfo>() {
- public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
- final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
- final SingleListChangeInfo d = new SingleListChangeInfo();
- final Set<Change.Id> starred = currentUser.get().getStarredChanges();
- d.setChanges(filter(db.changes().get(starred), starred, ac));
- Collections.sort(d.getChanges(), new Comparator<ChangeInfo>() {
- public int compare(final ChangeInfo o1, final ChangeInfo o2) {
- return o1.getLastUpdatedOn().compareTo(o2.getLastUpdatedOn());
- }
- });
- d.setAccounts(ac.create());
- return d;
- }
- });
- }
-
- public void myDraftChanges(final AsyncCallback<SingleListChangeInfo> callback) {
- run(callback, new Action<SingleListChangeInfo>() {
- public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
- final Account.Id me = getAccountId();
- final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
- final SingleListChangeInfo d = new SingleListChangeInfo();
- final Set<Change.Id> starred = currentUser.get().getStarredChanges();
- final Set<Change.Id> drafted = draftedBy(db, me);
- d.setChanges(filter(db.changes().get(drafted), starred, ac));
- Collections.sort(d.getChanges(), new Comparator<ChangeInfo>() {
- public int compare(final ChangeInfo o1, final ChangeInfo o2) {
- return o1.getLastUpdatedOn().compareTo(o2.getLastUpdatedOn());
- }
- });
- d.setAccounts(ac.create());
- return d;
- }
- });
- }
-
public void toggleStars(final ToggleStarRequest req,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
@@ -449,102 +304,6 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
return r;
}
- private static Set<Change.Id> draftedBy(final ReviewDb db, final Account.Id me)
- throws OrmException {
- final Set<Change.Id> existing = new HashSet<Change.Id>();
- if (me != null) {
- for (final PatchLineComment sc : db.patchComments().draftByAuthor(me)) {
- final Change.Id c =
- sc.getKey().getParentKey().getParentKey().getParentKey();
- existing.add(c);
- }
- }
- return existing;
- }
-
- /**
- * @return a set of all the account ID's matching the given user name in
- * either of the following columns: ssh name, email address, full name
- */
- private static Set<Account.Id> getAccountSources(final ReviewDb db,
- final String userName) throws OrmException {
- Set<Account.Id> result = new HashSet<Account.Id>();
- String a = userName;
- String b = userName + "\u9fa5";
- addAll(result, db.accounts().suggestByFullName(a, b, 10));
- for (AccountExternalId extId : db.accountExternalIds().suggestByKey(
- new AccountExternalId.Key(SCHEME_USERNAME, a),
- new AccountExternalId.Key(SCHEME_USERNAME, b), 10)) {
- result.add(extId.getAccountId());
- }
- for (AccountExternalId extId : db.accountExternalIds()
- .suggestByEmailAddress(a, b, 10)) {
- result.add(extId.getAccountId());
- }
- return result;
- }
-
- private static void addAll(Set<Account.Id> result, ResultSet<Account> rs) {
- for (Account account : rs) {
- result.add(account.getId());
- }
- }
-
- /**
- * @return a set of all the changes created by userName. This method tries to
- * find userName in 1) the ssh user names, 2) the full names and 3)
- * the email addresses. The returned changes are unique and sorted by
- * time stamp, newer first.
- */
- private List<Change> changesCreatedBy(final ReviewDb db, final String userName)
- throws OrmException {
- final List<Change> resultChanges = new ArrayList<Change>();
- for (Account.Id account : getAccountSources(db, userName)) {
- for (Change change : db.changes().byOwnerOpen(account)) {
- resultChanges.add(change);
- }
- for (Change change : db.changes().byOwnerClosedAll(account)) {
- resultChanges.add(change);
- }
- }
- return resultChanges;
- }
-
- /**
- * @return a set of all the changes reviewed by userName. This method tries to
- * find userName in 1) the ssh user names, 2) the full names and the
- * email addresses. The returned changes are unique and sorted by time
- * stamp, newer first.
- */
- private Set<Change.Id> changesReviewedBy(final ReviewDb db,
- final String userName) throws OrmException {
- final Set<Change.Id> resultChanges = new HashSet<Change.Id>();
- for (Account.Id account : getAccountSources(db, userName)) {
- for (PatchSetApproval a : db.patchSetApprovals().openByUser(account)) {
- resultChanges.add(a.getPatchSetId().getParentKey());
- }
- for (PatchSetApproval a : db.patchSetApprovals().closedByUserAll(account)) {
- resultChanges.add(a.getPatchSetId().getParentKey());
- }
- }
- return resultChanges;
- }
-
- /**
- * @return a set of all the changes referencing tracking id. This method find
- * all changes with a reference to the given external tracking id.
- * The returned changes are unique and sorted by time stamp, newer first.
- */
- private Set<Change.Id> changesReferencingTr(final ReviewDb db,
- final String trackingId) throws OrmException {
- final Set<Change.Id> resultChanges = new HashSet<Change.Id>();
- for (final TrackingId tr : db.trackingIds().byTrackingId(
- new TrackingId.Id(trackingId))) {
- resultChanges.add(tr.getChangeId());
- }
- return resultChanges;
- }
-
private abstract class QueryNext implements Action<SingleListChangeInfo> {
protected final String pos;
protected final int limit;
@@ -556,30 +315,22 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
this.slim = limit + 1;
}
- public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
+ public SingleListChangeInfo run(final ReviewDb db) throws OrmException,
+ InvalidQueryException {
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = currentUser.get().getStarredChanges();
- boolean results = true;
- String sortKey = pos;
final ArrayList<ChangeInfo> list = new ArrayList<ChangeInfo>();
- while (results && list.size() < slim) {
- results = false;
- final ResultSet<Change> rs = query(db, slim, sortKey);
- for (final Change c : rs) {
- results = true;
- if (canRead(c)) {
- final ChangeInfo ci = new ChangeInfo(c);
- ac.want(ci.getOwner());
- ci.setStarred(starred.contains(ci.getId()));
- list.add(ci);
- if (list.size() == slim) {
- rs.close();
- break;
- }
- }
- sortKey = c.getSortKey();
+ final ResultSet<Change> rs = query(db, slim, pos);
+ for (final Change c : rs) {
+ final ChangeInfo ci = new ChangeInfo(c);
+ ac.want(ci.getOwner());
+ ci.setStarred(starred.contains(ci.getId()));
+ list.add(ci);
+ if (list.size() == slim) {
+ rs.close();
+ break;
}
}
@@ -598,7 +349,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
}
abstract ResultSet<Change> query(final ReviewDb db, final int slim,
- String sortKey) throws OrmException;
+ String sortKey) throws OrmException, InvalidQueryException;
}
private abstract class QueryPrev extends QueryNext {
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 11c2d9dda7..77662a18a3 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
@@ -37,6 +37,7 @@ 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.ChangeUserName;
+import com.google.gerrit.server.account.ClearPassword;
import com.google.gerrit.server.account.GeneratePassword;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
@@ -75,6 +76,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
private final AccountManager accountManager;
private final boolean useContactInfo;
+ private final ClearPassword.Factory clearPasswordFactory;
private final GeneratePassword.Factory generatePasswordFactory;
private final ChangeUserName.CurrentUser changeUserNameFactory;
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
@@ -88,6 +90,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
final RegisterNewEmailSender.Factory esf, final SshKeyCache skc,
final AccountByEmailCache abec, final AccountCache uac,
final AccountManager am,
+ final ClearPassword.Factory clearPasswordFactory,
final GeneratePassword.Factory generatePasswordFactory,
final ChangeUserName.CurrentUser changeUserNameFactory,
final DeleteExternalIds.Factory deleteExternalIdsFactory,
@@ -106,6 +109,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
useContactInfo = contactStore != null && contactStore.isEnabled();
+ this.clearPasswordFactory = clearPasswordFactory;
this.generatePasswordFactory = generatePasswordFactory;
this.changeUserNameFactory = changeUserNameFactory;
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
@@ -183,6 +187,12 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
Handler.wrap(generatePasswordFactory.create(key)).to(callback);
}
+ @Override
+ public void clearPassword(AccountExternalId.Key key,
+ AsyncCallback<AccountExternalId> callback) {
+ Handler.wrap(clearPasswordFactory.create(key)).to(callback);
+ }
+
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
externalIdDetailFactory.create().to(callback);
}
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 9c1f530a64..4fd8e28f35 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
@@ -17,9 +17,11 @@ package com.google.gerrit.httpd.rpc.account;
import com.google.gerrit.common.data.AccountProjectWatchInfo;
import com.google.gerrit.common.data.AccountService;
import com.google.gerrit.common.data.AgreementInfo;
+import com.google.gerrit.common.errors.InvalidQueryException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Project;
@@ -28,8 +30,11 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
+import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -46,24 +51,37 @@ class AccountServiceImpl extends BaseServiceImplementation implements
private final AccountCache accountCache;
private final ProjectControl.Factory projectControlFactory;
private final AgreementInfoFactory.Factory agreementInfoFactory;
+ private final ChangeQueryBuilder.Factory queryBuilder;
@Inject
AccountServiceImpl(final Provider<ReviewDb> schema,
final Provider<IdentifiedUser> identifiedUser,
final AccountCache accountCache,
final ProjectControl.Factory projectControlFactory,
- final AgreementInfoFactory.Factory agreementInfoFactory) {
+ final AgreementInfoFactory.Factory agreementInfoFactory,
+ final ChangeQueryBuilder.Factory queryBuilder) {
super(schema, identifiedUser);
this.currentUser = identifiedUser;
this.accountCache = accountCache;
this.projectControlFactory = projectControlFactory;
this.agreementInfoFactory = agreementInfoFactory;
+ this.queryBuilder = queryBuilder;
}
public void myAccount(final AsyncCallback<Account> callback) {
callback.onSuccess(currentUser.get().getAccount());
}
+ @Override
+ public void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback) {
+ run(callback, new Action<AccountDiffPreference>() {
+ @Override
+ public AccountDiffPreference run(ReviewDb db) throws OrmException {
+ return currentUser.get().getAccountDiffPreference();
+ }
+ });
+ }
+
public void changePreferences(final AccountGeneralPreferences pref,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
@@ -80,6 +98,23 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
+ @Override
+ public void changeDiffPreferences(final AccountDiffPreference diffPref,
+ AsyncCallback<VoidResult> callback) {
+ run(callback, new Action<VoidResult>(){
+ public VoidResult run(ReviewDb db) throws OrmException {
+ Account.Id accountId = getAccountId();
+ if (!diffPref.getAccountId().equals(getAccountId())) {
+ throw new IllegalArgumentException("diffPref.getAccountId() "
+ + diffPref.getAccountId() + " doesn't match"
+ + " the accountId of the signed in user " + getAccountId());
+ }
+ db.accountDiffPreferences().upsert(Collections.singleton(diffPref));
+ return VoidResult.INSTANCE;
+ }
+ });
+ }
+
public void myProjectWatch(
final AsyncCallback<List<AccountProjectWatchInfo>> callback) {
run(callback, new Action<List<AccountProjectWatchInfo>>() {
@@ -109,19 +144,33 @@ class AccountServiceImpl extends BaseServiceImplementation implements
});
}
- public void addProjectWatch(final String projectName,
+ public void addProjectWatch(final String projectName, final String filter,
final AsyncCallback<AccountProjectWatchInfo> callback) {
run(callback, new Action<AccountProjectWatchInfo>() {
public AccountProjectWatchInfo run(ReviewDb db) throws OrmException,
- NoSuchProjectException {
+ NoSuchProjectException, InvalidQueryException {
final Project.NameKey nameKey = new Project.NameKey(projectName);
final ProjectControl ctl = projectControlFactory.validateFor(nameKey);
- final AccountProjectWatch watch =
- new AccountProjectWatch(
- new AccountProjectWatch.Key(((IdentifiedUser) ctl
- .getCurrentUser()).getAccountId(), nameKey));
- db.accountProjectWatches().insert(Collections.singleton(watch));
+ if (filter != null) {
+ try {
+ ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
+ builder.setAllowFile(true);
+ builder.parse(filter);
+ } catch (QueryParseException badFilter) {
+ throw new InvalidQueryException(badFilter.getMessage(), filter);
+ }
+ }
+
+ AccountProjectWatch watch =
+ new AccountProjectWatch(new AccountProjectWatch.Key(
+ ((IdentifiedUser) ctl.getCurrentUser()).getAccountId(),
+ nameKey, filter));
+ try {
+ db.accountProjectWatches().insert(Collections.singleton(watch));
+ } catch (OrmDuplicateKeyException alreadyHave) {
+ watch = db.accountProjectWatches().get(watch.getKey());
+ }
return new AccountProjectWatchInfo(watch, ctl.getProject());
}
});
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
index 5f0851eadc..4a4d9d13f4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
@@ -127,7 +127,6 @@ class AbandonChange extends Handler<ChangeDetail> {
// Email the reviewers
final AbandonedSender cm = abandonedSenderFactory.create(change);
cm.setFrom(currentUser.getAccountId());
- cm.setReviewDb(db);
cm.setChangeMessage(cmsg);
cm.send();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
index 8e64469253..4ab907224d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.CanSubmitResult;
@@ -44,8 +45,8 @@ class SubmitAction extends Handler<ChangeDetail> {
private final FunctionState.Factory functionState;
private final IdentifiedUser user;
private final ChangeDetailFactory.Factory changeDetailFactory;
- @Inject
- private ChangeControl.Factory changeControlFactory;
+ private final ChangeControl.Factory changeControlFactory;
+ private final MergeOp.Factory opFactory;
private final PatchSet.Id patchSetId;
@@ -53,13 +54,17 @@ class SubmitAction extends Handler<ChangeDetail> {
SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
final FunctionState.Factory fs, final IdentifiedUser user,
final ChangeDetailFactory.Factory changeDetailFactory,
+ final ChangeControl.Factory changeControlFactory,
+ final MergeOp.Factory opFactory,
@Assisted final PatchSet.Id patchSetId) {
this.db = db;
this.merger = mq;
this.approvalTypes = at;
this.functionState = fs;
this.user = user;
+ this.changeControlFactory = changeControlFactory;
this.changeDetailFactory = changeDetailFactory;
+ this.opFactory = opFactory;
this.patchSetId = patchSetId;
}
@@ -76,7 +81,7 @@ class SubmitAction extends Handler<ChangeDetail> {
CanSubmitResult err =
changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
if (err == CanSubmitResult.OK) {
- ChangeUtil.submit(patchSetId, user, db, merger);
+ ChangeUtil.submit(opFactory, patchSetId, user, db, merger);
return changeDetailFactory.create(changeId).call();
} else {
throw new IllegalStateException(err.getMessage());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
index d04f4ef1a0..ae36388dc9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
@@ -130,7 +130,6 @@ class AddReviewer extends Handler<ReviewerResult> {
final AddReviewerSender cm;
cm = addReviewerSenderFactory.create(control.getChange());
cm.setFrom(currentUser.getAccountId());
- cm.setReviewDb(db);
cm.addReviewers(added);
cm.send();
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 b0ee4d9593..da52596903 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,11 +20,11 @@ import com.google.gerrit.common.data.ApprovalSummarySet;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.PatchDetailService;
import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.common.data.PatchScriptSettings;
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.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountPatchReview;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
@@ -92,13 +92,13 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
}
public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa,
- final PatchSet.Id psb, final PatchScriptSettings s,
+ final PatchSet.Id psb, final AccountDiffPreference dp,
final AsyncCallback<PatchScript> callback) {
if (psb == null) {
callback.onFailure(new NoSuchEntityException());
return;
}
- patchScriptFactoryFactory.create(patchKey, psa, psb, s).to(callback);
+ patchScriptFactoryFactory.create(patchKey, psa, psb, dp).to(callback);
}
public void saveDraft(final PatchLineComment comment,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
index bca7599970..a58c7b954f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
@@ -16,15 +16,14 @@ package com.google.gerrit.httpd.rpc.patch;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Patch.PatchType;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -66,7 +65,7 @@ class PatchScriptBuilder {
private Repository db;
private Change change;
- private PatchScriptSettings settings;
+ private AccountDiffPreference diffPrefs;
private ObjectId aId;
private ObjectId bId;
@@ -92,11 +91,11 @@ class PatchScriptBuilder {
this.change = c;
}
- void setSettings(final PatchScriptSettings s) {
- settings = s;
+ void setDiffPrefs(final AccountDiffPreference dp) {
+ diffPrefs = dp;
- context = settings.getContext();
- if (context == AccountGeneralPreferences.WHOLE_FILE_CONTEXT) {
+ context = diffPrefs.getContext();
+ if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
context = MAX_CONTEXT;
} else if (context > MAX_CONTEXT) {
context = MAX_CONTEXT;
@@ -117,7 +116,7 @@ class PatchScriptBuilder {
//
return new PatchScript(change.getKey(), content.getChangeType(), content
.getOldName(), content.getNewName(), content.getHeaderLines(),
- settings, a.dst, b.dst, Collections.<Edit> emptyList(),
+ diffPrefs, a.dst, b.dst, Collections.<Edit> emptyList(),
a.displayMethod, b.displayMethod, comments, history, false, false);
}
@@ -150,24 +149,24 @@ class PatchScriptBuilder {
// IF the file is really large, we disable things to avoid choking
// the browser client.
//
- settings.setContext(Math.min(25, context));
- settings.getPrettySettings().setSyntaxHighlighting(false);
- context = settings.getContext();
+ diffPrefs.setContext((short) Math.min(25, context));
+ diffPrefs.setSyntaxHighlighting(false);
+ context = diffPrefs.getContext();
hugeFile = true;
- } else if (settings.getPrettySettings().isSyntaxHighlighting()) {
+ } else if (diffPrefs.isSyntaxHighlighting()) {
// In order to syntax highlight the file properly we need to
// give the client the complete file contents. So force our
// context temporarily to the complete file size.
//
context = MAX_CONTEXT;
}
- packContent(settings.getWhitespace() != Whitespace.IGNORE_NONE);
+ packContent(diffPrefs.getIgnoreWhitespace() != Whitespace.IGNORE_NONE);
}
return new PatchScript(change.getKey(), content.getChangeType(), content
.getOldName(), content.getNewName(), content.getHeaderLines(),
- settings, a.dst, b.dst, edits, a.displayMethod, b.displayMethod,
+ diffPrefs, a.dst, b.dst, edits, a.displayMethod, b.displayMethod,
comments, history, hugeFile, intralineDifference);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index bfc207431d..ee8241837b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -16,16 +16,16 @@ package com.google.gerrit.httpd.rpc.patch;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.common.data.PatchScriptSettings;
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Patch.ChangeType;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -62,7 +62,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
PatchScriptFactory create(Patch.Key patchKey,
@Assisted("patchSetA") PatchSet.Id patchSetA,
@Assisted("patchSetB") PatchSet.Id patchSetB,
- PatchScriptSettings settings);
+ AccountDiffPreference diffPrefs);
}
private static final Logger log =
@@ -79,7 +79,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
@Nullable
private final PatchSet.Id psa;
private final PatchSet.Id psb;
- private final PatchScriptSettings settings;
+ private final AccountDiffPreference diffPrefs;
private final PatchSet.Id patchSetId;
private final Change.Id changeId;
@@ -102,7 +102,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
@Assisted final Patch.Key patchKey,
@Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
@Assisted("patchSetB") final PatchSet.Id patchSetB,
- @Assisted final PatchScriptSettings settings) {
+ @Assisted final AccountDiffPreference diffPrefs) {
this.repoManager = grm;
this.builderFactory = builderFactory;
this.patchListCache = patchListCache;
@@ -113,7 +113,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
this.patchKey = patchKey;
this.psa = patchSetA;
this.psb = patchSetB;
- this.settings = settings;
+ this.diffPrefs = diffPrefs;
patchSetId = patchKey.getParentKey();
changeId = patchSetId.getParentKey();
@@ -143,7 +143,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
throw new NoSuchChangeException(changeId, e);
}
try {
- final PatchList list = listFor(keyFor(settings.getWhitespace()));
+ final PatchList list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
final boolean intraline = list.hasIntralineDifference();
final PatchScriptBuilder b = newBuilder(list, git);
final PatchListEntry content = list.get(patchKey.getFileName());
@@ -172,11 +172,11 @@ class PatchScriptFactory extends Handler<PatchScript> {
}
private PatchScriptBuilder newBuilder(final PatchList list, Repository git) {
- final PatchScriptSettings s = new PatchScriptSettings(settings);
+ final AccountDiffPreference dp = new AccountDiffPreference(diffPrefs);
final PatchScriptBuilder b = builderFactory.get();
b.setRepository(git);
b.setChange(change);
- b.setSettings(s);
+ b.setDiffPrefs(dp);
b.setTrees(list.getOldId(), list.getNewId());
return b;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
index 06f15c22d3..3c2c93f3ae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
@@ -144,17 +144,34 @@ class AddRefRight extends Handler<ProjectDetail> {
while (refPattern.startsWith("/")) {
refPattern = refPattern.substring(1);
}
- if (!refPattern.startsWith(Constants.R_REFS)) {
- refPattern = Constants.R_HEADS + refPattern;
- }
- if (refPattern.endsWith("/*")) {
- final String prefix = refPattern.substring(0, refPattern.length() - 2);
- if (!"refs".equals(prefix) && !Repository.isValidRefName(prefix)) {
+
+ if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
+ String example = RefControl.shortestExample(refPattern);
+
+ if (!example.startsWith(Constants.R_REFS)) {
+ refPattern = RefRight.REGEX_PREFIX + Constants.R_HEADS
+ + refPattern.substring(RefRight.REGEX_PREFIX.length());
+ example = RefControl.shortestExample(refPattern);
+ }
+
+ if (!Repository.isValidRefName(example)) {
throw new InvalidNameException();
}
+
} else {
- if (!Repository.isValidRefName(refPattern)) {
- throw new InvalidNameException();
+ if (!refPattern.startsWith(Constants.R_REFS)) {
+ refPattern = Constants.R_HEADS + refPattern;
+ }
+
+ if (refPattern.endsWith("/*")) {
+ final String prefix = refPattern.substring(0, refPattern.length() - 2);
+ if (!"refs".equals(prefix) && !Repository.isValidRefName(prefix)) {
+ throw new InvalidNameException();
+ }
+ } else {
+ if (!Repository.isValidRefName(refPattern)) {
+ throw new InvalidNameException();
+ }
}
}
@@ -162,7 +179,7 @@ class AddRefRight extends Handler<ProjectDetail> {
refPattern = "-" + refPattern;
}
- if (!controlForRef(projectControl, refPattern).isOwner()) {
+ if (!projectControl.controlForRef(refPattern).isOwner()) {
throw new NoSuchRefException(refPattern);
}
@@ -187,11 +204,4 @@ class AddRefRight extends Handler<ProjectDetail> {
projectCache.evictAll();
return projectDetailFactory.create(projectName).call();
}
-
- private RefControl controlForRef(ProjectControl p, String ref) {
- if (ref.endsWith("/*")) {
- ref = ref.substring(0, ref.length() - 1);
- }
- return p.controlForRef(ref);
- }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
index db179ed019..92154c4cbd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
@@ -71,7 +71,7 @@ class DeleteRefRights extends Handler<ProjectDetail> {
if (!projectName.equals(k.getProjectNameKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
- if (!controlForRef(projectControl, k.getRefPattern()).isOwner()) {
+ if (!projectControl.controlForRef(k.getRefPattern()).isOwner()) {
throw new NoSuchRefException(k.getRefPattern());
}
}
@@ -85,11 +85,4 @@ class DeleteRefRights extends Handler<ProjectDetail> {
projectCache.evictAll();
return projectDetailFactory.create(projectName).call();
}
-
- private RefControl controlForRef(ProjectControl p, String ref) {
- if (ref.endsWith("/*")) {
- ref = ref.substring(0, ref.length() - 1);
- }
- return p.controlForRef(ref);
- }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
index cdd535b015..3ff3892f9a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
@@ -26,7 +26,6 @@ import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -77,7 +76,7 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
for (final RefRight r : projectState.getInheritedRights()) {
InheritedRefRight refRight = new InheritedRefRight(
- r, true, controlForRef(pc, r.getRefPattern()).isOwner());
+ r, true, pc.controlForRef(r.getRefPattern()).isOwner());
if (!refRights.contains(refRight)) {
refRights.add(refRight);
wantGroup(r.getAccountGroupId());
@@ -86,7 +85,7 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
for (final RefRight r : projectState.getLocalRights()) {
refRights.add(new InheritedRefRight(
- r, false, controlForRef(pc, r.getRefPattern()).isOwner()));
+ r, false, pc.controlForRef(r.getRefPattern()).isOwner()));
wantGroup(r.getAccountGroupId());
}
@@ -144,11 +143,4 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
groups.put(groupId, groupCache.get(groupId));
}
}
-
- private RefControl controlForRef(ProjectControl p, String ref) {
- if (ref.endsWith("/*")) {
- ref = ref.substring(0, ref.length() - 1);
- }
- return p.controlForRef(ref);
- }
}
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
index 4c482d4d6b..e05f811173 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
@@ -14,12 +14,12 @@
package com.google.gerrit.httpd.rpc.project;
+import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.classextension.EasyMock.createStrictMock;
-import static org.easymock.classextension.EasyMock.replay;
-import static org.easymock.classextension.EasyMock.verify;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index 27199bd769..1000777281 100644
--- a/gerrit-launcher/pom.xml
+++ b/gerrit-launcher/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index 9167c37efd..10cb14aec7 100644
--- a/gerrit-main/pom.xml
+++ b/gerrit-main/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-main</artifactId>
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index a83d411ffb..df64d13874 100644
--- a/gerrit-patch-commonsnet/pom.xml
+++ b/gerrit-patch-commonsnet/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
index e380df431e..4db7de631c 100644
--- a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
+++ b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
@@ -17,8 +17,6 @@ package org.apache.commons.net.smtp;
import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
import org.apache.commons.codec.binary.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -26,20 +24,16 @@ import java.net.SocketException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLSocketFactory;
public class AuthSMTPClient extends SMTPClient {
- private static final Logger log = LoggerFactory.getLogger(AuthSMTPClient.class);
private static final String UTF_8 = "UTF-8";
private String authTypes;
- private Set<String> allowedRcptTo;
public AuthSMTPClient(final String charset) {
super(charset);
@@ -68,45 +62,6 @@ public class AuthSMTPClient extends SMTPClient {
}
}
- public void setAllowRcpt(final String[] allowed) {
- if (allowed != null && allowed.length > 0) {
- if (allowedRcptTo == null) {
- allowedRcptTo = new HashSet<String>();
- }
- for (final String addr : allowed) {
- allowedRcptTo.add(addr);
- }
- }
- }
-
- @Override
- public int rcpt(final String forwardPath) throws IOException {
- if (allowRcpt(forwardPath)) {
- return super.rcpt(forwardPath);
- } else {
- log.warn("Not emailing " + forwardPath + " (prohibited by allowrcpt)");
- return SMTPReply.ACTION_OK;
- }
- }
-
- private boolean allowRcpt(String addr) {
- if (allowedRcptTo == null) {
- return true;
- }
- if (addr.startsWith("<") && addr.endsWith(">")) {
- addr = addr.substring(1, addr.length() - 1);
- }
- if (allowedRcptTo.contains(addr)) {
- return true;
- }
- final int at = addr.indexOf('@');
- if (at > 0) {
- return allowedRcptTo.contains(addr.substring(at))
- || allowedRcptTo.contains(addr.substring(at + 1));
- }
- return false;
- }
-
@Override
public String[] getReplyStrings() {
return _replyLines.toArray(new String[_replyLines.size()]);
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index a58ef5ce8e..68a08c3d16 100644
--- a/gerrit-patch-jgit/pom.xml
+++ b/gerrit-patch-jgit/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-jgit</artifactId>
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index cf05fc647e..66c5349799 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-pgm</artifactId>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index c5a545d77a..30176ef8a0 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -111,7 +111,7 @@ public class ProjectQoSFilter implements Filter {
WorkQueue.Executor executor = getExecutor();
if (cont.isInitial()) {
- TaskThunk task = new TaskThunk(cont, req);
+ TaskThunk task = new TaskThunk(executor, cont, req);
if (maxWait > 0) {
cont.setTimeout(maxWait);
}
@@ -163,13 +163,16 @@ public class ProjectQoSFilter implements Filter {
private final class TaskThunk implements CancelableRunnable,
ContinuationListener {
+ private final WorkQueue.Executor executor;
private final Continuation cont;
private final String name;
private final Object lock = new Object();
private boolean done;
private Thread worker;
- TaskThunk(final Continuation cont, final HttpServletRequest req) {
+ TaskThunk(final WorkQueue.Executor executor, final Continuation cont,
+ final HttpServletRequest req) {
+ this.executor = executor;
this.cont = cont;
this.name = generateName(req);
}
@@ -219,7 +222,6 @@ public class ProjectQoSFilter implements Filter {
@Override
public void onTimeout(Continuation self) {
- WorkQueue.Executor executor = getExecutor();
executor.remove(this);
}
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 72f752b30c..e7234630a6 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
@@ -14,9 +14,9 @@
package com.google.gerrit.pgm.init;
-import static org.easymock.classextension.EasyMock.createStrictMock;
-import static org.easymock.classextension.EasyMock.replay;
-import static org.easymock.classextension.EasyMock.verify;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
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 5dc0cd3684..72b02d5dd6 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
@@ -14,11 +14,11 @@
package com.google.gerrit.pgm.init;
+import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
-import static org.easymock.classextension.EasyMock.createStrictMock;
-import static org.easymock.classextension.EasyMock.replay;
-import static org.easymock.classextension.EasyMock.verify;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index efcd6d0855..99eece6076 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-prettify</artifactId>
@@ -45,6 +45,12 @@ limitations under the License.
</dependency>
<dependency>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-reviewdb</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<scope>provided</scope>
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/PrettyFormatter.gwt.xml b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/PrettyFormatter.gwt.xml
index 93ce272151..48591f8239 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/PrettyFormatter.gwt.xml
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/PrettyFormatter.gwt.xml
@@ -1,19 +1,27 @@
<!--
Copyright (C) 2008 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
+ distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<module>
+ <replace-with class='com.google.gerrit.prettify.client.PrivateScopeImplIE6'>
+ <when-type-is class='com.google.gerrit.prettify.client.PrivateScopeImpl'/>
+ <any>
+ <when-property-is name="user.agent" value="ie6" />
+ <when-property-is name="user.agent" value="ie8" />
+ </any>
+ </replace-with>
+
<inherits name='com.google.gwt.resources.Resources'/>
<inherits name='com.google.gwtexpui.safehtml.SafeHtml'/>
<source path='common' />
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/ClientSideFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/ClientSideFormatter.java
index 2cfb6ae415..4bce954e35 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/ClientSideFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/ClientSideFormatter.java
@@ -16,7 +16,9 @@ package com.google.gerrit.prettify.client;
import com.google.gerrit.prettify.common.PrettyFactory;
import com.google.gerrit.prettify.common.PrettyFormatter;
-import com.google.gwt.resources.client.TextResource;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.ui.RootPanel;
/** Evaluates prettify using the host browser's JavaScript engine. */
public class ClientSideFormatter extends PrettyFormatter {
@@ -27,37 +29,36 @@ public class ClientSideFormatter extends PrettyFormatter {
}
};
+ private static final PrivateScopeImpl prettify;
+
static {
Resources.I.prettify_css().ensureInjected();
Resources.I.gerrit_css().ensureInjected();
- compile(Resources.I.core());
- compile(Resources.I.lang_css());
- compile(Resources.I.lang_hs());
- compile(Resources.I.lang_lisp());
- compile(Resources.I.lang_lua());
- compile(Resources.I.lang_ml());
- compile(Resources.I.lang_proto());
- compile(Resources.I.lang_sql());
- compile(Resources.I.lang_vb());
- compile(Resources.I.lang_wiki());
- }
+ prettify = GWT.create(PrivateScopeImpl.class);
+ RootPanel.get().add(prettify);
- private static void compile(TextResource core) {
- eval(core.getText());
+ prettify.compile(Resources.I.core());
+ prettify.compile(Resources.I.lang_css());
+ prettify.compile(Resources.I.lang_hs());
+ prettify.compile(Resources.I.lang_lisp());
+ prettify.compile(Resources.I.lang_lua());
+ prettify.compile(Resources.I.lang_ml());
+ prettify.compile(Resources.I.lang_proto());
+ prettify.compile(Resources.I.lang_sql());
+ prettify.compile(Resources.I.lang_vb());
+ prettify.compile(Resources.I.lang_wiki());
}
- private static native void eval(String js)
- /*-{ eval(js); }-*/;
-
@Override
protected String prettify(String html, String type) {
- return go(html, type, settings.getTabSize());
+ return go(prettify.getContext(), html, type, diffPrefs.getTabSize());
}
- private static native String go(String srcText, String srcType, int tabSize)
+ private static native String go(JavaScriptObject ctx, String srcText,
+ String srcType, int tabSize)
/*-{
- window['PR_TAB_WIDTH'] = tabSize;
- return window.prettyPrintOne(srcText, srcType);
+ ctx.PR_TAB_WIDTH = tabSize;
+ return ctx.prettyPrintOne(srcText, srcType);
}-*/;
}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImpl.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImpl.java
new file mode 100644
index 0000000000..65ee2127cc
--- /dev/null
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImpl.java
@@ -0,0 +1,67 @@
+// 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.prettify.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.resources.client.TextResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.NamedFrame;
+
+/**
+ * Creates a private JavaScript environment, typically inside an IFrame.
+ * <p>
+ * Instances must be created through {@code GWT.create(PrivateScopeImpl.class)}.
+ * A scope must remain attached to the primary document for its entire life.
+ * Behavior is undefined if a scope is detached and attached again later. It is
+ * best to attach the scope with {@code RootPanel.get().add(scope)} as soon as
+ * it has been created.
+ */
+public class PrivateScopeImpl extends Composite {
+ private static int scopeId;
+
+ protected final String scopeName;
+
+ public PrivateScopeImpl() {
+ scopeName = nextScopeName();
+
+ NamedFrame frame = new NamedFrame(scopeName);
+ frame.setUrl("javascript:''");
+ initWidget(frame);
+
+ setVisible(false);
+ }
+
+ public void compile(TextResource js) {
+ eval(js.getText());
+ }
+
+ public void eval(String js) {
+ nativeEval(getContext(), js);
+ }
+
+ public JavaScriptObject getContext() {
+ return nativeGetContext(scopeName);
+ }
+
+ private static String nextScopeName() {
+ return "_PrivateScope" + (++scopeId);
+ }
+
+ private static native void nativeEval(JavaScriptObject ctx, String js)
+ /*-{ ctx.eval(js); }-*/;
+
+ private static native JavaScriptObject nativeGetContext(String scopeName)
+ /*-{ return $wnd[scopeName]; }-*/;
+}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImplIE6.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImplIE6.java
new file mode 100644
index 0000000000..abb4e15812
--- /dev/null
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrivateScopeImplIE6.java
@@ -0,0 +1,46 @@
+// 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.prettify.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** IE6 requires us to initialize the document before we can use it. */
+public class PrivateScopeImplIE6 extends PrivateScopeImpl {
+ private JavaScriptObject context;
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ context = nativeInitContext(scopeName);
+ }
+
+ @Override
+ public JavaScriptObject getContext() {
+ return context;
+ }
+
+ private static native JavaScriptObject nativeInitContext(String scopeName)
+ /*-{
+ var fe = $wnd[scopeName];
+ fe.document.write(
+ '<script>'
+ + 'parent._PrivateScopeNewChild = this;'
+ + '</' + 'script>'
+ );
+ var ctx = $wnd._PrivateScopeNewChild;
+ $wnd._PrivateScopeNewChild = undefined;
+ return ctx;
+ }-*/;
+}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
index 4406477ca7..5d1592d1a8 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.prettify.common;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -71,7 +72,8 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
protected SparseFileContent content;
protected EditFilter side;
protected List<Edit> edits;
- protected PrettySettings settings;
+ protected AccountDiffPreference diffPrefs;
+ protected String fileName;
protected Set<Integer> trailingEdits;
private int col;
@@ -105,8 +107,12 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
edits = all;
}
- public void setPrettySettings(PrettySettings how) {
- settings = how;
+ public void setDiffPrefs(AccountDiffPreference how) {
+ diffPrefs = how;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
}
/**
@@ -122,7 +128,7 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
String html = toHTML(src);
- if (settings.isSyntaxHighlighting() && getFileType() != null
+ if (diffPrefs.isSyntaxHighlighting() && getFileType() != null
&& src.isWholeFile()) {
// The prettify parsers don't like &#39; as an entity for the
// single quote character. Replace them all out so we don't
@@ -205,7 +211,7 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
cleanText(txt, pos, start);
pos = txt.indexOf(';', start + 1) + 1;
- if (settings.getLineLength() <= col) {
+ if (diffPrefs.getLineLength() <= col) {
buf.append("<br />");
col = 0;
}
@@ -219,14 +225,14 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
private void cleanText(String txt, int pos, int end) {
while (pos < end) {
- int free = settings.getLineLength() - col;
+ int free = diffPrefs.getLineLength() - col;
if (free <= 0) {
// The current line is full. Throw an explicit line break
// onto the end, and we'll continue on the next line.
//
buf.append("<br />");
col = 0;
- free = settings.getLineLength();
+ free = diffPrefs.getLineLength();
}
int n = Math.min(end - pos, free);
@@ -305,7 +311,7 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
private String toHTML(SparseFileContent src) {
SafeHtml html;
- if (settings.isIntralineDifference()) {
+ if (diffPrefs.isIntralineDifference()) {
html = colorLineEdits(src);
} else {
SafeHtmlBuilder b = new SafeHtmlBuilder();
@@ -321,7 +327,7 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
html = html.replaceAll("\r([^\n])", r);
}
- if (settings.isShowWhiteSpaceErrors()) {
+ if (diffPrefs.isShowWhitespaceErrors()) {
// We need to do whitespace errors before showing tabs, because
// these patterns rely on \t as a literal, before it expands.
//
@@ -329,8 +335,8 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
html = showTrailingWhitespace(html);
}
- if (settings.isShowTabs()) {
- String t = 1 < settings.getTabSize() ? "\t" : "";
+ if (diffPrefs.isShowTabs()) {
+ String t = 1 < diffPrefs.getTabSize() ? "\t" : "";
html = html.replaceAll("\t", "<span class=\"vt\">\u00BB</span>" + t);
}
@@ -496,17 +502,17 @@ public abstract class PrettyFormatter implements SparseHtmlFile {
private String expandTabs(String html) {
StringBuilder tmp = new StringBuilder();
int i = 0;
- if (settings.isShowTabs()) {
+ if (diffPrefs.isShowTabs()) {
i = 1;
}
- for (; i < settings.getTabSize(); i++) {
+ for (; i < diffPrefs.getTabSize(); i++) {
tmp.append("&nbsp;");
}
return html.replaceAll("\t", tmp.toString());
}
private String getFileType() {
- String srcType = settings.getFilename();
+ String srcType = fileName;
if (srcType == null) {
return null;
}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettySettings.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettySettings.java
deleted file mode 100644
index 3ef17f540a..0000000000
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettySettings.java
+++ /dev/null
@@ -1,106 +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.prettify.common;
-
-/** Settings to configure a {@link PrettyFormatter}. */
-public class PrettySettings {
- protected String fileName;
- protected boolean showWhiteSpaceErrors;
- protected int lineLength;
- protected int tabSize;
- protected boolean showTabs;
- protected boolean syntaxHighlighting;
- protected boolean intralineDifference;
-
- public PrettySettings() {
- showWhiteSpaceErrors = true;
- lineLength = 100;
- tabSize = 8;
- showTabs = true;
- syntaxHighlighting = true;
- intralineDifference = true;
- }
-
- public PrettySettings(PrettySettings pretty) {
- fileName = pretty.fileName;
- showWhiteSpaceErrors = pretty.showWhiteSpaceErrors;
- lineLength = pretty.lineLength;
- tabSize = pretty.tabSize;
- showTabs = pretty.showTabs;
- syntaxHighlighting = pretty.syntaxHighlighting;
- intralineDifference = pretty.intralineDifference;
- }
-
- public String getFilename() {
- return fileName;
- }
-
- public PrettySettings setFileName(final String name) {
- fileName = name;
- return this;
- }
-
- public boolean isShowWhiteSpaceErrors() {
- return showWhiteSpaceErrors;
- }
-
- public PrettySettings setShowWhiteSpaceErrors(final boolean show) {
- showWhiteSpaceErrors = show;
- return this;
- }
-
- public int getLineLength() {
- return lineLength;
- }
-
- public PrettySettings setLineLength(final int len) {
- lineLength = len;
- return this;
- }
-
- public int getTabSize() {
- return tabSize;
- }
-
- public PrettySettings setTabSize(final int len) {
- tabSize = len;
- return this;
- }
-
- public boolean isShowTabs() {
- return showTabs;
- }
-
- public PrettySettings setShowTabs(final boolean show) {
- showTabs = show;
- return this;
- }
-
- public boolean isSyntaxHighlighting() {
- return syntaxHighlighting;
- }
-
- public void setSyntaxHighlighting(final boolean on) {
- syntaxHighlighting = on;
- }
-
- public boolean isIntralineDifference() {
- return intralineDifference;
- }
-
- public void setIntralineDifference(final boolean on) {
- intralineDifference = on;
- }
-}
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index 726098c583..5626739adf 100644
--- a/gerrit-reviewdb/pom.xml
+++ b/gerrit-reviewdb/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-reviewdb</artifactId>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
index bc5495f8a0..f428a22790 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
@@ -52,6 +52,10 @@ import java.sql.Timestamp;
* <li>{@link StarredChange}: user has starred the change, tracking
* notifications of updates on that change, or just book-marking it for faster
* future reference. One record per starred change.</li>
+ *
+ * <li>{@link AccountDiffPreference}: user's preferences for rendering side-to-side
+ * and unified diff</li>
+ *
* </ul>
*/
public final class Account {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
new file mode 100644
index 0000000000..4a3dd18120
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
@@ -0,0 +1,188 @@
+// 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.reviewdb;
+
+import com.google.gwtorm.client.Column;
+
+/** Diff formatting preferences of an account */
+public class AccountDiffPreference {
+
+ /** Default number of lines of context. */
+ public static final short DEFAULT_CONTEXT = 10;
+
+ /** Context setting to display the entire file. */
+ public static final short WHOLE_FILE_CONTEXT = -1;
+
+ /** Typical valid choices for the default context setting. */
+ public static final short[] CONTEXT_CHOICES =
+ {3, 10, 25, 50, 75, 100, WHOLE_FILE_CONTEXT};
+
+ public static enum Whitespace implements CodedEnum {
+ IGNORE_NONE('N'), //
+ IGNORE_SPACE_AT_EOL('E'), //
+ IGNORE_SPACE_CHANGE('S'), //
+ IGNORE_ALL_SPACE('A');
+
+ private final char code;
+
+ private Whitespace(final char c) {
+ code = c;
+ }
+
+ public char getCode() {
+ return code;
+ }
+
+ public static Whitespace forCode(final char c) {
+ for (final Whitespace s : Whitespace.values()) {
+ if (s.code == c) {
+ return s;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static AccountDiffPreference createDefault(Account.Id accountId) {
+ AccountDiffPreference p = new AccountDiffPreference(accountId);
+ p.setIgnoreWhitespace(Whitespace.IGNORE_NONE);
+ p.setTabSize(8);
+ p.setLineLength(100);
+ p.setSyntaxHighlighting(true);
+ p.setShowWhitespaceErrors(true);
+ p.setIntralineDifference(true);
+ p.setShowTabs(true);
+ p.setContext(DEFAULT_CONTEXT);
+ return p;
+ }
+
+ @Column(id = 1, name = Column.NONE)
+ protected Account.Id accountId;
+
+ @Column(id = 2)
+ protected char ignoreWhitespace;
+
+ @Column(id = 3)
+ protected int tabSize;
+
+ @Column(id = 4)
+ protected int lineLength;
+
+ @Column(id = 5)
+ protected boolean syntaxHighlighting;
+
+ @Column(id = 6)
+ protected boolean showWhitespaceErrors;
+
+ @Column(id = 7)
+ protected boolean intralineDifference;
+
+ @Column(id = 8)
+ protected boolean showTabs;
+
+ /** Number of lines of context when viewing a patch. */
+ @Column(id = 9)
+ protected short context;
+
+ protected AccountDiffPreference() {
+ }
+
+ public AccountDiffPreference(Account.Id accountId) {
+ this.accountId = accountId;
+ }
+
+ public AccountDiffPreference(AccountDiffPreference p) {
+ this.accountId = p.accountId;
+ this.ignoreWhitespace = p.ignoreWhitespace;
+ this.tabSize = p.tabSize;
+ this.lineLength = p.lineLength;
+ this.syntaxHighlighting = p.syntaxHighlighting;
+ this.showWhitespaceErrors = p.showWhitespaceErrors;
+ this.intralineDifference = p.intralineDifference;
+ this.showTabs = p.showTabs;
+ this.context = p.context;
+ }
+
+ public Account.Id getAccountId() {
+ return accountId;
+ }
+
+ public Whitespace getIgnoreWhitespace() {
+ return Whitespace.forCode(ignoreWhitespace);
+ }
+
+ public void setIgnoreWhitespace(Whitespace ignoreWhitespace) {
+ this.ignoreWhitespace = ignoreWhitespace.getCode();
+ }
+
+ public int getTabSize() {
+ return tabSize;
+ }
+
+ public void setTabSize(int tabSize) {
+ this.tabSize = tabSize;
+ }
+
+ public int getLineLength() {
+ return lineLength;
+ }
+
+ public void setLineLength(int lineLength) {
+ this.lineLength = lineLength;
+ }
+
+ public boolean isSyntaxHighlighting() {
+ return syntaxHighlighting;
+ }
+
+ public void setSyntaxHighlighting(boolean syntaxHighlighting) {
+ this.syntaxHighlighting = syntaxHighlighting;
+ }
+
+ public boolean isShowWhitespaceErrors() {
+ return showWhitespaceErrors;
+ }
+
+ public void setShowWhitespaceErrors(boolean showWhitespaceErrors) {
+ this.showWhitespaceErrors = showWhitespaceErrors;
+ }
+
+ public boolean isIntralineDifference() {
+ return intralineDifference;
+ }
+
+ public void setIntralineDifference(boolean intralineDifference) {
+ this.intralineDifference = intralineDifference;
+ }
+
+ public boolean isShowTabs() {
+ return showTabs;
+ }
+
+ public void setShowTabs(boolean showTabs) {
+ this.showTabs = showTabs;
+ }
+
+ /** Get the number of lines of context when viewing a patch. */
+ public short getContext() {
+ return context;
+ }
+
+ /** Set the number of lines of context when viewing a patch. */
+ public void setContext(final short context) {
+ assert 0 <= context || context == WHOLE_FILE_CONTEXT;
+ this.context = context;
+ }
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreferenceAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreferenceAccess.java
new file mode 100644
index 0000000000..d1d134bc10
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreferenceAccess.java
@@ -0,0 +1,26 @@
+// 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.reviewdb;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+
+public interface AccountDiffPreferenceAccess extends Access<AccountDiffPreference, Account.Id> {
+
+ @PrimaryKey("accountId")
+ AccountDiffPreference get(Account.Id key) throws OrmException;
+
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGeneralPreferences.java
index 64654c773d..9b607b0e98 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGeneralPreferences.java
@@ -18,15 +18,6 @@ import com.google.gwtorm.client.Column;
/** Preferences about a single user. */
public final class AccountGeneralPreferences {
- /** Default number of lines of context. */
- public static final short DEFAULT_CONTEXT = 10;
-
- /** Context setting to display the entire file. */
- public static final short WHOLE_FILE_CONTEXT = -1;
-
- /** Typical valid choices for the default context setting. */
- public static final short[] CONTEXT_CHOICES =
- {3, 10, 25, 50, 75, 100, WHOLE_FILE_CONTEXT};
/** Default number of items to display per page. */
public static final short DEFAULT_PAGESIZE = 25;
@@ -44,10 +35,6 @@ public final class AccountGeneralPreferences {
REPO_DOWNLOAD, PULL, CHECKOUT, CHERRY_PICK, FORMAT_PATCH;
}
- /** Default number of lines of context when viewing a patch. */
- @Column(id = 1)
- protected short defaultContext;
-
/** Number of changes to show in a screen. */
@Column(id = 2)
protected short maximumPageSize;
@@ -68,17 +55,11 @@ public final class AccountGeneralPreferences {
@Column(id = 6, length = 20, notNull = false)
protected String downloadCommand;
- public AccountGeneralPreferences() {
- }
-
- /** Get the default number of lines of context when viewing a patch. */
- public short getDefaultContext() {
- return defaultContext;
- }
+ /** If true we CC the user on their own changes. */
+ @Column(id = 7)
+ protected boolean copySelfOnEmail;
- /** Set the number of lines of context when viewing a patch. */
- public void setDefaultContext(final short s) {
- defaultContext = s;
+ public AccountGeneralPreferences() {
}
public short getMaximumPageSize() {
@@ -135,11 +116,19 @@ public final class AccountGeneralPreferences {
}
}
+ public boolean isCopySelfOnEmails() {
+ return copySelfOnEmail;
+ }
+
+ public void setCopySelfOnEmails(boolean includeSelfOnEmail) {
+ copySelfOnEmail = includeSelfOnEmail;
+ }
+
public void resetToDefaults() {
- defaultContext = DEFAULT_CONTEXT;
maximumPageSize = DEFAULT_PAGESIZE;
showSiteHeader = true;
useFlashClipboard = true;
+ copySelfOnEmail = false;
downloadUrl = null;
downloadCommand = null;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
index 5d1565b145..52bef2ba9c 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
@@ -16,9 +16,12 @@ package com.google.gerrit.reviewdb;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
+import com.google.gwtorm.client.StringKey;
/** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch {
+ public static final String FILTER_ALL = "*";
+
public static class Key extends CompoundKey<Account.Id> {
private static final long serialVersionUID = 1L;
@@ -28,14 +31,19 @@ public final class AccountProjectWatch {
@Column(id = 2)
protected Project.NameKey projectName;
+ @Column(id = 3)
+ protected Filter filter;
+
protected Key() {
accountId = new Account.Id();
projectName = new Project.NameKey();
+ filter = new Filter();
}
- public Key(final Account.Id a, final Project.NameKey g) {
+ public Key(Account.Id a, Project.NameKey g, String f) {
accountId = a;
projectName = g;
+ filter = new Filter(f);
}
@Override
@@ -45,7 +53,31 @@ public final class AccountProjectWatch {
@Override
public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {projectName};
+ return new com.google.gwtorm.client.Key<?>[] {projectName, filter};
+ }
+ }
+
+ public static class Filter extends StringKey<com.google.gwtorm.client.Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ @Column(id = 1)
+ protected String filter;
+
+ protected Filter() {
+ }
+
+ public Filter(String f) {
+ filter = f != null && !f.isEmpty() ? f : FILTER_ALL;
+ }
+
+ @Override
+ public String get() {
+ return filter;
+ }
+
+ @Override
+ protected void set(String newValue) {
+ filter = newValue;
}
}
@@ -83,6 +115,10 @@ public final class AccountProjectWatch {
return key.projectName;
}
+ public String getFilter() {
+ return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
+ }
+
public boolean isNotifyNewChanges() {
return notifyNewChanges;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatchAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatchAccess.java
index ce444895b6..254aac9859 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatchAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatchAccess.java
@@ -28,15 +28,6 @@ public interface AccountProjectWatchAccess extends
@Query("WHERE key.accountId = ?")
ResultSet<AccountProjectWatch> byAccount(Account.Id id) throws OrmException;
- @Query("WHERE notifyNewChanges = true AND key.projectName = ?")
- ResultSet<AccountProjectWatch> notifyNewChanges(Project.NameKey name)
- throws OrmException;
-
- @Query("WHERE notifyAllComments = true AND key.projectName = ?")
- ResultSet<AccountProjectWatch> notifyAllComments(Project.NameKey name)
- throws OrmException;
-
- @Query("WHERE notifySubmittedChanges = true AND key.projectName = ?")
- ResultSet<AccountProjectWatch> notifySubmittedChanges(Project.NameKey name)
- throws OrmException;
+ @Query("WHERE key.projectName = ?")
+ ResultSet<AccountProjectWatch> byProject(Project.NameKey name) throws OrmException;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java
index 7fafb13517..e4ae63dc71 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java
@@ -351,6 +351,10 @@ public final class Change {
@Column(id = 13)
protected String subject;
+ /** Topic name assigned by the user, if any. */
+ @Column(id = 14, notNull = false)
+ protected String topic;
+
protected Change() {
}
@@ -456,4 +460,12 @@ public final class Change {
open = newStatus.isOpen();
status = newStatus.getCode();
}
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public void setTopic(String topic) {
+ this.topic = topic;
+ }
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java
index 6beacde7eb..26785a8b81 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java
@@ -25,6 +25,9 @@ public interface PatchLineCommentAccess extends
@PrimaryKey("key")
PatchLineComment get(PatchLineComment.Key id) throws OrmException;
+ @Query("WHERE key.patchKey.patchSetId.changeId = ?")
+ ResultSet<PatchLineComment> byChange(Change.Id id) throws OrmException;
+
@Query("WHERE key.patchKey = ? AND status = '"
+ PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn")
ResultSet<PatchLineComment> published(Patch.Key patch) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
index 0db09e49d1..ec70051aed 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
@@ -25,6 +25,9 @@ public final class RefRight {
/** Pattern that matches all references in a project. */
public static final String ALL = "refs/*";
+ /** Prefix that triggers a regular expression pattern. */
+ public static final String REGEX_PREFIX = "^";
+
public static class RefPattern extends
StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
index c592cb1360..f9b3cfae1e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
@@ -76,6 +76,9 @@ public interface ReviewDb extends Schema {
AccountGroupAgreementAccess accountGroupAgreements();
@Relation
+ AccountDiffPreferenceAccess accountDiffPreferences();
+
+ @Relation
StarredChangeAccess starredChanges();
@Relation
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java
index bd798a064a..7df7619df3 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java
@@ -127,6 +127,14 @@ public final class TrackingId {
return key.changeId;
}
+ public String getTrackingId() {
+ return key.trackingId.get();
+ }
+
+ public String getSystem() {
+ return key.trackingSystem.get();
+ }
+
@Override
public int hashCode() {
return key.hashCode();
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
index 5f6b375812..0d41729425 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
@@ -47,17 +47,9 @@ ON account_group_members (group_id);
-- *********************************************************************
-- AccountProjectWatchAccess
-- @PrimaryKey covers: byAccount
--- covers: notifyNewChanges
-CREATE INDEX account_project_watches_ntNew
-ON account_project_watches (notify_new_changes, project_name);
-
--- covers: notifyAllComments
-CREATE INDEX account_project_watches_ntCmt
-ON account_project_watches (notify_all_comments, project_name);
-
--- covers: notifySubmittedChanges
-CREATE INDEX account_project_watches_ntSub
-ON account_project_watches (notify_submitted_changes, project_name);
+-- covers: byProject
+CREATE INDEX account_project_watches_byProject
+ON account_project_watches (project_name);
-- *********************************************************************
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
index 56acddbc4a..b44351cd77 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
@@ -86,20 +86,9 @@ ON account_group_members (group_id);
-- *********************************************************************
-- AccountProjectWatchAccess
-- @PrimaryKey covers: byAccount
--- covers: notifyNewChanges
-CREATE INDEX account_project_watches_ntNew
-ON account_project_watches (project_name)
-WHERE notify_new_changes = 'Y';
-
--- covers: notifyAllComments
-CREATE INDEX account_project_watches_ntCmt
-ON account_project_watches (project_name)
-WHERE notify_all_comments = 'Y';
-
--- covers: notifySubmittedChanges
-CREATE INDEX account_project_watches_ntSub
-ON account_project_watches (project_name)
-WHERE notify_submitted_changes = 'Y';
+-- covers: byProject
+CREATE INDEX account_project_watches_byProject
+ON account_project_watches (project_name);
-- *********************************************************************
diff --git a/gerrit-server/.settings/org.eclipse.jdt.core.prefs b/gerrit-server/.settings/org.eclipse.jdt.core.prefs
index 04afc7fac5..2f45466d84 100644
--- a/gerrit-server/.settings/org.eclipse.jdt.core.prefs
+++ b/gerrit-server/.settings/org.eclipse.jdt.core.prefs
@@ -1,14 +1,8 @@
-#Tue May 12 17:44:13 PDT 2009
+#Fri Jul 16 23:39:13 PDT 2010
eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
@@ -252,6 +246,8 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index 8f3169a918..db8093cc20 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-server</artifactId>
@@ -54,6 +54,11 @@ limitations under the License.
</dependency>
<dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
</dependency>
@@ -133,6 +138,11 @@ limitations under the License.
<groupId>com.google.gerrit</groupId>
<artifactId>juniversalchardet</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>dk.brics.automaton</groupId>
+ <artifactId>automaton</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g b/gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g
index 7842c1b2fe..74b785128e 100644
--- a/gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g
+++ b/gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g
@@ -19,13 +19,10 @@ options {
}
tokens {
- FIELD_NAME;
- DEFAULT_FIELD;
- SINGLE_WORD;
- EXACT_PHRASE;
AND;
OR;
NOT;
+ DEFAULT_FIELD;
}
@header {
@@ -63,6 +60,8 @@ package com.google.gerrit.server.query;
final QueryLexer lexer = new QueryLexer(new ANTLRStringStream(value));
lexer.mSINGLE_WORD();
return lexer.nextToken().getType() == QueryParser.EOF;
+ } catch (QueryParseInternalException e) {
+ return false;
} catch (RecognitionException e) {
return false;
}
@@ -81,6 +80,13 @@ package com.google.gerrit.server.query;
package com.google.gerrit.server.query;
}
@lexer::members {
+ @Override
+ public void displayRecognitionError(String[] tokenNames,
+ RecognitionException e) {
+ String hdr = getErrorHeader(e);
+ String msg = getErrorMessage(e, tokenNames);
+ throw new QueryParser.QueryParseInternalException(hdr + " " + msg);
+ }
}
query
@@ -110,10 +116,12 @@ conditionAnd2
conditionNot
: '-' conditionBase -> ^(NOT conditionBase)
| NOT^ conditionBase
+ | VARIABLE_ASSIGN^ conditionOr ')'!
| conditionBase
;
conditionBase
- : (FIELD_NAME ':') => FIELD_NAME^ ':'! fieldValue
+ : '('! conditionOr ')'!
+ | (FIELD_NAME ':') => FIELD_NAME^ ':'! fieldValue
| fieldValue -> ^(DEFAULT_FIELD fieldValue)
;
@@ -121,7 +129,6 @@ fieldValue
: n=FIELD_NAME -> SINGLE_WORD[n]
| SINGLE_WORD
| EXACT_PHRASE
- | '('! conditionOr ')'!
;
AND: 'AND' ;
@@ -133,7 +140,14 @@ WS
;
FIELD_NAME
- : ('a'..'z')+
+ : ('a'..'z' | '_')+
+ ;
+
+VARIABLE_ASSIGN
+ : ('A'..'Z') ('A'..'Z' | 'a'..'Z')* '=' '(' {
+ String s = $text;
+ setText(s.substring(0, s.length() - 2));
+ }
;
EXACT_PHRASE
@@ -164,7 +178,9 @@ fragment NON_WORD
// '/' permit
| ':'
| ';'
- | '<' | '=' | '>'
+ // '<' permit
+ // '=' permit
+ // '>' permit
| '?'
| '[' | ']'
| '{' | '}'
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 259b0fb090..f2d0ad08c5 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
@@ -24,18 +24,22 @@ import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.events.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.CommentAddedEvent;
+import com.google.gerrit.server.events.EventFactory;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.google.inject.internal.Nullable;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -60,68 +64,6 @@ public class ChangeHookRunner {
/** A logger for this class. */
private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class);
- public static abstract class ChangeEvent {
- }
-
- public static class ApprovalAttribute {
- public String type;
- public String description;
- public String value;
- }
-
- public static class AuthorAttribute {
- public String name;
- public String email;
- }
-
- public static class ChangeAttribute {
- public String project;
- public String branch;
- public String id;
- public String number;
- public String subject;
- public AuthorAttribute owner;
- public String url;
- }
-
- public static class PatchSetAttribute {
- public String number;
- public String revision;
- public String ref;
- public AuthorAttribute uploader;
- }
-
- public static class CommentAddedEvent extends ChangeEvent {
- public final String type = "comment-added";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AuthorAttribute author;
- public ApprovalAttribute[] approvals;
- public String comment;
- }
-
- public static class ChangeMergedEvent extends ChangeEvent {
- public final String type = "change-merged";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AuthorAttribute submitter;
- }
-
- public static class ChangeAbandonedEvent extends ChangeEvent {
- public final String type = "change-abandoned";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AuthorAttribute abandoner;
- public String reason;
- }
-
- public static class PatchSetCreatedEvent extends ChangeEvent {
- public final String type = "patchset-created";
- public ChangeAttribute change;
- public PatchSetAttribute patchSet;
- public AuthorAttribute uploader;
- }
-
private static class ChangeListenerHolder {
final ChangeListener listener;
final IdentifiedUser user;
@@ -160,8 +102,7 @@ public class ChangeHookRunner {
private final ApprovalTypes approvalTypes;
- private final Provider<String> urlProvider;
-
+ private final EventFactory eventFactory;
/**
* Create a new ChangeHookRunner.
@@ -179,13 +120,13 @@ public class ChangeHookRunner {
final ProjectCache projectCache,
final AccountCache accountCache,
final ApprovalTypes approvalTypes,
- @CanonicalWebUrl @Nullable final Provider<String> cwu) {
+ final EventFactory eventFactory) {
this.repoManager = repoManager;
this.hookQueue = queue.createQueue(1, "hook");
this.projectCache = projectCache;
this.accountCache = accountCache;
this.approvalTypes = approvalTypes;
- this.urlProvider = cwu;
+ this.eventFactory = eventFactory;
final File hooksPath = sitePath.resolve(getValue(config, "hooks", "path", sitePath.hooks_dir.getAbsolutePath()));
@@ -241,9 +182,9 @@ public class ChangeHookRunner {
final PatchSetCreatedEvent event = new PatchSetCreatedEvent();
final AccountState uploader = accountCache.get(patchSet.getUploader());
- event.change = getChangeAttribute(change);
- event.patchSet = getPatchSetAttribute(patchSet);
- event.uploader = getAccountAttribute(uploader.getAccount());
+ event.change = eventFactory.asChangeAttribute(change);
+ event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+ event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
@@ -279,9 +220,9 @@ public class ChangeHookRunner {
public void doCommentAddedHook(final Change change, final Account account, final PatchSet patchSet, final String comment, final Map<ApprovalCategory.Id, ApprovalCategoryValue.Id> approvals) {
final CommentAddedEvent event = new CommentAddedEvent();
- event.change = getChangeAttribute(change);
- event.author = getAccountAttribute(account);
- event.patchSet = getPatchSetAttribute(patchSet);
+ event.change = eventFactory.asChangeAttribute(change);
+ event.author = eventFactory.asAccountAttribute(account);
+ event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
event.comment = comment;
if (approvals.size() > 0) {
@@ -329,9 +270,9 @@ public class ChangeHookRunner {
public void doChangeMergedHook(final Change change, final Account account, final PatchSet patchSet) {
final ChangeMergedEvent event = new ChangeMergedEvent();
- event.change = getChangeAttribute(change);
- event.submitter = getAccountAttribute(account);
- event.patchSet = getPatchSetAttribute(patchSet);
+ event.change = eventFactory.asChangeAttribute(change);
+ event.submitter = eventFactory.asAccountAttribute(account);
+ event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
@@ -363,8 +304,8 @@ public class ChangeHookRunner {
public void doChangeAbandonedHook(final Change change, final Account account, final String reason) {
final ChangeAbandonedEvent event = new ChangeAbandonedEvent();
- event.change = getChangeAttribute(change);
- event.abandoner = getAccountAttribute(account);
+ event.change = eventFactory.asChangeAttribute(change);
+ event.abandoner = eventFactory.asAccountAttribute(account);
event.reason = reason;
fireEvent(change, event);
@@ -404,22 +345,6 @@ public class ChangeHookRunner {
return pc.controlFor(change).isVisible();
}
- /** Get a link to the change; null if the server doesn't know its own address. */
- private String getChangeUrl(final Change change) {
- if (change != null && getGerritUrl() != null) {
- final StringBuilder r = new StringBuilder();
- r.append(getGerritUrl());
- r.append(change.getChangeId());
- return r.toString();
- }
- return null;
- }
-
- private String getGerritUrl() {
- return urlProvider.get();
- }
-
-
/**
* Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
* @param approval
@@ -436,54 +361,6 @@ public class ChangeHookRunner {
}
/**
- * Create an AuthorAttribute for the given account suitable for serialization to JSON.
- *
- * @param account
- * @return object suitable for serialization to JSON
- */
- private AuthorAttribute getAccountAttribute(final Account account) {
- AuthorAttribute author = new AuthorAttribute();
- author.name = account.getFullName();
- author.email = account.getPreferredEmail();
- return author;
- }
-
- /**
- * Create a ChangeAttribute for the given change suitable for serialization to JSON.
- *
- * @param change
- * @return object suitable for serialization to JSON
- */
- private ChangeAttribute getChangeAttribute(final Change change) {
- ChangeAttribute a = new ChangeAttribute();
- a.project = change.getProject().get();
- a.branch = change.getDest().getShortName();
- a.id = change.getKey().get();
- a.number = change.getId().toString();
- a.subject = change.getSubject();
- final AccountState owner = accountCache.get(change.getOwner());
- a.owner = getAccountAttribute(owner.getAccount());
- a.url = getChangeUrl(change);
- return a;
- }
-
- /**
- * Create an PatchSetAttribute for the given patchset suitable for serialization to JSON.
- *
- * @param patchSet
- * @return object suitable for serialization to JSON
- */
- private PatchSetAttribute getPatchSetAttribute(final PatchSet patchSet) {
- PatchSetAttribute p = new PatchSetAttribute();
- p.revision = patchSet.getRevision().get();
- p.number = Integer.toString(patchSet.getPatchSetId());
- p.ref = patchSet.getRefName();
- final AccountState uploader = accountCache.get(patchSet.getUploader());
- p.uploader = getAccountAttribute(uploader.getAccount());
- return p;
- }
-
- /**
* Get the display name for the given account.
*
* @param account Account to get name for.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java
index 7e8e853610..65a9857e9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeListener.java
@@ -14,7 +14,8 @@
package com.google.gerrit.common;
-import com.google.gerrit.common.ChangeHookRunner.ChangeEvent;
+import com.google.gerrit.server.events.ChangeEvent;
+
public interface ChangeListener {
public void onChangeEvent(ChangeEvent event);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
index a17149743f..1bd20662b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
@@ -15,11 +15,13 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.Collection;
import java.util.Collections;
import java.util.Set;
@@ -42,6 +44,11 @@ public class AnonymousUser extends CurrentUser {
}
@Override
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ return Collections.emptySet();
+ }
+
+ @Override
public String toString() {
return "ANONYMOUS";
}
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 740ce9c527..36fc2ff80a 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
@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.TrackingId;
import com.google.gerrit.server.config.TrackingFooter;
import com.google.gerrit.server.config.TrackingFooters;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmConcurrencyException;
@@ -135,8 +136,8 @@ public class ChangeUtil {
db.trackingIds().delete(toDelete);
}
- public static void submit(PatchSet.Id patchSetId, IdentifiedUser user, ReviewDb db, MergeQueue merger)
- throws OrmException {
+ public static void submit(MergeOp.Factory opFactory, PatchSet.Id patchSetId,
+ IdentifiedUser user, ReviewDb db, MergeQueue merger) throws OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final PatchSetApproval approval = createSubmitApproval(patchSetId, user, db);
@@ -154,7 +155,7 @@ public class ChangeUtil {
});
if (change.getStatus() == Change.Status.SUBMITTED) {
- merger.merge(change.getDest());
+ merger.merge(opFactory, change.getDest());
}
}
@@ -177,18 +178,22 @@ public class ChangeUtil {
return new PatchSetApproval(akey, (short) 1);
}
-
- public static void computeSortKey(final Change c) {
+ public static String sortKey(long lastUpdated, int id){
// The encoding uses minutes since Wed Oct 1 00:00:00 2008 UTC.
// We overrun approximately 4,085 years later, so ~6093.
//
- final long lastUpdatedOn =
- (c.getLastUpdatedOn().getTime() / 1000L) - 1222819200L;
+ final long lastUpdatedOn = (lastUpdated / 1000L) - 1222819200L;
final StringBuilder r = new StringBuilder(16);
r.setLength(16);
formatHexInt(r, 0, (int) (lastUpdatedOn / 60));
- formatHexInt(r, 8, c.getId().get());
- c.setSortKey(r.toString());
+ formatHexInt(r, 8, id);
+ return r.toString();
+ }
+
+ public static void computeSortKey(final Change c) {
+ long lastUpdated = c.getLastUpdatedOn().getTime();
+ int id = c.getId().get();
+ c.setSortKey(sortKey(lastUpdated, id));
}
private static final char[] hexchar =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 17e69c7da3..751d2158a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -15,10 +15,12 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.servlet.RequestScoped;
+import java.util.Collection;
import java.util.Set;
/**
@@ -59,6 +61,9 @@ public abstract class CurrentUser {
/** Set of changes starred by this user. */
public abstract Set<Change.Id> getStarredChanges();
+ /** Filters selecting changes the user wants to monitor. */
+ public abstract Collection<AccountProjectWatch> getNotificationFilters();
+
/** Is the user a non-interactive user? */
public boolean isBatchUser() {
return getEffectiveGroups().contains(authConfig.getBatchUsersGroup());
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 6900538f38..78cbed3bb8 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
@@ -15,7 +15,9 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
@@ -28,7 +30,6 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.PersonIdent;
@@ -41,9 +42,11 @@ import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.URL;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.TimeZone;
@@ -73,6 +76,11 @@ public class IdentifiedUser extends CurrentUser {
return create(AccessPath.UNKNOWN, null, id);
}
+ public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
+ return new IdentifiedUser(AccessPath.UNKNOWN, authConfig, canonicalUrl,
+ realm, accountCache, null, db, id);
+ }
+
public IdentifiedUser create(AccessPath accessPath,
Provider<SocketAddress> remotePeerProvider, Account.Id id) {
return new IdentifiedUser(accessPath, authConfig, canonicalUrl, realm,
@@ -138,6 +146,7 @@ public class IdentifiedUser extends CurrentUser {
private Set<String> emailAddresses;
private Set<AccountGroup.Id> effectiveGroups;
private Set<Change.Id> starredChanges;
+ private Collection<AccountProjectWatch> notificationFilters;
private IdentifiedUser(final AccessPath accessPath,
final AuthConfig authConfig, final Provider<String> canonicalUrl,
@@ -174,6 +183,20 @@ public class IdentifiedUser extends CurrentUser {
return state().getAccount();
}
+ public AccountDiffPreference getAccountDiffPreference() {
+ AccountDiffPreference diffPref;
+ try {
+ diffPref = dbProvider.get().accountDiffPreferences().get(getAccountId());
+ if (diffPref == null) {
+ diffPref = AccountDiffPreference.createDefault(getAccountId());
+ }
+ } catch (OrmException e) {
+ log.warn("Cannot query account diff preferences", e);
+ diffPref = AccountDiffPreference.createDefault(getAccountId());
+ }
+ return diffPref;
+ }
+
public Set<String> getEmailAddresses() {
if (emailAddresses == null) {
emailAddresses = state().getEmailAddresses();
@@ -206,8 +229,6 @@ public class IdentifiedUser extends CurrentUser {
.byAccount(getAccountId())) {
h.add(sc.getChangeId());
}
- } catch (ProvisionException e) {
- log.warn("Cannot query starred by user changes", e);
} catch (OrmException e) {
log.warn("Cannot query starred by user changes", e);
}
@@ -216,6 +237,25 @@ public class IdentifiedUser extends CurrentUser {
return starredChanges;
}
+ @Override
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ if (notificationFilters == null) {
+ if (dbProvider == null) {
+ throw new OutOfScopeException("Not in request scoped user");
+ }
+ List<AccountProjectWatch> r;
+ try {
+ r = dbProvider.get().accountProjectWatches() //
+ .byAccount(getAccountId()).toList();
+ } catch (OrmException e) {
+ log.warn("Cannot query notification filters of a user", e);
+ r = Collections.emptyList();
+ }
+ notificationFilters = Collections.unmodifiableList(r);
+ }
+ return notificationFilters;
+ }
+
public PersonIdent newRefLogIdent() {
return newRefLogIdent(new Date(), TimeZone.getDefault());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
index 26e510f9d3..e422b1990f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
@@ -15,12 +15,15 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.net.SocketAddress;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -57,6 +60,11 @@ public class PeerDaemonUser extends CurrentUser {
return Collections.emptySet();
}
+ @Override
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ return Collections.emptySet();
+ }
+
public SocketAddress getRemoteAddress() {
return peer;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
index 1b010a6ec7..32933241d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
@@ -15,11 +15,14 @@
package com.google.gerrit.server;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -71,6 +74,11 @@ public class ReplicationUser extends CurrentUser {
return Collections.emptySet();
}
+ @Override
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ return Collections.emptySet();
+ }
+
public boolean isEverythingVisible() {
return getEffectiveGroups() == EVERYTHING_VISIBLE;
}
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 d4f7a4d2fc..64046fa9a4 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
@@ -19,8 +19,7 @@ import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
-import com.google.gwtorm.client.OrmException;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -43,69 +42,73 @@ public class AccountByEmailCacheImpl implements AccountByEmailCache {
protected void configure() {
final TypeLiteral<Cache<String, Set<Account.Id>>> type =
new TypeLiteral<Cache<String, Set<Account.Id>>>() {};
- core(type, CACHE_NAME);
+ core(type, CACHE_NAME).populateWith(Loader.class);
bind(AccountByEmailCacheImpl.class);
bind(AccountByEmailCache.class).to(AccountByEmailCacheImpl.class);
}
};
}
- private final SchemaFactory<ReviewDb> schema;
- private final SelfPopulatingCache<String, Set<Account.Id>> self;
+ private final Cache<String, Set<Account.Id>> cache;
@Inject
- AccountByEmailCacheImpl(final SchemaFactory<ReviewDb> schema,
- @Named(CACHE_NAME) final Cache<String, Set<Account.Id>> rawCache) {
- this.schema = schema;
- this.self = new SelfPopulatingCache<String, Set<Account.Id>>(rawCache) {
- @Override
- protected Set<Account.Id> createEntry(final String key) throws Exception {
- return lookup(key);
- }
-
- @Override
- protected Set<Account.Id> missing(final String key) {
- return Collections.emptySet();
- }
- };
- }
-
- private Set<Account.Id> lookup(final String email) throws OrmException {
- final ReviewDb db = schema.open();
- try {
- final HashSet<Account.Id> r = new HashSet<Account.Id>();
- for (Account a : db.accounts().byPreferredEmail(email)) {
- r.add(a.getId());
- }
- for (AccountExternalId a : db.accountExternalIds().byEmailAddress(email)) {
- r.add(a.getAccountId());
- }
- return pack(r);
- } finally {
- db.close();
- }
+ AccountByEmailCacheImpl(
+ @Named(CACHE_NAME) final Cache<String, Set<Account.Id>> cache) {
+ this.cache = cache;
}
public Set<Account.Id> get(final String email) {
- return self.get(email);
+ return cache.get(email);
}
public void evict(final String email) {
- self.remove(email);
+ cache.remove(email);
}
- private static Set<Account.Id> pack(final Set<Account.Id> c) {
- switch (c.size()) {
- case 0:
- return Collections.emptySet();
- case 1:
- return one(c);
- default:
- return Collections.unmodifiableSet(new HashSet<Account.Id>(c));
+ static class Loader extends EntryCreator<String, Set<Account.Id>> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ Loader(final SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public Set<Account.Id> createEntry(final String email) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ final HashSet<Account.Id> r = new HashSet<Account.Id>();
+ for (Account a : db.accounts().byPreferredEmail(email)) {
+ r.add(a.getId());
+ }
+ for (AccountExternalId a : db.accountExternalIds()
+ .byEmailAddress(email)) {
+ r.add(a.getAccountId());
+ }
+ return pack(r);
+ } finally {
+ db.close();
+ }
}
- }
- private static <T> Set<T> one(final Set<T> c) {
- return Collections.singleton(c.iterator().next());
+ @Override
+ public Set<Account.Id> missing(final String key) {
+ return Collections.emptySet();
+ }
+
+ private static Set<Account.Id> pack(final Set<Account.Id> c) {
+ switch (c.size()) {
+ case 0:
+ return Collections.emptySet();
+ case 1:
+ return one(c);
+ default:
+ return Collections.unmodifiableSet(new HashSet<Account.Id>(c));
+ }
+ }
+
+ private static <T> Set<T> one(final Set<T> c) {
+ return Collections.singleton(c.iterator().next());
+ }
}
}
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 dcd0bdad42..52ccc66d39 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
@@ -21,7 +21,7 @@ import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
@@ -39,136 +39,147 @@ import java.util.Set;
/** Caches important (but small) account state to avoid database hits. */
@Singleton
public class AccountCacheImpl implements AccountCache {
- private static final String CACHE_NAME = "accounts";
+ private static final String BYID_NAME = "accounts";
+ private static final String BYUSER_NAME = "accounts_byname";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<Object, AccountState>> type =
- new TypeLiteral<Cache<Object, AccountState>>() {};
- core(type, CACHE_NAME);
+ final TypeLiteral<Cache<Account.Id, AccountState>> byIdType =
+ new TypeLiteral<Cache<Account.Id, AccountState>>() {};
+ core(byIdType, BYID_NAME).populateWith(ByIdLoader.class);
+
+ final TypeLiteral<Cache<String, Account.Id>> byUsernameType =
+ new TypeLiteral<Cache<String, Account.Id>>() {};
+ core(byUsernameType, BYUSER_NAME).populateWith(ByNameLoader.class);
+
bind(AccountCacheImpl.class);
bind(AccountCache.class).to(AccountCacheImpl.class);
}
};
}
- private final SchemaFactory<ReviewDb> schema;
- private final GroupCache groupCache;
- private final SelfPopulatingCache<Account.Id, AccountState> byId;
- private final SelfPopulatingCache<String, Account.Id> byName;
-
- private final Set<AccountGroup.Id> registered;
- private final Set<AccountGroup.Id> anonymous;
+ private final Cache<Account.Id, AccountState> byId;
+ private final Cache<String, Account.Id> byName;
@Inject
- AccountCacheImpl(final SchemaFactory<ReviewDb> sf, final AuthConfig auth,
- final GroupCache groupCache,
- @Named(CACHE_NAME) final Cache<Object, AccountState> rawCache) {
- schema = sf;
- registered = auth.getRegisteredGroups();
- anonymous = auth.getAnonymousGroups();
- this.groupCache = groupCache;
-
- byId = new SelfPopulatingCache<Account.Id, AccountState>((Cache) rawCache) {
- @Override
- protected AccountState createEntry(Account.Id key) throws Exception {
- return lookup(key);
- }
+ AccountCacheImpl(@Named(BYID_NAME) Cache<Account.Id, AccountState> byId,
+ @Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
+ this.byId = byId;
+ this.byName = byUsername;
+ }
- @Override
- protected AccountState missing(final Account.Id key) {
- return missingAccount(key);
- }
- };
+ public AccountState get(final Account.Id accountId) {
+ return byId.get(accountId);
+ }
- byName = new SelfPopulatingCache<String, Account.Id>((Cache) rawCache) {
- @Override
- protected Account.Id createEntry(String username) throws Exception {
- return lookup(username);
- }
- };
+ @Override
+ public AccountState getByUsername(String username) {
+ Account.Id id = byName.get(username);
+ return id != null ? byId.get(id) : null;
}
- private AccountState lookup(final Account.Id who) throws OrmException {
- final ReviewDb db = schema.open();
- try {
- final AccountState state = load(db, who);
- if (state.getUserName() != null) {
- byName.put(state.getUserName(), state.getAccount().getId());
- }
- return state;
- } finally {
- db.close();
- }
+ public void evict(final Account.Id accountId) {
+ byId.remove(accountId);
}
- private Account.Id lookup(final String username) throws OrmException {
- final ReviewDb db = schema.open();
- try {
- final AccountExternalId.Key key =
- new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME, username);
- final AccountExternalId id = db.accountExternalIds().get(key);
- return id != null ? id.getAccountId() : null;
- } finally {
- db.close();
- }
+ public void evictByUsername(String username) {
+ byName.remove(username);
}
- private AccountState load(final ReviewDb db, final Account.Id who)
- throws OrmException {
- final Account account = db.accounts().get(who);
- if (account == null) {
- // Account no longer exists? They are anonymous.
- //
- return missingAccount(who);
+ static class ByIdLoader extends EntryCreator<Account.Id, AccountState> {
+ private final SchemaFactory<ReviewDb> schema;
+ private final Set<AccountGroup.Id> registered;
+ private final Set<AccountGroup.Id> anonymous;
+ private final GroupCache groupCache;
+ private final Cache<String, Account.Id> byName;
+
+ @Inject
+ ByIdLoader(SchemaFactory<ReviewDb> sf, AuthConfig auth,
+ GroupCache groupCache,
+ @Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
+ this.schema = sf;
+ this.registered = auth.getRegisteredGroups();
+ this.anonymous = auth.getAnonymousGroups();
+ this.groupCache = groupCache;
+ this.byName = byUsername;
}
- final Collection<AccountExternalId> externalIds =
- Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
- who).toList());
-
- Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
- for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
- final AccountGroup.Id groupId = g.getAccountGroupId();
- final AccountGroup group = groupCache.get(groupId);
- if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
- internalGroups.add(groupId);
+ @Override
+ public AccountState createEntry(final Account.Id key) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ final AccountState state = load(db, key);
+ if (state.getUserName() != null) {
+ byName.put(state.getUserName(), state.getAccount().getId());
+ }
+ return state;
+ } finally {
+ db.close();
}
}
- if (internalGroups.isEmpty()) {
- internalGroups = registered;
- } else {
- internalGroups.addAll(registered);
- internalGroups = Collections.unmodifiableSet(internalGroups);
- }
+ private AccountState load(final ReviewDb db, final Account.Id who)
+ throws OrmException {
+ final Account account = db.accounts().get(who);
+ if (account == null) {
+ // Account no longer exists? They are anonymous.
+ //
+ return missing(who);
+ }
- return new AccountState(account, internalGroups, externalIds);
- }
+ final Collection<AccountExternalId> externalIds =
+ Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
+ who).toList());
+
+ Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
+ for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
+ final AccountGroup.Id groupId = g.getAccountGroupId();
+ final AccountGroup group = groupCache.get(groupId);
+ if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
+ internalGroups.add(groupId);
+ }
+ }
- private AccountState missingAccount(final Account.Id accountId) {
- final Account account = new Account(accountId);
- final Collection<AccountExternalId> ids = Collections.emptySet();
- return new AccountState(account, anonymous, ids);
- }
+ if (internalGroups.isEmpty()) {
+ internalGroups = registered;
+ } else {
+ internalGroups.addAll(registered);
+ internalGroups = Collections.unmodifiableSet(internalGroups);
+ }
- public AccountState get(final Account.Id accountId) {
- return byId.get(accountId);
- }
+ return new AccountState(account, internalGroups, externalIds);
+ }
- @Override
- public AccountState getByUsername(String username) {
- Account.Id id = byName.get(username);
- return id != null ? get(id) : null;
+ @Override
+ public AccountState missing(final Account.Id accountId) {
+ final Account account = new Account(accountId);
+ final Collection<AccountExternalId> ids = Collections.emptySet();
+ return new AccountState(account, anonymous, ids);
+ }
}
- public void evict(final Account.Id accountId) {
- byId.remove(accountId);
- }
+ static class ByNameLoader extends EntryCreator<String, Account.Id> {
+ private final SchemaFactory<ReviewDb> schema;
- public void evictByUsername(String username) {
- byName.remove(username);
+ @Inject
+ ByNameLoader(final SchemaFactory<ReviewDb> sf) {
+ this.schema = sf;
+ }
+
+ @Override
+ public Account.Id createEntry(final String username) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ final AccountExternalId.Key key = new AccountExternalId.Key( //
+ AccountExternalId.SCHEME_USERNAME, //
+ username);
+ final AccountExternalId id = db.accountExternalIds().get(key);
+ return id != null ? id.getAccountId() : null;
+ } finally {
+ db.close();
+ }
+ }
}
}
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 1509299447..e875a19933 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
@@ -91,6 +91,9 @@ public class ChangeUserName implements Callable<VoidResult> {
public VoidResult call() throws OrmException, NameAlreadyUsedException,
InvalidUserNameException {
final Collection<AccountExternalId> old = old();
+ if (!old.isEmpty()) {
+ throw new IllegalStateException("Username cannot be changed.");
+ }
if (newUsername != null && !newUsername.isEmpty()) {
if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
new file mode 100644
index 0000000000..1fc87fe411
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
@@ -0,0 +1,63 @@
+// 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.account;
+
+import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.reviewdb.AccountExternalId;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.Collections;
+import java.util.concurrent.Callable;
+
+/** Operation to clear a password for an account. */
+public class ClearPassword implements Callable<AccountExternalId> {
+ public interface Factory {
+ ClearPassword create(AccountExternalId.Key forUser);
+ }
+
+ private final AccountCache accountCache;
+ private final ReviewDb db;
+ private final IdentifiedUser user;
+
+ private final AccountExternalId.Key forUser;
+
+ @Inject
+ ClearPassword(final AccountCache accountCache, final ReviewDb db,
+ final IdentifiedUser user,
+
+ @Assisted AccountExternalId.Key forUser) {
+ this.accountCache = accountCache;
+ this.db = db;
+ this.user = user;
+
+ this.forUser = forUser;
+ }
+
+ public AccountExternalId call() throws OrmException, NoSuchEntityException {
+ AccountExternalId id = db.accountExternalIds().get(forUser);
+ if (id == null || !user.getAccountId().equals(id.getAccountId())) {
+ throw new NoSuchEntityException();
+ }
+
+ id.setPassword(null);
+ db.accountExternalIds().update(Collections.singleton(id));
+ accountCache.evict(user.getAccountId());
+ return id;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
index 9fedd806a2..d948aef8c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -19,9 +19,8 @@ import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
-import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -34,114 +33,45 @@ import java.util.Collection;
/** Tracks group objects in memory for efficient access. */
@Singleton
public class GroupCacheImpl implements GroupCache {
- private static final String CACHE_NAME = "groups";
+ private static final String BYID_NAME = "groups";
+ private static final String BYNAME_NAME = "groups_byname";
+ private static final String BYEXT_NAME = "groups_byext";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<com.google.gwtorm.client.Key<?>, AccountGroup>> byId =
- new TypeLiteral<Cache<com.google.gwtorm.client.Key<?>, AccountGroup>>() {};
- core(byId, CACHE_NAME);
+ final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> byId =
+ new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {};
+ core(byId, BYID_NAME).populateWith(ByIdLoader.class);
+
+ final TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>> byName =
+ new TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>>() {};
+ core(byName, BYNAME_NAME).populateWith(ByNameLoader.class);
+
+ final TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>> byExternalName =
+ new TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>>() {};
+ core(byExternalName, BYEXT_NAME) //
+ .populateWith(ByExternalNameLoader.class);
+
bind(GroupCacheImpl.class);
bind(GroupCache.class).to(GroupCacheImpl.class);
}
};
}
- private final SchemaFactory<ReviewDb> schema;
- private final AccountGroup.Id administrators;
- private final SelfPopulatingCache<AccountGroup.Id, AccountGroup> byId;
- private final SelfPopulatingCache<AccountGroup.NameKey, AccountGroup> byName;
- private final SelfPopulatingCache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName;
+ private final Cache<AccountGroup.Id, AccountGroup> byId;
+ private final Cache<AccountGroup.NameKey, AccountGroup> byName;
+ private final Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName;
@Inject
GroupCacheImpl(
- final SchemaFactory<ReviewDb> sf,
- final AuthConfig authConfig,
- @Named(CACHE_NAME) final Cache<com.google.gwtorm.client.Key<?>, AccountGroup> rawAny) {
- schema = sf;
- administrators = authConfig.getAdministratorsGroup();
-
- byId =
- new SelfPopulatingCache<AccountGroup.Id, AccountGroup>((Cache) rawAny) {
- @Override
- public AccountGroup createEntry(final AccountGroup.Id key)
- throws Exception {
- return lookup(key);
- }
-
- @Override
- protected AccountGroup missing(final AccountGroup.Id key) {
- return missingGroup(key);
- }
- };
-
- byName =
- new SelfPopulatingCache<AccountGroup.NameKey, AccountGroup>(
- (Cache) rawAny) {
- @Override
- public AccountGroup createEntry(final AccountGroup.NameKey key)
- throws Exception {
- return lookup(key);
- }
- };
-
- byExternalName =
- new SelfPopulatingCache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>(
- (Cache) rawAny) {
- @Override
- public Collection<AccountGroup> createEntry(
- final AccountGroup.ExternalNameKey key) throws Exception {
- return lookup(key);
- }
- };
- }
-
- private AccountGroup lookup(final AccountGroup.Id groupId)
- throws OrmException {
- final ReviewDb db = schema.open();
- try {
- final AccountGroup group = db.accountGroups().get(groupId);
- if (group != null) {
- return group;
- } else {
- return missingGroup(groupId);
- }
- } finally {
- db.close();
- }
- }
-
- private AccountGroup missingGroup(final AccountGroup.Id groupId) {
- final AccountGroup.NameKey name =
- new AccountGroup.NameKey("Deleted Group" + groupId.toString());
- final AccountGroup g = new AccountGroup(name, groupId);
- g.setType(AccountGroup.Type.SYSTEM);
- g.setOwnerGroupId(administrators);
- return g;
- }
-
- private AccountGroup lookup(final AccountGroup.NameKey name)
- throws OrmException {
- final AccountGroupName r;
- final ReviewDb db = schema.open();
- try {
- r = db.accountGroupNames().get(name);
- } finally {
- db.close();
- }
- return r != null ? get(r.getId()) : null;
- }
-
- private Collection<AccountGroup> lookup(
- final AccountGroup.ExternalNameKey name) throws OrmException {
- final ReviewDb db = schema.open();
- try {
- return db.accountGroups().byExternalName(name).toList();
- } finally {
- db.close();
- }
+ @Named(BYID_NAME) Cache<AccountGroup.Id, AccountGroup> byId,
+ @Named(BYNAME_NAME) Cache<AccountGroup.NameKey, AccountGroup> byName,
+ @Named(BYEXT_NAME) Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName) {
+ this.byId = byId;
+ this.byName = byName;
+ this.byExternalName = byExternalName;
}
public AccountGroup get(final AccountGroup.Id groupId) {
@@ -166,4 +96,88 @@ public class GroupCacheImpl implements GroupCache {
final AccountGroup.ExternalNameKey externalName) {
return byExternalName.get(externalName);
}
+
+ static class ByIdLoader extends EntryCreator<AccountGroup.Id, AccountGroup> {
+ private final SchemaFactory<ReviewDb> schema;
+ private final AccountGroup.Id administrators;
+
+ @Inject
+ ByIdLoader(final SchemaFactory<ReviewDb> sf, final AuthConfig authConfig) {
+ schema = sf;
+ administrators = authConfig.getAdministratorsGroup();
+ }
+
+ @Override
+ public AccountGroup createEntry(final AccountGroup.Id key) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ final AccountGroup group = db.accountGroups().get(key);
+ if (group != null) {
+ return group;
+ } else {
+ return missing(key);
+ }
+ } finally {
+ db.close();
+ }
+ }
+
+ @Override
+ public AccountGroup missing(final AccountGroup.Id key) {
+ final AccountGroup.NameKey name =
+ new AccountGroup.NameKey("Deleted Group" + key.toString());
+ final AccountGroup g = new AccountGroup(name, key);
+ g.setType(AccountGroup.Type.SYSTEM);
+ g.setOwnerGroupId(administrators);
+ return g;
+ }
+ }
+
+ static class ByNameLoader extends
+ EntryCreator<AccountGroup.NameKey, AccountGroup> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByNameLoader(final SchemaFactory<ReviewDb> sf) {
+ schema = sf;
+ }
+
+ @Override
+ public AccountGroup createEntry(final AccountGroup.NameKey key)
+ throws Exception {
+ final AccountGroupName r;
+ final ReviewDb db = schema.open();
+ try {
+ r = db.accountGroupNames().get(key);
+ if (r != null) {
+ return db.accountGroups().get(r.getId());
+ } else {
+ return null;
+ }
+ } finally {
+ db.close();
+ }
+ }
+ }
+
+ static class ByExternalNameLoader extends
+ EntryCreator<AccountGroup.ExternalNameKey, Collection<AccountGroup>> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByExternalNameLoader(final SchemaFactory<ReviewDb> sf) {
+ schema = sf;
+ }
+
+ @Override
+ public Collection<AccountGroup> createEntry(
+ final AccountGroup.ExternalNameKey key) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return db.accountGroups().byExternalName(key).toList();
+ } finally {
+ db.close();
+ }
+ }
+ }
}
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
new file mode 100644
index 0000000000..6f6a4d440f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -0,0 +1,316 @@
+// 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.server.auth.ldap;
+
+import com.google.gerrit.common.data.ParamertizedString;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.net.ssl.SSLSocketFactory;
+
+@Singleton class Helper {
+ private final GroupCache groupCache;
+ private final Config config;
+ private final String server;
+ private final String username;
+ private final String password;
+ private final String referral;
+ private final boolean sslVerify;
+ private volatile LdapSchema ldapSchema;
+
+ @Inject
+ Helper(@GerritServerConfig final Config config, final GroupCache groupCache) {
+ this.groupCache = groupCache;
+ this.config = config;
+ this.server = LdapRealm.required(config, "server");
+ this.username = LdapRealm.optional(config, "username");
+ this.password = LdapRealm.optional(config, "password");
+ this.referral = LdapRealm.optional(config, "referral");
+ this.sslVerify = config.getBoolean("ldap", "sslverify", true);
+ }
+
+ private Properties createContextProperties() {
+ final Properties env = new Properties();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, LdapRealm.LDAP);
+ env.put(Context.PROVIDER_URL, server);
+ if (server.startsWith("ldaps:") && !sslVerify) {
+ Class<? extends SSLSocketFactory> factory = BlindSSLSocketFactory.class;
+ env.put("java.naming.ldap.factory.socket", factory.getName());
+ }
+ return env;
+ }
+
+ DirContext open() throws NamingException {
+ final Properties env = createContextProperties();
+ if (username != null) {
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, username);
+ env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
+ env.put(Context.REFERRAL, referral != null ? referral : "ignore");
+ }
+ return new InitialDirContext(env);
+ }
+
+ DirContext authenticate(String dn, String password) throws AccountException {
+ final Properties env = createContextProperties();
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, dn);
+ env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
+ env.put(Context.REFERRAL, referral != null ? referral : "ignore");
+ try {
+ return new InitialDirContext(env);
+ } catch (NamingException e) {
+ throw new AccountException("Incorrect username or password", e);
+ }
+ }
+
+ LdapSchema getSchema(DirContext ctx) {
+ if (ldapSchema == null) {
+ synchronized (this) {
+ if (ldapSchema == null) {
+ ldapSchema = new LdapSchema(ctx);
+ }
+ }
+ }
+ return ldapSchema;
+ }
+
+ LdapQuery.Result findAccount(final Helper.LdapSchema schema,
+ final DirContext ctx, final String username) throws NamingException,
+ AccountException {
+ final HashMap<String, String> params = new HashMap<String, String>();
+ params.put(LdapRealm.USERNAME, username);
+
+ final List<LdapQuery.Result> res = new ArrayList<LdapQuery.Result>();
+ for (LdapQuery accountQuery : schema.accountQueryList) {
+ res.addAll(accountQuery.query(ctx, params));
+ }
+
+ switch (res.size()) {
+ case 0:
+ throw new AccountException("No such user:" + username);
+
+ case 1:
+ return res.get(0);
+
+ default:
+ throw new AccountException("Duplicate users: " + username);
+ }
+ }
+
+ Set<AccountGroup.Id> queryForGroups(final DirContext ctx,
+ final String username, LdapQuery.Result account)
+ throws NamingException, AccountException {
+ final LdapSchema schema = getSchema(ctx);
+ final Set<String> groupDNs = new HashSet<String>();
+
+ if (!schema.groupMemberQueryList.isEmpty()) {
+ final HashMap<String, String> params = new HashMap<String, String>();
+
+ if (schema.groupNeedsAccount) {
+ if (account == null) {
+ account = findAccount(schema, ctx, username);
+ }
+ for (String name : schema.groupMemberQueryList.get(0).getParameters()) {
+ params.put(name, account.get(name));
+ }
+ }
+
+ params.put(LdapRealm.USERNAME, username);
+
+ for (LdapQuery groupMemberQuery : schema.groupMemberQueryList) {
+ for (LdapQuery.Result r : groupMemberQuery.query(ctx, params)) {
+ recursivelyExpandGroups(groupDNs, schema, ctx, r.getDN());
+ }
+ }
+ }
+
+ if (schema.accountMemberField != null) {
+ if (account == null) {
+ account = findAccount(schema, ctx, username);
+ }
+
+ final Attribute groupAtt = account.getAll(schema.accountMemberField);
+ if (groupAtt != null) {
+ final NamingEnumeration<?> groups = groupAtt.getAll();
+ while (groups.hasMore()) {
+ final String nextDN = (String) groups.next();
+ recursivelyExpandGroups(groupDNs, schema, ctx, nextDN);
+ }
+ }
+ }
+
+ final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
+ for (String dn : groupDNs) {
+ for (AccountGroup group : groupCache
+ .get(new AccountGroup.ExternalNameKey(dn))) {
+ if (group.getType() == AccountGroup.Type.LDAP) {
+ actual.add(group.getId());
+ }
+ }
+ }
+
+ if (actual.isEmpty()) {
+ return Collections.emptySet();
+ } else {
+ return Collections.unmodifiableSet(actual);
+ }
+ }
+
+ private void recursivelyExpandGroups(final Set<String> groupDNs,
+ final LdapSchema schema, final DirContext ctx, final String groupDN) {
+ if (groupDNs.add(groupDN) && schema.accountMemberField != null) {
+ // Recursively identify the groups it is a member of.
+ //
+ try {
+ final Attribute in =
+ ctx.getAttributes(groupDN).get(schema.accountMemberField);
+ if (in != null) {
+ final NamingEnumeration<?> groups = in.getAll();
+ while (groups.hasMore()) {
+ final String nextDN = (String) groups.next();
+ recursivelyExpandGroups(groupDNs, schema, ctx, nextDN);
+ }
+ }
+ } catch (NamingException e) {
+ LdapRealm.log.warn("Could not find group " + groupDN, e);
+ }
+ }
+ }
+
+ class LdapSchema {
+ final LdapType type;
+
+ final ParamertizedString accountFullName;
+ final ParamertizedString accountEmailAddress;
+ final ParamertizedString accountSshUserName;
+ final String accountMemberField;
+ final List<LdapQuery> accountQueryList;
+
+ boolean groupNeedsAccount;
+ final List<String> groupBases;
+ final SearchScope groupScope;
+ final ParamertizedString groupPattern;
+ final List<LdapQuery> groupMemberQueryList;
+
+ LdapSchema(final DirContext ctx) {
+ type = discoverLdapType(ctx);
+ groupMemberQueryList = new ArrayList<LdapQuery>();
+ accountQueryList = new ArrayList<LdapQuery>();
+
+ final Set<String> accountAtts = new HashSet<String>();
+
+ // Group query
+ //
+
+ groupBases = LdapRealm.optionalList(config, "groupBase");
+ groupScope = LdapRealm.scope(config, "groupScope");
+ groupPattern = LdapRealm.paramString(config, "groupPattern", type.groupPattern());
+ final String groupMemberPattern =
+ LdapRealm.optdef(config, "groupMemberPattern", type.groupMemberPattern());
+
+ for (String groupBase : groupBases) {
+ if (groupMemberPattern != null) {
+ final LdapQuery groupMemberQuery =
+ new LdapQuery(groupBase, groupScope, new ParamertizedString(
+ groupMemberPattern), Collections.<String> emptySet());
+ if (groupMemberQuery.getParameters().isEmpty()) {
+ throw new IllegalArgumentException(
+ "No variables in ldap.groupMemberPattern");
+ }
+
+ for (final String name : groupMemberQuery.getParameters()) {
+ if (!LdapRealm.USERNAME.equals(name)) {
+ groupNeedsAccount = true;
+ accountAtts.add(name);
+ }
+ }
+
+ groupMemberQueryList.add(groupMemberQuery);
+ }
+ }
+
+ // Account query
+ //
+ accountFullName =
+ LdapRealm.paramString(config, "accountFullName", type.accountFullName());
+ if (accountFullName != null) {
+ accountAtts.addAll(accountFullName.getParameterNames());
+ }
+ accountEmailAddress =
+ LdapRealm.paramString(config, "accountEmailAddress", type
+ .accountEmailAddress());
+ if (accountEmailAddress != null) {
+ accountAtts.addAll(accountEmailAddress.getParameterNames());
+ }
+ accountSshUserName =
+ LdapRealm.paramString(config, "accountSshUserName", type.accountSshUserName());
+ if (accountSshUserName != null) {
+ accountAtts.addAll(accountSshUserName.getParameterNames());
+ }
+ accountMemberField =
+ LdapRealm.optdef(config, "accountMemberField", type.accountMemberField());
+ if (accountMemberField != null) {
+ accountAtts.add(accountMemberField);
+ }
+
+ final SearchScope accountScope = LdapRealm.scope(config, "accountScope");
+ final String accountPattern =
+ LdapRealm.reqdef(config, "accountPattern", type.accountPattern());
+
+ for (String accountBase : LdapRealm.requiredList(config, "accountBase")) {
+ final LdapQuery accountQuery =
+ new LdapQuery(accountBase, accountScope, new ParamertizedString(
+ accountPattern), accountAtts);
+ if (accountQuery.getParameters().isEmpty()) {
+ throw new IllegalArgumentException(
+ "No variables in ldap.accountPattern");
+ }
+ accountQueryList.add(accountQuery);
+ }
+ }
+
+ LdapType discoverLdapType(DirContext ctx) {
+ try {
+ return LdapType.guessType(ctx);
+ } catch (NamingException e) {
+ LdapRealm.log.warn("Cannot discover type of LDAP server at " + server
+ + ", assuming the server is RFC 2307 compliant.", e);
+ return LdapType.RFC_2307;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
index 352e4664e0..810df285e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
@@ -34,11 +34,15 @@ public class LdapModule extends CacheModule {
protected void configure() {
final TypeLiteral<Cache<String, Set<AccountGroup.Id>>> groups =
new TypeLiteral<Cache<String, Set<AccountGroup.Id>>>() {};
+ core(groups, GROUP_CACHE).maxAge(1, HOURS) //
+ .populateWith(LdapRealm.MemberLoader.class);
+
final TypeLiteral<Cache<String, Account.Id>> usernames =
new TypeLiteral<Cache<String, Account.Id>>() {};
+ core(usernames, USERNAME_CACHE) //
+ .populateWith(LdapRealm.UserLoader.class);
- core(groups, GROUP_CACHE).maxAge(1, HOURS);
- core(usernames, USERNAME_CACHE);
bind(Realm.class).to(LdapRealm.class).in(Scopes.SINGLETON);
+ bind(Helper.class);
}
}
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 bad97e83df..de33b4497f 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,14 +26,13 @@ import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.EmailExpander;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
import com.google.gerrit.server.cache.Cache;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -44,7 +43,6 @@ import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -52,62 +50,40 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Properties;
import java.util.Set;
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.net.ssl.SSLSocketFactory;
@Singleton
class LdapRealm implements Realm {
- private static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
- private static final String LDAP = "com.sun.jndi.ldap.LdapCtxFactory";
- private static final String USERNAME = "username";
+ static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
+ static final String LDAP = "com.sun.jndi.ldap.LdapCtxFactory";
+ static final String USERNAME = "username";
private static final String GROUPNAME = "groupname";
- private final Config config;
- private final String server;
- private final String username;
- private final String password;
- private final String referral;
- private final boolean sslVerify;
-
+ private final Helper helper;
private final AuthConfig authConfig;
- private final SchemaFactory<ReviewDb> schema;
private final EmailExpander emailExpander;
- private final SelfPopulatingCache<String, Account.Id> usernameCache;
+ private final Cache<String, Account.Id> usernameCache;
private final Set<Account.FieldName> readOnlyAccountFields;
- private final GroupCache groupCache;
- private final SelfPopulatingCache<String, Set<AccountGroup.Id>> membershipCache;
-
- private volatile LdapSchema ldapSchema;
+ private final Cache<String, Set<AccountGroup.Id>> membershipCache;
@Inject
LdapRealm(
+ final Helper helper,
final AuthConfig authConfig,
- final GroupCache groupCache,
final EmailExpander emailExpander,
- final SchemaFactory<ReviewDb> schema,
- @Named(LdapModule.GROUP_CACHE) final Cache<String, Set<AccountGroup.Id>> rawGroup,
- @Named(LdapModule.USERNAME_CACHE) final Cache<String, Account.Id> rawUsername,
+ @Named(LdapModule.GROUP_CACHE) final Cache<String, Set<AccountGroup.Id>> membershipCache,
+ @Named(LdapModule.USERNAME_CACHE) final Cache<String, Account.Id> usernameCache,
@GerritServerConfig final Config config) {
- this.config = config;
+ this.helper = helper;
this.authConfig = authConfig;
- this.groupCache = groupCache;
this.emailExpander = emailExpander;
- this.schema = schema;
+ this.usernameCache = usernameCache;
+ this.membershipCache = membershipCache;
- this.server = required(config, "server");
- this.username = optional(config, "username");
- this.password = optional(config, "password");
- this.referral = optional(config, "referral");
- this.sslVerify = config.getBoolean("ldap", "sslverify", true);
this.readOnlyAccountFields = new HashSet<Account.FieldName>();
if (optdef(config, "accountFullName", "DEFAULT") != null) {
@@ -116,38 +92,17 @@ class LdapRealm implements Realm {
if (optdef(config, "accountSshUserName", "DEFAULT") != null) {
readOnlyAccountFields.add(Account.FieldName.USER_NAME);
}
-
- membershipCache =
- new SelfPopulatingCache<String, Set<AccountGroup.Id>>(rawGroup) {
- @Override
- public Set<AccountGroup.Id> createEntry(final String username)
- throws Exception {
- return queryForGroups(username);
- }
-
- @Override
- protected Set<AccountGroup.Id> missing(final String key) {
- return Collections.emptySet();
- }
- };
-
- usernameCache = new SelfPopulatingCache<String, Account.Id>(rawUsername) {
- @Override
- public Account.Id createEntry(final String username) throws Exception {
- return queryForUsername(username);
- }
- };
}
- private static SearchScope scope(final Config c, final String setting) {
+ static SearchScope scope(final Config c, final String setting) {
return ConfigUtil.getEnum(c, "ldap", null, setting, SearchScope.SUBTREE);
}
- private static String optional(final Config config, final String name) {
+ static String optional(final Config config, final String name) {
return config.getString("ldap", null, name);
}
- private static String required(final Config config, final String name) {
+ static String required(final Config config, final String name) {
final String v = optional(config, name);
if (v == null || "".equals(v)) {
throw new IllegalArgumentException("No ldap." + name + " configured");
@@ -155,13 +110,13 @@ class LdapRealm implements Realm {
return v;
}
- private static List<String> optionalList(final Config config,
+ static List<String> optionalList(final Config config,
final String name) {
String s[] = config.getStringList("ldap", null, name);
return Arrays.asList(s);
}
- private static List<String> requiredList(final Config config,
+ static List<String> requiredList(final Config config,
final String name) {
List<String> vlist = optionalList(config, name);
@@ -172,7 +127,7 @@ class LdapRealm implements Realm {
return vlist;
}
- private static String optdef(final Config c, final String n, final String d) {
+ static String optdef(final Config c, final String n, final String d) {
final String[] v = c.getStringList("ldap", null, n);
if (v == null || v.length == 0) {
return d;
@@ -185,7 +140,7 @@ class LdapRealm implements Realm {
}
}
- private static String reqdef(final Config c, final String n, final String d) {
+ static String reqdef(final Config c, final String n, final String d) {
final String v = optdef(c, n, d);
if (v == null) {
throw new IllegalArgumentException("No ldap." + n + " configured");
@@ -193,7 +148,7 @@ class LdapRealm implements Realm {
return v;
}
- private static ParamertizedString paramString(Config c, String n, String d) {
+ static ParamertizedString paramString(Config c, String n, String d) {
String expression = optdef(c, n, d);
if (expression == null) {
return null;
@@ -230,19 +185,19 @@ class LdapRealm implements Realm {
try {
final DirContext ctx;
if (authConfig.getAuthType() == AuthType.LDAP_BIND) {
- ctx = authenticate(username, who.getPassword());
+ ctx = helper.authenticate(username, who.getPassword());
} else {
- ctx = open();
+ ctx = helper.open();
}
try {
- final LdapSchema schema = getSchema(ctx);
- final LdapQuery.Result m = findAccount(schema, ctx, username);
+ final Helper.LdapSchema schema = helper.getSchema(ctx);
+ final LdapQuery.Result m = helper.findAccount(schema, ctx, username);
if (authConfig.getAuthType() == AuthType.LDAP) {
// We found the user account, but we need to verify
// the password matches it before we can continue.
//
- authenticate(m.getDN(), who.getPassword());
+ helper.authenticate(m.getDN(), who.getPassword());
}
who.setDisplayName(apply(schema.accountFullName, m));
@@ -265,7 +220,7 @@ class LdapRealm implements Realm {
// in the middle of authenticating the user, its likely we will
// need to know what access rights they have soon.
//
- membershipCache.put(username, queryForGroups(ctx, username, m));
+ membershipCache.put(username, helper.queryForGroups(ctx, username, m));
return who;
} finally {
try {
@@ -293,99 +248,6 @@ class LdapRealm implements Realm {
return r;
}
- private Set<AccountGroup.Id> queryForGroups(final String username)
- throws NamingException, AccountException {
- final DirContext ctx = open();
- try {
- return queryForGroups(ctx, username, null);
- } finally {
- try {
- ctx.close();
- } catch (NamingException e) {
- log.warn("Cannot close LDAP query handle", e);
- }
- }
- }
-
- private Set<AccountGroup.Id> queryForGroups(final DirContext ctx,
- final String username, LdapQuery.Result account) throws NamingException,
- AccountException {
- final LdapSchema schema = getSchema(ctx);
- final Set<String> groupDNs = new HashSet<String>();
-
- if (!schema.groupMemberQueryList.isEmpty()) {
- final HashMap<String, String> params = new HashMap<String, String>();
-
- if (schema.groupNeedsAccount) {
- if (account == null) {
- account = findAccount(schema, ctx, username);
- }
- for (String name : schema.groupMemberQueryList.get(0).getParameters()) {
- params.put(name, account.get(name));
- }
- }
-
- params.put(USERNAME, username);
-
- for (LdapQuery groupMemberQuery : schema.groupMemberQueryList) {
- for (LdapQuery.Result r : groupMemberQuery.query(ctx, params)) {
- recursivelyExpandGroups(groupDNs, schema, ctx, r.getDN());
- }
- }
- }
-
- if (schema.accountMemberField != null) {
- if (account == null) {
- account = findAccount(schema, ctx, username);
- }
-
- final Attribute groupAtt = account.getAll(schema.accountMemberField);
- if (groupAtt != null) {
- final NamingEnumeration<?> groups = groupAtt.getAll();
- while (groups.hasMore()) {
- final String nextDN = (String) groups.next();
- recursivelyExpandGroups(groupDNs, schema, ctx, nextDN);
- }
- }
- }
-
- final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
- for (String dn : groupDNs) {
- for (AccountGroup group : groupCache
- .get(new AccountGroup.ExternalNameKey(dn))) {
- if (group.getType() == AccountGroup.Type.LDAP) {
- actual.add(group.getId());
- }
- }
- }
-
- if (actual.isEmpty()) {
- return Collections.emptySet();
- } else {
- return Collections.unmodifiableSet(actual);
- }
- }
-
- private void recursivelyExpandGroups(final Set<String> groupDNs,
- final LdapSchema schema, final DirContext ctx, final String groupDN) {
- if (groupDNs.add(groupDN) && schema.accountMemberField != null) {
- // Recursively identify the groups it is a member of.
- //
- try {
- final Attribute in =
- ctx.getAttributes(groupDN).get(schema.accountMemberField);
- if (in != null) {
- final NamingEnumeration<?> groups = in.getAll();
- while (groups.hasMore()) {
- final String nextDN = (String) groups.next();
- recursivelyExpandGroups(groupDNs, schema, ctx, nextDN);
- }
- }
- } catch (NamingException e) {
- log.warn("Could not find group " + groupDN, e);
- }
- }
- }
private static String findId(final Collection<AccountExternalId> ids) {
for (final AccountExternalId i : ids) {
@@ -408,9 +270,9 @@ class LdapRealm implements Realm {
out = new HashSet<AccountGroup.ExternalNameKey>();
try {
- final DirContext ctx = open();
+ final DirContext ctx = helper.open();
try {
- final LdapSchema schema = getSchema(ctx);
+ final LdapSchema schema = helper.getSchema(ctx);
final ParamertizedString filter =
ParamertizedString.asis(schema.groupPattern
.replace(GROUPNAME, name).toString());
@@ -435,192 +297,59 @@ class LdapRealm implements Realm {
return out;
}
- private Account.Id queryForUsername(final String username) {
- try {
- final ReviewDb db = schema.open();
- try {
- final AccountExternalId extId =
- db.accountExternalIds().get(
- new AccountExternalId.Key(SCHEME_GERRIT, username));
- return extId != null ? extId.getAccountId() : null;
- } finally {
- db.close();
- }
- } catch (OrmException e) {
- log.warn("Cannot query for username in database", e);
- return null;
- }
- }
-
- private Properties createContextProperties() {
- final Properties env = new Properties();
- env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP);
- env.put(Context.PROVIDER_URL, server);
- if (server.startsWith("ldaps:") && !sslVerify) {
- Class<? extends SSLSocketFactory> factory = BlindSSLSocketFactory.class;
- env.put("java.naming.ldap.factory.socket", factory.getName());
- }
- return env;
- }
-
- private DirContext open() throws NamingException {
- final Properties env = createContextProperties();
- if (username != null) {
- env.put(Context.SECURITY_AUTHENTICATION, "simple");
- env.put(Context.SECURITY_PRINCIPAL, username);
- env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
- env.put(Context.REFERRAL, referral != null ? referral : "ignore");
- }
- return new InitialDirContext(env);
- }
-
- private DirContext authenticate(String dn, String password)
- throws AccountException {
- final Properties env = createContextProperties();
- env.put(Context.SECURITY_AUTHENTICATION, "simple");
- env.put(Context.SECURITY_PRINCIPAL, dn);
- env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
- env.put(Context.REFERRAL, referral != null ? referral : "ignore");
- try {
- return new InitialDirContext(env);
- } catch (NamingException e) {
- throw new AccountException("Incorrect username or password", e);
- }
- }
-
- private LdapQuery.Result findAccount(final LdapSchema schema,
- final DirContext ctx, final String username) throws NamingException,
- AccountException {
- final HashMap<String, String> params = new HashMap<String, String>();
- params.put(USERNAME, username);
-
- final List<LdapQuery.Result> res = new ArrayList<LdapQuery.Result>();
- for (LdapQuery accountQuery : schema.accountQueryList) {
- res.addAll(accountQuery.query(ctx, params));
- }
-
- switch (res.size()) {
- case 0:
- throw new AccountException("No such user:" + username);
-
- case 1:
- return res.get(0);
+ static class UserLoader extends EntryCreator<String, Account.Id> {
+ private final SchemaFactory<ReviewDb> schema;
- default:
- throw new AccountException("Duplicate users: " + username);
+ @Inject
+ UserLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
}
- }
- private LdapSchema getSchema(DirContext ctx) {
- if (ldapSchema == null) {
- synchronized (this) {
- if (ldapSchema == null) {
- ldapSchema = new LdapSchema(ctx);
+ @Override
+ public Account.Id createEntry(final String username) throws Exception {
+ try {
+ final ReviewDb db = schema.open();
+ try {
+ final AccountExternalId extId =
+ db.accountExternalIds().get(
+ new AccountExternalId.Key(SCHEME_GERRIT, username));
+ return extId != null ? extId.getAccountId() : null;
+ } finally {
+ db.close();
}
+ } catch (OrmException e) {
+ log.warn("Cannot query for username in database", e);
+ return null;
}
}
- return ldapSchema;
}
- private class LdapSchema {
- final LdapType type;
-
- final ParamertizedString accountFullName;
- final ParamertizedString accountEmailAddress;
- final ParamertizedString accountSshUserName;
- final String accountMemberField;
- final List<LdapQuery> accountQueryList;
-
- boolean groupNeedsAccount;
- final List<String> groupBases;
- final SearchScope groupScope;
- final ParamertizedString groupPattern;
- final List<LdapQuery> groupMemberQueryList;
-
- LdapSchema(final DirContext ctx) {
- type = discoverLdapType(ctx);
- groupMemberQueryList = new ArrayList<LdapQuery>();
- accountQueryList = new ArrayList<LdapQuery>();
-
- final Set<String> accountAtts = new HashSet<String>();
-
- // Group query
- //
-
- groupBases = optionalList(config, "groupBase");
- groupScope = scope(config, "groupScope");
- groupPattern = paramString(config, "groupPattern", type.groupPattern());
- final String groupMemberPattern =
- optdef(config, "groupMemberPattern", type.groupMemberPattern());
-
- for (String groupBase : groupBases) {
- if (groupMemberPattern != null) {
- final LdapQuery groupMemberQuery =
- new LdapQuery(groupBase, groupScope, new ParamertizedString(
- groupMemberPattern), Collections.<String> emptySet());
- if (groupMemberQuery.getParameters().isEmpty()) {
- throw new IllegalArgumentException(
- "No variables in ldap.groupMemberPattern");
- }
-
- for (final String name : groupMemberQuery.getParameters()) {
- if (!USERNAME.equals(name)) {
- groupNeedsAccount = true;
- accountAtts.add(name);
- }
- }
-
- groupMemberQueryList.add(groupMemberQuery);
- }
- }
+ static class MemberLoader extends EntryCreator<String, Set<AccountGroup.Id>> {
+ private final Helper helper;
- // Account query
- //
- accountFullName =
- paramString(config, "accountFullName", type.accountFullName());
- if (accountFullName != null) {
- accountAtts.addAll(accountFullName.getParameterNames());
- }
- accountEmailAddress =
- paramString(config, "accountEmailAddress", type.accountEmailAddress());
- if (accountEmailAddress != null) {
- accountAtts.addAll(accountEmailAddress.getParameterNames());
- }
- accountSshUserName =
- paramString(config, "accountSshUserName", type.accountSshUserName());
- if (accountSshUserName != null) {
- accountAtts.addAll(accountSshUserName.getParameterNames());
- }
- accountMemberField =
- optdef(config, "accountMemberField", type.accountMemberField());
- if (accountMemberField != null) {
- accountAtts.add(accountMemberField);
- }
+ @Inject
+ MemberLoader(final Helper helper) {
+ this.helper = helper;
+ }
- final SearchScope accountScope = scope(config, "accountScope");
- final String accountPattern =
- reqdef(config, "accountPattern", type.accountPattern());
-
- for (String accountBase : requiredList(config, "accountBase")) {
- final LdapQuery accountQuery =
- new LdapQuery(accountBase, accountScope, new ParamertizedString(
- accountPattern), accountAtts);
- if (accountQuery.getParameters().isEmpty()) {
- throw new IllegalArgumentException(
- "No variables in ldap.accountPattern");
+ @Override
+ public Set<AccountGroup.Id> createEntry(final String username)
+ throws Exception {
+ final DirContext ctx = helper.open();
+ try {
+ return helper.queryForGroups(ctx, username, null);
+ } finally {
+ try {
+ ctx.close();
+ } catch (NamingException e) {
+ log.warn("Cannot close LDAP query handle", e);
}
- accountQueryList.add(accountQuery);
}
}
- LdapType discoverLdapType(DirContext ctx) {
- try {
- return LdapType.guessType(ctx);
- } catch (NamingException e) {
- log.warn("Cannot discover type of LDAP server at " + server
- + ", assuming the server is RFC 2307 compliant.", e);
- return LdapType.RFC_2307;
- }
+ @Override
+ public Set<AccountGroup.Id> missing(final String key) {
+ return Collections.emptySet();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
index 86979b8483..7159501af7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
@@ -33,6 +33,9 @@ public interface Cache<K, V> {
/** Remove any existing value from the cache, no-op if not present. */
public void remove(K key);
+ /** Remove all cached items. */
+ public void removeAll();
+
/**
* Get the time an element will survive in the cache.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
index 23c094e626..7fb3b3b591 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
@@ -16,7 +16,10 @@ package com.google.gerrit.server.cache;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
+import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.name.Names;
import java.io.Serializable;
@@ -35,7 +38,8 @@ public abstract class CacheModule extends AbstractModule {
* @return binding to describe the cache. Caller must set at least the name on
* the returned binding.
*/
- protected <K, V> UnnamedCacheBinding core(final TypeLiteral<Cache<K, V>> type) {
+ protected <K, V> UnnamedCacheBinding<K, V> core(
+ final TypeLiteral<Cache<K, V>> type) {
return core(Key.get(type));
}
@@ -49,15 +53,15 @@ public abstract class CacheModule extends AbstractModule {
* and with {@code @Named} annotations.
* @return binding to describe the cache.
*/
- protected <K, V> NamedCacheBinding core(final TypeLiteral<Cache<K, V>> type,
- final String name) {
+ protected <K, V> NamedCacheBinding<K, V> core(
+ final TypeLiteral<Cache<K, V>> type, final String name) {
return core(Key.get(type, Names.named(name))).name(name);
}
- private <K, V> UnnamedCacheBinding core(final Key<Cache<K, V>> key) {
+ private <K, V> UnnamedCacheBinding<K, V> core(final Key<Cache<K, V>> key) {
final boolean disk = false;
- final CacheProvider<K, V> b = new CacheProvider<K, V>(disk);
- bind(key).toProvider(b);
+ final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
+ bind(key).toProvider(b).in(Scopes.SINGLETON);
return b;
}
@@ -72,7 +76,7 @@ public abstract class CacheModule extends AbstractModule {
* @return binding to describe the cache. Caller must set at least the name on
* the returned binding.
*/
- protected <K extends Serializable, V extends Serializable> UnnamedCacheBinding disk(
+ protected <K extends Serializable, V extends Serializable> UnnamedCacheBinding<K, V> disk(
final TypeLiteral<Cache<K, V>> type) {
return disk(Key.get(type));
}
@@ -87,15 +91,31 @@ public abstract class CacheModule extends AbstractModule {
* and with {@code @Named} annotations.
* @return binding to describe the cache.
*/
- protected <K extends Serializable, V extends Serializable> NamedCacheBinding disk(
+ protected <K extends Serializable, V extends Serializable> NamedCacheBinding<K, V> disk(
final TypeLiteral<Cache<K, V>> type, final String name) {
return disk(Key.get(type, Names.named(name))).name(name);
}
- private <K, V> UnnamedCacheBinding disk(final Key<Cache<K, V>> key) {
+ private <K, V> UnnamedCacheBinding<K, V> disk(final Key<Cache<K, V>> key) {
final boolean disk = true;
- final CacheProvider<K, V> b = new CacheProvider<K, V>(disk);
- bind(key).toProvider(b);
+ final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
+ bind(key).toProvider(b).in(Scopes.SINGLETON);
return b;
}
+
+ <K, V> Provider<EntryCreator<K, V>> getEntryCreator(CacheProvider<K, V> cp,
+ Class<? extends EntryCreator<K, V>> type) {
+ Key<EntryCreator<K, V>> key = newKey();
+ bind(key).to(type).in(Scopes.SINGLETON);
+ return getProvider(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <K, V> Key<EntryCreator<K, V>> newKey() {
+ return (Key<EntryCreator<K, V>>) newKeyImpl();
+ }
+
+ private static Key<?> newKeyImpl() {
+ return Key.get(EntryCreator.class, UniqueAnnotations.create());
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
index 6a7293ece0..0ba424f2cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
@@ -27,7 +27,8 @@ import net.sf.ehcache.Ehcache;
import java.util.concurrent.TimeUnit;
final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
- NamedCacheBinding, UnnamedCacheBinding {
+ NamedCacheBinding<K, V>, UnnamedCacheBinding<K, V> {
+ private final CacheModule module;
private final boolean disk;
private int memoryLimit;
private int diskLimit;
@@ -35,9 +36,11 @@ final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
private EvictionPolicy evictionPolicy;
private String cacheName;
private ProxyEhcache cache;
+ private Provider<EntryCreator<K, V>> entryCreator;
- CacheProvider(final boolean disk) {
+ CacheProvider(final boolean disk, CacheModule module) {
this.disk = disk;
+ this.module = module;
memoryLimit(1024);
maxAge(90, DAYS);
@@ -84,7 +87,7 @@ final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
return evictionPolicy;
}
- public NamedCacheBinding name(final String name) {
+ public NamedCacheBinding<K, V> name(final String name) {
if (cacheName != null) {
throw new IllegalStateException("Cache name already set");
}
@@ -92,12 +95,12 @@ final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
return this;
}
- public NamedCacheBinding memoryLimit(final int objects) {
+ public NamedCacheBinding<K, V> memoryLimit(final int objects) {
memoryLimit = objects;
return this;
}
- public NamedCacheBinding diskLimit(final int objects) {
+ public NamedCacheBinding<K, V> diskLimit(final int objects) {
if (!disk) {
// TODO This should really be a compile time type error, but I'm
// too lazy to create the mess of permutations required to setup
@@ -109,21 +112,30 @@ final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
return this;
}
- public NamedCacheBinding maxAge(final long duration, final TimeUnit unit) {
+ public NamedCacheBinding<K, V> maxAge(final long duration, final TimeUnit unit) {
maxAge = SECONDS.convert(duration, unit);
return this;
}
@Override
- public NamedCacheBinding evictionPolicy(final EvictionPolicy policy) {
+ public NamedCacheBinding<K, V> evictionPolicy(final EvictionPolicy policy) {
evictionPolicy = policy;
return this;
}
+ public NamedCacheBinding<K, V> populateWith(
+ Class<? extends EntryCreator<K, V>> creator) {
+ entryCreator = module.getEntryCreator(this, creator);
+ return this;
+ }
+
public Cache<K, V> get() {
if (cache == null) {
throw new ProvisionException("Cache \"" + cacheName + "\" not available");
}
+ if (entryCreator != null) {
+ return new PopulatingCache<K, V>(cache, entryCreator.get());
+ }
return new SimpleCache<K, V>(cache);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java
new file mode 100644
index 0000000000..af07e08636
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java
@@ -0,0 +1,40 @@
+// 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.server.cache;
+
+/**
+ * Creates a cache entry on demand when its not found.
+ *
+ * @param <K> type of the cache's key.
+ * @param <V> type of the cache's value element.
+ */
+public abstract class EntryCreator<K, V> {
+ /**
+ * Invoked on a cache miss, to compute the cache entry.
+ *
+ * @param key entry whose content needs to be obtained.
+ * @return new cache content. The caller will automatically put this object
+ * into the cache.
+ * @throws Exception the cache content cannot be computed. No entry will be
+ * stored in the cache, and {@link #missing(Object)} will be invoked
+ * instead. Future requests for the same key will retry this method.
+ */
+ public abstract V createEntry(K key) throws Exception;
+
+ /** Invoked when {@link #createEntry(Object)} fails, by default return null. */
+ public V missing(K key) {
+ return null;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
index d486425e78..3394c71d05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
@@ -17,16 +17,19 @@ package com.google.gerrit.server.cache;
import java.util.concurrent.TimeUnit;
/** Configure a cache declared within a {@link CacheModule} instance. */
-public interface NamedCacheBinding {
+public interface NamedCacheBinding<K, V> {
/** Set the number of objects to cache in memory. */
- public NamedCacheBinding memoryLimit(int objects);
+ public NamedCacheBinding<K, V> memoryLimit(int objects);
/** Set the number of objects to cache in memory. */
- public NamedCacheBinding diskLimit(int objects);
+ public NamedCacheBinding<K, V> diskLimit(int objects);
/** Set the time an element lives before being expired. */
- public NamedCacheBinding maxAge(long duration, TimeUnit durationUnits);
+ public NamedCacheBinding<K, V> maxAge(long duration, TimeUnit durationUnits);
/** Set the eviction policy for elements when the cache is full. */
- public NamedCacheBinding evictionPolicy(EvictionPolicy policy);
+ public NamedCacheBinding<K, V> evictionPolicy(EvictionPolicy policy);
+
+ /** Populate the cache with items from the EntryCreator. */
+ public NamedCacheBinding<K, V> populateWith(Class<? extends EntryCreator<K, V>> creator);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/SelfPopulatingCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PopulatingCache.java
index 7067ef46f7..0822cc0555 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/SelfPopulatingCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PopulatingCache.java
@@ -29,61 +29,39 @@ import java.util.concurrent.TimeUnit;
/**
* A decorator for {@link Cache} which automatically constructs missing entries.
* <p>
- * On a cache miss {@link #createEntry(Object)} is invoked, allowing the
- * application specific subclass to compute the entry and return it for caching.
- * During a miss the cache takes a lock related to the missing key, ensuring
- * that at most one thread performs the creation work, and other threads wait
- * for the result. Concurrent creations are possible if two different keys miss
- * and hash to different locks in the internal lock table.
+ * On a cache miss {@link EntryCreator#createEntry(Object)} is invoked, allowing
+ * the application specific subclass to compute the entry and return it for
+ * caching. During a miss the cache takes a lock related to the missing key,
+ * ensuring that at most one thread performs the creation work, and other
+ * threads wait for the result. Concurrent creations are possible if two
+ * different keys miss and hash to different locks in the internal lock table.
*
* @param <K> type of key used to name cache entries.
* @param <V> type of value stored within a cache entry.
*/
-public abstract class SelfPopulatingCache<K, V> implements Cache<K, V> {
+class PopulatingCache<K, V> implements Cache<K, V> {
private static final Logger log =
- LoggerFactory.getLogger(SelfPopulatingCache.class);
+ LoggerFactory.getLogger(PopulatingCache.class);
private final net.sf.ehcache.constructs.blocking.SelfPopulatingCache self;
+ private final EntryCreator<K, V> creator;
- /**
- * Create a new cache which uses another cache to store entries.
- *
- * @param backingStore cache which will store the entries for this cache.
- */
- @SuppressWarnings("unchecked")
- public SelfPopulatingCache(final Cache<K, V> backingStore) {
- final Ehcache s = ((SimpleCache) backingStore).getEhcache();
+ PopulatingCache(Ehcache s, EntryCreator<K, V> entryCreator) {
+ creator = entryCreator;
final CacheEntryFactory f = new CacheEntryFactory() {
@SuppressWarnings("unchecked")
@Override
public Object createEntry(Object key) throws Exception {
- return SelfPopulatingCache.this.createEntry((K) key);
+ return creator.createEntry((K) key);
}
};
self = new net.sf.ehcache.constructs.blocking.SelfPopulatingCache(s, f);
}
/**
- * Invoked on a cache miss, to compute the cache entry.
- *
- * @param key entry whose content needs to be obtained.
- * @return new cache content. The caller will automatically put this object
- * into the cache.
- * @throws Exception the cache content cannot be computed. No entry will be
- * stored in the cache, and {@link #missing(Object)} will be invoked
- * instead. Future requests for the same key will retry this method.
- */
- protected abstract V createEntry(K key) throws Exception;
-
- /** Invoked when {@link #createEntry(Object)} fails, by default return null. */
- protected V missing(K key) {
- return null;
- }
-
- /**
- * Get the element from the cache, or {@link #missing(Object)} if not found.
+ * Get the element from the cache, or {@link EntryCreator#missing(Object)} if not found.
* <p>
- * The {@link #missing(Object)} method is only invoked if:
+ * The {@link EntryCreator#missing(Object)} method is only invoked if:
* <ul>
* <li>{@code key == null}, in which case the application should return a
* suitable return value that callers can accept, or throw a RuntimeException.
@@ -99,7 +77,7 @@ public abstract class SelfPopulatingCache<K, V> implements Cache<K, V> {
@SuppressWarnings("unchecked")
public V get(final K key) {
if (key == null) {
- return missing(key);
+ return creator.missing(key);
}
final Element m;
@@ -107,12 +85,12 @@ public abstract class SelfPopulatingCache<K, V> implements Cache<K, V> {
m = self.get(key);
} catch (IllegalStateException err) {
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return missing(key);
+ return creator.missing(key);
} catch (CacheException err) {
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return missing(key);
+ return creator.missing(key);
}
- return m != null ? (V) m.getObjectValue() : missing(key);
+ return m != null ? (V) m.getObjectValue() : creator.missing(key);
}
public void remove(final K key) {
@@ -122,7 +100,7 @@ public abstract class SelfPopulatingCache<K, V> implements Cache<K, V> {
}
/** Remove all cached items, forcing them to be created again on demand. */
- public void removeAll(){
+ public void removeAll() {
self.removeAll();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java
index d776412892..2283f961de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java
@@ -72,6 +72,10 @@ final class SimpleCache<K, V> implements Cache<K, V> {
}
}
+ public void removeAll() {
+ self.removeAll();
+ }
+
@Override
public long getTimeToLive(final TimeUnit unit) {
final long maxAge = self.getCacheConfiguration().getTimeToLiveSeconds();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
index 328ebd837e..43039e1475 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.cache;
/** Configure a cache declared within a {@link CacheModule} instance. */
-public interface UnnamedCacheBinding {
+public interface UnnamedCacheBinding<K, V> {
/** Set the name of the cache. */
- public NamedCacheBinding name(String cacheName);
+ public NamedCacheBinding<K, V> name(String cacheName);
}
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 e722782c70..f889a2b8e9 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
@@ -140,6 +140,27 @@ public class ConfigUtil {
return defaultValue;
}
+ try {
+ return getTimeUnit(s, defaultValue, wantUnit);
+ } catch (IllegalArgumentException notTime) {
+ throw notTimeUnit(section, subsection, setting, valueString);
+ }
+ }
+
+ /**
+ * Parse a numerical time unit, such as "1 minute", from a string.
+ *
+ * @param s the string to parse.
+ * @param defaultValue default value to return if no value was set in the
+ * configuration file.
+ * @param wantUnit the units of {@code defaultValue} and the return value, as
+ * well as the units to assume if the value does not contain an
+ * indication of the units.
+ * @return the setting, or {@code defaultValue} if not set, expressed in
+ * {@code units}.
+ */
+ public static long getTimeUnit(String s, long defaultValue, TimeUnit wantUnit) {
+ final String valueString = s;
final String unitName;
final int sp = s.indexOf(' ');
if (sp > 0) {
@@ -198,13 +219,13 @@ public class ConfigUtil {
inputMul = 365;
} else {
- throw notTimeUnit(section, subsection, setting, valueString);
+ throw notTimeUnit(valueString);
}
try {
return wantUnit.convert(Long.parseLong(s) * inputMul, inputUnit);
} catch (NumberFormatException nfe) {
- throw notTimeUnit(section, subsection, setting, valueString);
+ throw notTimeUnit(valueString);
}
}
@@ -278,6 +299,10 @@ public class ConfigUtil {
+ valueString);
}
+ private static IllegalArgumentException notTimeUnit(final String val) {
+ return new IllegalArgumentException("Invalid time unit value: " + val);
+ }
+
private ConfigUtil() {
}
}
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 5f2c6f915a..fa2aaad0a4 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
@@ -37,35 +37,32 @@ import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.ldap.LdapModule;
import com.google.gerrit.server.cache.CachePool;
+import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.git.ChangeMergeQueue;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.PushReplication;
import com.google.gerrit.server.git.ReloadSubmitQueueOp;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.mail.AbandonedSender;
-import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.mail.FromAddressGenerator;
import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
-import com.google.gerrit.server.mail.MergeFailSender;
-import com.google.gerrit.server.mail.MergedSender;
-import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.PatchListCacheImpl;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ChangeControl;
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.tools.ToolsCatalog;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.inject.Inject;
-
import com.google.inject.TypeLiteral;
+
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
@@ -125,12 +122,12 @@ public class GerritGlobalModule extends FactoryModule {
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(WorkQueue.class);
bind(ToolsCatalog.class);
+ bind(EventFactory.class);
bind(ReplicationQueue.class).to(PushReplication.class).in(SINGLETON);
factory(PushAllProjectsOp.Factory.class);
bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
- factory(MergeOp.Factory.class);
factory(ReloadSubmitQueueOp.Factory.class);
bind(FromAddressGenerator.class).toProvider(
@@ -139,13 +136,9 @@ public class GerritGlobalModule extends FactoryModule {
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
+ bind(ChangeControl.GenericFactory.class);
+ bind(ProjectControl.GenericFactory.class);
factory(FunctionState.Factory.class);
-
- factory(AbandonedSender.Factory.class);
- factory(CommentSender.Factory.class);
- factory(MergedSender.Factory.class);
- factory(MergeFailSender.Factory.class);
- factory(RegisterNewEmailSender.Factory.class);
factory(ReplicationUser.Factory.class);
install(new LifecycleModule() {
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 9221ad986f..5a759951c9 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
@@ -21,13 +21,21 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender;
+import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.MergeFailSender;
+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.patch.PublishComments;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeQueryRewriter;
import com.google.inject.servlet.RequestScoped;
/** Bindings for {@link RequestScoped} entities. */
@@ -39,12 +47,15 @@ public class GerritRequestModule extends FactoryModule {
RequestScoped.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(AccountResolver.class);
+ bind(ChangeQueryRewriter.class);
bind(ChangeControl.Factory.class).in(SINGLETON);
bind(GroupControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON);
+ factory(ChangeQueryBuilder.Factory.class);
factory(ReceiveCommits.Factory.class);
+ factory(MergeOp.Factory.class);
// Not really per-request, but dammit, I don't know where else to
// easily park this stuff.
@@ -53,5 +64,10 @@ public class GerritRequestModule extends FactoryModule {
factory(CreateChangeSender.Factory.class);
factory(PublishComments.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
+ factory(AbandonedSender.Factory.class);
+ factory(CommentSender.Factory.class);
+ factory(MergedSender.Factory.class);
+ factory(MergeFailSender.Factory.class);
+ factory(RegisterNewEmailSender.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
new file mode 100644
index 0000000000..2ad7ffe4f5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
@@ -0,0 +1,20 @@
+// 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.events;
+
+public class AccountAttribute {
+ public String name;
+ public String email;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ApprovalAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ApprovalAttribute.java
new file mode 100644
index 0000000000..baa660c798
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ApprovalAttribute.java
@@ -0,0 +1,24 @@
+// 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.events;
+
+public class ApprovalAttribute {
+ public String type;
+ public String description;
+ public String value;
+
+ public Long grantedOn;
+ public AccountAttribute by;
+}
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
new file mode 100644
index 0000000000..baaf30cb3e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
@@ -0,0 +1,23 @@
+// 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.events;
+
+public class ChangeAbandonedEvent extends ChangeEvent {
+ public final String type = "change-abandoned";
+ public ChangeAttribute change;
+ public PatchSetAttribute patchSet;
+ public AccountAttribute abandoner;
+ public String reason;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java
new file mode 100644
index 0000000000..79a7e5ba09
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java
@@ -0,0 +1,39 @@
+// 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.events;
+
+import com.google.gerrit.reviewdb.Change;
+
+import java.util.List;
+
+public class ChangeAttribute {
+ public String project;
+ public String branch;
+ public String topic;
+ public String id;
+ public String number;
+ public String subject;
+ public AccountAttribute owner;
+ public String url;
+
+ public Long lastUpdated;
+ public String sortKey;
+ public Boolean open;
+ public Change.Status status;
+
+ public List<TrackingIdAttribute> trackingIds;
+ public PatchSetAttribute currentPatchSet;
+ public List<PatchSetAttribute> patchSets;
+}
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
new file mode 100644
index 0000000000..904a0a0489
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeEvent.java
@@ -0,0 +1,18 @@
+// 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.events;
+
+public abstract class ChangeEvent {
+}
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
new file mode 100644
index 0000000000..0d5fc31a3e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeMergedEvent.java
@@ -0,0 +1,22 @@
+// 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.events;
+
+public class ChangeMergedEvent extends ChangeEvent {
+ public final String type = "change-merged";
+ public ChangeAttribute change;
+ public PatchSetAttribute patchSet;
+ public AccountAttribute submitter;
+}
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
new file mode 100644
index 0000000000..f00caaf950
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/CommentAddedEvent.java
@@ -0,0 +1,24 @@
+// 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.events;
+
+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;
+}
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
new file mode 100644
index 0000000000..3ad397e0b0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -0,0 +1,194 @@
+// 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.events;
+
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.TrackingId;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.internal.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+@Singleton
+public class EventFactory {
+ private final AccountCache accountCache;
+ private final Provider<String> urlProvider;
+ private final ApprovalTypes approvalTypes;
+
+ @Inject
+ EventFactory(AccountCache accountCache,
+ @CanonicalWebUrl @Nullable Provider<String> urlProvider,
+ ApprovalTypes approvalTypes) {
+ this.accountCache = accountCache;
+ this.urlProvider = urlProvider;
+ this.approvalTypes = approvalTypes;
+ }
+
+ /**
+ * Create a ChangeAttribute for the given change suitable for serialization to
+ * JSON.
+ *
+ * @param change
+ * @return object suitable for serialization to JSON
+ */
+ public ChangeAttribute asChangeAttribute(final Change change) {
+ ChangeAttribute a = new ChangeAttribute();
+ a.project = change.getProject().get();
+ a.branch = change.getDest().getShortName();
+ a.topic = change.getTopic();
+ a.id = change.getKey().get();
+ a.number = change.getId().toString();
+ a.subject = change.getSubject();
+ a.url = getChangeUrl(change);
+ a.owner = asAccountAttribute(change.getOwner());
+ return a;
+ }
+
+ /**
+ * Extend the existing ChangeAttribute with additional fields.
+ *
+ * @param a
+ * @param change
+ */
+ public void extend(ChangeAttribute a, Change change) {
+ a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
+ a.sortKey = change.getSortKey();
+ a.open = change.getStatus().isOpen();
+ a.status = change.getStatus();
+ }
+
+ public void addTrackingIds(ChangeAttribute a, Collection<TrackingId> ids) {
+ if (!ids.isEmpty()) {
+ a.trackingIds = new ArrayList<TrackingIdAttribute>(ids.size());
+ for (TrackingId t : ids) {
+ a.trackingIds.add(asTrackingIdAttribute(t));
+ }
+ }
+ }
+
+ public void addPatchSets(ChangeAttribute a, Collection<PatchSet> ps) {
+ if (!ps.isEmpty()) {
+ a.patchSets = new ArrayList<PatchSetAttribute>(ps.size());
+ for (PatchSet p : ps) {
+ a.patchSets.add(asPatchSetAttribute(p));
+ }
+ }
+ }
+
+ public TrackingIdAttribute asTrackingIdAttribute(TrackingId id) {
+ TrackingIdAttribute a = new TrackingIdAttribute();
+ a.system = id.getSystem();
+ a.id = id.getTrackingId();
+ return a;
+ }
+
+ /**
+ * Create a PatchSetAttribute for the given patchset suitable for
+ * serialization to JSON.
+ *
+ * @param patchSet
+ * @return object suitable for serialization to JSON
+ */
+ public PatchSetAttribute asPatchSetAttribute(final PatchSet patchSet) {
+ PatchSetAttribute p = new PatchSetAttribute();
+ p.revision = patchSet.getRevision().get();
+ p.number = Integer.toString(patchSet.getPatchSetId());
+ p.ref = patchSet.getRefName();
+ p.uploader = asAccountAttribute(patchSet.getUploader());
+ return p;
+ }
+
+ public void addApprovals(PatchSetAttribute p,
+ Collection<PatchSetApproval> list) {
+ if (!list.isEmpty()) {
+ p.approvals = new ArrayList<ApprovalAttribute>(list.size());
+ for (PatchSetApproval a : list) {
+ if (a.getValue() != 0) {
+ p.approvals.add(asApprovalAttribute(a));
+ }
+ }
+ if (p.approvals.isEmpty()) {
+ p.approvals = null;
+ }
+ }
+ }
+
+ /**
+ * Create an AuthorAttribute for the given account suitable for serialization
+ * to JSON.
+ *
+ * @param id
+ * @return object suitable for serialization to JSON
+ */
+ public AccountAttribute asAccountAttribute(Account.Id id) {
+ return asAccountAttribute(accountCache.get(id).getAccount());
+ }
+
+ /**
+ * Create an AuthorAttribute for the given account suitable for serialization
+ * to JSON.
+ *
+ * @param account
+ * @return object suitable for serialization to JSON
+ */
+ public AccountAttribute asAccountAttribute(final Account account) {
+ AccountAttribute who = new AccountAttribute();
+ who.name = account.getFullName();
+ who.email = account.getPreferredEmail();
+ return who;
+ }
+
+ /**
+ * Create an ApprovalAttribute for the given approval suitable for
+ * serialization to JSON.
+ *
+ * @param approval
+ * @return object suitable for serialization to JSON
+ */
+ public ApprovalAttribute asApprovalAttribute(PatchSetApproval approval) {
+ ApprovalAttribute a = new ApprovalAttribute();
+ a.type = approval.getCategoryId().get();
+ a.value = Short.toString(approval.getValue());
+ a.by = asAccountAttribute(approval.getAccountId());
+ a.grantedOn = approval.getGranted().getTime() / 1000L;
+
+ ApprovalType at = approvalTypes.getApprovalType(approval.getCategoryId());
+ if (at != null) {
+ a.description = at.getCategory().getName();
+ }
+ return a;
+ }
+
+ /** Get a link to the change; null if the server doesn't know its own address. */
+ private String getChangeUrl(final Change change) {
+ if (change != null && urlProvider.get() != null) {
+ final StringBuilder r = new StringBuilder();
+ r.append(urlProvider.get());
+ r.append(change.getChangeId());
+ return r.toString();
+ }
+ return null;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java
new file mode 100644
index 0000000000..5de4d6fcef
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java
@@ -0,0 +1,26 @@
+// 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.events;
+
+import java.util.List;
+
+public class PatchSetAttribute {
+ public String number;
+ public String revision;
+ public String ref;
+ public AccountAttribute uploader;
+
+ public List<ApprovalAttribute> approvals;
+}
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
new file mode 100644
index 0000000000..15e39784bb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
@@ -0,0 +1,22 @@
+// 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.events;
+
+public class PatchSetCreatedEvent extends ChangeEvent {
+ public final String type = "patchset-created";
+ public ChangeAttribute change;
+ public PatchSetAttribute patchSet;
+ public AccountAttribute uploader;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java
new file mode 100644
index 0000000000..1c5e7d72c2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java
@@ -0,0 +1,21 @@
+// 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.events;
+
+public class QueryStats {
+ public final String type = "stats";
+ public int rowCount;
+ public long runTimeMilliseconds;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/TrackingIdAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/TrackingIdAttribute.java
new file mode 100644
index 0000000000..7d55dd2da8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/TrackingIdAttribute.java
@@ -0,0 +1,20 @@
+// 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.events;
+
+public class TrackingIdAttribute {
+ public String system;
+ public String id;
+}
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 d6c8f4ab3b..a7a969f5ed 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
@@ -19,12 +19,30 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.RemotePeer;
+import com.google.gerrit.server.RequestCleanup;
+import com.google.gerrit.server.config.GerritRequestModule;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.servlet.RequestScoped;
+
+import com.jcraft.jsch.HostKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.net.SocketAddress;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -38,18 +56,47 @@ public class ChangeMergeQueue implements MergeQueue {
new HashMap<Branch.NameKey, RecheckJob>();
private final WorkQueue workQueue;
- private final MergeOp.Factory opFactory;
+ private final Provider<MergeOp.Factory> bgFactory;
@Inject
- ChangeMergeQueue(final WorkQueue wq, final MergeOp.Factory of) {
+ ChangeMergeQueue(final WorkQueue wq, Injector parent) {
workQueue = wq;
- opFactory = of;
+
+ Injector child = parent.createChildInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bindScope(RequestScoped.class, MyScope.REQUEST);
+ install(new GerritRequestModule());
+
+ bind(CurrentUser.class).to(IdentifiedUser.class);
+ bind(IdentifiedUser.class).toProvider(new Provider<IdentifiedUser>() {
+ @Override
+ public IdentifiedUser get() {
+ throw new OutOfScopeException("No user on merge thread");
+ }
+ });
+ bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
+ new Provider<SocketAddress>() {
+ @Override
+ public SocketAddress get() {
+ throw new OutOfScopeException("No remote peer on merge thread");
+ }
+ });
+ bind(SshInfo.class).toInstance(new SshInfo() {
+ @Override
+ public List<HostKey> getHostKeys() {
+ return Collections.emptyList();
+ }
+ });
+ }
+ });
+ bgFactory = child.getProvider(MergeOp.Factory.class);
}
@Override
- public void merge(final Branch.NameKey branch) {
+ public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
if (start(branch)) {
- mergeImpl(branch);
+ mergeImpl(mof, branch);
}
}
@@ -127,7 +174,7 @@ public class ChangeMergeQueue implements MergeQueue {
e.needMerge = false;
}
- private void mergeImpl(final Branch.NameKey branch) {
+ private void mergeImpl(MergeOp.Factory opFactory, Branch.NameKey branch) {
try {
opFactory.create(branch).merge();
} catch (Throwable e) {
@@ -137,6 +184,26 @@ public class ChangeMergeQueue implements MergeQueue {
}
}
+ private void mergeImpl(Branch.NameKey branch) {
+ try {
+ MyScope ctx = new MyScope();
+ MyScope old = MyScope.set(ctx);
+ try {
+ try {
+ bgFactory.get().create(branch).merge();
+ } finally {
+ ctx.cleanup.run();
+ }
+ } finally {
+ MyScope.set(old);
+ }
+ } catch (Throwable e) {
+ log.error("Merge attempt for " + branch + " failed", e);
+ } finally {
+ finish(branch);
+ }
+ }
+
private synchronized void recheck(final RecheckJob e) {
final long remainingDelay = e.recheckAt - System.currentTimeMillis();
if (MILLISECONDS.convert(10, SECONDS) < remainingDelay) {
@@ -194,4 +261,65 @@ public class ChangeMergeQueue implements MergeQueue {
return "recheck " + project.get() + " " + dest.getShortName();
}
}
+
+ private static class MyScope {
+ private static final ThreadLocal<MyScope> current =
+ new ThreadLocal<MyScope>();
+
+ private static MyScope getContext() {
+ final MyScope ctx = current.get();
+ if (ctx == null) {
+ throw new OutOfScopeException("Not in command/request");
+ }
+ return ctx;
+ }
+
+ static MyScope set(MyScope ctx) {
+ MyScope old = current.get();
+ current.set(ctx);
+ return old;
+ }
+
+ static final Scope REQUEST = new Scope() {
+ public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
+ return new Provider<T>() {
+ public T get() {
+ return getContext().get(key, creator);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s]", creator, REQUEST);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "MergeQueue.REQUEST";
+ }
+ };
+
+ private static final Key<RequestCleanup> RC_KEY =
+ Key.get(RequestCleanup.class);
+
+ private final RequestCleanup cleanup;
+ private final Map<Key<?>, Object> map;
+
+ MyScope() {
+ cleanup = new RequestCleanup();
+ map = new HashMap<Key<?>, Object>();
+ map.put(RC_KEY, cleanup);
+ }
+
+ synchronized <T> T get(Key<T> key, Provider<T> creator) {
+ @SuppressWarnings("unchecked")
+ T t = (T) map.get(key);
+ if (t == null) {
+ t = creator.get();
+ map.put(key, t);
+ }
+ return t;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
index f87f518e72..676e4b68e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
@@ -22,6 +22,7 @@ import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
import java.io.File;
import java.io.IOException;
@@ -76,7 +77,7 @@ public class GitProjectImporter {
continue;
}
- if (FileKey.isGitRepository(f)) {
+ if (FileKey.isGitRepository(f, FS.DETECTED)) {
if (name.equals(".git")) {
name = prefix.substring(0, prefix.length() - 1);
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 f0e6366a6b..e8df230e00 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
@@ -29,6 +29,7 @@ import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.lib.WindowCache;
import org.eclipse.jgit.lib.WindowCacheConfig;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.slf4j.Logger;
@@ -90,7 +91,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
}
try {
- final FileKey loc = FileKey.lenient(new File(basePath, name));
+ final FileKey loc = FileKey.lenient(new File(basePath, name), FS.DETECTED);
return RepositoryCache.open(loc);
} catch (IOException e1) {
final RepositoryNotFoundException e2;
@@ -107,12 +108,12 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
}
try {
- File dir = FileKey.resolve(new File(basePath, name));
+ File dir = FileKey.resolve(new File(basePath, name), FS.DETECTED);
FileKey loc;
if (dir != null) {
// Already exists on disk, use the repository we found.
//
- loc = FileKey.exact(dir);
+ loc = FileKey.exact(dir, FS.DETECTED);
} else {
// It doesn't exist under any of the standard permutations
// of the repository name, so prefer the standard bare name.
@@ -120,7 +121,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
if (!name.endsWith(".git")) {
name = name + ".git";
}
- loc = FileKey.exact(new File(basePath, name));
+ loc = FileKey.exact(new File(basePath, name), FS.DETECTED);
}
return RepositoryCache.open(loc, false);
} catch (IOException e1) {
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 5280e0c7d5..66e8d61eae 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
@@ -1179,7 +1179,6 @@ public class MergeOp {
if (submitter != null) {
cm.setFrom(submitter.getAccountId());
}
- cm.setReviewDb(schema);
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
cm.send();
} catch (OrmException e) {
@@ -1241,7 +1240,6 @@ public class MergeOp {
cm.setFrom(submitter.getAccountId());
}
}
- cm.setReviewDb(schema);
cm.setPatchSet(schema.patchSets().get(c.currentPatchSetId()));
cm.setChangeMessage(msg);
cm.send();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java
index abb0ab81c6..39017eb9b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeQueue.java
@@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.Branch;
import java.util.concurrent.TimeUnit;
public interface MergeQueue {
- void merge(Branch.NameKey branch);
+ void merge(MergeOp.Factory mof, Branch.NameKey branch);
void schedule(Branch.NameKey branch);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
index 9d56b08d99..76b8bf0b9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
@@ -44,6 +44,7 @@ import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.SshConfigSessionFactory;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.QuotedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -231,7 +232,7 @@ public class PushReplication implements ReplicationQueue {
try {
sshSession =
sshFactory.getSession(replicateURI.getUser(), replicateURI.getPass(),
- replicateURI.getHost(), replicateURI.getPort());
+ replicateURI.getHost(), replicateURI.getPort(), FS.DETECTED);
sshSession.connect();
Channel channel = sshSession.openChannel("exec");
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 63ff58adab..fbcdc09043 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
@@ -55,7 +55,6 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -159,6 +158,8 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
private Map<ObjectId, Ref> refsById;
+ private String destTopicName;
+
@Inject
ReceiveCommits(final ReviewDb db, final ApprovalTypes approvalTypes,
final AccountResolver accountResolver,
@@ -579,38 +580,55 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
destBranchName = Constants.R_HEADS + destBranchName;
}
- if (rp.getAdvertisedRefs().containsKey(destBranchName)) {
- // We advertised the branch to the client so we know
- // the branch exists. Target this branch for the upload.
- //
- destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
+ final String head;
+ try {
+ head = repo.getFullBranch();
+ } catch (IOException e) {
+ log.error("Cannot read HEAD symref", e);
+ reject(cmd, "internal error");
+ return;
+ }
- } else {
- // We didn't advertise the branch, because it doesn't exist yet.
- // Allow it anyway if HEAD is a symbolic reference to the name.
- //
- final String head;
- try {
- head = repo.getFullBranch();
- } catch (IOException e) {
- log.error("Cannot read HEAD symref", e);
- reject(cmd, "internal error");
- return;
+ // Split the destination branch by branch and topic. The topic
+ // suffix is entirely optional, so it might not even exist.
+ //
+ int split = destBranchName.length();
+ for (;;) {
+ String name = destBranchName.substring(0, split);
+
+ if (rp.getAdvertisedRefs().containsKey(name)) {
+ // We advertised the branch to the client so we know
+ // the branch exists. Target this branch for the upload.
+ //
+ break;
+ } else if (head.equals(name)) {
+ // We didn't advertise the branch, because it doesn't exist yet.
+ // Allow it anyway as HEAD is a symbolic reference to the name.
+ //
+ break;
}
- if (head.equals(destBranchName)) {
- destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
+ split = name.lastIndexOf('/', split - 1);
+ if (split <= Constants.R_REFS.length()) {
+ String n = destBranchName;
+ if (n.startsWith(Constants.R_HEADS))
+ n = n.substring(Constants.R_HEADS.length());
+ reject(cmd, "branch " + n + " not found");
+ return;
}
}
- if (destBranch == null) {
- String n = destBranchName;
- if (n.startsWith(Constants.R_HEADS))
- n = n.substring(Constants.R_HEADS.length());
- reject(cmd, "branch " + n + " not found");
- return;
+ if (split < destBranchName.length()) {
+ destTopicName = destBranchName.substring(split + 1);
+ } else {
+ // We use empty string here to denote the topic wasn't
+ // supplied, but the caller used the syntax that allows
+ // for a topic to be given.
+ //
+ destTopicName = "";
}
-
+ destBranch = new Branch.NameKey(project.getNameKey(), //
+ destBranchName.substring(0, split));
destBranchCtl = projectControl.controlForRef(destBranch);
if (!destBranchCtl.canUpload()) {
reject(cmd);
@@ -858,6 +876,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
final Change change =
new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
+ change.setTopic(destTopicName.isEmpty() ? null : destTopicName);
change.nextPatchSetId();
final PatchSet ps = new PatchSet(change.currPatchSetId());
@@ -912,7 +931,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
cm = createChangeSenderFactory.create(change);
cm.setFrom(me);
cm.setPatchSet(ps, info);
- cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
cm.send();
@@ -1158,6 +1176,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
+ if (destTopicName != null) {
+ change.setTopic(destTopicName.isEmpty() //
+ ? null //
+ : destTopicName);
+ }
change.setStatus(Change.Status.NEW);
change.setCurrentPatchSet(result.info);
ChangeUtil.updated(change);
@@ -1210,7 +1233,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
cm.setFrom(me);
cm.setPatchSet(ps, result.info);
cm.setChangeMessage(result.msg);
- cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
cm.addReviewers(oldReviewers);
@@ -1559,7 +1581,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
try {
final MergedSender cm = mergedSenderFactory.create(result.change);
cm.setFrom(currentUser.getAccountId());
- cm.setReviewDb(db);
cm.setPatchSet(result.patchSet, result.info);
cm.setDest(new Branch.NameKey(project.getNameKey(),
result.mergedIntoRef));
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 bba69c88b3..d2b5c29c3c 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
@@ -25,8 +25,8 @@ public class AbandonedSender extends ReplyToChangeSender {
}
@Inject
- public AbandonedSender(@Assisted Change c) {
- super(c, "abandon");
+ public AbandonedSender(EmailArguments ea, @Assisted Change c) {
+ super(ea, c, "abandon");
}
@Override
@@ -39,7 +39,7 @@ public class AbandonedSender extends ReplyToChangeSender {
}
@Override
- protected void format() {
+ protected void formatChange() {
appendText(getNameFor(fromId));
appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n");
appendText("\n");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
index c9a6d98411..be62ba070a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -25,8 +26,9 @@ public class AddReviewerSender extends NewChangeSender {
}
@Inject
- public AddReviewerSender(@Assisted Change c) {
- super(c);
+ public AddReviewerSender(EmailArguments ea, SshInfo sshInfo,
+ @Assisted Change c) {
+ super(ea, sshInfo, c);
}
@Override
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
new file mode 100644
index 0000000000..e7c463cc02
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -0,0 +1,445 @@
+// 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.mail;
+
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ChangeMessage;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.PatchSetInfo;
+import com.google.gerrit.reviewdb.StarredChange;
+import com.google.gerrit.reviewdb.UserIdentity;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.mail.EmailHeader.AddressList;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.ProjectState;
+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.ChangeQueryBuilder;
+import com.google.gwtorm.client.OrmException;
+
+import org.eclipse.jgit.util.SystemReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Sends an email to one or more interested parties. */
+public abstract class ChangeEmail extends OutgoingEmail {
+ protected final Change change;
+ protected String projectName;
+ protected PatchSet patchSet;
+ protected PatchSetInfo patchSetInfo;
+ protected ChangeMessage changeMessage;
+
+ private ProjectState projectState;
+ protected ChangeData changeData;
+ private boolean inFooter;
+
+ protected ChangeEmail(EmailArguments ea, final Change c, final String mc) {
+ super(ea, mc);
+ change = c;
+ changeData = change != null ? new ChangeData(change) : null;
+ }
+
+ public void setPatchSet(final PatchSet ps) {
+ patchSet = ps;
+ }
+
+ public void setPatchSet(final PatchSet ps, final PatchSetInfo psi) {
+ patchSet = ps;
+ patchSetInfo = psi;
+ }
+
+ public void setChangeMessage(final ChangeMessage cm) {
+ changeMessage = cm;
+ }
+
+ /** Format the message body by calling {@link #appendText(String)}. */
+ protected void format() {
+ formatChange();
+ if (getChangeUrl() != null) {
+ openFooter();
+ appendText("To view visit ");
+ appendText(getChangeUrl());
+ appendText("\n");
+ }
+ if (getSettingsUrl() != null) {
+ openFooter();
+ appendText("To unsubscribe, visit ");
+ appendText(getSettingsUrl());
+ appendText("\n");
+ }
+
+ if (inFooter) {
+ appendText("\n");
+ } else {
+ openFooter();
+ }
+ appendText("Gerrit-MessageType: " + messageClass + "\n");
+ appendText("Gerrit-Project: " + projectName + "\n");
+ appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n");
+ appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n");
+
+ try {
+ HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
+ for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
+ change.getId())) {
+ reviewers.add(p.getAccountId());
+ }
+
+ TreeSet<String> names = new TreeSet<String>();
+ for (Account.Id who : reviewers) {
+ names.add(getNameEmailFor(who));
+ }
+
+ for (String name : names) {
+ appendText("Gerrit-Reviewer: " + name + "\n");
+ }
+ } catch (OrmException e) {
+ }
+ }
+
+ /** Format the message body by calling {@link #appendText(String)}. */
+ protected abstract void formatChange();
+
+ /** Setup the message headers and envelope (TO, CC, BCC). */
+ protected void init() {
+ super.init();
+ if (args.projectCache != null) {
+ projectState = args.projectCache.get(change.getProject());
+ projectName =
+ projectState != null ? projectState.getProject().getName() : null;
+ } else {
+ projectState = null;
+ projectName = null;
+ }
+
+ if (patchSet == null) {
+ try {
+ patchSet = args.db.get().patchSets().get(change.currentPatchSetId());
+ } catch (OrmException err) {
+ patchSet = null;
+ }
+ }
+
+ if (patchSet != null && patchSetInfo == null) {
+ try {
+ patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId());
+ } catch (PatchSetInfoNotAvailableException err) {
+ patchSetInfo = null;
+ }
+ }
+
+ if (changeMessage != null && changeMessage.getWrittenOn() != null) {
+ setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
+ }
+ setChangeSubjectHeader();
+ setHeader("X-Gerrit-Change-Id", "" + change.getKey().get());
+ setListIdHeader();
+ setChangeUrlHeader();
+ setCommitIdHeader();
+
+ inFooter = false;
+ }
+
+ private void setListIdHeader() {
+ // Set a reasonable list id so that filters can be used to sort messages
+ //
+ final StringBuilder listid = new StringBuilder();
+ listid.append("gerrit-");
+ listid.append(projectName.replace('/', '-'));
+ listid.append("@");
+ listid.append(getGerritHost());
+
+ final String listidStr = listid.toString();
+ setHeader("Mailing-List", "list " + listidStr);
+ setHeader("List-Id", "<" + listidStr.replace('@', '.') + ">");
+ if (getSettingsUrl() != null) {
+ setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
+ }
+ }
+
+ private void setChangeUrlHeader() {
+ final String u = getChangeUrl();
+ if (u != null) {
+ setHeader("X-Gerrit-ChangeURL", "<" + u + ">");
+ }
+ }
+
+ private void setCommitIdHeader() {
+ if (patchSet != null && patchSet.getRevision() != null
+ && patchSet.getRevision().get() != null
+ && patchSet.getRevision().get().length() > 0) {
+ setHeader("X-Gerrit-Commit", patchSet.getRevision().get());
+ }
+ }
+
+ private void setChangeSubjectHeader() {
+ final StringBuilder subj = new StringBuilder();
+ subj.append("[");
+ subj.append(change.getDest().getShortName());
+ subj.append("] ");
+ subj.append("Change ");
+ subj.append(change.getKey().abbreviate());
+ subj.append(": (");
+ subj.append(projectName);
+ subj.append(") ");
+ if (change.getSubject().length() > 60) {
+ subj.append(change.getSubject().substring(0, 60));
+ subj.append("...");
+ } else {
+ subj.append(change.getSubject());
+ }
+ setHeader("Subject", subj.toString());
+ }
+
+ /** Get a link to the change; null if the server doesn't know its own address. */
+ protected String getChangeUrl() {
+ if (change != null && getGerritUrl() != null) {
+ final StringBuilder r = new StringBuilder();
+ r.append(getGerritUrl());
+ r.append(change.getChangeId());
+ return r.toString();
+ }
+ return null;
+ }
+
+ protected String getChangeMessageThreadId() {
+ final StringBuilder r = new StringBuilder();
+ r.append('<');
+ r.append("gerrit");
+ r.append('.');
+ r.append(change.getCreatedOn().getTime());
+ r.append('.');
+ r.append(change.getKey().get());
+ r.append('@');
+ r.append(getGerritHost());
+ r.append('>');
+ return r.toString();
+ }
+
+ private void openFooter() {
+ if (!inFooter) {
+ inFooter = true;
+ appendText("-- \n");
+ }
+ }
+
+ /** Format the sender's "cover letter", {@link #getCoverLetter()}. */
+ protected void formatCoverLetter() {
+ final String cover = getCoverLetter();
+ if (!"".equals(cover)) {
+ appendText(cover);
+ appendText("\n\n");
+ }
+ }
+
+ /** Get the text of the "cover letter", from {@link ChangeMessage}. */
+ protected String getCoverLetter() {
+ if (changeMessage != null) {
+ final String txt = changeMessage.getMessage();
+ if (txt != null) {
+ return txt.trim();
+ }
+ }
+ return "";
+ }
+
+ /** Format the change message and the affected file list. */
+ protected void formatChangeDetail() {
+ if (patchSetInfo != null) {
+ appendText(patchSetInfo.getMessage().trim());
+ appendText("\n");
+ } else {
+ appendText(change.getSubject().trim());
+ appendText("\n");
+ }
+
+ if (patchSet != null) {
+ appendText("---\n");
+ for (PatchListEntry p : getPatchList().getPatches()) {
+ appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
+ }
+ appendText("\n");
+ }
+ }
+
+ /** Get the patch list corresponding to this patch set. */
+ protected PatchList getPatchList() {
+ if (patchSet != null) {
+ return args.patchListCache.get(change, patchSet);
+ }
+ return null;
+ }
+
+ /** Get the project entity the change is in; null if its been deleted. */
+ protected ProjectState getProjectState() {
+ return projectState;
+ }
+
+ /** Get the groups which own the project. */
+ protected Set<AccountGroup.Id> getProjectOwners() {
+ final ProjectState r;
+
+ r = args.projectCache.get(change.getProject());
+ return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
+ }
+
+ /** TO or CC all vested parties (change owner, patch set uploader, author). */
+ protected void rcptToAuthors(final RecipientType rt) {
+ add(rt, change.getOwner());
+ if (patchSet != null) {
+ add(rt, patchSet.getUploader());
+ }
+ if (patchSetInfo != null) {
+ add(rt, patchSetInfo.getAuthor());
+ add(rt, patchSetInfo.getCommitter());
+ }
+ }
+
+ /** BCC any user who has starred this change. */
+ protected void bccStarredBy() {
+ try {
+ // BCC anyone who has starred this change.
+ //
+ for (StarredChange w : args.db.get().starredChanges().byChange(
+ change.getId())) {
+ add(RecipientType.BCC, w.getAccountId());
+ }
+ } catch (OrmException err) {
+ // Just don't BCC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
+ }
+ }
+
+ /** BCC any user who has set "notify all comments" on this project. */
+ protected void bccWatchesNotifyAllComments() {
+ try {
+ // BCC anyone else who has interest in this project's changes
+ //
+ for (final AccountProjectWatch w : getWatches()) {
+ if (w.isNotifyAllComments()) {
+ add(RecipientType.BCC, w.getAccountId());
+ }
+ }
+ } catch (OrmException err) {
+ // Just don't CC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
+ }
+ }
+
+ /** Returns all watches that are relevant */
+ protected final List<AccountProjectWatch> getWatches() throws OrmException {
+ if (changeData == null) {
+ return Collections.emptyList();
+ }
+
+ List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>();
+ Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
+
+ for (AccountProjectWatch w : args.db.get().accountProjectWatches()
+ .byProject(change.getProject())) {
+ projectWatchers.add(w.getAccountId());
+ add(matching, w);
+ }
+
+ for (AccountProjectWatch w : args.db.get().accountProjectWatches()
+ .byProject(args.wildProject)) {
+ if (!projectWatchers.contains(w.getAccountId())) {
+ add(matching, w);
+ }
+ }
+
+ return Collections.unmodifiableList(matching);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void add(List<AccountProjectWatch> matching, AccountProjectWatch w)
+ throws OrmException {
+ IdentifiedUser user =
+ args.identifiedUserFactory.create(args.db, w.getAccountId());
+ ChangeQueryBuilder qb = args.queryBuilder.create(user);
+ Predicate<ChangeData> p = qb.is_visible();
+ if (w.getFilter() != null) {
+ try {
+ qb.setAllowFile(true);
+ p = Predicate.and(qb.parse(w.getFilter()), p);
+ p = args.queryRewriter.get().rewrite(p);
+ if (p.match(changeData)) {
+ matching.add(w);
+ }
+ } catch (QueryParseException e) {
+ // Ignore broken filter expressions.
+ }
+ } else if (p.match(changeData)) {
+ matching.add(w);
+ }
+ }
+
+ /** Any user who has published comments on this change. */
+ protected void ccAllApprovals() {
+ ccApprovals(true);
+ }
+
+ /** Users who have non-zero approval codes on the change. */
+ protected void ccExistingReviewers() {
+ ccApprovals(false);
+ }
+
+ private void ccApprovals(final boolean includeZero) {
+ try {
+ // CC anyone else who has posted an approval mark on this change
+ //
+ for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange(
+ change.getId())) {
+ if (!includeZero && ap.getValue() == 0) {
+ continue;
+ }
+ add(RecipientType.CC, ap.getAccountId());
+ }
+ } catch (OrmException err) {
+ }
+ }
+
+ protected boolean isVisibleTo(final Account.Id to) {
+ return projectState == null
+ || change == null
+ || projectState.controlFor(args.identifiedUserFactory.create(to))
+ .controlFor(change).isVisible();
+ }
+}
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 a010ac1576..be6360fe95 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
@@ -27,7 +27,9 @@ import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/** Send comments, after the author of them hit used Publish Comments in the UI. */
public class CommentSender extends ReplyToChangeSender {
@@ -38,12 +40,19 @@ public class CommentSender extends ReplyToChangeSender {
private List<PatchLineComment> inlineComments = Collections.emptyList();
@Inject
- public CommentSender(@Assisted Change c) {
- super(c, "comment");
+ public CommentSender(EmailArguments ea, @Assisted Change c) {
+ super(ea, c, "comment");
}
public void setPatchLineComments(final List<PatchLineComment> plc) {
inlineComments = plc;
+
+ Set<String> paths = new HashSet<String>();
+ for (PatchLineComment c : plc) {
+ Patch.Key p = c.getKey().getParentKey();
+ paths.add(p.getFileName());
+ }
+ changeData.setCurrentFilePaths(paths);
}
@Override
@@ -56,7 +65,7 @@ public class CommentSender extends ReplyToChangeSender {
}
@Override
- protected void format() {
+ protected void formatChange() {
if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) {
appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n");
appendText("\n");
@@ -124,7 +133,7 @@ public class CommentSender extends ReplyToChangeSender {
private Repository getRepository() {
try {
- return server.openRepository(projectName);
+ return args.server.openRepository(projectName);
} catch (RepositoryNotFoundException e) {
return null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index d25d584149..ea57cde1e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -34,8 +34,9 @@ public class CreateChangeSender extends NewChangeSender {
}
@Inject
- public CreateChangeSender(@Assisted Change c) {
- super(c);
+ public CreateChangeSender(EmailArguments ea, SshInfo sshInfo,
+ @Assisted Change c) {
+ super(ea, sshInfo, c);
}
@Override
@@ -46,37 +47,32 @@ public class CreateChangeSender extends NewChangeSender {
}
private void bccWatchers() {
- if (db != null) {
- try {
- // BCC anyone else who has interest in this project's changes
- //
- final ProjectState ps = getProjectState();
- if (ps != null) {
- // Try to mark interested owners with a TO and not a BCC line.
- //
- final Set<Account.Id> owners = new HashSet<Account.Id>();
- for (AccountGroup.Id g : getProjectOwners()) {
- for (AccountGroupMember m : db.accountGroupMembers().byGroup(g)) {
- owners.add(m.getAccountId());
- }
- }
+ try {
+ // Try to mark interested owners with a TO and not a BCC line.
+ //
+ final Set<Account.Id> owners = new HashSet<Account.Id>();
+ for (AccountGroup.Id g : getProjectOwners()) {
+ for (AccountGroupMember m : args.db.get().accountGroupMembers()
+ .byGroup(g)) {
+ owners.add(m.getAccountId());
+ }
+ }
- // BCC anyone who has interest in this project's changes
- //
- for (AccountProjectWatch w : db.accountProjectWatches()
- .notifyNewChanges(ps.getProject().getNameKey())) {
- if (owners.contains(w.getAccountId())) {
- add(RecipientType.TO, w.getAccountId());
- } else {
- add(RecipientType.BCC, w.getAccountId());
- }
+ // BCC anyone who has interest in this project's changes
+ //
+ for (final AccountProjectWatch w : getWatches()) {
+ if (w.isNotifyNewChanges()) {
+ if (owners.contains(w.getAccountId())) {
+ add(RecipientType.TO, w.getAccountId());
+ } else {
+ add(RecipientType.BCC, w.getAccountId());
}
}
- } catch (OrmException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
}
+ } catch (OrmException err) {
+ // Just don't CC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
}
}
}
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
new file mode 100644
index 0000000000..8a7ffd6b70
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -0,0 +1,75 @@
+// 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.mail;
+
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeQueryRewriter;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import javax.annotation.Nullable;
+
+class EmailArguments {
+ final GitRepositoryManager server;
+ final ProjectCache projectCache;
+ final AccountCache accountCache;
+ final PatchListCache patchListCache;
+ final FromAddressGenerator fromAddressGenerator;
+ final EmailSender emailSender;
+ final PatchSetInfoFactory patchSetInfoFactory;
+ final IdentifiedUser.GenericFactory identifiedUserFactory;
+ final Provider<String> urlProvider;
+ final Project.NameKey wildProject;
+
+ final ChangeQueryBuilder.Factory queryBuilder;
+ final Provider<ChangeQueryRewriter> queryRewriter;
+ final Provider<ReviewDb> db;
+
+ @Inject
+ EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
+ AccountCache accountCache, PatchListCache patchListCache,
+ FromAddressGenerator fromAddressGenerator, EmailSender emailSender,
+ PatchSetInfoFactory patchSetInfoFactory,
+ GenericFactory identifiedUserFactory,
+ @CanonicalWebUrl @Nullable Provider<String> urlProvider,
+ @WildProjectName Project.NameKey wildProject,
+ ChangeQueryBuilder.Factory queryBuilder,
+ Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db) {
+ this.server = server;
+ this.projectCache = projectCache;
+ this.accountCache = accountCache;
+ this.patchListCache = patchListCache;
+ this.fromAddressGenerator = fromAddressGenerator;
+ this.emailSender = emailSender;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.identifiedUserFactory = identifiedUserFactory;
+ this.urlProvider = urlProvider;
+ this.wildProject = wildProject;
+ this.queryBuilder = queryBuilder;
+ this.queryRewriter = queryRewriter;
+ this.db = db;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
index f2e89ac604..10f6510ffa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
@@ -19,6 +19,7 @@ import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -118,6 +119,14 @@ abstract class EmailHeader {
list.add(addr);
}
+ void remove(java.lang.String email) {
+ for (Iterator<Address> i = list.iterator(); i.hasNext();) {
+ if (i.next().email.equals(email)) {
+ i.remove();
+ }
+ }
+ }
+
@Override
boolean isEmpty() {
return list.isEmpty();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
index 411e6ca326..9c4f4c45bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
@@ -22,6 +22,14 @@ public interface EmailSender {
boolean isEnabled();
/**
+ * Can the address receive messages from us?
+ *
+ * @param address the address to consider.
+ * @return true if this sender will deliver to the address.
+ */
+ boolean canEmail(String address);
+
+ /**
* Sends an email message.
*
* @param from who the message is from.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
index 11a541ba04..00750ef306 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
@@ -25,8 +25,8 @@ public class MergeFailSender extends ReplyToChangeSender {
}
@Inject
- public MergeFailSender(@Assisted Change c) {
- super(c, "comment");
+ public MergeFailSender(EmailArguments ea, @Assisted Change c) {
+ super(ea, c, "comment");
}
@Override
@@ -37,7 +37,7 @@ public class MergeFailSender extends ReplyToChangeSender {
}
@Override
- protected void format() {
+ protected void formatChange() {
appendText("Change " + change.getKey().abbreviate());
if (patchSetInfo != null && patchSetInfo.getAuthor() != null
&& patchSetInfo.getAuthor().getName() != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index a1f7d9e901..caf19e472e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -23,7 +23,6 @@ import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetApproval;
-import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -37,15 +36,14 @@ public class MergedSender extends ReplyToChangeSender {
public MergedSender create(Change change);
}
+ private final ApprovalTypes approvalTypes;
private Branch.NameKey dest;
@Inject
- private ApprovalTypes approvalTypes;
-
- @Inject
- public MergedSender(@Assisted Change c) {
- super(c, "merged");
+ public MergedSender(EmailArguments ea, ApprovalTypes at, @Assisted Change c) {
+ super(ea, c, "merged");
dest = c.getDest();
+ approvalTypes = at;
}
public void setDest(final Branch.NameKey key) {
@@ -63,7 +61,7 @@ public class MergedSender extends ReplyToChangeSender {
}
@Override
- protected void format() {
+ protected void formatChange() {
appendText("Change " + change.getKey().abbreviate());
if (patchSetInfo != null && patchSetInfo.getAuthor() != null
&& patchSetInfo.getAuthor().getName() != null) {
@@ -78,7 +76,7 @@ public class MergedSender extends ReplyToChangeSender {
}
private void formatApprovals() {
- if (db != null && patchSet != null) {
+ if (patchSet != null) {
try {
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
@@ -86,8 +84,8 @@ public class MergedSender extends ReplyToChangeSender {
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
- for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(
- patchSet.getId())) {
+ for (PatchSetApproval ca : args.db.get().patchSetApprovals()
+ .byPatchSet(patchSet.getId())) {
if (ca.getValue() > 0) {
insert(pos, ca);
} else if (ca.getValue() < 0) {
@@ -157,22 +155,18 @@ public class MergedSender extends ReplyToChangeSender {
}
private void bccWatchesNotifySubmittedChanges() {
- if (db != null) {
- try {
- // BCC anyone else who has interest in this project's changes
- //
- final ProjectState ps = getProjectState();
- if (ps != null) {
- for (AccountProjectWatch w : db.accountProjectWatches()
- .notifySubmittedChanges(ps.getProject().getNameKey())) {
- add(RecipientType.BCC, w.getAccountId());
- }
+ try {
+ // BCC anyone else who has interest in this project's changes
+ //
+ for (final AccountProjectWatch w : getWatches()) {
+ if (w.isNotifySubmittedChanges()) {
+ add(RecipientType.BCC, w.getAccountId());
}
- } catch (OrmException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
}
+ } catch (OrmException err) {
+ // Just don't CC everyone. Better to send a partial message to those
+ // we already have queued up then to fail deliver entirely to people
+ // who have a lower interest in the change.
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
index 6c35f0e9a9..dc8c2c2b33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
@@ -17,7 +17,6 @@ package com.google.gerrit.server.mail;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.ssh.SshInfo;
-import com.google.inject.Inject;
import com.jcraft.jsch.HostKey;
@@ -28,15 +27,14 @@ import java.util.List;
import java.util.Set;
/** Sends an email alerting a user to a new change for them to review. */
-public abstract class NewChangeSender extends OutgoingEmail {
- @Inject
- private SshInfo sshInfo;
-
+public abstract class NewChangeSender extends ChangeEmail {
+ private final SshInfo sshInfo;
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
- protected NewChangeSender(Change c) {
- super(c, "newchange");
+ protected NewChangeSender(EmailArguments ea, SshInfo sshInfo, Change c) {
+ super(ea, c, "newchange");
+ this.sshInfo = sshInfo;
}
public void addReviewers(final Collection<Account.Id> cc) {
@@ -59,7 +57,7 @@ public abstract class NewChangeSender extends OutgoingEmail {
}
@Override
- protected void format() {
+ protected void formatChange() {
formatSalutation();
formatChangeDetail();
@@ -114,24 +112,31 @@ public abstract class NewChangeSender extends OutgoingEmail {
}
private String getPullUrl() {
- final List<HostKey> hostKeys = sshInfo.getHostKeys();
- if (hostKeys.isEmpty()) {
+ final String host = getSshHost();
+ if (host == null) {
return "";
}
- final String host = hostKeys.get(0).getHost();
final StringBuilder r = new StringBuilder();
r.append("git pull ssh://");
- if (host.startsWith("*:")) {
- r.append(getGerritHost());
- r.append(host.substring(1));
- } else {
- r.append(host);
- }
+ r.append(host);
r.append("/");
r.append(projectName);
r.append(" ");
r.append(patchSet.getRefName());
return r.toString();
}
+
+ public String getSshHost() {
+ final List<HostKey> hostKeys = sshInfo.getHostKeys();
+ if (hostKeys.isEmpty()) {
+ return null;
+ }
+
+ final String host = hostKeys.get(0).getHost();
+ if (host.startsWith("*:")) {
+ return getGerritHost() + host.substring(1);
+ }
+ return host;
+ }
}
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 41f532ccd7..fe666367d3 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
@@ -22,25 +22,24 @@ import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
import com.google.gerrit.reviewdb.UserIdentity;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.mail.EmailHeader.AddressList;
import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+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.ChangeQueryBuilder;
import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
import org.eclipse.jgit.util.SystemReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.net.MalformedURLException;
import java.net.URL;
@@ -49,105 +48,47 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
-
-import javax.annotation.Nullable;
+import java.util.TreeSet;
/** Sends an email to one or more interested parties. */
public abstract class OutgoingEmail {
+ private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);
+
private static final String HDR_TO = "To";
private static final String HDR_CC = "CC";
- private static final Random RNG = new Random();
- private final String messageClass;
- protected final Change change;
- protected String projectName;
+ protected String messageClass;
private final HashSet<Account.Id> rcptTo = new HashSet<Account.Id>();
private final Map<String, EmailHeader> headers;
private final List<Address> smtpRcptTo = new ArrayList<Address>();
private Address smtpFromAddress;
private StringBuilder body;
- private boolean inFooter;
+ protected final EmailArguments args;
protected Account.Id fromId;
- protected PatchSet patchSet;
- protected PatchSetInfo patchSetInfo;
- protected ChangeMessage changeMessage;
- protected ReviewDb db;
-
- @Inject
- protected GitRepositoryManager server;
-
- @Inject
- private ProjectCache projectCache;
-
- @Inject
- private AccountCache accountCache;
-
- @Inject
- private PatchListCache patchListCache;
-
- @Inject
- private FromAddressGenerator fromAddressGenerator;
-
- @Inject
- private EmailSender emailSender;
- @Inject
- private PatchSetInfoFactory patchSetInfoFactory;
-
- @Inject
- private IdentifiedUser.GenericFactory identifiedUserFactory;
-
- @Inject
- @CanonicalWebUrl
- @Nullable
- private Provider<String> urlProvider;
-
- private ProjectState projectState;
-
- protected OutgoingEmail(final Change c, final String mc) {
- change = c;
+ protected OutgoingEmail(EmailArguments ea, final String mc) {
+ args = ea;
messageClass = mc;
headers = new LinkedHashMap<String, EmailHeader>();
}
- protected OutgoingEmail(final String mc) {
- this(null, mc);
- }
-
public void setFrom(final Account.Id id) {
fromId = id;
}
- public void setPatchSet(final PatchSet ps) {
- patchSet = ps;
- }
-
- public void setPatchSet(final PatchSet ps, final PatchSetInfo psi) {
- patchSet = ps;
- patchSetInfo = psi;
- }
-
- public void setChangeMessage(final ChangeMessage cm) {
- changeMessage = cm;
- }
-
- public void setReviewDb(final ReviewDb d) {
- db = d;
- }
-
/**
* Format and enqueue the message for delivery.
*
* @throws EmailException
*/
public void send() throws EmailException {
- if (!emailSender.isEnabled()) {
+ if (!args.emailSender.isEnabled()) {
// Server has explicitly disabled email sending.
//
return;
@@ -157,49 +98,38 @@ public abstract class OutgoingEmail {
format();
if (shouldSendMessage()) {
if (fromId != null) {
- // If we are impersonating a user, make sure they receive a CC of
- // this message so they can always review and audit what we sent
- // on their behalf to others.
- //
- add(RecipientType.CC, fromId);
- }
- if (change != null) {
- if (getChangeUrl() != null) {
- openFooter();
- appendText("To view visit ");
- appendText(getChangeUrl());
- appendText("\n");
- }
- if (getSettingsUrl() != null) {
- openFooter();
- appendText("To unsubscribe, visit ");
- appendText(getSettingsUrl());
- appendText("\n");
- }
+ final Account fromUser = args.accountCache.get(fromId).getAccount();
+
+ if (fromUser.getGeneralPreferences().isCopySelfOnEmails()) {
+ // If we are impersonating a user, make sure they receive a CC of
+ // this message so they can always review and audit what we sent
+ // on their behalf to others.
+ //
+ add(RecipientType.CC, fromId);
+
+ } else if (rcptTo.remove(fromId)) {
+ // If they don't want a copy, but we queued one up anyway,
+ // drop them from the recipient lists.
+ //
+ final String fromEmail = fromUser.getPreferredEmail();
+ for (Iterator<Address> i = smtpRcptTo.iterator(); i.hasNext();) {
+ if (i.next().email.equals(fromEmail)) {
+ i.remove();
+ }
+ }
+ for (EmailHeader hdr : headers.values()) {
+ if (hdr instanceof AddressList) {
+ ((AddressList) hdr).remove(fromEmail);
+ }
+ }
- if (inFooter) {
- appendText("\n");
- } else {
- openFooter();
+ if (smtpRcptTo.isEmpty()) {
+ return;
+ }
}
- appendText("Gerrit-MessageType: " + messageClass + "\n");
- appendText("Gerrit-Project: " + projectName + "\n");
- appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n");
}
- if (headers.get("Message-ID").isEmpty()) {
- final StringBuilder rndid = new StringBuilder();
- rndid.append("<");
- rndid.append(System.currentTimeMillis());
- rndid.append("-");
- rndid.append(Integer.toString(RNG.nextInt(999999), 36));
- rndid.append("@");
- rndid.append(SystemReader.getInstance().getHostname());
- rndid.append(">");
- setHeader("Message-ID", rndid.toString());
- }
-
- emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString());
+ args.emailSender.send(smtpFromAddress, smtpRcptTo, headers, body.toString());
}
}
@@ -208,27 +138,11 @@ public abstract class OutgoingEmail {
/** Setup the message headers and envelope (TO, CC, BCC). */
protected void init() {
- if (change != null && projectCache != null) {
- projectState = projectCache.get(change.getProject());
- projectName =
- projectState != null ? projectState.getProject().getName() : null;
- } else {
- projectState = null;
- projectName = null;
- }
-
- smtpFromAddress = fromAddressGenerator.from(fromId);
- if (changeMessage != null && changeMessage.getWrittenOn() != null) {
- setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
- } else {
- setHeader("Date", new Date());
- }
+ smtpFromAddress = args.fromAddressGenerator.from(fromId);
+ setHeader("Date", new Date());
headers.put("From", new EmailHeader.AddressList(smtpFromAddress));
headers.put(HDR_TO, new EmailHeader.AddressList());
headers.put(HDR_CC, new EmailHeader.AddressList());
- if (change != null) {
- setChangeSubjectHeader();
- }
setHeader("Message-ID", "");
if (fromId != null) {
@@ -244,17 +158,10 @@ public abstract class OutgoingEmail {
}
setHeader("X-Gerrit-MessageType", messageClass);
- if (change != null) {
- setHeader("X-Gerrit-Change-Id", "" + change.getKey().get());
- setListIdHeader();
- setChangeUrlHeader();
- setCommitIdHeader();
- }
body = new StringBuilder();
- inFooter = false;
- if (fromId != null && fromAddressGenerator.isGenericAddress(fromId)) {
- final Account account = accountCache.get(fromId).getAccount();
+ if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) {
+ final Account account = args.accountCache.get(fromId).getAccount();
final String name = account.getFullName();
final String email = account.getPreferredEmail();
@@ -270,75 +177,6 @@ public abstract class OutgoingEmail {
body.append(":\n\n");
}
}
-
- if (change != null && db != null) {
- if (patchSet == null) {
- try {
- patchSet = db.patchSets().get(change.currentPatchSetId());
- } catch (OrmException err) {
- patchSet = null;
- }
- }
-
- if (patchSet != null && patchSetInfo == null) {
- try {
- patchSetInfo = patchSetInfoFactory.get(patchSet.getId());
- } catch (PatchSetInfoNotAvailableException err) {
- patchSetInfo = null;
- }
- }
- }
- }
-
- private void setListIdHeader() {
- // Set a reasonable list id so that filters can be used to sort messages
- //
- final StringBuilder listid = new StringBuilder();
- listid.append("gerrit-");
- listid.append(projectName.replace('/', '-'));
- listid.append("@");
- listid.append(getGerritHost());
-
- final String listidStr = listid.toString();
- setHeader("Mailing-List", "list " + listidStr);
- setHeader("List-Id", "<" + listidStr.replace('@', '.') + ">");
- if (getSettingsUrl() != null) {
- setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
- }
- }
-
- private void setChangeUrlHeader() {
- final String u = getChangeUrl();
- if (u != null) {
- setHeader("X-Gerrit-ChangeURL", "<" + u + ">");
- }
- }
-
- private void setCommitIdHeader() {
- if (patchSet != null && patchSet.getRevision() != null
- && patchSet.getRevision().get() != null
- && patchSet.getRevision().get().length() > 0) {
- setHeader("X-Gerrit-Commit", patchSet.getRevision().get());
- }
- }
-
- private void setChangeSubjectHeader() {
- final StringBuilder subj = new StringBuilder();
- subj.append("[");
- subj.append(change.getDest().getShortName());
- subj.append("] ");
- subj.append("Change ");
- subj.append(change.getKey().abbreviate());
- subj.append(": (");
- subj.append(projectName);
- subj.append(") ");
- if (change.getSubject().length() > 60) {
- subj.append(change.getSubject().substring(0, 60));
- subj.append("...");
- } else {
- subj.append(change.getSubject());
- }
- setHeader("Subject", subj.toString());
}
protected String getGerritHost() {
@@ -357,18 +195,7 @@ public abstract class OutgoingEmail {
return SystemReader.getInstance().getHostname();
}
- /** Get a link to the change; null if the server doesn't know its own address. */
- protected String getChangeUrl() {
- if (change != null && getGerritUrl() != null) {
- final StringBuilder r = new StringBuilder();
- r.append(getGerritUrl());
- r.append(change.getChangeId());
- return r.toString();
- }
- return null;
- }
-
- private String getSettingsUrl() {
+ public String getSettingsUrl() {
if (getGerritUrl() != null) {
final StringBuilder r = new StringBuilder();
r.append(getGerritUrl());
@@ -379,21 +206,7 @@ public abstract class OutgoingEmail {
}
protected String getGerritUrl() {
- return urlProvider.get();
- }
-
- protected String getChangeMessageThreadId() {
- final StringBuilder r = new StringBuilder();
- r.append('<');
- r.append("gerrit");
- r.append('.');
- r.append(change.getCreatedOn().getTime());
- r.append('.');
- r.append(change.getKey().get());
- r.append('@');
- r.append(getGerritHost());
- r.append('>');
- return r.toString();
+ return args.urlProvider.get();
}
/** Set a header in the outgoing message. */
@@ -412,67 +225,13 @@ public abstract class OutgoingEmail {
}
}
- private void openFooter() {
- if (!inFooter) {
- inFooter = true;
- appendText("-- \n");
- }
- }
-
- /** Format the sender's "cover letter", {@link #getCoverLetter()}. */
- protected void formatCoverLetter() {
- final String cover = getCoverLetter();
- if (!"".equals(cover)) {
- appendText(cover);
- appendText("\n\n");
- }
- }
-
- /** Get the text of the "cover letter", from {@link ChangeMessage}. */
- protected String getCoverLetter() {
- if (changeMessage != null) {
- final String txt = changeMessage.getMessage();
- if (txt != null) {
- return txt.trim();
- }
- }
- return "";
- }
-
- /** Format the change message and the affected file list. */
- protected void formatChangeDetail() {
- if (patchSetInfo != null) {
- appendText(patchSetInfo.getMessage().trim());
- appendText("\n");
- } else {
- appendText(change.getSubject().trim());
- appendText("\n");
- }
-
- if (patchSet != null) {
- appendText("---\n");
- for (PatchListEntry p : getPatchList().getPatches()) {
- appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
- }
- appendText("\n");
- }
- }
-
- /** Get the patch list corresponding to this patch set. */
- protected PatchList getPatchList() {
- if (patchSet != null) {
- return patchListCache.get(change, patchSet);
- }
- return null;
- }
-
/** Lookup a human readable name for an account, usually the "full name". */
protected String getNameFor(final Account.Id accountId) {
if (accountId == null) {
return "Anonymous Coward";
}
- final Account userAccount = accountCache.get(accountId).getAccount();
+ final Account userAccount = args.accountCache.get(accountId).getAccount();
String name = userAccount.getFullName();
if (name == null) {
name = userAccount.getPreferredEmail();
@@ -483,6 +242,24 @@ public abstract class OutgoingEmail {
return name;
}
+ protected String getNameEmailFor(Account.Id accountId) {
+ AccountState who = args.accountCache.get(accountId);
+ String name = who.getAccount().getFullName();
+ String email = who.getAccount().getPreferredEmail();
+
+ if (name != null && email != null) {
+ return name + " <" + email + ">";
+
+ } else if (name != null) {
+ return name;
+ } else if (email != null) {
+ return email;
+
+ } else /* (name == null && email == null) */{
+ return "Anonymous Coward #" + accountId;
+ }
+ }
+
protected boolean shouldSendMessage() {
if (body.length() == 0) {
// If we have no message body, don't send.
@@ -490,7 +267,7 @@ public abstract class OutgoingEmail {
return false;
}
- if (rcptTo.isEmpty()) {
+ if (smtpRcptTo.isEmpty()) {
// If we have nobody to send this message to, then all of our
// selection filters previously for this type of message were
// unable to match a destination. Don't bother sending it.
@@ -507,19 +284,6 @@ public abstract class OutgoingEmail {
return true;
}
- /** Get the project entity the change is in; null if its been deleted. */
- protected ProjectState getProjectState() {
- return projectState;
- }
-
- /** Get the groups which own the project. */
- protected Set<AccountGroup.Id> getProjectOwners() {
- final ProjectState r;
-
- r = projectCache.get(change.getProject());
- return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
- }
-
/** Schedule this message for delivery to the listed accounts. */
protected void add(final RecipientType rt, final Collection<Account.Id> list) {
for (final Account.Id id : list) {
@@ -527,89 +291,12 @@ public abstract class OutgoingEmail {
}
}
- /** TO or CC all vested parties (change owner, patch set uploader, author). */
- protected void rcptToAuthors(final RecipientType rt) {
- add(rt, change.getOwner());
- if (patchSet != null) {
- add(rt, patchSet.getUploader());
- }
- if (patchSetInfo != null) {
- add(rt, patchSetInfo.getAuthor());
- add(rt, patchSetInfo.getCommitter());
- }
- }
-
- private void add(final RecipientType rt, final UserIdentity who) {
+ protected void add(final RecipientType rt, final UserIdentity who) {
if (who != null && who.getAccount() != null) {
add(rt, who.getAccount());
}
}
- /** BCC any user who has starred this change. */
- protected void bccStarredBy() {
- if (db != null) {
- try {
- // BCC anyone who has starred this change.
- //
- for (StarredChange w : db.starredChanges().byChange(change.getId())) {
- add(RecipientType.BCC, w.getAccountId());
- }
- } catch (OrmException err) {
- // Just don't BCC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
- }
- }
- }
-
- /** BCC any user who has set "notify all comments" on this project. */
- protected void bccWatchesNotifyAllComments() {
- if (db != null) {
- try {
- // BCC anyone else who has interest in this project's changes
- //
- final ProjectState ps = getProjectState();
- if (ps != null) {
- for (final AccountProjectWatch w : db.accountProjectWatches()
- .notifyAllComments(ps.getProject().getNameKey())) {
- add(RecipientType.BCC, w.getAccountId());
- }
- }
- } catch (OrmException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
- }
- }
- }
-
- /** Any user who has published comments on this change. */
- protected void ccAllApprovals() {
- ccApprovals(true);
- }
-
- /** Users who have non-zero approval codes on the change. */
- protected void ccExistingReviewers() {
- ccApprovals(false);
- }
-
- private void ccApprovals(final boolean includeZero) {
- if (db != null) {
- try {
- // CC anyone else who has posted an approval mark on this change
- //
- for (PatchSetApproval ap : db.patchSetApprovals().byChange(
- change.getId())) {
- if (!includeZero && ap.getValue() == 0) {
- continue;
- }
- add(RecipientType.CC, ap.getAccountId());
- }
- } catch (OrmException err) {
- }
- }
- }
-
/** Schedule delivery of this message to the given account. */
protected void add(final RecipientType rt, final Account.Id to) {
if (!rcptTo.contains(to) && isVisibleTo(to)) {
@@ -618,30 +305,31 @@ public abstract class OutgoingEmail {
}
}
- private boolean isVisibleTo(final Account.Id to) {
- return projectState == null
- || change == null
- || projectState.controlFor(identifiedUserFactory.create(to))
- .controlFor(change).isVisible();
+ protected boolean isVisibleTo(final Account.Id to) {
+ return true;
}
/** Schedule delivery of this message to the given account. */
protected void add(final RecipientType rt, final Address addr) {
if (addr != null && addr.email != null && addr.email.length() > 0) {
- smtpRcptTo.add(addr);
- switch (rt) {
- case TO:
- ((EmailHeader.AddressList) headers.get(HDR_TO)).add(addr);
- break;
- case CC:
- ((EmailHeader.AddressList) headers.get(HDR_CC)).add(addr);
- break;
+ if (args.emailSender.canEmail(addr.email)) {
+ smtpRcptTo.add(addr);
+ switch (rt) {
+ case TO:
+ ((EmailHeader.AddressList) headers.get(HDR_TO)).add(addr);
+ break;
+ case CC:
+ ((EmailHeader.AddressList) headers.get(HDR_CC)).add(addr);
+ break;
+ }
+ } else {
+ log.warn("Not emailing " + addr.email + " (prohibited by allowrcpt)");
}
}
}
private Address toAddress(final Account.Id id) {
- final Account a = accountCache.get(id).getAccount();
+ final Account a = args.accountCache.get(id).getAccount();
final String e = a.getPreferredEmail();
if (e == null) {
return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
index d22cc5902f..9b201fd4b5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -28,14 +28,14 @@ public class RegisterNewEmailSender extends OutgoingEmail {
public RegisterNewEmailSender create(String address);
}
+ private final AuthConfig authConfig;
private final String addr;
@Inject
- private AuthConfig authConfig;
-
- @Inject
- public RegisterNewEmailSender(@Assisted final String address) {
- super("registernewemail");
+ public RegisterNewEmailSender(EmailArguments ea, AuthConfig ac,
+ @Assisted final String address) {
+ super(ea, "registernewemail");
+ authConfig = ac;
addr = address;
}
@@ -56,14 +56,7 @@ public class RegisterNewEmailSender extends OutgoingEmail {
final StringBuilder url = new StringBuilder();
url.append(getGerritUrl());
url.append("#VE,");
- try {
- url.append(authConfig.getEmailRegistrationToken().newToken(
- Base64.encodeBytes(addr.getBytes("UTF-8"))));
- } catch (XsrfException e) {
- throw new IllegalArgumentException(e);
- } catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException(e);
- }
+ url.append(getEmailRegistrationToken());
appendText("Welcome to Gerrit Code Review at ");
appendText(getGerritHost());
@@ -93,4 +86,15 @@ public class RegisterNewEmailSender extends OutgoingEmail {
+ " Replies to this message will not\n");
appendText("be read or answered.\n");
}
+
+ public String getEmailRegistrationToken() {
+ try {
+ return authConfig.getEmailRegistrationToken().newToken(
+ Base64.encodeBytes(addr.getBytes("UTF-8")));
+ } catch (XsrfException e) {
+ throw new IllegalArgumentException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
index 70ed4f5b2c..841aa355e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -34,15 +34,14 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
public ReplacePatchSetSender create(Change change);
}
- @Inject
- private SshInfo sshInfo;
-
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
+ private final SshInfo sshInfo;
@Inject
- public ReplacePatchSetSender(@Assisted Change c) {
- super(c, "newpatchset");
+ public ReplacePatchSetSender(EmailArguments ea, SshInfo si, @Assisted Change c) {
+ super(ea, c, "newpatchset");
+ sshInfo = si;
}
public void addReviewers(final Collection<Account.Id> cc) {
@@ -68,7 +67,7 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
}
@Override
- protected void format() {
+ protected void formatChange() {
formatSalutation();
formatChangeDetail();
@@ -126,24 +125,31 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
}
private String getPullUrl() {
- final List<HostKey> hostKeys = sshInfo.getHostKeys();
- if (hostKeys.isEmpty()) {
+ final String host = getSshHost();
+ if (host == null) {
return "";
}
- final String host = hostKeys.get(0).getHost();
final StringBuilder r = new StringBuilder();
r.append("git pull ssh://");
- if (host.startsWith("*:")) {
- r.append(getGerritHost());
- r.append(host.substring(1));
- } else {
- r.append(host);
- }
+ r.append(host);
r.append("/");
r.append(projectName);
r.append(" ");
r.append(patchSet.getRefName());
return r.toString();
}
+
+ public String getSshHost() {
+ final List<HostKey> hostKeys = sshInfo.getHostKeys();
+ if (hostKeys.isEmpty()) {
+ return null;
+ }
+
+ final String host = hostKeys.get(0).getHost();
+ if (host.startsWith("*:")) {
+ return getGerritHost() + host.substring(1);
+ }
+ return host;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
index 99e9565e42..05d2753dee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.mail;
import com.google.gerrit.reviewdb.Change;
/** Alert a user to a reply to a change, usually commentary made during review. */
-public abstract class ReplyToChangeSender extends OutgoingEmail {
- protected ReplyToChangeSender(Change c, String mc) {
- super(c, mc);
+public abstract class ReplyToChangeSender extends ChangeEmail {
+ protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) {
+ super(ea, c, mc);
}
@Override
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 1fb31a037b..d67d3b35f4 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
@@ -29,8 +29,11 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Set;
/** Sends email via a nearby SMTP server. */
@Singleton
@@ -47,7 +50,7 @@ public class SmtpEmailSender implements EmailSender {
private String smtpPass;
private Encryption smtpEncryption;
private boolean sslVerify;
- private String[] allowrcpt;
+ private Set<String> allowrcpt;
@Inject
SmtpEmailSender(@GerritServerConfig final Config cfg) {
@@ -79,7 +82,12 @@ public class SmtpEmailSender implements EmailSender {
smtpUser = cfg.getString("sendemail", null, "smtpuser");
smtpPass = cfg.getString("sendemail", null, "smtppass");
- allowrcpt = cfg.getStringList("sendemail", null, "allowrcpt");
+
+ Set<String> rcpt = new HashSet<String>();
+ for (String addr : cfg.getStringList("sendemail", null, "allowrcpt")) {
+ rcpt.add(addr);
+ }
+ allowrcpt = Collections.unmodifiableSet(rcpt);
}
@Override
@@ -88,7 +96,29 @@ public class SmtpEmailSender implements EmailSender {
}
@Override
- public void send(final Address from, final Collection<Address> rcpt,
+ public boolean canEmail(String address) {
+ if (!isEnabled()) {
+ return false;
+ }
+
+ if (allowrcpt.isEmpty()) {
+ return true;
+ }
+
+ if (allowrcpt.contains(address)) {
+ return true;
+ }
+
+ String domain = address.substring(address.lastIndexOf('@') + 1);
+ if (allowrcpt.contains(domain) || allowrcpt.contains("@" + domain)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void send(final Address from, Collection<Address> rcpt,
final Map<String, EmailHeader> callerHeaders, final String body)
throws EmailException {
if (!isEnabled()) {
@@ -161,7 +191,6 @@ public class SmtpEmailSender implements EmailSender {
private SMTPClient open() throws EmailException {
final AuthSMTPClient client = new AuthSMTPClient("UTF-8");
- client.setAllowRcpt(allowrcpt);
if (smtpEncryption == Encryption.SSL) {
client.enableSSL(sslVerify);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
index 209d0b74d4..1a6d2ae1ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
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 835db514b6..f55763f75d 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
@@ -11,19 +11,64 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+//
+// Some portions (e.g. outputDiff) below are:
+//
+// Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
+// Copyright (C) 2009, Johannes E. Schindelin
+// Copyright (C) 2009, Johannes Schindelin <johannes.schindelin@gmx.de>
+// and other copyright owners as documented in the project's IP log.
+//
+// This program and the accompanying materials are made available
+// under the terms of the Eclipse Distribution License v1.0 which
+// accompanies this distribution, is reproduced below, and is
+// available at http://www.eclipse.org/org/documents/edl-v10.php
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+//
+// - Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// - Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+//
+// - Neither the name of the Eclipse Foundation, Inc. nor the
+// names of its contributors may be used to endorse or promote
+// products derived from this software without specific prior
+// written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
package com.google.gerrit.server.patch;
-import static com.google.gerrit.common.data.PatchScriptSettings.Whitespace.IGNORE_NONE;
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.cache.EvictionPolicy;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -32,11 +77,16 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.MyersDiff;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.diff.RawTextIgnoreAllWhitespace;
+import org.eclipse.jgit.diff.RawTextIgnoreTrailingWhitespace;
+import org.eclipse.jgit.diff.RawTextIgnoreWhitespaceChange;
+import org.eclipse.jgit.diff.RenameDetector;
import org.eclipse.jgit.diff.ReplaceEdit;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -51,9 +101,10 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -63,7 +114,6 @@ import java.util.regex.Pattern;
@Singleton
public class PatchListCacheImpl implements PatchListCache {
private static final String CACHE_NAME = "diff";
- private static final boolean dynamic = false;
private static final Pattern BLANK_LINE_RE =
Pattern.compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
@@ -79,6 +129,7 @@ public class PatchListCacheImpl implements PatchListCache {
disk(type, CACHE_NAME) //
.memoryLimit(128) // very large items, cache only a few
.evictionPolicy(EvictionPolicy.LRU) // prefer most recent
+ .populateWith(Loader.class) //
;
bind(PatchListCacheImpl.class);
bind(PatchListCache.class).to(PatchListCacheImpl.class);
@@ -86,37 +137,20 @@ public class PatchListCacheImpl implements PatchListCache {
};
}
- private final GitRepositoryManager repoManager;
- private final SelfPopulatingCache<PatchListKey, PatchList> self;
- private final boolean computeIntraline;
+ private final Cache<PatchListKey, PatchList> cache;
@Inject
- PatchListCacheImpl(final GitRepositoryManager grm,
- @GerritServerConfig final Config config,
- @Named(CACHE_NAME) final Cache<PatchListKey, PatchList> raw) {
- repoManager = grm;
- computeIntraline = config.getBoolean("cache", "diff", "intraline", true);
- self = new SelfPopulatingCache<PatchListKey, PatchList>(raw) {
- @Override
- protected PatchList createEntry(final PatchListKey key) throws Exception {
- return compute(key);
- }
- };
+ PatchListCacheImpl(
+ @Named(CACHE_NAME) final Cache<PatchListKey, PatchList> thecache) {
+ cache = thecache;
}
public PatchList get(final PatchListKey key) {
- if (dynamic) {
- try {
- return compute(key);
- } catch (Exception e) {
- throw new RuntimeException("Cannot lookup " + key, e);
- }
- }
- return self.get(key);
+ return cache.get(key);
}
public PatchList get(final Change change, final PatchSet patchSet) {
- return get(change, patchSet, IGNORE_NONE);
+ return get(change, patchSet, Whitespace.IGNORE_NONE);
}
public PatchList get(final Change change, final PatchSet patchSet,
@@ -127,417 +161,406 @@ public class PatchListCacheImpl implements PatchListCache {
return get(new PatchListKey(projectKey, a, b, whitespace));
}
- private PatchList compute(final PatchListKey key)
- throws MissingObjectException, IncorrectObjectTypeException, IOException {
- final Repository repo = repoManager.openRepository(key.projectKey.get());
- try {
- return readPatchList(key, repo);
- } finally {
- repo.close();
- }
- }
+ static class Loader extends EntryCreator<PatchListKey, PatchList> {
+ private final GitRepositoryManager repoManager;
+ private final boolean computeIntraline;
- private PatchList readPatchList(final PatchListKey key, final Repository repo)
- throws IOException {
- final RevWalk rw = new RevWalk(repo);
- final RevCommit b = rw.parseCommit(key.getNewId());
- final AnyObjectId a = aFor(key, repo, b);
-
- final List<String> args = new ArrayList<String>();
- args.add("git");
- args.add("--git-dir=.");
- args.add("diff-tree");
- args.add("-M");
- switch (key.getWhitespace()) {
- case IGNORE_NONE:
- break;
- case IGNORE_SPACE_AT_EOL:
- args.add("--ignore-space-at-eol");
- break;
- case IGNORE_SPACE_CHANGE:
- args.add("--ignore-space-change");
- break;
- case IGNORE_ALL_SPACE:
- args.add("--ignore-all-space");
- break;
- default:
- throw new IOException("Unsupported whitespace " + key.getWhitespace());
- }
- if (a == null /* want combined diff */) {
- args.add("--cc");
- args.add(b.name());
- } else {
- args.add("--unified=1");
- args.add(a.name());
- args.add(b.name());
+ @Inject
+ Loader(GitRepositoryManager mgr, @GerritServerConfig Config config) {
+ repoManager = mgr;
+ computeIntraline = config.getBoolean("cache", "diff", "intraline", true);
}
- final org.eclipse.jgit.patch.Patch p = new org.eclipse.jgit.patch.Patch();
- final Process diffProcess = exec(repo, args);
- try {
- diffProcess.getOutputStream().close();
- diffProcess.getErrorStream().close();
-
- final InputStream in = diffProcess.getInputStream();
+ @Override
+ public PatchList createEntry(final PatchListKey key) throws Exception {
+ final Repository repo = repoManager.openRepository(key.projectKey.get());
try {
- p.parse(in);
+ return readPatchList(key, repo);
} finally {
- in.close();
- }
- } finally {
- try {
- final int rc = diffProcess.waitFor();
- if (rc != 0) {
- throw new IOException("git diff-tree exited abnormally: " + rc);
- }
- } catch (InterruptedException ie) {
+ repo.close();
}
}
- RevTree aTree = a != null ? rw.parseTree(a) : null;
- RevTree bTree = b.getTree();
+ private PatchList readPatchList(final PatchListKey key,
+ final Repository repo) throws IOException {
+ // TODO(jeffschu) correctly handle merge commits
- final int cnt = p.getFiles().size();
- final PatchListEntry[] entries = new PatchListEntry[cnt];
- for (int i = 0; i < cnt; i++) {
- entries[i] = newEntry(repo, aTree, bTree, p.getFiles().get(i));
- }
- return new PatchList(a, b, computeIntraline, entries);
- }
+ final RevWalk rw = new RevWalk(repo);
+ final RevCommit b = rw.parseCommit(key.getNewId());
+ final AnyObjectId a = aFor(key, repo, b);
- private PatchListEntry newEntry(Repository repo, RevTree aTree,
- RevTree bTree, FileHeader fileHeader) throws IOException {
- final FileMode oldMode = fileHeader.getOldMode();
- final FileMode newMode = fileHeader.getNewMode();
-
- if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
+ if (a == null) {
+ return new PatchList(a, b, computeIntraline, new PatchListEntry[0]);
+ }
- if (aTree == null // want combined diff
- || fileHeader.getPatchType() != PatchType.UNIFIED
- || fileHeader.getHunks().isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
+ RevTree aTree = rw.parseTree(a);
+ RevTree bTree = b.getTree();
+
+ final TreeWalk walk = new TreeWalk(repo);
+ walk.reset();
+ walk.setRecursive(true);
+ walk.addTree(aTree);
+ walk.addTree(bTree);
+ walk.setFilter(TreeFilter.ANY_DIFF);
+
+ DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
+ df.setRepository(repo);
+ switch (key.getWhitespace()) {
+ case IGNORE_ALL_SPACE:
+ df.setRawTextFactory(RawTextIgnoreAllWhitespace.FACTORY);
+ break;
+ case IGNORE_NONE:
+ df.setRawTextFactory(RawText.FACTORY);
+ break;
+ case IGNORE_SPACE_AT_EOL:
+ df.setRawTextFactory(RawTextIgnoreTrailingWhitespace.FACTORY);
+ break;
+ case IGNORE_SPACE_CHANGE:
+ df.setRawTextFactory(RawTextIgnoreWhitespaceChange.FACTORY);
+ break;
+ }
- List<Edit> edits = fileHeader.toEditList();
- if (edits.isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
- if (!computeIntraline) {
- return new PatchListEntry(fileHeader, edits);
- }
+ RenameDetector rd = new RenameDetector(repo);
+ rd.addAll(DiffEntry.scan(walk));
+ List<DiffEntry> diffEntries = rd.compute();
- switch (fileHeader.getChangeType()) {
- case ADD:
- case DELETE:
- return new PatchListEntry(fileHeader, edits);
+ final int cnt = diffEntries.size();
+ final PatchListEntry[] entries = new PatchListEntry[cnt];
+ for (int i = 0; i < cnt; i++) {
+ FileHeader fh = df.createFileHeader(diffEntries.get(i));
+ entries[i] = newEntry(repo, aTree, bTree, fh);
+ }
+ return new PatchList(a, b, computeIntraline, entries);
}
- Text aContent = null;
- Text bContent = null;
-
- for (int i = 0; i < edits.size(); i++) {
- Edit e = edits.get(i);
+ private PatchListEntry newEntry(Repository repo, RevTree aTree,
+ RevTree bTree, FileHeader fileHeader) throws IOException {
+ final FileMode oldMode = fileHeader.getOldMode();
+ final FileMode newMode = fileHeader.getNewMode();
- if (e.getType() == Edit.Type.REPLACE) {
- if (aContent == null) {
- edits = new ArrayList<Edit>(edits);
- aContent = read(repo, fileHeader.getOldName(), aTree);
- bContent = read(repo, fileHeader.getNewName(), bTree);
- combineLineEdits(edits, aContent, bContent);
- i = -1; // restart the entire scan after combining lines.
- continue;
- }
+ if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
- CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
- CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
+ if (aTree == null // want combined diff
+ || fileHeader.getPatchType() != PatchType.UNIFIED
+ || fileHeader.getHunks().isEmpty()) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
- List<Edit> wordEdits = new MyersDiff(a, b).getEdits();
+ List<Edit> edits = fileHeader.toEditList();
+ if (edits.isEmpty()) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
+ if (!computeIntraline) {
+ return new PatchListEntry(fileHeader, edits);
+ }
- // Combine edits that are really close together. If they are
- // just a few characters apart we tend to get better results
- // by joining them together and taking the whole span.
- //
- for (int j = 0; j < wordEdits.size() - 1;) {
- Edit c = wordEdits.get(j);
- Edit n = wordEdits.get(j + 1);
+ switch (fileHeader.getChangeType()) {
+ case ADD:
+ case DELETE:
+ return new PatchListEntry(fileHeader, edits);
+ }
- if (n.getBeginA() - c.getEndA() <= 5
- || n.getBeginB() - c.getEndB() <= 5) {
- int ab = c.getBeginA();
- int ae = n.getEndA();
+ Text aContent = null;
+ Text bContent = null;
- int bb = c.getBeginB();
- int be = n.getEndB();
+ for (int i = 0; i < edits.size(); i++) {
+ Edit e = edits.get(i);
- if (canCoalesce(a, c.getEndA(), n.getBeginA())
- && canCoalesce(b, c.getEndB(), n.getBeginB())) {
- wordEdits.set(j, new Edit(ab, ae, bb, be));
- wordEdits.remove(j + 1);
- continue;
- }
+ if (e.getType() == Edit.Type.REPLACE) {
+ if (aContent == null) {
+ edits = new ArrayList<Edit>(edits);
+ aContent = read(repo, fileHeader.getOldName(), aTree);
+ bContent = read(repo, fileHeader.getNewName(), bTree);
+ combineLineEdits(edits, aContent, bContent);
+ i = -1; // restart the entire scan after combining lines.
+ continue;
}
- j++;
- }
+ CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
+ CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
- // Apply some simple rules to fix up some of the edits. Our
- // logic above, along with our per-character difference tends
- // to produce some crazy stuff.
- //
- for (int j = 0; j < wordEdits.size(); j++) {
- Edit c = wordEdits.get(j);
- int ab = c.getBeginA();
- int ae = c.getEndA();
+ List<Edit> wordEdits = new MyersDiff(a, b).getEdits();
- int bb = c.getBeginB();
- int be = c.getEndB();
-
- // Sometimes the diff generator produces an INSERT or DELETE
- // right up against a REPLACE, but we only find this after
- // we've also played some shifting games on the prior edit.
- // If that happened to us, coalesce them together so we can
- // correct this mess for the user. If we don't we wind up
- // with silly stuff like "es" -> "es = Addresses".
+ // Combine edits that are really close together. If they are
+ // just a few characters apart we tend to get better results
+ // by joining them together and taking the whole span.
//
- if (1 < j) {
- Edit p = wordEdits.get(j - 1);
- if (p.getEndA() == ab || p.getEndB() == bb) {
- if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
- ab = p.getBeginA();
- }
- if (p.getEndB() == bb && p.getBeginB() < p.getEndB()) {
- bb = p.getBeginB();
+ for (int j = 0; j < wordEdits.size() - 1;) {
+ Edit c = wordEdits.get(j);
+ Edit n = wordEdits.get(j + 1);
+
+ if (n.getBeginA() - c.getEndA() <= 5
+ || n.getBeginB() - c.getEndB() <= 5) {
+ int ab = c.getBeginA();
+ int ae = n.getEndA();
+
+ int bb = c.getBeginB();
+ int be = n.getEndB();
+
+ if (canCoalesce(a, c.getEndA(), n.getBeginA())
+ && canCoalesce(b, c.getEndB(), n.getBeginB())) {
+ wordEdits.set(j, new Edit(ab, ae, bb, be));
+ wordEdits.remove(j + 1);
+ continue;
}
- wordEdits.remove(--j);
}
- }
- // We sometimes collapsed an edit together in a strange way,
- // such that the edges of each text is identical. Fix by
- // by dropping out that incorrectly replaced region.
- //
- while (ab < ae && bb < be && a.equals(ab, b, bb)) {
- ab++;
- bb++;
- }
- while (ab < ae && bb < be && a.equals(ae - 1, b, be - 1)) {
- ae--;
- be--;
+ j++;
}
- // The leading part of an edit and its trailing part in the same
- // text might be identical. Slide down that edit and use the tail
- // rather than the leading bit. If however the edit is only on a
- // whitespace block try to shift it to the left margin, assuming
- // that it is an indentation change.
+ // Apply some simple rules to fix up some of the edits. Our
+ // logic above, along with our per-character difference tends
+ // to produce some crazy stuff.
//
- boolean aShift = true;
- if (ab < ae && isOnlyWhitespace(a, ab, ae)) {
- int lf = findLF(wordEdits, j, a, ab);
- if (lf < ab && a.charAt(lf) == '\n') {
- int nb = lf + 1;
- int p = 0;
- while (p < ae - ab) {
- if (a.equals(ab + p, a, ab + p))
- p++;
- else
- break;
- }
- if (p == ae - ab) {
- ab = nb;
- ae = nb + p;
- aShift = false;
+ for (int j = 0; j < wordEdits.size(); j++) {
+ Edit c = wordEdits.get(j);
+ int ab = c.getBeginA();
+ int ae = c.getEndA();
+
+ int bb = c.getBeginB();
+ int be = c.getEndB();
+
+ // Sometimes the diff generator produces an INSERT or DELETE
+ // right up against a REPLACE, but we only find this after
+ // we've also played some shifting games on the prior edit.
+ // If that happened to us, coalesce them together so we can
+ // correct this mess for the user. If we don't we wind up
+ // with silly stuff like "es" -> "es = Addresses".
+ //
+ if (1 < j) {
+ Edit p = wordEdits.get(j - 1);
+ if (p.getEndA() == ab || p.getEndB() == bb) {
+ if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
+ ab = p.getBeginA();
+ }
+ if (p.getEndB() == bb && p.getBeginB() < p.getEndB()) {
+ bb = p.getBeginB();
+ }
+ wordEdits.remove(--j);
}
}
- }
- if (aShift) {
- while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
- && a.equals(ab - 1, a, ae - 1)) {
- ab--;
+
+ // We sometimes collapsed an edit together in a strange way,
+ // such that the edges of each text is identical. Fix by
+ // by dropping out that incorrectly replaced region.
+ //
+ while (ab < ae && bb < be && a.equals(ab, b, bb)) {
+ ab++;
+ bb++;
+ }
+ while (ab < ae && bb < be && a.equals(ae - 1, b, be - 1)) {
ae--;
+ be--;
}
- if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
- while (ab < ae && ae < a.size() && a.equals(ab, a, ae)) {
- ab++;
- ae++;
- if (a.charAt(ae - 1) == '\n') {
- break;
+
+ // The leading part of an edit and its trailing part in the same
+ // text might be identical. Slide down that edit and use the tail
+ // rather than the leading bit. If however the edit is only on a
+ // whitespace block try to shift it to the left margin, assuming
+ // that it is an indentation change.
+ //
+ boolean aShift = true;
+ if (ab < ae && isOnlyWhitespace(a, ab, ae)) {
+ int lf = findLF(wordEdits, j, a, ab);
+ if (lf < ab && a.charAt(lf) == '\n') {
+ int nb = lf + 1;
+ int p = 0;
+ while (p < ae - ab) {
+ if (a.equals(ab + p, a, ab + p))
+ p++;
+ else
+ break;
+ }
+ if (p == ae - ab) {
+ ab = nb;
+ ae = nb + p;
+ aShift = false;
}
}
}
- }
-
- boolean bShift = true;
- if (bb < be && isOnlyWhitespace(b, bb, be)) {
- int lf = findLF(wordEdits, j, b, bb);
- if (lf < bb && b.charAt(lf) == '\n') {
- int nb = lf + 1;
- int p = 0;
- while (p < be - bb) {
- if (b.equals(bb + p, b, bb + p))
- p++;
- else
- break;
+ if (aShift) {
+ while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
+ && a.equals(ab - 1, a, ae - 1)) {
+ ab--;
+ ae--;
}
- if (p == be - bb) {
- bb = nb;
- be = nb + p;
- bShift = false;
+ if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
+ while (ab < ae && ae < a.size() && a.equals(ab, a, ae)) {
+ ab++;
+ ae++;
+ if (a.charAt(ae - 1) == '\n') {
+ break;
+ }
+ }
}
}
- }
- if (bShift) {
- while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
- && b.equals(bb - 1, b, be - 1)) {
- bb--;
- be--;
+
+ boolean bShift = true;
+ if (bb < be && isOnlyWhitespace(b, bb, be)) {
+ int lf = findLF(wordEdits, j, b, bb);
+ if (lf < bb && b.charAt(lf) == '\n') {
+ int nb = lf + 1;
+ int p = 0;
+ while (p < be - bb) {
+ if (b.equals(bb + p, b, bb + p))
+ p++;
+ else
+ break;
+ }
+ if (p == be - bb) {
+ bb = nb;
+ be = nb + p;
+ bShift = false;
+ }
+ }
}
- if (!b.isLineStart(bb) || !b.contains(bb, be, '\n')) {
- while (bb < be && be < b.size() && b.equals(bb, b, be)) {
- bb++;
- be++;
- if (b.charAt(be - 1) == '\n') {
- break;
+ if (bShift) {
+ while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
+ && b.equals(bb - 1, b, be - 1)) {
+ bb--;
+ be--;
+ }
+ if (!b.isLineStart(bb) || !b.contains(bb, be, '\n')) {
+ while (bb < be && be < b.size() && b.equals(bb, b, be)) {
+ bb++;
+ be++;
+ if (b.charAt(be - 1) == '\n') {
+ break;
+ }
}
}
}
- }
- // If most of a line was modified except the LF was common, make
- // the LF part of the modification region. This is easier to read.
- //
- if (ab < ae //
- && (ab == 0 || a.charAt(ab - 1) == '\n') //
- && ae < a.size() && a.charAt(ae) == '\n') {
- ae++;
- }
- if (bb < be //
- && (bb == 0 || b.charAt(bb - 1) == '\n') //
- && be < b.size() && b.charAt(be) == '\n') {
- be++;
+ // If most of a line was modified except the LF was common, make
+ // the LF part of the modification region. This is easier to read.
+ //
+ if (ab < ae //
+ && (ab == 0 || a.charAt(ab - 1) == '\n') //
+ && ae < a.size() && a.charAt(ae) == '\n') {
+ ae++;
+ }
+ if (bb < be //
+ && (bb == 0 || b.charAt(bb - 1) == '\n') //
+ && be < b.size() && b.charAt(be) == '\n') {
+ be++;
+ }
+
+ wordEdits.set(j, new Edit(ab, ae, bb, be));
}
- wordEdits.set(j, new Edit(ab, ae, bb, be));
+ edits.set(i, new ReplaceEdit(e, wordEdits));
}
-
- edits.set(i, new ReplaceEdit(e, wordEdits));
}
+
+ return new PatchListEntry(fileHeader, edits);
}
- return new PatchListEntry(fileHeader, edits);
- }
+ private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
+ for (int j = 0; j < edits.size() - 1;) {
+ Edit c = edits.get(j);
+ Edit n = edits.get(j + 1);
- private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
- for (int j = 0; j < edits.size() - 1;) {
- Edit c = edits.get(j);
- Edit n = edits.get(j + 1);
-
- // Combine edits that are really close together. Right now our rule
- // is, coalesce two line edits which are only one line apart if that
- // common context line is either a "pointless line", or is identical
- // on both sides and starts a new block of code. These are mostly
- // block reindents to add or remove control flow operators.
- //
- final int ad = n.getBeginA() - c.getEndA();
- final int bd = n.getBeginB() - c.getEndB();
- if ((1 <= ad && isBlankLineGap(a, c.getEndA(), n.getBeginA()))
- || (1 <= bd && isBlankLineGap(b, c.getEndB(), n.getBeginB()))
- || (ad == 1 && bd == 1 && isControlBlockStart(a, c.getEndA()))) {
- int ab = c.getBeginA();
- int ae = n.getEndA();
-
- int bb = c.getBeginB();
- int be = n.getEndB();
-
- edits.set(j, new Edit(ab, ae, bb, be));
- edits.remove(j + 1);
- continue;
- }
+ // Combine edits that are really close together. Right now our rule
+ // is, coalesce two line edits which are only one line apart if that
+ // common context line is either a "pointless line", or is identical
+ // on both sides and starts a new block of code. These are mostly
+ // block reindents to add or remove control flow operators.
+ //
+ final int ad = n.getBeginA() - c.getEndA();
+ final int bd = n.getBeginB() - c.getEndB();
+ if ((1 <= ad && isBlankLineGap(a, c.getEndA(), n.getBeginA()))
+ || (1 <= bd && isBlankLineGap(b, c.getEndB(), n.getBeginB()))
+ || (ad == 1 && bd == 1 && isControlBlockStart(a, c.getEndA()))) {
+ int ab = c.getBeginA();
+ int ae = n.getEndA();
- j++;
- }
- }
+ int bb = c.getBeginB();
+ int be = n.getEndB();
- private static boolean isBlankLineGap(Text a, int b, int e) {
- for (; b < e; b++) {
- if (!BLANK_LINE_RE.matcher(a.getLine(b)).matches()) {
- return false;
+ edits.set(j, new Edit(ab, ae, bb, be));
+ edits.remove(j + 1);
+ continue;
+ }
+
+ j++;
}
}
- return true;
- }
- private static boolean isControlBlockStart(Text a, int idx) {
- final String l = a.getLine(idx);
- return CONTROL_BLOCK_START_RE.matcher(l).find();
- }
-
- private static boolean canCoalesce(CharText a, int b, int e) {
- while (b < e) {
- if (a.charAt(b++) == '\n') {
- return false;
+ private static boolean isBlankLineGap(Text a, int b, int e) {
+ for (; b < e; b++) {
+ if (!BLANK_LINE_RE.matcher(a.getLine(b)).matches()) {
+ return false;
+ }
}
+ return true;
}
- return true;
- }
- private static int findLF(List<Edit> edits, int j, CharText t, int b) {
- int lf = b;
- int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
- while (limit < lf && t.charAt(lf) != '\n') {
- lf--;
+ private static boolean isControlBlockStart(Text a, int idx) {
+ final String l = a.getLine(idx);
+ return CONTROL_BLOCK_START_RE.matcher(l).find();
}
- return lf;
- }
- private static boolean isOnlyWhitespace(CharText t, final int b, final int e) {
- for (int c = b; c < e; c++) {
- if (!Character.isWhitespace(t.charAt(c))) {
- return false;
+ private static boolean canCoalesce(CharText a, int b, int e) {
+ while (b < e) {
+ if (a.charAt(b++) == '\n') {
+ return false;
+ }
}
+ return true;
}
- return b < e;
- }
- private static Text read(Repository repo, String path, RevTree tree)
- throws IOException {
- TreeWalk tw = TreeWalk.forPath(repo, path, tree);
- if (tw == null || tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) {
- return Text.EMPTY;
- }
- ObjectLoader ldr = repo.openObject(tw.getObjectId(0));
- if (ldr == null) {
- return Text.EMPTY;
+ private static int findLF(List<Edit> edits, int j, CharText t, int b) {
+ int lf = b;
+ int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
+ while (limit < lf && t.charAt(lf) != '\n') {
+ lf--;
+ }
+ return lf;
}
- return new Text(ldr.getCachedBytes());
- }
- private static AnyObjectId aFor(final PatchListKey key,
- final Repository repo, final RevCommit b) throws IOException {
- if (key.getOldId() != null) {
- return key.getOldId();
+ private static boolean isOnlyWhitespace(CharText t, final int b, final int e) {
+ for (int c = b; c < e; c++) {
+ if (!Character.isWhitespace(t.charAt(c))) {
+ return false;
+ }
+ }
+ return b < e;
}
- switch (b.getParentCount()) {
- case 0:
- return emptyTree(repo);
- case 1:
- return b.getParent(0);
- default:
- // merge commit, return null to force combined diff behavior
- return null;
+ private static Text read(Repository repo, String path, RevTree tree)
+ throws IOException {
+ TreeWalk tw = TreeWalk.forPath(repo, path, tree);
+ if (tw == null || tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) {
+ return Text.EMPTY;
+ }
+ ObjectLoader ldr = repo.openObject(tw.getObjectId(0));
+ if (ldr == null) {
+ return Text.EMPTY;
+ }
+ return new Text(ldr.getCachedBytes());
}
- }
- private static Process exec(final Repository repo, final List<String> args)
- throws IOException {
- final String[] argv = args.toArray(new String[args.size()]);
- return Runtime.getRuntime().exec(argv, null, repo.getDirectory());
- }
+ private static AnyObjectId aFor(final PatchListKey key,
+ final Repository repo, final RevCommit b) throws IOException {
+ if (key.getOldId() != null) {
+ return key.getOldId();
+ }
- private static ObjectId emptyTree(final Repository repo) throws IOException {
- return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
+ switch (b.getParentCount()) {
+ case 0:
+ return emptyTree(repo);
+ case 1:
+ return b.getParent(0);
+ default:
+ // merge commit, return null to force combined diff behavior
+ return null;
+ }
+ }
+
+ private static ObjectId emptyTree(final Repository repo) throws IOException {
+ return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
+ }
}
}
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 35dd5710c3..26ee17bdac 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
@@ -21,8 +21,8 @@ import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeCanBeNull;
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
-import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
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/PublishComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
index 6fdb06eba8..f3e28902e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
@@ -284,7 +284,6 @@ public class PublishComments implements Callable<VoidResult> {
cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId));
cm.setChangeMessage(message);
cm.setPatchLineComments(drafts);
- cm.setReviewDb(db);
cm.send();
} catch (EmailException e) {
log.error("Cannot send comments by email for patch set " + patchSetId, e);
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 0ee2c9ee0e..c9435d3151 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
@@ -16,6 +16,7 @@ package com.google.gerrit.server.project;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
@@ -24,7 +25,6 @@ import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.OrmException;
@@ -37,6 +37,25 @@ import java.util.List;
/** Access control management for a user accessing a single change. */
public class ChangeControl {
+ public static class GenericFactory {
+ private final ProjectControl.GenericFactory projectControl;
+
+ @Inject
+ GenericFactory(ProjectControl.GenericFactory p) {
+ projectControl = p;
+ }
+
+ public ChangeControl controlFor(Change change, CurrentUser user)
+ throws NoSuchChangeException {
+ final Project.NameKey projectKey = change.getProject();
+ try {
+ return projectControl.controlFor(projectKey, user).controlFor(change);
+ } catch (NoSuchProjectException e) {
+ throw new NoSuchChangeException(change.getId(), e);
+ }
+ }
+ }
+
public static class Factory {
private final ProjectControl.Factory projectControl;
private final Provider<ReviewDb> db;
@@ -140,6 +159,10 @@ public class ChangeControl {
;
}
+ public short normalize(ApprovalCategory.Id category, short score) {
+ return getRefControl().normalize(category, score);
+ }
+
/** Can this user add a patch set to this change? */
public boolean canAddPatchSet() {
return getRefControl().canUpload();
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 740a2a621c..48eef871ff 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
@@ -19,9 +19,7 @@ import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
-import com.google.gerrit.server.config.WildProjectName;
-import com.google.gwtorm.client.OrmException;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -29,7 +27,6 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -43,59 +40,20 @@ public class ProjectCacheImpl implements ProjectCache {
@Override
protected void configure() {
final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
- new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
- core(type, CACHE_NAME);
+ new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
+ core(type, CACHE_NAME).populateWith(Loader.class);
bind(ProjectCacheImpl.class);
bind(ProjectCache.class).to(ProjectCacheImpl.class);
}
};
}
- private final ProjectState.Factory projectStateFactory;
- private final SchemaFactory<ReviewDb> schema;
- private final SelfPopulatingCache<Project.NameKey, ProjectState> byName;
+ private final Cache<Project.NameKey, ProjectState> byName;
@Inject
- ProjectCacheImpl(final ProjectState.Factory psf,
- final SchemaFactory<ReviewDb> sf,
- @WildProjectName final Project.NameKey wp,
+ ProjectCacheImpl(
@Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName) {
- projectStateFactory = psf;
- schema = sf;
-
- this.byName =
- new SelfPopulatingCache<Project.NameKey, ProjectState>(byName) {
- @Override
- public ProjectState createEntry(final Project.NameKey key)
- throws Exception {
- return lookup(key);
- }
- };
- }
-
- /**
- * Lookup for a state of a specified project on database
- *
- * @param key the project name key
- * @return the project state
- * @throws OrmException
- */
- private ProjectState lookup(final Project.NameKey key) throws OrmException {
- final ReviewDb db = schema.open();
- try {
- final Project p = db.projects().get(key);
- if (p == null) {
- return null;
- }
-
- final Collection<RefRight> rights =
- Collections.unmodifiableCollection(db.refRights().byProject(
- p.getNameKey()).toList());
-
- return projectStateFactory.create(p, rights);
- } finally {
- db.close();
- }
+ this.byName = byName;
}
/**
@@ -119,4 +77,34 @@ public class ProjectCacheImpl implements ProjectCache {
public void evictAll() {
byName.removeAll();
}
+
+ static class Loader extends EntryCreator<Project.NameKey, ProjectState> {
+ private final ProjectState.Factory projectStateFactory;
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ Loader(ProjectState.Factory psf, SchemaFactory<ReviewDb> sf) {
+ projectStateFactory = psf;
+ schema = sf;
+ }
+
+ @Override
+ public ProjectState createEntry(Project.NameKey key) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ final Project p = db.projects().get(key);
+ if (p == null) {
+ return null;
+ }
+
+ final Collection<RefRight> rights =
+ Collections.unmodifiableCollection(db.refRights().byProject(
+ p.getNameKey()).toList());
+
+ return projectStateFactory.create(p, rights);
+ } finally {
+ db.close();
+ }
+ }
+ }
}
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 24fe3f0547..67f543b947 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
@@ -33,6 +33,24 @@ public class ProjectControl {
public static final int VISIBLE = 1 << 0;
public static final int OWNER = 1 << 1;
+ public static class GenericFactory {
+ private final ProjectCache projectCache;
+
+ @Inject
+ GenericFactory(final ProjectCache pc) {
+ projectCache = pc;
+ }
+
+ public ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
+ throws NoSuchProjectException {
+ final ProjectState p = projectCache.get(nameKey);
+ if (p == null) {
+ throw new NoSuchProjectException(nameKey);
+ }
+ return p.controlFor(user);
+ }
+ }
+
public static class Factory {
private final ProjectCache projectCache;
private final Provider<CurrentUser> user;
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 32fa01cbd4..66ea6e3bbe 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
@@ -28,12 +28,16 @@ import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANNOTATED;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_SIGNED;
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
+import com.google.gerrit.common.data.ParamertizedString;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import dk.brics.automaton.RegExp;
+
+import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
@@ -45,10 +49,13 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
+import java.util.regex.Pattern;
/** Manages access control for Git references (aka branches, tags). */
@@ -59,9 +66,17 @@ public class RefControl {
private Boolean canForgeAuthor;
private Boolean canForgeCommitter;
- RefControl(final ProjectControl projectControl, final String refName) {
+ RefControl(final ProjectControl projectControl, String ref) {
+ if (isRE(ref)) {
+ ref = shortestExample(ref);
+
+ } else if (ref.endsWith("/*")) {
+ ref = ref.substring(0, ref.length() - 1);
+
+ }
+
this.projectControl = projectControl;
- this.refName = refName;
+ this.refName = ref;
}
public String getRefName() {
@@ -94,11 +109,12 @@ public class RefControl {
// calls us to find out if there is ownership of all references in
// order to determine project level ownership.
//
- if (!RefRight.ALL.equals(getRefName()) && getProjectControl().isOwner()) {
- return true;
+ if (getRefName().equals(
+ RefRight.ALL.substring(0, RefRight.ALL.length() - 1))) {
+ return getCurrentUser().isAdministrator();
+ } else {
+ return getProjectControl().isOwner();
}
-
- return false;
}
/** Can this user see this reference exists? */
@@ -233,6 +249,24 @@ public class RefControl {
return canPerform(FORGE_IDENTITY, FORGE_SERVER);
}
+ public short normalize(ApprovalCategory.Id category, short score) {
+ short minAllowed = 0, maxAllowed = 0;
+ for (RefRight r : getApplicableRights(category)) {
+ if (getCurrentUser().getEffectiveGroups().contains(r.getAccountGroupId())) {
+ minAllowed = (short) Math.min(minAllowed, r.getMinValue());
+ maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue());
+ }
+ }
+
+ if (score < minAllowed) {
+ score = minAllowed;
+ }
+ if (score > maxAllowed) {
+ score = maxAllowed;
+ }
+ return score;
+ }
+
/**
* Convenience holder class used to map a ref pattern to the list of
* {@code RefRight}s that use it in the database.
@@ -302,19 +336,95 @@ public class RefControl {
return val >= level;
}
- public static final Comparator<String> DESCENDING_SORT =
+ /**
+ * Order the Ref Pattern by the most specific. This sort is done by:
+ * <ul>
+ * <li>1 - The minor value of Levenshtein string distance between the branch
+ * name and the regex string shortest example. A shorter distance is a more
+ * specific match.
+ * <li>2 - Finites first, infinities after.
+ * <li>3 - Number of transitions.
+ * <li>4 - Length of the expression text.
+ * </ul>
+ *
+ * Levenshtein distance is a measure of the similarity between two strings.
+ * The distance is the number of deletions, insertions, or substitutions
+ * required to transform one string into another.
+ *
+ * For example, if given refs/heads/m* and refs/heads/*, the distances are 5
+ * and 6. It means that refs/heads/m* is more specific because it's closer to
+ * refs/heads/master than refs/heads/*.
+ *
+ * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the
+ * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more
+ * transitions, which after all turns it more specific.
+ */
+ private final Comparator<String> BY_MOST_SPECIFIC_SORT =
new Comparator<String>() {
+ public int compare(final String pattern1, final String pattern2) {
+ int cmp = distance(pattern2) - distance(pattern1);
+ if (cmp == 0) {
+ boolean p1_finite = finite(pattern1);
+ boolean p2_finite = finite(pattern2);
+
+ if (p1_finite && !p2_finite) {
+ cmp = -1;
+ } else if (!p1_finite && p2_finite) {
+ cmp = 1;
+ } else /* if (f1 == f2) */{
+ cmp = 0;
+ }
+ }
+ if (cmp == 0) {
+ cmp = transitions(pattern1) - transitions(pattern2);
+ }
+ if (cmp == 0) {
+ cmp = pattern2.length() - pattern1.length();
+ }
+ return cmp;
+ }
- @Override
- public int compare(String a, String b) {
- int aLength = a.length();
- int bLength = b.length();
- if (bLength == aLength) {
- return a.compareTo(b);
- }
- return bLength - aLength;
- }
- };
+ private int distance(String pattern) {
+ String example;
+ if (isRE(pattern)) {
+ example = shortestExample(pattern);
+
+ } else if (pattern.endsWith("/*")) {
+ example = pattern.substring(0, pattern.length() - 1) + '1';
+
+ } else if (pattern.equals(getRefName())) {
+ return 0;
+
+ } else {
+ return Math.max(pattern.length(), getRefName().length());
+ }
+ return StringUtils.getLevenshteinDistance(example, getRefName());
+ }
+
+ private boolean finite(String pattern) {
+ if (isRE(pattern)) {
+ return toRegExp(pattern).toAutomaton().isFinite();
+
+ } else if (pattern.endsWith("/*")) {
+ return false;
+
+ } else {
+ return true;
+ }
+ }
+
+ private int transitions(String pattern) {
+ if (isRE(pattern)) {
+ return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
+
+ } else if (pattern.endsWith("/*")) {
+ return pattern.length();
+
+ } else {
+ return pattern.length();
+ }
+ }
+ };
/**
* Sorts all given rights into a map, ordered by descending length of
@@ -338,10 +448,10 @@ public class RefControl {
* @param actionRights
* @return A sorted map keyed off the ref pattern of all rights.
*/
- private static SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
+ private SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
List<RefRight> actionRights) {
SortedMap<String, RefRightsForPattern> rights =
- new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
+ new TreeMap<String, RefRightsForPattern>(BY_MOST_SPECIFIC_SORT);
for (RefRight actionRight : actionRights) {
RefRightsForPattern patternRights =
rights.get(actionRight.getRefPattern());
@@ -391,7 +501,7 @@ public class RefControl {
private List<RefRight> filter(Collection<RefRight> all) {
List<RefRight> mine = new ArrayList<RefRight>(all.size());
for (RefRight right : all) {
- if (matches(getRefName(), right.getRefPattern())) {
+ if (matches(right.getRefPattern())) {
mine.add(right);
}
}
@@ -402,13 +512,70 @@ public class RefControl {
return projectControl.getProjectState();
}
- public static boolean matches(String refName, String refPattern) {
- if (refPattern.endsWith("/*")) {
+ private boolean matches(String refPattern) {
+ if (isTemplate(refPattern)) {
+ ParamertizedString template = new ParamertizedString(refPattern);
+ HashMap<String, String> p = new HashMap<String, String>();
+
+ if (getCurrentUser() instanceof IdentifiedUser) {
+ p.put("username", ((IdentifiedUser) getCurrentUser()).getUserName());
+ } else {
+ // Right now we only template the username. If not available
+ // this rule cannot be matched at all.
+ //
+ return false;
+ }
+
+ if (isRE(refPattern)) {
+ for (Map.Entry<String, String> ent : p.entrySet()) {
+ ent.setValue(escape(ent.getValue()));
+ }
+ }
+
+ refPattern = template.replace(p);
+ }
+
+ if (isRE(refPattern)) {
+ return Pattern.matches(refPattern, getRefName());
+
+ } else if (refPattern.endsWith("/*")) {
String prefix = refPattern.substring(0, refPattern.length() - 1);
- return refName.startsWith(prefix);
+ return getRefName().startsWith(prefix);
} else {
- return refName.equals(refPattern);
+ return getRefName().equals(refPattern);
+ }
+ }
+
+ private static boolean isTemplate(String refPattern) {
+ return 0 <= refPattern.indexOf("${");
+ }
+
+ private static String escape(String value) {
+ // Right now the only special character allowed in a
+ // variable value is a . in the username.
+ //
+ return value.replace(".", "\\.");
+ }
+
+ private static boolean isRE(String refPattern) {
+ return refPattern.startsWith(RefRight.REGEX_PREFIX);
+ }
+
+ public static String shortestExample(String pattern) {
+ if (isRE(pattern)) {
+ return toRegExp(pattern).toAutomaton().getShortestExample(true);
+ } else if (pattern.endsWith("/*")) {
+ return pattern.substring(0, pattern.length() - 1) + '1';
+ } else {
+ return pattern;
+ }
+ }
+
+ private static RegExp toRegExp(String refPattern) {
+ if (isRE(refPattern)) {
+ refPattern = refPattern.substring(1);
}
+ return new RegExp(refPattern);
}
}
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 5e95c18c2b..88ea4d3ebb 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
@@ -14,6 +14,8 @@
package com.google.gerrit.server.query;
+import com.google.gwtorm.client.OrmException;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -21,64 +23,90 @@ import java.util.Collections;
import java.util.List;
/** Requires all predicates to be true. */
-public final class AndPredicate extends Predicate {
- private final Predicate[] children;
+public class AndPredicate<T> extends Predicate<T> {
+ private final List<Predicate<T>> children;
+ private final int cost;
- public AndPredicate(final Predicate... that) {
+ protected AndPredicate(final Predicate<T>... that) {
this(Arrays.asList(that));
}
- public AndPredicate(final Collection<Predicate> that) {
- final ArrayList<Predicate> tmp = new ArrayList<Predicate>(that.size());
- for (Predicate p : that) {
- if (p instanceof AndPredicate) {
- tmp.addAll(p.getChildren());
+ protected AndPredicate(final Collection<? extends Predicate<T>> that) {
+ final ArrayList<Predicate<T>> t = new ArrayList<Predicate<T>>(that.size());
+ int c = 0;
+ for (Predicate<T> p : that) {
+ if (getClass() == p.getClass()) {
+ for (Predicate<T> gp : p.getChildren()) {
+ t.add(gp);
+ c += gp.getCost();
+ }
} else {
- tmp.add(p);
+ t.add(p);
+ c += p.getCost();
}
}
- if (tmp.size() < 2) {
+ if (t.size() < 2) {
throw new IllegalArgumentException("Need at least two predicates");
}
- children = new Predicate[tmp.size()];
- tmp.toArray(children);
+ children = t;
+ cost = c;
+ }
+
+ @Override
+ public final List<Predicate<T>> getChildren() {
+ return Collections.unmodifiableList(children);
+ }
+
+ @Override
+ public final int getChildCount() {
+ return children.size();
+ }
+
+ @Override
+ public final Predicate<T> getChild(final int i) {
+ return children.get(i);
}
@Override
- public List<Predicate> getChildren() {
- return Collections.unmodifiableList(Arrays.asList(children));
+ public Predicate<T> copy(final Collection<? extends Predicate<T>> children) {
+ return new AndPredicate<T>(children);
}
@Override
- public int getChildCount() {
- return children.length;
+ public boolean match(final T object) throws OrmException {
+ for (final Predicate<T> c : children) {
+ if (!c.match(object)) {
+ return false;
+ }
+ }
+ return true;
}
@Override
- public Predicate getChild(final int i) {
- return children[i];
+ public int getCost() {
+ return cost;
}
@Override
public int hashCode() {
- return children[0].hashCode() * 31 + children[1].hashCode();
+ return getChild(0).hashCode() * 31 + getChild(1).hashCode();
}
@Override
public boolean equals(final Object other) {
- return other instanceof AndPredicate
- && getChildren().equals(((AndPredicate) other).getChildren());
+ return getClass() == other.getClass()
+ && getChildren().equals(((Predicate<?>) other).getChildren());
}
@Override
- public String toString() {
+ public final String toString() {
final StringBuilder r = new StringBuilder();
r.append("(");
- for (int i = 0; i < children.length; i++) {
+ for (int i = 0; i < getChildCount(); i++) {
if (i != 0) {
r.append(" ");
}
- r.append(children[i]);
+ r.append(getChild(i));
}
r.append(")");
return r.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/ChangeQueryBuilder.java
deleted file mode 100644
index b12b56d8ae..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/ChangeQueryBuilder.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.server.query;
-
-import com.google.gerrit.reviewdb.RevId;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-
-/**
- * Parses a query string meant to be applied to change objects.
- * <p>
- * This class is thread-safe, and may be reused across threads to parse queries.
- */
-@Singleton
-public class ChangeQueryBuilder extends QueryBuilder {
- public static final String FIELD_CHANGE = "change";
- public static final String FIELD_COMMIT = "commit";
- public static final String FIELD_REVIEWER = "reviewer";
- public static final String FIELD_OWNER = "owner";
-
- private static final String CHANGE_RE = "^[1-9][0-9]*$";
- private static final String COMMIT_RE =
- "^([0-9a-fA-F]{4," + RevId.LEN + "})$";
-
- @Operator
- public Predicate change(final String value) {
- match(value, CHANGE_RE);
- return new OperatorPredicate(FIELD_CHANGE, value);
- }
-
- @Operator
- public Predicate commit(final String value) {
- final AbbreviatedObjectId id = AbbreviatedObjectId.fromString(value);
- return new ObjectIdPredicate(FIELD_COMMIT, id);
- }
-
- @Operator
- public Predicate owner(final String value) {
- return new OperatorPredicate(FIELD_OWNER, value);
- }
-
- @Operator
- public Predicate reviewer(final String value) {
- return new OperatorPredicate(FIELD_REVIEWER, value);
- }
-
- @Override
- protected Predicate defaultField(final String value)
- throws QueryParseException {
- if (value.matches(CHANGE_RE)) {
- return change(value);
-
- } else if (value.matches(COMMIT_RE)) {
- return commit(value);
-
- } else {
- throw error("Unsupported query:" + value);
- }
- }
-
- private static void match(String val, String re) {
- if (!val.matches(re)) {
- throw new IllegalArgumentException("Invalid value :" + val);
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
new file mode 100644
index 0000000000..b1806b43b4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
@@ -0,0 +1,54 @@
+// 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.server.query;
+
+/** Predicate to filter a field by matching integer value. */
+public abstract class IntPredicate<T> extends OperatorPredicate<T> {
+ private final int value;
+
+ public IntPredicate(final String name, final String value) {
+ super(name, value);
+ this.value = Integer.parseInt(value);
+ }
+
+ public IntPredicate(final String name, final int value) {
+ super(name, String.valueOf(value));
+ this.value = value;
+ }
+
+ public int intValue() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return getOperator().hashCode() * 31 + value;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (getClass() == other.getClass()) {
+ final IntPredicate<?> p = (IntPredicate<?>) other;
+ return getOperator().equals(p.getOperator())
+ && intValue() == p.intValue();
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getOperator() + ":" + getValue();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
index ddb03a6803..9e651a32e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
@@ -14,25 +14,57 @@
package com.google.gerrit.server.query;
+import com.google.gwtorm.client.OrmException;
+
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
/** Negates the result of another predicate. */
-public final class NotPredicate extends Predicate {
- private final Predicate that;
+public class NotPredicate<T> extends Predicate<T> {
+ private final Predicate<T> that;
- public NotPredicate(final Predicate that) {
+ protected NotPredicate(final Predicate<T> that) {
+ if (that instanceof NotPredicate) {
+ throw new IllegalArgumentException("Double negation unsupported");
+ }
this.that = that;
}
@Override
- public Predicate not() {
+ public final List<Predicate<T>> getChildren() {
+ return Collections.singletonList(that);
+ }
+
+ @Override
+ public final int getChildCount() {
+ return 1;
+ }
+
+ @Override
+ public final Predicate<T> getChild(final int i) {
+ if (i != 0) {
+ throw new ArrayIndexOutOfBoundsException(i);
+ }
return that;
}
@Override
- public List<Predicate> getChildren() {
- return Collections.singletonList(that);
+ public Predicate<T> copy(final Collection<? extends Predicate<T>> children) {
+ if (children.size() != 1) {
+ throw new IllegalArgumentException("Expected exactly one child");
+ }
+ return new NotPredicate<T>(children.iterator().next());
+ }
+
+ @Override
+ public boolean match(final T object) throws OrmException {
+ return !that.match(object);
+ }
+
+ @Override
+ public int getCost() {
+ return that.getCost();
}
@Override
@@ -42,12 +74,12 @@ public final class NotPredicate extends Predicate {
@Override
public boolean equals(final Object other) {
- return other instanceof NotPredicate
- && getChildren().equals(((Predicate) other).getChildren());
+ return getClass() == other.getClass()
+ && getChildren().equals(((Predicate<?>) other).getChildren());
}
@Override
- public String toString() {
+ public final String toString() {
return "-" + that.toString();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java
index bd9eeeaad7..0d68fea98a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java
@@ -14,12 +14,14 @@
package com.google.gerrit.server.query;
+import com.google.gerrit.server.query.OperatorPredicate;
+
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ObjectId;
/** Predicate for a field of {@link ObjectId}. */
-public final class ObjectIdPredicate extends OperatorPredicate {
+public abstract class ObjectIdPredicate<T> extends OperatorPredicate<T> {
private final AbbreviatedObjectId id;
public ObjectIdPredicate(final String name, final AbbreviatedObjectId id) {
@@ -47,7 +49,7 @@ public final class ObjectIdPredicate extends OperatorPredicate {
@Override
public boolean equals(Object other) {
if (other instanceof ObjectIdPredicate) {
- final ObjectIdPredicate p = (ObjectIdPredicate) other;
+ final ObjectIdPredicate<?> p = (ObjectIdPredicate<?>) other;
return getOperator().equals(p.getOperator()) && id.equals(p.id);
}
return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
index fbd6af1225..4c6e203f78 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
@@ -14,9 +14,11 @@
package com.google.gerrit.server.query;
+import java.util.Collection;
+
/** Predicate to filter a field by matching value. */
-public class OperatorPredicate extends Predicate {
+public abstract class OperatorPredicate<T> extends Predicate<T> {
private final String name;
private final String value;
@@ -34,6 +36,14 @@ public class OperatorPredicate extends Predicate {
}
@Override
+ public Predicate<T> copy(final Collection<? extends Predicate<T>> children) {
+ if (!children.isEmpty()) {
+ throw new IllegalArgumentException("Expected 0 children");
+ }
+ return this;
+ }
+
+ @Override
public int hashCode() {
return getOperator().hashCode() * 31 + getValue().hashCode();
}
@@ -41,7 +51,7 @@ public class OperatorPredicate extends Predicate {
@Override
public boolean equals(final Object other) {
if (getClass() == other.getClass()) {
- final OperatorPredicate p = (OperatorPredicate) other;
+ final OperatorPredicate<?> p = (OperatorPredicate<?>) other;
return getOperator().equals(p.getOperator())
&& getValue().equals(p.getValue());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
index a8b8d2b8d0..08f50f4968 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.query;
+import com.google.gwtorm.client.OrmException;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -21,64 +23,90 @@ import java.util.Collections;
import java.util.List;
/** Requires one predicate to be true. */
-public final class OrPredicate extends Predicate {
- private final Predicate[] children;
+public class OrPredicate<T> extends Predicate<T> {
+ private final List<Predicate<T>> children;
+ private final int cost;
- public OrPredicate(final Predicate... that) {
+ protected OrPredicate(final Predicate<T>... that) {
this(Arrays.asList(that));
}
- public OrPredicate(final Collection<Predicate> that) {
- final ArrayList<Predicate> tmp = new ArrayList<Predicate>(that.size());
- for (Predicate p : that) {
- if (p instanceof OrPredicate) {
- tmp.addAll(p.getChildren());
+ protected OrPredicate(final Collection<? extends Predicate<T>> that) {
+ final ArrayList<Predicate<T>> t = new ArrayList<Predicate<T>>(that.size());
+ int c = 0;
+ for (Predicate<T> p : that) {
+ if (getClass() == p.getClass()) {
+ for (Predicate<T> gp : p.getChildren()) {
+ t.add(gp);
+ c += gp.getCost();
+ }
} else {
- tmp.add(p);
+ t.add(p);
+ c += p.getCost();
}
}
- if (tmp.size() < 2) {
+ if (t.size() < 2) {
throw new IllegalArgumentException("Need at least two predicates");
}
- children = new Predicate[tmp.size()];
- tmp.toArray(children);
+ children = t;
+ cost = c;
+ }
+
+ @Override
+ public final List<Predicate<T>> getChildren() {
+ return Collections.unmodifiableList(children);
+ }
+
+ @Override
+ public final int getChildCount() {
+ return children.size();
+ }
+
+ @Override
+ public final Predicate<T> getChild(final int i) {
+ return children.get(i);
}
@Override
- public List<Predicate> getChildren() {
- return Collections.unmodifiableList(Arrays.asList(children));
+ public Predicate<T> copy(final Collection<? extends Predicate<T>> children) {
+ return new OrPredicate<T>(children);
}
@Override
- public int getChildCount() {
- return children.length;
+ public boolean match(final T object) throws OrmException {
+ for (final Predicate<T> c : children) {
+ if (c.match(object)) {
+ return true;
+ }
+ }
+ return false;
}
@Override
- public Predicate getChild(final int i) {
- return children[i];
+ public int getCost() {
+ return cost;
}
@Override
public int hashCode() {
- return children[0].hashCode() * 31 + children[1].hashCode();
+ return getChild(0).hashCode() * 31 + getChild(1).hashCode();
}
@Override
public boolean equals(final Object other) {
- return other instanceof OrPredicate
- && getChildren().equals(((OrPredicate) other).getChildren());
+ return getClass() == other.getClass()
+ && getChildren().equals(((Predicate<?>) other).getChildren());
}
@Override
- public String toString() {
+ public final String toString() {
final StringBuilder r = new StringBuilder();
r.append("(");
- for (int i = 0; i < children.length; i++) {
+ for (int i = 0; i < getChildCount(); i++) {
if (i != 0) {
r.append(" OR ");
}
- r.append(children[i]);
+ r.append(getChild(i));
}
r.append(")");
return r.toString();
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 70da79db8a..2455cbd7dc 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
@@ -14,6 +14,8 @@
package com.google.gerrit.server.query;
+import com.google.gwtorm.client.OrmException;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -21,44 +23,60 @@ import java.util.List;
/**
* An abstract predicate tree for any form of query.
* <p>
- * Implementations should be immutable, and therefore also be thread-safe. They
- * also should ensure their immutable promise by defensively copying any
- * structures which might be modified externally, but were passed into the
- * object's constructor.
+ * Implementations should be immutable, such that the meaning of a predicate
+ * never changes once constructed. They should ensure their immutable promise by
+ * defensively copying any structures which might be modified externally, but
+ * was passed into the object's constructor.
+ * <p>
+ * However, implementations <i>may</i> retain non-thread-safe caches internally,
+ * to speed up evaluation operations within the context of one thread's
+ * evaluation of the predicate. As a result, callers should assume predicates
+ * are not thread-safe, but that two predicate graphs produce the same results
+ * given the same inputs if they are {@link #equals(Object)}.
* <p>
* Predicates should support deep inspection whenever possible, so that generic
* algorithms can be written to operate against them. Predicates which contain
* other predicates should override {@link #getChildren()} to return the list of
* children nested within the predicate.
+ *
+ * @type <T> type of object the predicate can evaluate in memory.
*/
-public abstract class Predicate {
+public abstract class Predicate<T> {
/** Combine the passed predicates into a single AND node. */
- public static Predicate and(final Predicate... that) {
- return new AndPredicate(that);
+ public static <T> Predicate<T> and(final Predicate<T>... that) {
+ return new AndPredicate<T>(that);
}
/** Combine the passed predicates into a single AND node. */
- public static Predicate and(final Collection<Predicate> that) {
- return new AndPredicate(that);
+ public static <T> Predicate<T> and(
+ final Collection<? extends Predicate<T>> that) {
+ return new AndPredicate<T>(that);
}
/** Combine the passed predicates into a single OR node. */
- public static Predicate or(final Predicate... that) {
- return new OrPredicate(that);
+ public static <T> Predicate<T> or(final Predicate<T>... that) {
+ return new OrPredicate<T>(that);
}
/** Combine the passed predicates into a single OR node. */
- public static Predicate or(final Collection<Predicate> that) {
- return new OrPredicate(that);
+ public static <T> Predicate<T> or(
+ final Collection<? extends Predicate<T>> that) {
+ return new OrPredicate<T>(that);
}
- /** Invert the passed node; same as {@code that.not()}. */
- public static Predicate not(final Predicate that) {
- return that.not();
+ /** Invert the passed node. */
+ @SuppressWarnings("unchecked")
+ public static <T> Predicate<T> not(final Predicate<T> that) {
+ if (that instanceof NotPredicate) {
+ // Negate of a negate is the original predicate.
+ //
+ return that.getChild(0);
+ }
+ return new NotPredicate<T>(that);
}
/** Get the children of this predicate, if any. */
- public List<Predicate> getChildren() {
+ public List<Predicate<T>> getChildren() {
return Collections.emptyList();
}
@@ -68,14 +86,22 @@ public abstract class Predicate {
}
/** Same as {@code getChildren().get(i)} */
- public Predicate getChild(final int i) {
+ public Predicate<T> getChild(final int i) {
return getChildren().get(i);
}
- /** Obtain the inverse of this predicate. */
- public Predicate not() {
- return new NotPredicate(this);
- }
+ /** Create a copy of this predicate, with new children. */
+ public abstract Predicate<T> copy(Collection<? extends Predicate<T>> children);
+
+ /**
+ * Does this predicate match this object?
+ *
+ * @throws OrmException
+ */
+ public abstract boolean match(T object) throws OrmException;
+
+ /** @return a cost estimate to run this predicate, higher figures cost more. */
+ public abstract int getCost();
@Override
public abstract int hashCode();
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 da967f45ce..a0aeb58396 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
@@ -24,6 +24,7 @@ import static com.google.gerrit.server.query.QueryParser.FIELD_NAME;
import static com.google.gerrit.server.query.QueryParser.NOT;
import static com.google.gerrit.server.query.QueryParser.OR;
import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
+import static com.google.gerrit.server.query.QueryParser.VARIABLE_ASSIGN;
import org.antlr.runtime.tree.Tree;
@@ -34,15 +35,14 @@ import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* Base class to support writing parsers for query languages.
* <p>
- * This class is thread-safe, and may be reused across threads to parse queries,
- * so implementations of this class should also strive to be thread-safe.
- * <p>
* Subclasses may document their supported query operators by declaring public
* methods that perform the query conversion into a {@link Predicate}. For
* example, to support "is:starred", "is:unread", and nothing else, a subclass
@@ -70,33 +70,53 @@ import java.util.Map;
* <p>
* Subclasses may also declare a handler for values which appear without
* operator by overriding {@link #defaultField(String)}.
+ *
+ * @param <T> type of object the predicates can evaluate in memory.
*/
-public abstract class QueryBuilder {
- private final Map<String, OperatorFactory> opFactories =
- new HashMap<String, OperatorFactory>();
-
- protected QueryBuilder() {
- // Guess at the supported operators by scanning methods.
- //
- Class<?> c = getClass();
- while (c != QueryBuilder.class) {
- for (final Method method : c.getDeclaredMethods()) {
- if (method.getAnnotation(Operator.class) != null
- && Predicate.class.isAssignableFrom(method.getReturnType())
- && method.getParameterTypes().length == 1
- && method.getParameterTypes()[0] == String.class
- && (method.getModifiers() & Modifier.ABSTRACT) == 0
- && (method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) {
- final String name = method.getName().toLowerCase();
- if (!opFactories.containsKey(name)) {
- opFactories.put(name, new ReflectionFactory(name, method));
+public abstract class QueryBuilder<T> {
+ /**
+ * Defines the operators known by a QueryBuilder.
+ *
+ * This class is thread-safe and may be reused or cached.
+ *
+ * @param <T> type of object the predicates can evaluate in memory.
+ * @param <Q> type of the query builder subclass.
+ */
+ public static class Definition<T, Q extends QueryBuilder<T>> {
+ private final Map<String, OperatorFactory<T, Q>> opFactories =
+ new HashMap<String, OperatorFactory<T, Q>>();
+
+ public Definition(Class<Q> clazz) {
+ // Guess at the supported operators by scanning methods.
+ //
+ Class<?> c = clazz;
+ while (c != QueryBuilder.class) {
+ for (final Method method : c.getDeclaredMethods()) {
+ if (method.getAnnotation(Operator.class) != null
+ && Predicate.class.isAssignableFrom(method.getReturnType())
+ && method.getParameterTypes().length == 1
+ && method.getParameterTypes()[0] == String.class
+ && (method.getModifiers() & Modifier.ABSTRACT) == 0
+ && (method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) {
+ final String name = method.getName().toLowerCase();
+ if (!opFactories.containsKey(name)) {
+ opFactories.put(name, new ReflectionFactory<T, Q>(name, method));
+ }
}
}
+ c = c.getSuperclass();
}
- c = c.getSuperclass();
}
}
+ @SuppressWarnings("unchecked")
+ private final Map<String, OperatorFactory> opFactories;
+
+ @SuppressWarnings("unchecked")
+ protected QueryBuilder(Definition<T, ? extends QueryBuilder<T>> def) {
+ opFactories = (Map) def.opFactories;
+ }
+
/**
* Parse a user supplied query string into a predicate.
*
@@ -107,11 +127,11 @@ public abstract class QueryBuilder {
* due to an operator not being supported, or due to an invalid value
* being passed to a recognized operator.
*/
- public Predicate parse(final String query) throws QueryParseException {
+ public Predicate<T> parse(final String query) throws QueryParseException {
return toPredicate(QueryParser.parse(query));
}
- private Predicate toPredicate(final Tree r) throws QueryParseException,
+ private Predicate<T> toPredicate(final Tree r) throws QueryParseException,
IllegalArgumentException {
switch (r.getType()) {
case AND:
@@ -127,12 +147,26 @@ public abstract class QueryBuilder {
case FIELD_NAME:
return operator(r.getText(), onlyChildOf(r));
+ case VARIABLE_ASSIGN: {
+ final String var = r.getText();
+ final Tree opTree = onlyChildOf(r);
+ if (opTree.getType() == FIELD_NAME) {
+ final Tree val = onlyChildOf(opTree);
+ if (val.getType() == SINGLE_WORD && "*".equals(val.getText())) {
+ final String op = opTree.getText();
+ final WildPatternPredicate<T> pat = new WildPatternPredicate<T>(op);
+ return new VariablePredicate<T>(var, pat);
+ }
+ }
+ return new VariablePredicate<T>(var, toPredicate(opTree));
+ }
+
default:
throw error("Unsupported operator: " + r);
}
}
- private Predicate operator(final String name, final Tree val)
+ private Predicate<T> operator(final String name, final Tree val)
throws QueryParseException {
switch (val.getType()) {
// Expand multiple values, "foo:(a b c)", as though they were written
@@ -140,13 +174,13 @@ public abstract class QueryBuilder {
//
case AND:
case OR: {
- final Predicate[] p = new Predicate[val.getChildCount()];
- for (int i = 0; i < p.length; i++) {
+ List<Predicate<T>> p = new ArrayList<Predicate<T>>(val.getChildCount());
+ for (int i = 0; i < val.getChildCount(); i++) {
final Tree c = val.getChild(i);
if (c.getType() != DEFAULT_FIELD) {
throw error("Nested operator not expected: " + c);
}
- p[i] = operator(name, onlyChildOf(c));
+ p.add(operator(name, onlyChildOf(c)));
}
return val.getType() == AND ? and(p) : or(p);
}
@@ -163,16 +197,17 @@ public abstract class QueryBuilder {
}
}
- private Predicate operator(final String name, final String value)
+ @SuppressWarnings("unchecked")
+ private Predicate<T> operator(final String name, final String value)
throws QueryParseException {
final OperatorFactory f = opFactories.get(name);
if (f == null) {
throw error("Unsupported operator " + name + ":" + value);
}
- return f.create(value);
+ return f.create(this, value);
}
- private Predicate defaultField(final Tree r) throws QueryParseException {
+ private Predicate<T> defaultField(final Tree r) throws QueryParseException {
switch (r.getType()) {
case SINGLE_WORD:
case EXACT_PHRASE:
@@ -197,14 +232,65 @@ public abstract class QueryBuilder {
* @return predicate representing this value.
* @throws QueryParseException the parser does not recognize this value.
*/
- protected Predicate defaultField(final String value)
+ protected Predicate<T> defaultField(final String value)
throws QueryParseException {
throw error("Unsupported query:" + value);
}
- private Predicate[] children(final Tree r) throws QueryParseException,
+ /**
+ * Locate a predicate in the predicate tree.
+ *
+ * @param p the predicate to find.
+ * @param clazz type of the predicate instance.
+ * @return the predicate, null if not found.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends Predicate<T>> P find(Predicate<T> p, Class<P> clazz) {
+ if (clazz.isAssignableFrom(p.getClass())) {
+ return (P) p;
+ }
+
+ for (Predicate<T> c : p.getChildren()) {
+ P r = find(c, clazz);
+ if (r != null) {
+ return r;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Locate a predicate in the predicate tree.
+ *
+ * @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.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends OperatorPredicate<T>> P find(Predicate<T> p,
+ Class<P> clazz, String name) {
+ if (p instanceof OperatorPredicate
+ && ((OperatorPredicate) p).getOperator().equals(name)
+ && clazz.isAssignableFrom(p.getClass())) {
+ return (P) p;
+ }
+
+ for (Predicate<T> c : p.getChildren()) {
+ P r = find(c, clazz, name);
+ if (r != null) {
+ return r;
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Predicate<T>[] children(final Tree r) throws QueryParseException,
IllegalArgumentException {
- final Predicate[] p = new Predicate[r.getChildCount()];
+ final Predicate<T>[] p = new Predicate[r.getChildCount()];
for (int i = 0; i < p.length; i++) {
p[i] = toPredicate(r.getChild(i));
}
@@ -227,8 +313,8 @@ public abstract class QueryBuilder {
}
/** Converts a value string passed to an operator into a {@link Predicate}. */
- protected interface OperatorFactory {
- Predicate create(String value) throws QueryParseException;
+ protected interface OperatorFactory<T, Q extends QueryBuilder<T>> {
+ Predicate<T> create(Q builder, String value) throws QueryParseException;
}
/** Denotes a method which is a query operator. */
@@ -237,7 +323,8 @@ public abstract class QueryBuilder {
protected @interface Operator {
}
- private class ReflectionFactory implements OperatorFactory {
+ private static class ReflectionFactory<T, Q extends QueryBuilder<T>>
+ implements OperatorFactory<T, Q> {
private final String name;
private final Method method;
@@ -246,15 +333,20 @@ public abstract class QueryBuilder {
this.method = method;
}
+ @SuppressWarnings("unchecked")
@Override
- public Predicate create(final String value) throws QueryParseException {
+ public Predicate<T> create(Q builder, String value)
+ throws QueryParseException {
try {
- return (Predicate) method.invoke(QueryBuilder.this, value);
+ return (Predicate<T>) method.invoke(builder, value);
} catch (RuntimeException e) {
throw error("Error in operator " + name + ":" + value, e);
} catch (IllegalAccessException e) {
throw error("Error in operator " + name + ":" + value, e);
} catch (InvocationTargetException e) {
+ if (e.getCause() instanceof QueryParseException) {
+ throw (QueryParseException) e.getCause();
+ }
throw error("Error in operator " + name + ":" + value, e.getCause());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
new file mode 100644
index 0000000000..fea37e1a6d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
@@ -0,0 +1,518 @@
+// 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.server.query;
+
+import com.google.inject.name.Named;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Rewrites a Predicate tree by applying rewrite rules.
+ * <p>
+ * Subclasses may document their rewrite rules by declaring public methods with
+ * {@link Rewrite} annotations, such as:
+ *
+ * <pre>
+ * &#064;Rewrite(&quot;A=(owner:*) B=(status:*)&quot;)
+ * public Predicate r1_ownerStatus(@Named(&quot;A&quot;) OperatorPredicate owner,
+ * &#064;Named(&quot;B&quot;) OperatorPredicate status) {
+ * }
+ * </pre>
+ * <p>
+ * Rewrite methods are applied in order by declared name, so naming methods with
+ * a numeric prefix to ensure a specific ordering (if required) is suggested.
+ *
+ * @type <T> type of object the predicate can evaluate in memory.
+ */
+public abstract class QueryRewriter<T> {
+ /**
+ * Defines the rewrite rules known by a QueryRewriter.
+ *
+ * This class is thread-safe and may be reused or cached.
+ *
+ * @param <T> type of object the predicates can evaluate in memory.
+ * @param <R> type of the rewriter subclass.
+ */
+ public static class Definition<T, R extends QueryRewriter<T>> {
+ private final List<RewriteRule<T>> rewriteRules;
+
+ public Definition(Class<R> clazz, QueryBuilder<T> qb) {
+ rewriteRules = new ArrayList<RewriteRule<T>>();
+
+ Class<?> c = clazz;
+ while (c != QueryRewriter.class) {
+ final Method[] declared = c.getDeclaredMethods();
+ Arrays.sort(declared, new Comparator<Method>() {
+ @Override
+ public int compare(Method o1, Method o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+ for (Method m : declared) {
+ final Rewrite rp = m.getAnnotation(Rewrite.class);
+ if ((m.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT
+ && (m.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC
+ && rp != null) {
+ rewriteRules.add(new MethodRewrite(qb, rp.value(), m));
+ }
+ }
+ c = c.getSuperclass();
+ }
+ }
+ }
+
+ private final List<RewriteRule<T>> rewriteRules;
+
+ protected QueryRewriter(final Definition<T, ? extends QueryRewriter<T>> def) {
+ this.rewriteRules = def.rewriteRules;
+ }
+
+ /** Combine the passed predicates into a single AND node. */
+ public Predicate<T> and(Collection<? extends Predicate<T>> that) {
+ return Predicate.and(that);
+ }
+
+ /** Combine the passed predicates into a single AND node. */
+ public Predicate<T> and(Predicate<T>... that) {
+ return and(Arrays.asList(that));
+ }
+
+ /** Combine the passed predicates into a single OR node. */
+ public Predicate<T> or(Collection<? extends Predicate<T>> that) {
+ return Predicate.or(that);
+ }
+
+ /** Combine the passed predicates into a single OR node. */
+ public Predicate<T> or(Predicate<T>... that) {
+ return or(Arrays.asList(that));
+ }
+
+ /** Invert the passed node. */
+ public Predicate<T> not(Predicate<T> that) {
+ return Predicate.not(that);
+ }
+
+ /**
+ * Apply rewrites to a graph until it stops changing.
+ *
+ * @param in the graph to rewrite.
+ * @return the rewritten graph.
+ */
+ public Predicate<T> rewrite(Predicate<T> in) {
+ Predicate<T> old;
+ do {
+ old = in;
+ in = rewriteOne(in);
+
+ if (old.equals(in) && in.getChildCount() > 0) {
+ List<Predicate<T>> n = new ArrayList<Predicate<T>>(in.getChildCount());
+ for (Predicate<T> p : in.getChildren()) {
+ n.add(rewrite(p));
+ }
+ n = removeDuplicates(n);
+ if (n.size() == 1 && (isAND(in) || isOR(in))) {
+ in = n.get(0);
+ } else {
+ in = in.copy(n);
+ }
+ }
+
+ } while (!old.equals(in));
+ return replaceGenericNodes(in);
+ }
+
+ protected Predicate<T> replaceGenericNodes(final Predicate<T> in) {
+ if (in.getClass() == NotPredicate.class) {
+ return not(replaceGenericNodes(in.getChild(0)));
+
+ } else if (in.getClass() == AndPredicate.class) {
+ List<Predicate<T>> n = new ArrayList<Predicate<T>>(in.getChildCount());
+ for (Predicate<T> c : in.getChildren()) {
+ n.add(replaceGenericNodes(c));
+ }
+ return and(n);
+
+ } else if (in.getClass() == OrPredicate.class) {
+ List<Predicate<T>> n = new ArrayList<Predicate<T>>(in.getChildCount());
+ for (Predicate<T> c : in.getChildren()) {
+ n.add(replaceGenericNodes(c));
+ }
+ return or(n);
+
+ } else {
+ return in;
+ }
+ }
+
+ private Predicate<T> rewriteOne(Predicate<T> input) {
+ Predicate<T> best = null;
+ for (RewriteRule<T> r : rewriteRules) {
+ Predicate<T> n = r.rewrite(this, input);
+ if (n == null) {
+ continue;
+ }
+
+ if (!r.useBestCost()) {
+ return n;
+ }
+
+ if (best == null || n.getCost() < best.getCost()) {
+ best = n;
+ continue;
+ }
+ }
+ return best != null ? best : input;
+ }
+
+ private static class MatchResult<T> {
+ private static final MatchResult<?> FAIL = new MatchResult<Object>(null);
+ private static final MatchResult<?> OK = new MatchResult<Object>(null);
+
+ @SuppressWarnings("unchecked")
+ static <T> MatchResult<T> fail() {
+ return (MatchResult<T>) FAIL;
+ }
+
+ @SuppressWarnings("unchecked")
+ static <T> MatchResult<T> ok() {
+ return (MatchResult<T>) OK;
+ }
+
+ final Predicate<T> extra;
+
+ MatchResult(Predicate<T> extra) {
+ this.extra = extra;
+ }
+
+ boolean success() {
+ return this != FAIL;
+ }
+ }
+
+ private MatchResult<T> match(final Map<String, Predicate<T>> outVars,
+ final Predicate<T> pattern, final Predicate<T> actual) {
+ if (pattern instanceof VariablePredicate) {
+ final VariablePredicate<T> v = (VariablePredicate<T>) pattern;
+ final MatchResult<T> r = match(outVars, v.getChild(0), actual);
+ if (r.success()) {
+ Predicate<T> old = outVars.get(v.getName());
+ if (old == null) {
+ outVars.put(v.getName(), actual);
+ return r;
+ } else if (old.equals(actual)) {
+ return r;
+ } else {
+ return MatchResult.fail();
+ }
+ } else {
+ return MatchResult.fail();
+ }
+ }
+
+ final int cnt = pattern.getChildCount();
+ if ((isAND(pattern) && isAND(actual)) //
+ || (isOR(pattern) && isOR(actual)) //
+ || (isNOT(pattern) && isNOT(actual)) //
+ ) {
+ // Order doesn't actually matter here. That does make our logic quite
+ // a bit more complex as we need to consult each child at most once,
+ // but in any order.
+ //
+ final LinkedList<Predicate<T>> have = dup(actual);
+ final LinkedList<Predicate<T>> extra = new LinkedList<Predicate<T>>();
+ for (final Predicate<T> pat : pattern.getChildren()) {
+ boolean found = false;
+ for (final Iterator<Predicate<T>> i = have.iterator(); i.hasNext();) {
+ final MatchResult<T> r = match(outVars, pat, i.next());
+ if (r.success()) {
+ found = true;
+ i.remove();
+ if (r.extra != null) {
+ extra.add(r.extra);
+ }
+ break;
+ }
+ }
+ if (!found) {
+ return MatchResult.fail();
+ }
+ }
+ have.addAll(extra);
+ switch (have.size()) {
+ case 0:
+ return MatchResult.ok();
+ case 1:
+ if (isNOT(actual)) {
+ return new MatchResult<T>(actual.copy(have));
+ }
+ return new MatchResult<T>(have.get(0));
+ default:
+ return new MatchResult<T>(actual.copy(have));
+ }
+
+ } else if (pattern.equals(actual)) {
+ return MatchResult.ok();
+
+ } else if (pattern instanceof WildPatternPredicate
+ && actual instanceof OperatorPredicate
+ && ((OperatorPredicate<T>) pattern).getOperator().equals(
+ ((OperatorPredicate<T>) actual).getOperator())) {
+ return MatchResult.ok();
+
+ } else {
+ return MatchResult.fail();
+ }
+ }
+
+ private static <T> LinkedList<Predicate<T>> dup(final Predicate<T> actual) {
+ return new LinkedList<Predicate<T>>(actual.getChildren());
+ }
+
+ /**
+ * Denotes a method which wants to replace a predicate expression.
+ * <p>
+ * This annotation must be applied to a public method which returns
+ * {@link Predicate}. The arguments of the method should {@link Predicate}, or
+ * any subclass of it. The annotation value is a query language string which
+ * describes the subtree this rewrite applies to. Method arguments should be
+ * named with a {@link Named} annotation, and the same names should be used in
+ * the query.
+ * <p>
+ * For example:
+ *
+ * <pre>
+ * &#064;Rewrite(&quot;A=(owner:*) B=(status:*)&quot;)
+ * public Predicate ownerStatus(@Named(&quot;A&quot;) OperatorPredicate owner,
+ * &#064;Named(&quot;B&quot;) OperatorPredicate status) {
+ * }
+ * </pre>
+ *
+ * matches an AND Predicate with at least two children, one being an operator
+ * predicate called "owner" and the other being an operator predicate called
+ * "status". The variables in the query are matched by name against the
+ * parameters.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ protected @interface Rewrite {
+ String value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ protected @interface NoCostComputation {
+ }
+
+ /** Applies a rewrite rule to a Predicate. */
+ protected interface RewriteRule<T> {
+ /**
+ * Apply a rewrite rule to the Predicate.
+ *
+ * @param input the input predicate to be tested, and possibly rewritten.
+ * @return a rewritten form of the predicate if this rule matches with the
+ * tree {@code input} and has a rewrite for it; {@code null} if this
+ * rule does not want this predicate.
+ */
+ Predicate<T> rewrite(QueryRewriter<T> rewriter, Predicate<T> input);
+
+ /** @return true if the best cost should be selected. */
+ boolean useBestCost();
+ }
+
+ /** Implements the magic behind {@link Rewrite} annotations. */
+ private static class MethodRewrite<T> implements RewriteRule<T> {
+ private final Method method;
+ private final Predicate<T> pattern;
+ private final String[] argNames;
+ private final Class<? extends Predicate<T>>[] argTypes;
+ private final boolean useBestCost;
+
+ @SuppressWarnings("unchecked")
+ MethodRewrite(QueryBuilder<T> queryBuilder, String patternText, Method m) {
+ method = m;
+ useBestCost = m.getAnnotation(NoCostComputation.class) == null;
+
+ Predicate<T> p;
+ try {
+ p = queryBuilder.parse(patternText);
+ } catch (QueryParseException e) {
+ throw new RuntimeException("Bad @Rewrite(\"" + patternText + "\")"
+ + " on " + m.toGenericString() + " in " + m.getDeclaringClass()
+ + ": " + e.getMessage(), e);
+ }
+ if (!Predicate.class.isAssignableFrom(m.getReturnType())) {
+ throw new RuntimeException(m.toGenericString() + " in "
+ + m.getDeclaringClass() + " must return " + Predicate.class);
+ }
+
+ pattern = p;
+ argNames = new String[method.getParameterTypes().length];
+ argTypes = new Class[argNames.length];
+ for (int i = 0; i < argNames.length; i++) {
+ Named name = null;
+ for (Annotation a : method.getParameterAnnotations()[i]) {
+ if (a instanceof Named) {
+ name = (Named) a;
+ break;
+ }
+ }
+ if (name == null) {
+ throw new RuntimeException("Argument " + (i + 1) + " of "
+ + m.toGenericString() + " in " + m.getDeclaringClass()
+ + " has no @Named annotation");
+ }
+ if (!Predicate.class.isAssignableFrom(method.getParameterTypes()[i])) {
+ throw new RuntimeException("Argument " + (i + 1) + " of "
+ + m.toGenericString() + " in " + m.getDeclaringClass()
+ + " must be of type " + Predicate.class);
+ }
+ argNames[i] = name.value();
+ argTypes[i] = (Class<Predicate<T>>) method.getParameterTypes()[i];
+ }
+ }
+
+ @Override
+ public boolean useBestCost() {
+ return useBestCost;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Predicate<T> rewrite(QueryRewriter<T> rewriter,
+ final Predicate<T> input) {
+ final HashMap<String, Predicate<T>> args =
+ new HashMap<String, Predicate<T>>();
+ final MatchResult<T> res = rewriter.match(args, pattern, input);
+ if (!res.success()) {
+ return null;
+ }
+
+ final Predicate[] argList = new Predicate[argNames.length];
+ for (int i = 0; i < argList.length; i++) {
+ argList[i] = args.get(argNames[i]);
+ if (argList[i] == null) {
+ final String a = "@Named(\"" + argNames[i] + "\")";
+ throw error(new IllegalStateException("No value bound for " + a));
+ }
+ if (!argTypes[i].isInstance(argList[i])) {
+ return null;
+ }
+ }
+
+ final Predicate<T> rep;
+ try {
+ rep = (Predicate<T>) method.invoke(rewriter, (Object[]) argList);
+ } catch (IllegalArgumentException e) {
+ throw error(e);
+ } catch (IllegalAccessException e) {
+ throw error(e);
+ } catch (InvocationTargetException e) {
+ throw error(e.getCause());
+ }
+
+ if (rep instanceof RewritePredicate) {
+ ((RewritePredicate) rep).init(method.getName(), argList);
+ }
+
+ if (res.extra == null) {
+ return rep;
+ }
+
+ Predicate<T> extra = removeDuplicates(res.extra);
+ Predicate<T>[] newArgs = new Predicate[] {extra, rep};
+ return input.copy(Arrays.asList(newArgs));
+ }
+
+ private IllegalArgumentException error(Throwable e) {
+ final String msg = "Cannot apply " + method.getName();
+ return new IllegalArgumentException(msg, e);
+ }
+ }
+
+ private static <T> Predicate<T> removeDuplicates(Predicate<T> in) {
+ if (in.getChildCount() > 0) {
+ List<Predicate<T>> n = removeDuplicates(in.getChildren());
+ if (n.size() == 1 && (isAND(in) || isOR(in))) {
+ in = n.get(0);
+ } else {
+ in = in.copy(n);
+ }
+ }
+ return in;
+ }
+
+ private static <T> List<Predicate<T>> removeDuplicates(List<Predicate<T>> n) {
+ List<Predicate<T>> r = new ArrayList<Predicate<T>>();
+ for (Predicate<T> p : n) {
+ if (!r.contains(p)) {
+ r.add(p);
+ }
+ }
+ return r;
+ }
+
+ private static <T> void expand(final List<Predicate<T>> out,
+ final List<Predicate<T>> allOR, final List<Predicate<T>> tmp,
+ final List<Predicate<T>> nonOR) {
+ if (tmp.size() == allOR.size()) {
+ final int sz = nonOR.size() + tmp.size();
+ final List<Predicate<T>> newList = new ArrayList<Predicate<T>>(sz);
+ newList.addAll(nonOR);
+ newList.addAll(tmp);
+ out.add(Predicate.and(newList));
+
+ } else {
+ for (final Predicate<T> c : allOR.get(tmp.size()).getChildren()) {
+ try {
+ tmp.add(c);
+ expand(out, allOR, tmp, nonOR);
+ } finally {
+ tmp.remove(tmp.size() - 1);
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> boolean isAND(final Predicate<T> p) {
+ return p instanceof AndPredicate;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> boolean isOR(final Predicate<T> p) {
+ return p instanceof OrPredicate;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> boolean isNOT(final Predicate<T> p) {
+ return p instanceof NotPredicate;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java
new file mode 100644
index 0000000000..c0a00ca991
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java
@@ -0,0 +1,68 @@
+// 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;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class RewritePredicate<T> extends Predicate<T> {
+ private String name = getClass().getName();
+ private List<Predicate<T>> children = Collections.emptyList();
+
+ void init(String name, Predicate<T>[] args) {
+ this.name = name;
+ this.children = Arrays.asList(args);
+ }
+
+ @Override
+ public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return getClass() == other.getClass()
+ && children.equals(((RewritePredicate) other).children);
+ }
+
+ @Override
+ public int hashCode() {
+ int h = getClass().hashCode();
+ if (!children.isEmpty()) {
+ h *= 31;
+ h += children.get(0).hashCode();
+ }
+ return h;
+ }
+
+ @Override
+ public final String toString() {
+ final StringBuilder r = new StringBuilder();
+ r.append(name);
+ if (!children.isEmpty()) {
+ r.append("(");
+ for (int i = 0; i < children.size(); i++) {
+ if (i != 0) {
+ r.append(" ");
+ }
+ r.append(children.get(i));
+ }
+ r.append(")");
+ }
+ return r.toString();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java
new file mode 100644
index 0000000000..0f6f957f46
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java
@@ -0,0 +1,96 @@
+// 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.server.query;
+
+import com.google.gwtorm.client.OrmException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Holds another predicate in a named variable.
+ *
+ * @see QueryRewriter
+ */
+public class VariablePredicate<T> extends Predicate<T> {
+ private final String name;
+ private final Predicate<T> that;
+
+ protected VariablePredicate(final String name, final Predicate<T> that) {
+ this.name = name;
+ this.that = that;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public final List<Predicate<T>> getChildren() {
+ return Collections.singletonList(that);
+ }
+
+ @Override
+ public final int getChildCount() {
+ return 1;
+ }
+
+ @Override
+ public final Predicate<T> getChild(final int i) {
+ if (i != 0) {
+ throw new ArrayIndexOutOfBoundsException(i);
+ }
+ return that;
+ }
+
+ @Override
+ public Predicate<T> copy(final Collection<? extends Predicate<T>> children) {
+ if (children.size() != 1) {
+ throw new IllegalArgumentException("Expected exactly one child");
+ }
+ return new VariablePredicate<T>(getName(), children.iterator().next());
+ }
+
+ @Override
+ public boolean match(final T object) throws OrmException {
+ return that.match(object);
+ }
+
+ @Override
+ public int getCost() {
+ return that.getCost();
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode() * 31 + that.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (getClass() == other.getClass()) {
+ final VariablePredicate<?> v = (VariablePredicate<?>) other;
+ return getName().equals(v.getName())
+ && getChildren().equals(v.getChildren());
+ }
+ return false;
+ }
+
+ @Override
+ public final String toString() {
+ return getName() + "=(" + that.toString() + ")";
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java
new file mode 100644
index 0000000000..61c8ea928c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java
@@ -0,0 +1,59 @@
+// 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.server.query;
+
+/**
+ * Predicate only for use in rewrite rule patterns.
+ * <p>
+ * May <b>only</b> be used when nested immediately within a
+ * {@link VariablePredicate}. Within the QueryRewriter this predicate matches
+ * any other operator whose name matches this predicate's operator name.
+ *
+ * @see QueryRewriter
+ */
+public final class WildPatternPredicate<T> extends OperatorPredicate<T> {
+ public WildPatternPredicate(final String name) {
+ super(name, "*");
+ }
+
+ @Override
+ public boolean match(final T object) {
+ throw new UnsupportedOperationException("Cannot match " + toString());
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return getOperator().hashCode() * 31;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (getClass() == other.getClass()) {
+ final WildPatternPredicate<?> p = (WildPatternPredicate<?>) other;
+ return getOperator().equals(p.getOperator());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getOperator() + ":" + getValue();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java
new file mode 100644
index 0000000000..2b8d37d284
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AbstractResultSet.java
@@ -0,0 +1,31 @@
+// 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 com.google.gwtorm.client.ResultSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class AbstractResultSet<T> implements ResultSet<T> {
+ @Override
+ public List<T> toList() {
+ ArrayList<T> r = new ArrayList<T>();
+ for (T t : this) {
+ r.add(t);
+ }
+ return r;
+ }
+}
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
new file mode 100644
index 0000000000..18a0c82c0e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -0,0 +1,54 @@
+// 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 java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class AgePredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final long cut;
+
+ AgePredicate(Provider<ReviewDb> dbProvider, String value) {
+ super(ChangeQueryBuilder.FIELD_AGE, value);
+ this.dbProvider = dbProvider;
+
+ long s = ConfigUtil.getTimeUnit(getValue(), 0, SECONDS);
+ long ms = MILLISECONDS.convert(s, SECONDS);
+ this.cut = (System.currentTimeMillis() - ms) + 1;
+ }
+
+ long getCut() {
+ return cut;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ return change != null && change.getLastUpdatedOn().getTime() < cut;
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..e74b390db8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -0,0 +1,149 @@
+// 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 com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.client.impl.ListResultSet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+class AndSource extends AndPredicate<ChangeData> implements ChangeDataSource {
+ private static final Comparator<Predicate<ChangeData>> CMP =
+ new Comparator<Predicate<ChangeData>>() {
+ @Override
+ public int compare(Predicate<ChangeData> a, Predicate<ChangeData> b) {
+ int ai = a instanceof ChangeDataSource ? 0 : 1;
+ int bi = b instanceof ChangeDataSource ? 0 : 1;
+ int cmp = ai - bi;
+
+ if (cmp == 0 //
+ && a instanceof ChangeDataSource //
+ && b instanceof ChangeDataSource) {
+ ai = ((ChangeDataSource) a).hasChange() ? 0 : 1;
+ bi = ((ChangeDataSource) b).hasChange() ? 0 : 1;
+ cmp = ai - bi;
+ }
+
+ if (cmp == 0) {
+ cmp = a.getCost() - b.getCost();
+ }
+
+ if (cmp == 0 //
+ && a instanceof ChangeDataSource //
+ && b instanceof ChangeDataSource) {
+ ChangeDataSource as = (ChangeDataSource) a;
+ ChangeDataSource bs = (ChangeDataSource) b;
+ cmp = as.getCardinality() - bs.getCardinality();
+ }
+
+ return cmp;
+ }
+ };
+
+ private static List<Predicate<ChangeData>> sort(
+ Collection<? extends Predicate<ChangeData>> that) {
+ ArrayList<Predicate<ChangeData>> r =
+ new ArrayList<Predicate<ChangeData>>(that);
+ Collections.sort(r, CMP);
+ return r;
+ }
+
+ private int cardinality = -1;
+
+ AndSource(final Collection<? extends Predicate<ChangeData>> that) {
+ super(sort(that));
+ }
+
+ @Override
+ public boolean hasChange() {
+ ChangeDataSource source = source();
+ return source != null && source.hasChange();
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ ChangeDataSource source = source();
+ if (source == null) {
+ throw new OrmException("No ChangeDataSource: " + this);
+ }
+
+ // TODO(spearce) This probably should be more lazy.
+ //
+ ArrayList<ChangeData> r = new ArrayList<ChangeData>();
+ ChangeData last = null;
+ boolean skipped = false;
+ for (ChangeData data : source.read()) {
+ if (match(data)) {
+ r.add(data);
+ } else {
+ skipped = true;
+ }
+ last = data;
+ }
+
+ if (skipped && last != null && source instanceof Paginated) {
+ // If we our source is a paginated source and we skipped at
+ // least one of its results, we may not have filled the full
+ // limit the caller wants. Restart the source and continue.
+ //
+ Paginated p = (Paginated) source;
+ while (skipped && r.size() < p.limit()) {
+ ChangeData lastBeforeRestart = last;
+ skipped = false;
+ last = null;
+ for (ChangeData data : p.restart(lastBeforeRestart)) {
+ if (match(data)) {
+ r.add(data);
+ } else {
+ skipped = true;
+ }
+ last = data;
+ }
+ }
+ }
+
+ return new ListResultSet<ChangeData>(r);
+ }
+
+ private ChangeDataSource source() {
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof ChangeDataSource) {
+ return (ChangeDataSource) p;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int getCardinality() {
+ if (cardinality < 0) {
+ cardinality = Integer.MAX_VALUE;
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof ChangeDataSource) {
+ int c = ((ChangeDataSource) p).getCardinality();
+ cardinality = Math.min(cardinality, c);
+ }
+ }
+ }
+ return cardinality;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
new file mode 100644
index 0000000000..ae48fdc6f9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
@@ -0,0 +1,47 @@
+// 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 com.google.gerrit.reviewdb.Branch;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class BranchPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final String shortName;
+
+ BranchPredicate(Provider<ReviewDb> dbProvider, String branch) {
+ super(ChangeQueryBuilder.FIELD_BRANCH, branch);
+ this.dbProvider = dbProvider;
+ this.shortName = new Branch.NameKey(null, branch).getShortName();
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+ return shortName.equals(change.getDest().getShortName());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..ba42803cc7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeCosts.java
@@ -0,0 +1,39 @@
+// 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
new file mode 100644
index 0000000000..479a5efa35
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -0,0 +1,189 @@
+// 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.server.query.change;
+
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchLineComment;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.TrackingId;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class ChangeData {
+ private final Change.Id legacyId;
+ private Change change;
+ private Collection<PatchSet> patches;
+ private Collection<PatchSetApproval> approvals;
+ private Collection<PatchSetApproval> currentApprovals;
+ private Collection<String> currentFiles;
+ private Collection<PatchLineComment> comments;
+ private Collection<TrackingId> trackingIds;
+ private CurrentUser visibleTo;
+
+ public ChangeData(final Change.Id id) {
+ legacyId = id;
+ }
+
+ public ChangeData(final Change c) {
+ legacyId = c.getId();
+ change = c;
+ }
+
+ public void setCurrentFilePaths(Collection<String> filePaths) {
+ currentFiles = filePaths;
+ }
+
+ public Collection<String> currentFilePaths(Provider<ReviewDb> db,
+ PatchListCache cache) throws OrmException {
+ if (currentFiles == null) {
+ Change c = change(db);
+ if (c == null) {
+ return null;
+ }
+ PatchSet ps = currentPatchSet(db);
+ if (ps == null) {
+ return null;
+ }
+
+ PatchList p = cache.get(c, ps);
+ List<String> r = new ArrayList<String>(p.getPatches().size());
+ for (PatchListEntry e : p.getPatches()) {
+ switch (e.getChangeType()) {
+ case ADDED:
+ case MODIFIED:
+ case COPIED:
+ r.add(e.getNewName());
+ break;
+ case DELETED:
+ r.add(e.getOldName());
+ break;
+ case RENAMED:
+ r.add(e.getOldName());
+ r.add(e.getNewName());
+ break;
+ }
+ }
+ currentFiles = r;
+ }
+ return currentFiles;
+ }
+
+ public Change.Id getId() {
+ return legacyId;
+ }
+
+ public Change getChange() {
+ return change;
+ }
+
+ public boolean hasChange() {
+ return change != null;
+ }
+
+ boolean fastIsVisibleTo(CurrentUser user) {
+ return visibleTo == user;
+ }
+
+ void cacheVisibleTo(CurrentUser user) {
+ visibleTo = user;
+ }
+
+ public Change change(Provider<ReviewDb> db) throws OrmException {
+ if (change == null) {
+ change = db.get().changes().get(legacyId);
+ }
+ return change;
+ }
+
+ public PatchSet currentPatchSet(Provider<ReviewDb> db) throws OrmException {
+ Change c = change(db);
+ if (c == null) {
+ return null;
+ }
+ for (PatchSet p : patches(db)) {
+ if (p.getId().equals(c.currentPatchSetId())) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ public Collection<PatchSetApproval> currentApprovals(Provider<ReviewDb> db)
+ throws OrmException {
+ if (currentApprovals == null) {
+ Change c = change(db);
+ if (c == null) {
+ currentApprovals = Collections.emptyList();
+ } else {
+ currentApprovals = approvalsFor(db, c.currentPatchSetId());
+ }
+ }
+ return currentApprovals;
+ }
+
+ public Collection<PatchSetApproval> approvalsFor(Provider<ReviewDb> db,
+ PatchSet.Id psId) throws OrmException {
+ List<PatchSetApproval> r = new ArrayList<PatchSetApproval>();
+ for (PatchSetApproval p : approvals(db)) {
+ if (p.getPatchSetId().equals(psId)) {
+ r.add(p);
+ }
+ }
+ return r;
+ }
+
+ public Collection<PatchSet> patches(Provider<ReviewDb> db)
+ throws OrmException {
+ if (patches == null) {
+ patches = db.get().patchSets().byChange(legacyId).toList();
+ }
+ return patches;
+ }
+
+ public Collection<PatchSetApproval> approvals(Provider<ReviewDb> db)
+ throws OrmException {
+ if (approvals == null) {
+ approvals = db.get().patchSetApprovals().byChange(legacyId).toList();
+ }
+ return approvals;
+ }
+
+ public Collection<PatchLineComment> comments(Provider<ReviewDb> db)
+ throws OrmException {
+ if (comments == null) {
+ comments = db.get().patchComments().byChange(legacyId).toList();
+ }
+ return comments;
+ }
+
+ public Collection<TrackingId> trackingIds(Provider<ReviewDb> db)
+ throws OrmException {
+ if (trackingIds == null) {
+ trackingIds = db.get().trackingIds().byChange(legacyId).toList();
+ }
+ return trackingIds;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
new file mode 100644
index 0000000000..fc7ba5922a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
@@ -0,0 +1,130 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gwtorm.client.ResultSet;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+abstract class ChangeDataResultSet<T> extends AbstractResultSet<ChangeData> {
+ static ResultSet<ChangeData> change(final ResultSet<Change> rs) {
+ return new ChangeDataResultSet<Change>(rs, true) {
+ @Override
+ ChangeData convert(Change t) {
+ return new ChangeData(t);
+ }
+ };
+ }
+
+ static ResultSet<ChangeData> patchSet(final ResultSet<PatchSet> rs) {
+ return new ChangeDataResultSet<PatchSet>(rs, false) {
+ @Override
+ ChangeData convert(PatchSet t) {
+ return new ChangeData(t.getId().getParentKey());
+ }
+ };
+ }
+
+ static ResultSet<ChangeData> patchSetApproval(
+ final ResultSet<PatchSetApproval> rs) {
+ return new ChangeDataResultSet<PatchSetApproval>(rs, false) {
+ @Override
+ ChangeData convert(PatchSetApproval t) {
+ return new ChangeData(t.getPatchSetId().getParentKey());
+ }
+ };
+ }
+
+ private final ResultSet<T> source;
+ private final boolean unique;
+
+ ChangeDataResultSet(ResultSet<T> source, boolean unique) {
+ this.source = source;
+ this.unique = unique;
+ }
+
+ @Override
+ public Iterator<ChangeData> iterator() {
+ if (unique) {
+ return new Iterator<ChangeData>() {
+ private final Iterator<T> itr = source.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return itr.hasNext();
+ }
+
+ @Override
+ public ChangeData next() {
+ return convert(itr.next());
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ } else {
+ return new Iterator<ChangeData>() {
+ private final Iterator<T> itr = source.iterator();
+ private final HashSet<Change.Id> seen = new HashSet<Change.Id>();
+ private ChangeData next;
+
+ @Override
+ public boolean hasNext() {
+ if (next != null) {
+ return true;
+ }
+ while (itr.hasNext()) {
+ ChangeData d = convert(itr.next());
+ if (seen.add(d.getId())) {
+ next = d;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ChangeData next() {
+ if (hasNext()) {
+ ChangeData r = next;
+ next = null;
+ return r;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ }
+
+ @Override
+ public void close() {
+ source.close();
+ }
+
+ abstract ChangeData convert(T t);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
new file mode 100644
index 0000000000..a770b5443d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
@@ -0,0 +1,29 @@
+// 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 com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+
+public interface ChangeDataSource {
+ /** @return an estimate of the number of results from {@link #read()}. */
+ public int getCardinality();
+
+ /** @return true if all returned ChangeData.hasChange() will be true. */
+ public boolean hasChange();
+
+ /** @return read from the database and return the changes. */
+ public abstract ResultSet<ChangeData> read() throws OrmException;
+}
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
new file mode 100644
index 0000000000..83107bb329
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -0,0 +1,69 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.inject.Provider;
+
+class ChangeIdPredicate extends OperatorPredicate<ChangeData> implements
+ ChangeDataSource {
+ private final Provider<ReviewDb> dbProvider;
+
+ ChangeIdPredicate(Provider<ReviewDb> dbProvider, String id) {
+ super(ChangeQueryBuilder.FIELD_CHANGE, id);
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public boolean match(final ChangeData cd) throws OrmException {
+ Change change = cd.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+
+ String key = change.getKey().get();
+ if (key.equals(getValue()) || key.startsWith(getValue())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ Change.Key a = new Change.Key(getValue());
+ Change.Key b = a.max();
+ return ChangeDataResultSet.change( //
+ dbProvider.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;
+ }
+}
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
new file mode 100644
index 0000000000..1b9f051e6a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -0,0 +1,453 @@
+// 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.server.query.change;
+
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.RevId;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.project.ChangeControl;
+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.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.regex.Pattern;
+
+/**
+ * Parses a query string meant to be applied to change objects.
+ */
+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 PAT_COMMIT =
+ Pattern.compile("^([0-9a-fA-F]{4," + RevId.LEN + "})$");
+ private static final Pattern PAT_EMAIL =
+ Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+");
+
+ private static final Pattern PAT_LABEL =
+ Pattern.compile("^[a-zA-Z][a-zA-Z0-9]*((=|>=|<=)[+-]?|[+-])\\d+$");
+
+ public static final String FIELD_AGE = "age";
+ public static final String FIELD_BRANCH = "branch";
+ public static final String FIELD_CHANGE = "change";
+ public static final String FIELD_COMMIT = "commit";
+ public static final String FIELD_DRAFTBY = "draftby";
+ 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_LABEL = "label";
+ public static final String FIELD_LIMIT = "limit";
+ public static final String FIELD_OWNER = "owner";
+ public static final String FIELD_PROJECT = "project";
+ public static final String FIELD_REF = "ref";
+ public static final String FIELD_REVIEWER = "reviewer";
+ public static final String FIELD_STARREDBY = "starredby";
+ public static final String FIELD_STATUS = "status";
+ public static final String FIELD_TOPIC = "topic";
+ public static final String FIELD_TR = "tr";
+ public static final String FIELD_VISIBLETO = "visibleto";
+ public static final String FIELD_WATCHEDBY = "watchedby";
+
+ private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
+ new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>(
+ ChangeQueryBuilder.class);
+
+ static class Arguments {
+ final Provider<ReviewDb> dbProvider;
+ final Provider<ChangeQueryRewriter> rewriter;
+ final IdentifiedUser.GenericFactory userFactory;
+ final ChangeControl.Factory changeControlFactory;
+ final ChangeControl.GenericFactory changeControlGenericFactory;
+ final AccountResolver accountResolver;
+ final GroupCache groupCache;
+ final AuthConfig authConfig;
+ final ApprovalTypes approvalTypes;
+ final Project.NameKey wildProjectName;
+ final PatchListCache patchListCache;
+
+ @Inject
+ Arguments(Provider<ReviewDb> dbProvider,
+ Provider<ChangeQueryRewriter> rewriter,
+ IdentifiedUser.GenericFactory userFactory,
+ ChangeControl.Factory changeControlFactory,
+ ChangeControl.GenericFactory changeControlGenericFactory,
+ AccountResolver accountResolver, GroupCache groupCache,
+ AuthConfig authConfig, ApprovalTypes approvalTypes,
+ @WildProjectName Project.NameKey wildProjectName,
+ PatchListCache patchListCache) {
+ this.dbProvider = dbProvider;
+ this.rewriter = rewriter;
+ this.userFactory = userFactory;
+ this.changeControlFactory = changeControlFactory;
+ this.changeControlGenericFactory = changeControlGenericFactory;
+ this.accountResolver = accountResolver;
+ this.groupCache = groupCache;
+ this.authConfig = authConfig;
+ this.approvalTypes = approvalTypes;
+ this.wildProjectName = wildProjectName;
+ this.patchListCache = patchListCache;
+ }
+ }
+
+ public interface Factory {
+ ChangeQueryBuilder create(CurrentUser user);
+ }
+
+ private final Arguments args;
+ private final CurrentUser currentUser;
+ private boolean allowsFile;
+
+ @Inject
+ ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
+ super(mydef);
+ this.args = args;
+ this.currentUser = currentUser;
+ }
+
+ public void setAllowFile(boolean on) {
+ allowsFile = on;
+ }
+
+ @Operator
+ public Predicate<ChangeData> age(String value) {
+ return new AgePredicate(args.dbProvider, value);
+ }
+
+ @Operator
+ public Predicate<ChangeData> change(String query) {
+ if (PAT_LEGACY_ID.matcher(query).matches()) {
+ return new LegacyChangeIdPredicate(args.dbProvider, Change.Id
+ .parse(query));
+
+ } else if (PAT_CHANGE_ID.matcher(query).matches()) {
+ if (query.charAt(0) == 'i') {
+ query = "I" + query.substring(1);
+ }
+ return new ChangeIdPredicate(args.dbProvider, query);
+ }
+
+ throw new IllegalArgumentException();
+ }
+
+ @Operator
+ public Predicate<ChangeData> status(String statusName) {
+ if ("open".equals(statusName)) {
+ return status_open();
+
+ } else if ("closed".equals(statusName)) {
+ return ChangeStatusPredicate.closed(args.dbProvider);
+
+ } else if ("reviewed".equalsIgnoreCase(statusName)) {
+ return new IsReviewedPredicate(args.dbProvider);
+
+ } else {
+ return new ChangeStatusPredicate(args.dbProvider, statusName);
+ }
+ }
+
+ public Predicate<ChangeData> status_open() {
+ return ChangeStatusPredicate.open(args.dbProvider);
+ }
+
+ @Operator
+ public Predicate<ChangeData> has(String value) {
+ if ("star".equalsIgnoreCase(value)) {
+ return new IsStarredByPredicate(args.dbProvider, currentUser);
+ }
+
+ if ("draft".equalsIgnoreCase(value)) {
+ if (currentUser instanceof IdentifiedUser) {
+ return new HasDraftByPredicate(args.dbProvider,
+ ((IdentifiedUser) currentUser).getAccountId());
+ }
+ }
+
+ throw new IllegalArgumentException();
+ }
+
+ @Operator
+ public Predicate<ChangeData> is(String value) {
+ if ("starred".equalsIgnoreCase(value)) {
+ return new IsStarredByPredicate(args.dbProvider, currentUser);
+ }
+
+ if ("watched".equalsIgnoreCase(value)) {
+ return new IsWatchedByPredicate(args, currentUser);
+ }
+
+ if ("visible".equalsIgnoreCase(value)) {
+ return is_visible();
+ }
+
+ if ("reviewed".equalsIgnoreCase(value)) {
+ return new IsReviewedPredicate(args.dbProvider);
+ }
+
+ try {
+ return status(value);
+ } catch (IllegalArgumentException e) {
+ // not status: alias?
+ }
+
+ throw new IllegalArgumentException();
+ }
+
+ @Operator
+ public Predicate<ChangeData> commit(String id) {
+ return new CommitPredicate(args.dbProvider, AbbreviatedObjectId
+ .fromString(id));
+ }
+
+ @Operator
+ public Predicate<ChangeData> project(String name) {
+ return new ProjectPredicate(args.dbProvider, name);
+ }
+
+ @Operator
+ public Predicate<ChangeData> branch(String name) {
+ return new BranchPredicate(args.dbProvider, name);
+ }
+
+ @Operator
+ public Predicate<ChangeData> topic(String name) {
+ return new TopicPredicate(args.dbProvider, name);
+ }
+
+ @Operator
+ public Predicate<ChangeData> ref(String ref) {
+ return new RefPredicate(args.dbProvider, ref);
+ }
+
+ @Operator
+ public Predicate<ChangeData> file(String file) throws QueryParseException {
+ if (!allowsFile) {
+ throw error("operator not permitted here: file:" + file);
+ }
+
+ if (file.startsWith("^")) {
+ return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
+ }
+
+ throw new IllegalArgumentException();
+ }
+
+ @Operator
+ public Predicate<ChangeData> label(String name) {
+ return new LabelPredicate(args.changeControlGenericFactory,
+ args.userFactory, args.dbProvider, args.approvalTypes, name);
+ }
+
+ @Operator
+ public Predicate<ChangeData> starredby(String who)
+ throws QueryParseException, OrmException {
+ Account account = args.accountResolver.find(who);
+ if (account == null) {
+ throw error("User " + who + " not found");
+ }
+ return new IsStarredByPredicate(args.dbProvider, //
+ args.userFactory.create(args.dbProvider, account.getId()));
+ }
+
+ @Operator
+ public Predicate<ChangeData> watchedby(String who)
+ throws QueryParseException, OrmException {
+ Account account = args.accountResolver.find(who);
+ if (account == null) {
+ throw error("User " + who + " not found");
+ }
+ return new IsWatchedByPredicate(args, args.userFactory.create(
+ args.dbProvider, account.getId()));
+ }
+
+ @Operator
+ public Predicate<ChangeData> draftby(String who) throws QueryParseException,
+ OrmException {
+ Account account = args.accountResolver.find(who);
+ if (account == null) {
+ throw error("User " + who + " not found");
+ }
+ return new HasDraftByPredicate(args.dbProvider, account.getId());
+ }
+
+ @Operator
+ public Predicate<ChangeData> visibleto(String who)
+ throws QueryParseException, OrmException {
+ Account account = args.accountResolver.find(who);
+ if (account != null) {
+ return visibleto(args.userFactory
+ .create(args.dbProvider, account.getId()));
+ }
+
+ // If its not an account, maybe its a group?
+ //
+ AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
+ if (g != null) {
+ return visibleto(new SingleGroupUser(args.authConfig, g.getId()));
+ }
+
+ Collection<AccountGroup> matches =
+ args.groupCache.get(new AccountGroup.ExternalNameKey(who));
+ if (matches != null && !matches.isEmpty()) {
+ HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
+ for (AccountGroup group : matches) {
+ ids.add(group.getId());
+ }
+ return visibleto(new SingleGroupUser(args.authConfig, ids));
+ }
+
+ throw error("No user or group matches \"" + who + "\".");
+ }
+
+ public Predicate<ChangeData> visibleto(CurrentUser user) {
+ return new IsVisibleToPredicate(args.dbProvider, //
+ args.changeControlFactory, //
+ user);
+ }
+
+ public Predicate<ChangeData> is_visible() {
+ return visibleto(currentUser);
+ }
+
+ @Operator
+ public Predicate<ChangeData> owner(String who) throws QueryParseException,
+ OrmException {
+ Account account = args.accountResolver.find(who);
+ if (account == null) {
+ throw error("User " + who + " not found");
+ }
+ return new OwnerPredicate(args.dbProvider, account.getId());
+ }
+
+ @Operator
+ public Predicate<ChangeData> reviewer(String nameOrEmail)
+ throws QueryParseException, OrmException {
+ Account account = args.accountResolver.find(nameOrEmail);
+ if (account == null) {
+ throw error("Reviewer " + nameOrEmail + " not found");
+ }
+ return new ReviewerPredicate(args.dbProvider, account.getId());
+ }
+
+ @Operator
+ public Predicate<ChangeData> tr(String trackingId) {
+ return new TrackingIdPredicate(args.dbProvider, trackingId);
+ }
+
+ @Operator
+ public Predicate<ChangeData> bug(String trackingId) {
+ return tr(trackingId);
+ }
+
+ @Operator
+ public Predicate<ChangeData> limit(String limit) {
+ return limit(Integer.parseInt(limit));
+ }
+
+ public Predicate<ChangeData> limit(int limit) {
+ return new IntPredicate<ChangeData>(FIELD_LIMIT, limit) {
+ @Override
+ public boolean match(ChangeData object) {
+ return true;
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+ };
+ }
+
+ @Operator
+ public Predicate<ChangeData> sortkey_after(String sortKey) {
+ return new SortKeyPredicate.After(args.dbProvider, sortKey);
+ }
+
+ @Operator
+ public Predicate<ChangeData> sortkey_before(String sortKey) {
+ return new SortKeyPredicate.Before(args.dbProvider, sortKey);
+ }
+
+ @Operator
+ public Predicate<ChangeData> resume_sortkey(String sortKey) {
+ return sortkey_before(sortKey);
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean hasLimit(Predicate<ChangeData> p) {
+ return find(p, IntPredicate.class, FIELD_LIMIT) != null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getLimit(Predicate<ChangeData> p) {
+ return ((IntPredicate) find(p, IntPredicate.class, FIELD_LIMIT)).intValue();
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean hasSortKey(Predicate<ChangeData> p) {
+ return find(p, SortKeyPredicate.class, "sortkey_after") != null
+ || find(p, SortKeyPredicate.class, "sortkey_before") != null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ 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);
+
+ } else if (PAT_COMMIT.matcher(query).matches()) {
+ return commit(query);
+
+ } else if (PAT_EMAIL.matcher(query).find()) {
+ try {
+ return Predicate.or(owner(query), reviewer(query));
+ } catch (OrmException err) {
+ throw error("Cannot lookup user", err);
+ }
+
+ } else if (PAT_LABEL.matcher(query).find()) {
+ return label(query);
+
+ } else {
+ throw error("Unsupported query:" + query);
+ }
+ }
+}
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
new file mode 100644
index 0000000000..98b12f75d7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -0,0 +1,601 @@
+// 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.server.query.change;
+
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ChangeAccess;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.query.IntPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryRewriter;
+import com.google.gerrit.server.query.RewritePredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+import java.util.Collection;
+
+public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
+ private static final QueryRewriter.Definition<ChangeData, ChangeQueryRewriter> mydef =
+ new QueryRewriter.Definition<ChangeData, ChangeQueryRewriter>(
+ ChangeQueryRewriter.class, new ChangeQueryBuilder(
+ new ChangeQueryBuilder.Arguments( //
+ new InvalidProvider<ReviewDb>(), //
+ new InvalidProvider<ChangeQueryRewriter>(), //
+ null, null, null, null, null, null, null, null, null),
+ null));
+
+ private final Provider<ReviewDb> dbProvider;
+
+ @Inject
+ ChangeQueryRewriter(Provider<ReviewDb> dbProvider) {
+ super(mydef);
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public Predicate<ChangeData> and(Collection<? extends Predicate<ChangeData>> l) {
+ return hasSource(l) ? new AndSource(l) : super.and(l);
+ }
+
+ @Override
+ public Predicate<ChangeData> or(Collection<? extends Predicate<ChangeData>> l) {
+ return hasSource(l) ? new OrSource(l) : super.or(l);
+ }
+
+ @Rewrite("-status:open")
+ @NoCostComputation
+ public Predicate<ChangeData> r00_notOpen() {
+ return ChangeStatusPredicate.closed(dbProvider);
+ }
+
+ @Rewrite("-status:closed")
+ @NoCostComputation
+ public Predicate<ChangeData> r00_notClosed() {
+ return ChangeStatusPredicate.open(dbProvider);
+ }
+
+ @SuppressWarnings("unchecked")
+ @NoCostComputation
+ @Rewrite("-status:merged")
+ public Predicate<ChangeData> r00_notMerged() {
+ return or(ChangeStatusPredicate.open(dbProvider),
+ new ChangeStatusPredicate(dbProvider, Change.Status.ABANDONED));
+ }
+
+ @SuppressWarnings("unchecked")
+ @NoCostComputation
+ @Rewrite("-status:abandoned")
+ public Predicate<ChangeData> r00_notAbandoned() {
+ return or(ChangeStatusPredicate.open(dbProvider),
+ new ChangeStatusPredicate(dbProvider, Change.Status.MERGED));
+ }
+
+ @SuppressWarnings("unchecked")
+ @NoCostComputation
+ @Rewrite("sortkey_before:z A=(age:*)")
+ public Predicate<ChangeData> r00_ageToSortKey(@Named("A") AgePredicate a) {
+ String cut = ChangeUtil.sortKey(a.getCut(), Integer.MAX_VALUE);
+ return and(new SortKeyPredicate.Before(dbProvider, cut), a);
+ }
+
+ @SuppressWarnings("unchecked")
+ @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;
+ }
+
+ @SuppressWarnings("unchecked")
+ @NoCostComputation
+ @Rewrite("A=(sortkey_before:*) B=(sortkey_before:*)")
+ public Predicate<ChangeData> r00_oldestSortKey(
+ @Named("A") SortKeyPredicate.Before a,
+ @Named("B") SortKeyPredicate.Before b) {
+ return a.getValue().compareTo(b.getValue()) <= 0 ? a : b;
+ }
+
+ @SuppressWarnings("unchecked")
+ @NoCostComputation
+ @Rewrite("A=(sortkey_after:*) B=(sortkey_after:*)")
+ public Predicate<ChangeData> r00_newestSortKey(
+ @Named("A") SortKeyPredicate.After a, @Named("B") SortKeyPredicate.After b) {
+ return a.getValue().compareTo(b.getValue()) >= 0 ? a : b;
+ }
+
+ @Rewrite("status:open P=(project:*) S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r10_byProjectOpenPrev(
+ @Named("P") final ProjectPredicate p,
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(500, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byProjectOpenPrev(p.getValueKey(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() //
+ && p.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:open P=(project:*) S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r10_byProjectOpenNext(
+ @Named("P") final ProjectPredicate p,
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(500, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byProjectOpenNext(p.getValueKey(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() //
+ && p.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:merged P=(project:*) S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r10_byProjectMergedPrev(
+ @Named("P") final ProjectPredicate p,
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byProjectClosedPrev(Change.Status.MERGED.getCode(), //
+ p.getValueKey(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+ && p.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:merged P=(project:*) S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r10_byProjectMergedNext(
+ @Named("P") final ProjectPredicate p,
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byProjectClosedNext(Change.Status.MERGED.getCode(), //
+ p.getValueKey(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+ && p.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:abandoned P=(project:*) S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r10_byProjectAbandonedPrev(
+ @Named("P") final ProjectPredicate p,
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byProjectClosedPrev(Change.Status.ABANDONED.getCode(), //
+ p.getValueKey(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+ && p.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:abandoned P=(project:*) S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r10_byProjectAbandonedNext(
+ @Named("P") final ProjectPredicate p,
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byProjectClosedNext(Change.Status.ABANDONED.getCode(), //
+ p.getValueKey(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+ && p.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:open S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byOpenPrev(
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(2000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.allOpenPrev(key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:open S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byOpenNext(
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(2000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.allOpenNext(key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:merged S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byMergedPrev(
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.allClosedPrev(Change.Status.MERGED.getCode(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:merged S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byMergedNext(
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.allClosedNext(Change.Status.MERGED.getCode(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:abandoned S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byAbandonedPrev(
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.allClosedPrev(Change.Status.ABANDONED.getCode(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:abandoned S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byAbandonedNext(
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.allClosedNext(Change.Status.ABANDONED.getCode(), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+ && s.match(cd);
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byClosedPrev(
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return or(r20_byMergedPrev(s, l), r20_byAbandonedPrev(s, l));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r20_byClosedNext(
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return or(r20_byMergedNext(s, l), r20_byAbandonedNext(s, l));
+ }
+
+ @Rewrite("status:open O=(owner:*)")
+ public Predicate<ChangeData> r25_byOwnerOpen(
+ @Named("O") final OwnerPredicate o) {
+ return new ChangeSource(50) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+ return a.byOwnerOpen(o.getAccountId());
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() && o.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:closed O=(owner:*)")
+ public Predicate<ChangeData> r25_byOwnerClosed(
+ @Named("O") final OwnerPredicate o) {
+ return new ChangeSource(5000) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+ return a.byOwnerClosedAll(o.getAccountId());
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isClosed() && o.match(cd);
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Rewrite("O=(owner:*)")
+ public Predicate<ChangeData> r26_byOwner(@Named("O") OwnerPredicate o) {
+ return or(r25_byOwnerOpen(o), r25_byOwnerClosed(o));
+ }
+
+ @Rewrite("status:open R=(reviewer:*)")
+ public Predicate<ChangeData> r30_byReviewerOpen(
+ @Named("R") final ReviewerPredicate r) {
+ return new Source() {
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ return ChangeDataResultSet.patchSetApproval(dbProvider.get()
+ .patchSetApprovals().openByUser(r.getAccountId()));
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ Change change = cd.change(dbProvider);
+ return change != null && change.getStatus().isOpen() && r.match(cd);
+ }
+
+ @Override
+ public int getCardinality() {
+ return 50;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
+ }
+ };
+ }
+
+ @Rewrite("status:closed R=(reviewer:*)")
+ public Predicate<ChangeData> r30_byReviewerClosed(
+ @Named("R") final ReviewerPredicate r) {
+ return new Source() {
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ return ChangeDataResultSet.patchSetApproval(dbProvider.get()
+ .patchSetApprovals().closedByUserAll(r.getAccountId()));
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ Change change = cd.change(dbProvider);
+ return change != null && change.getStatus().isClosed() && r.match(cd);
+ }
+
+ @Override
+ public int getCardinality() {
+ return 5000;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Rewrite("R=(reviewer:*)")
+ public Predicate<ChangeData> r31_byReviewer(
+ @Named("R") final ReviewerPredicate r) {
+ return or(r30_byReviewerOpen(r), r30_byReviewerClosed(r));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Rewrite("status:submitted")
+ public Predicate<ChangeData> r99_allSubmitted() {
+ return new ChangeSource(50) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+ return a.allSubmitted();
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.SUBMITTED;
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Rewrite("P=(project:*)")
+ public Predicate<ChangeData> r99_byProject(
+ @Named("P") final ProjectPredicate p) {
+ return new ChangeSource(1000000) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+ return a.byProject(p.getValueKey());
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return p.match(cd);
+ }
+ };
+ }
+
+ private static boolean hasSource(Collection<? extends Predicate<ChangeData>> l) {
+ for (Predicate<ChangeData> p : l) {
+ if (p instanceof ChangeDataSource) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private abstract static class Source extends RewritePredicate<ChangeData>
+ implements ChangeDataSource {
+ @Override
+ public boolean hasChange() {
+ return false;
+ }
+ }
+
+ private abstract class ChangeSource extends Source {
+ private final int cardinality;
+
+ ChangeSource(int card) {
+ this.cardinality = card;
+ }
+
+ abstract ResultSet<Change> scan(ChangeAccess a) throws OrmException;
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ return ChangeDataResultSet.change(scan(dbProvider.get().changes()));
+ }
+
+ @Override
+ public boolean hasChange() {
+ return true;
+ }
+
+ @Override
+ public int getCardinality() {
+ return cardinality;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.cost(ChangeCosts.CHANGES_SCAN, getCardinality());
+ }
+ }
+
+ private abstract class PaginatedSource extends ChangeSource implements
+ Paginated {
+ private final String startKey;
+ private final int limit;
+
+ PaginatedSource(int card, String start, int lim) {
+ super(card);
+ this.startKey = start;
+ this.limit = lim;
+ }
+
+ @Override
+ public int limit() {
+ return limit;
+ }
+
+ @Override
+ ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+ return scan(a, startKey, limit);
+ }
+
+ @Override
+ public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
+ return ChangeDataResultSet.change(scan(dbProvider.get().changes(), //
+ last.change(dbProvider).getSortKey(), //
+ limit));
+ }
+
+ abstract ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException;
+ }
+
+ private static final class InvalidProvider<T> implements Provider<T> {
+ @Override
+ public T get() {
+ throw new OutOfScopeException("Not available at init");
+ }
+ }
+}
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
new file mode 100644
index 0000000000..4ae2278771
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -0,0 +1,126 @@
+// 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.server.query.change;
+
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Predicate for a {@link Change.Status}.
+ * <p>
+ * 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.
+ */
+final class ChangeStatusPredicate extends OperatorPredicate<ChangeData> {
+ private static final Map<String, Change.Status> byName;
+ private static final EnumMap<Change.Status, String> byEnum;
+
+ static {
+ byName = new HashMap<String, Change.Status>();
+ byEnum = new EnumMap<Change.Status, String>(Change.Status.class);
+ for (final Change.Status s : Change.Status.values()) {
+ final String name = s.name().toLowerCase();
+ byName.put(name, s);
+ byEnum.put(s, name);
+ }
+ }
+
+ static Predicate<ChangeData> open(Provider<ReviewDb> dbProvider) {
+ List<Predicate<ChangeData>> r = new ArrayList<Predicate<ChangeData>>(4);
+ for (final Change.Status e : Change.Status.values()) {
+ if (e.isOpen()) {
+ r.add(new ChangeStatusPredicate(dbProvider, e));
+ }
+ }
+ return r.size() == 1 ? r.get(0) : or(r);
+ }
+
+ static Predicate<ChangeData> closed(Provider<ReviewDb> dbProvider) {
+ List<Predicate<ChangeData>> r = new ArrayList<Predicate<ChangeData>>(4);
+ for (final Change.Status e : Change.Status.values()) {
+ if (e.isClosed()) {
+ r.add(new ChangeStatusPredicate(dbProvider, e));
+ }
+ }
+ return r.size() == 1 ? r.get(0) : or(r);
+ }
+
+ private static Change.Status parse(final String value) {
+ final Change.Status s = byName.get(value);
+ if (s == null) {
+ throw new IllegalArgumentException();
+ }
+ return s;
+ }
+
+ private final Provider<ReviewDb> dbProvider;
+ private final Change.Status status;
+
+ ChangeStatusPredicate(Provider<ReviewDb> dbProvider, String value) {
+ this(dbProvider, parse(value));
+ }
+
+ ChangeStatusPredicate(Provider<ReviewDb> dbProvider, Change.Status status) {
+ super(ChangeQueryBuilder.FIELD_STATUS, byEnum.get(status));
+ this.dbProvider = dbProvider;
+ this.status = status;
+ }
+
+ Change.Status getStatus() {
+ return status;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ return change != null && status.equals(change.getStatus());
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return status.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ChangeStatusPredicate) {
+ final ChangeStatusPredicate p = (ChangeStatusPredicate) other;
+ return status.equals(p.status);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getOperator() + ":" + getValue();
+ }
+}
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
new file mode 100644
index 0000000000..c03cddc85b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -0,0 +1,77 @@
+// 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 com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.RevId;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.ObjectIdPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+
+class CommitPredicate extends ObjectIdPredicate<ChangeData> implements
+ ChangeDataSource {
+ private final Provider<ReviewDb> dbProvider;
+
+ CommitPredicate(Provider<ReviewDb> dbProvider, AbbreviatedObjectId id) {
+ super(ChangeQueryBuilder.FIELD_COMMIT, id);
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ for (PatchSet p : object.patches(dbProvider)) {
+ if (p.getRevision() != null && p.getRevision().get() != null) {
+ final ObjectId id = ObjectId.fromString(p.getRevision().get());
+ if (abbreviated().prefixCompare(id) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ final RevId id = new RevId(abbreviated().name());
+ if (id.isComplete()) {
+ return ChangeDataResultSet.patchSet(//
+ dbProvider.get().patchSets().byRevision(id));
+
+ } else {
+ return ChangeDataResultSet.patchSet(//
+ dbProvider.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());
+ }
+}
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
new file mode 100644
index 0000000000..07d4dd245e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -0,0 +1,81 @@
+// 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 com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchLineComment;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.client.impl.ListResultSet;
+import com.google.inject.Provider;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+class HasDraftByPredicate extends OperatorPredicate<ChangeData> implements
+ ChangeDataSource {
+ private final Provider<ReviewDb> db;
+ private final Account.Id accountId;
+
+ HasDraftByPredicate(Provider<ReviewDb> db, Account.Id accountId) {
+ super(ChangeQueryBuilder.FIELD_DRAFTBY, accountId.toString());
+ this.db = db;
+ this.accountId = accountId;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ for (PatchLineComment c : object.comments(db)) {
+ if (c.getStatus() == PatchLineComment.Status.DRAFT
+ && c.getAuthor().equals(accountId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ HashSet<Change.Id> ids = new HashSet<Change.Id>();
+ for (PatchLineComment sc : db.get().patchComments()
+ .draftByAuthor(accountId)) {
+ ids.add(sc.getKey().getParentKey().getParentKey().getParentKey());
+ }
+
+ ArrayList<ChangeData> r = new ArrayList<ChangeData>(ids.size());
+ for (Change.Id id : ids) {
+ r.add(new ChangeData(id));
+ }
+ return new ListResultSet<ChangeData>(r);
+ }
+
+ @Override
+ public boolean hasChange() {
+ return false;
+ }
+
+ @Override
+ public int getCardinality() {
+ return 20;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.cost(ChangeCosts.PATCH_SETS_SCAN, getCardinality());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
new file mode 100644
index 0000000000..46c774106d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -0,0 +1,54 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class IsReviewedPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+
+ IsReviewedPredicate(Provider<ReviewDb> dbProvider) {
+ super(ChangeQueryBuilder.FIELD_IS, "reviewed");
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change c = object.change(dbProvider);
+ if (c == null) {
+ return false;
+ }
+
+ PatchSet.Id current = c.currentPatchSetId();
+ for (PatchSetApproval p : object.approvals(dbProvider)) {
+ if (p.getPatchSetId().equals(current) && p.getValue() != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 2;
+ }
+}
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
new file mode 100644
index 0000000000..aaf8478586
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -0,0 +1,68 @@
+// 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 com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.inject.Provider;
+
+class IsStarredByPredicate extends OperatorPredicate<ChangeData> implements
+ ChangeDataSource {
+ private static String describe(CurrentUser user) {
+ if (user instanceof IdentifiedUser) {
+ return ((IdentifiedUser) user).getAccountId().toString();
+ }
+ return user.toString();
+ }
+
+ private final Provider<ReviewDb> db;
+ private final CurrentUser user;
+
+ IsStarredByPredicate(Provider<ReviewDb> db, CurrentUser user) {
+ super(ChangeQueryBuilder.FIELD_STARREDBY, describe(user));
+ this.db = db;
+ this.user = user;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) {
+ return user.getStarredChanges().contains(object.getId());
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ return ChangeDataResultSet.change( //
+ db.get().changes().get(user.getStarredChanges()));
+ }
+
+ @Override
+ public boolean hasChange() {
+ return true;
+ }
+
+ @Override
+ public int getCardinality() {
+ return 10;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.cost(ChangeCosts.IDS_MEMORY, getCardinality());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
new file mode 100644
index 0000000000..020e709514
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -0,0 +1,73 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class IsVisibleToPredicate extends OperatorPredicate<ChangeData> {
+ private static String describe(CurrentUser user) {
+ if (user instanceof IdentifiedUser) {
+ return ((IdentifiedUser) user).getAccountId().toString();
+ }
+ if (user instanceof SingleGroupUser) {
+ return "group:" + ((SingleGroupUser) user).getEffectiveGroups() //
+ .iterator().next().toString();
+ }
+ return user.toString();
+ }
+
+ private final Provider<ReviewDb> db;
+ private final ChangeControl.Factory changeControl;
+ private final CurrentUser user;
+
+ IsVisibleToPredicate(Provider<ReviewDb> db,
+ ChangeControl.Factory changeControlFactory, CurrentUser user) {
+ super(ChangeQueryBuilder.FIELD_VISIBLETO, describe(user));
+ this.db = db;
+ this.changeControl = changeControlFactory;
+ this.user = user;
+ }
+
+ @Override
+ public boolean match(final ChangeData cd) throws OrmException {
+ if (cd.fastIsVisibleTo(user)) {
+ return true;
+ }
+ try {
+ Change c = cd.change(db);
+ if (c != null && changeControl.controlFor(c).forUser(user).isVisible()) {
+ cd.cacheVisibleTo(user);
+ return true;
+ } else {
+ return false;
+ }
+ } catch (NoSuchChangeException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..870be730b9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -0,0 +1,121 @@
+// 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 com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.client.OrmException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
+ private static String describe(CurrentUser user) {
+ if (user instanceof IdentifiedUser) {
+ return ((IdentifiedUser) user).getAccountId().toString();
+ }
+ return user.toString();
+ }
+
+ private final ChangeQueryBuilder.Arguments args;
+ private final CurrentUser user;
+
+ private Map<Project.NameKey, List<Predicate<ChangeData>>> rules;
+
+ IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, CurrentUser user) {
+ super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user));
+ this.args = args;
+ this.user = user;
+ }
+
+ @Override
+ public boolean match(final ChangeData cd) throws OrmException {
+ if (rules == null) {
+ ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
+ rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
+ for (AccountProjectWatch w : user.getNotificationFilters()) {
+ List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
+ if (list == null) {
+ list = new ArrayList<Predicate<ChangeData>>(4);
+ rules.put(w.getProjectNameKey(), list);
+ }
+
+ Predicate<ChangeData> p = compile(builder, w);
+ if (p != null) {
+ list.add(p);
+ }
+ }
+ }
+
+ if (rules.isEmpty()) {
+ return false;
+ }
+
+ Change change = cd.change(args.dbProvider);
+ if (change == null) {
+ return false;
+ }
+
+ Project.NameKey project = change.getDest().getParentKey();
+ List<Predicate<ChangeData>> list = rules.get(project);
+ if (list == null) {
+ list = rules.get(args.wildProjectName);
+ }
+ if (list != null) {
+ for (Predicate<ChangeData> p : list) {
+ if (p.match(cd)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Predicate<ChangeData> compile(ChangeQueryBuilder builder,
+ AccountProjectWatch w) {
+ Predicate<ChangeData> p = builder.is_visible();
+ if (w.getFilter() != null) {
+ try {
+ p = Predicate.and(builder.parse(w.getFilter()), p);
+ if (builder.find(p, IsWatchedByPredicate.class) != null) {
+ // If the query is going to infinite loop, assume it
+ // will never match and return null. Yes this test
+ // prevents you from having a filter that matches what
+ // another user is filtering on. :-)
+ //
+ return null;
+ }
+ p = args.rewriter.get().rewrite(p);
+ } catch (QueryParseException e) {
+ return null;
+ }
+ }
+ return p;
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..e76c2782f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -0,0 +1,172 @@
+// 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 com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class LabelPredicate extends OperatorPredicate<ChangeData> {
+ private static enum Test {
+ EQ {
+ @Override
+ public boolean match(short psValue, short expValue) {
+ return psValue == expValue;
+ }
+ },
+ GT_EQ {
+ @Override
+ public boolean match(short psValue, short expValue) {
+ return psValue >= expValue;
+ }
+ },
+ LT_EQ {
+ @Override
+ public boolean match(short psValue, short expValue) {
+ return psValue <= expValue;
+ }
+ };
+
+ abstract boolean match(short psValue, short expValue);
+ }
+
+ private static ApprovalCategory.Id category(ApprovalTypes types, String toFind) {
+ if (types.getApprovalType(new ApprovalCategory.Id(toFind)) != null) {
+ return new ApprovalCategory.Id(toFind);
+ }
+
+ for (ApprovalType at : types.getApprovalTypes()) {
+ String name = at.getCategory().getName();
+ if (toFind.equalsIgnoreCase(name)) {
+ return at.getCategory().getId();
+
+ } else if (toFind.equalsIgnoreCase(name.replace(" ", ""))) {
+ return at.getCategory().getId();
+ }
+ }
+
+ for (ApprovalType at : types.getApprovalTypes()) {
+ if (toFind.equalsIgnoreCase(at.getCategory().getAbbreviatedName())) {
+ return at.getCategory().getId();
+ }
+ }
+
+ return new ApprovalCategory.Id(toFind);
+ }
+
+ private static Test op(String op) {
+ if ("=".equals(op)) {
+ return Test.EQ;
+
+ } else if (">=".equals(op)) {
+ return Test.GT_EQ;
+
+ } else if ("<=".equals(op)) {
+ return Test.LT_EQ;
+
+ } else {
+ throw new IllegalArgumentException("Unsupported operation " + op);
+ }
+ }
+
+ private static short value(String value) {
+ if (value.startsWith("+")) {
+ value = value.substring(1);
+ }
+ return Short.parseShort(value);
+ }
+
+ private final ChangeControl.GenericFactory ccFactory;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final Provider<ReviewDb> dbProvider;
+ private final Test test;
+ private final ApprovalCategory.Id category;
+ private final short expVal;
+
+ LabelPredicate(ChangeControl.GenericFactory ccFactory,
+ IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
+ ApprovalTypes types, String value) {
+ super(ChangeQueryBuilder.FIELD_LABEL, value);
+ this.ccFactory = ccFactory;
+ this.userFactory = userFactory;
+ this.dbProvider = dbProvider;
+
+ Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
+ Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
+ if (m1.find()) {
+ category = category(types, value.substring(0, m1.start()));
+ test = op(m1.group(1));
+ expVal = value(m1.group(2));
+
+ } else if (m2.find()) {
+ category = category(types, value.substring(0, m2.start()));
+ test = Test.EQ;
+ expVal = value(m2.group(1));
+
+ } else {
+ category = category(types, value);
+ test = Test.EQ;
+ expVal = 1;
+ }
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
+ if (p.getCategoryId().equals(category)) {
+ short psVal = p.getValue();
+ if (test.match(psVal, expVal)) {
+ // Double check the value is still permitted for the user.
+ //
+ try {
+ ChangeControl cc = ccFactory.controlFor(object.change(dbProvider), //
+ userFactory.create(dbProvider, p.getAccountId()));
+ if (!cc.isVisible()) {
+ // The user can't see the change anymore.
+ //
+ continue;
+ }
+ psVal = cc.normalize(category, psVal);
+ } catch (NoSuchChangeException e) {
+ // The project has disappeared.
+ //
+ continue;
+ }
+
+ if (test.match(psVal, expVal)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 2;
+ }
+}
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
new file mode 100644
index 0000000000..ac544a3a49
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
@@ -0,0 +1,68 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.client.impl.ListResultSet;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+class LegacyChangeIdPredicate extends OperatorPredicate<ChangeData> implements
+ ChangeDataSource {
+ private final Provider<ReviewDb> db;
+ private final Change.Id id;
+
+ LegacyChangeIdPredicate(Provider<ReviewDb> db, Change.Id id) {
+ super(ChangeQueryBuilder.FIELD_CHANGE, id.toString());
+ this.db = db;
+ this.id = id;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) {
+ return id.equals(object.getId());
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ Change c = db.get().changes().get(id);
+ if (c != null) {
+ return new ListResultSet<ChangeData>( //
+ Collections.singletonList(new ChangeData(c)));
+ } else {
+ return new ListResultSet<ChangeData>(Collections.<ChangeData> emptyList());
+ }
+ }
+
+ @Override
+ public boolean hasChange() {
+ return true;
+ }
+
+ @Override
+ public int getCardinality() {
+ return 1;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.IDS_MEMORY;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
new file mode 100644
index 0000000000..617a14ad6a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
@@ -0,0 +1,78 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.client.impl.ListResultSet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+class OrSource extends OrPredicate<ChangeData> implements ChangeDataSource {
+ private int cardinality = -1;
+
+ OrSource(final Collection<? extends Predicate<ChangeData>> that) {
+ super(that);
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ // TODO(spearce) This probably should be more lazy.
+ //
+ ArrayList<ChangeData> r = new ArrayList<ChangeData>();
+ HashSet<Change.Id> have = new HashSet<Change.Id>();
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof ChangeDataSource) {
+ for (ChangeData cd : ((ChangeDataSource) p).read()) {
+ if (have.add(cd.getId())) {
+ r.add(cd);
+ }
+ }
+ } else {
+ throw new OrmException("No ChangeDataSource: " + p);
+ }
+ }
+ return new ListResultSet<ChangeData>(r);
+ }
+
+ @Override
+ public boolean hasChange() {
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (!(p instanceof ChangeDataSource)
+ || !((ChangeDataSource) p).hasChange()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int getCardinality() {
+ if (cardinality < 0) {
+ cardinality = 0;
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof ChangeDataSource) {
+ cardinality += ((ChangeDataSource) p).getCardinality();
+ }
+ }
+ }
+ return cardinality;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
new file mode 100644
index 0000000000..224dce98e2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
@@ -0,0 +1,48 @@
+// 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 com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class OwnerPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final Account.Id id;
+
+ OwnerPredicate(Provider<ReviewDb> dbProvider, Account.Id id) {
+ super(ChangeQueryBuilder.FIELD_OWNER, id.toString());
+ this.dbProvider = dbProvider;
+ this.id = id;
+ }
+
+ Account.Id getAccountId() {
+ return id;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ return change != null && id.equals(change.getOwner());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..b046db620e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
@@ -0,0 +1,24 @@
+// 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 com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+
+interface Paginated {
+ int limit();
+
+ ResultSet<ChangeData> restart(ChangeData last) throws OrmException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
new file mode 100644
index 0000000000..91203d6b15
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
@@ -0,0 +1,51 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class ProjectPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+
+ ProjectPredicate(Provider<ReviewDb> dbProvider, String id) {
+ super(ChangeQueryBuilder.FIELD_PROJECT, id);
+ this.dbProvider = dbProvider;
+ }
+
+ Project.NameKey getValueKey() {
+ return new Project.NameKey(getValue());
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+
+ Project.NameKey p = change.getDest().getParentKey();
+ return p.equals(getValueKey());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..3433fa9a8c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -0,0 +1,330 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.events.ChangeAttribute;
+import com.google.gerrit.server.events.EventFactory;
+import com.google.gerrit.server.events.QueryStats;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gson.Gson;
+import com.google.gwtorm.client.OrmException;
+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.HashSet;
+import java.util.List;
+
+public class QueryProcessor {
+ private static final Logger log =
+ LoggerFactory.getLogger(QueryProcessor.class);
+
+ 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 int defaultLimit = 500;
+ private OutputFormat outputFormat = OutputFormat.TEXT;
+ private boolean includePatchSets;
+ private boolean includeCurrentPatchSet;
+
+ private OutputStream outputStream = DisabledOutputStream.INSTANCE;
+ private PrintWriter out;
+
+ @Inject
+ QueryProcessor(EventFactory eventFactory,
+ ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
+ ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db) {
+ this.eventFactory = eventFactory;
+ this.queryBuilder = queryBuilder.create(currentUser);
+ this.queryRewriter = queryRewriter;
+ this.db = db;
+ }
+
+ public void setIncludePatchSets(boolean on) {
+ includePatchSets = on;
+ }
+
+ public void setIncludeCurrentPatchSet(boolean on) {
+ includeCurrentPatchSet = 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 {
+ try {
+ final QueryStats stats = new QueryStats();
+ stats.runTimeMilliseconds = System.currentTimeMillis();
+
+ final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
+ Predicate<ChangeData> s = compileQuery(queryString, visibleToMe);
+ List<ChangeData> results = new ArrayList<ChangeData>();
+ HashSet<Change.Id> want = new HashSet<Change.Id>();
+ for (ChangeData d : ((ChangeDataSource) s).read()) {
+ if (d.hasChange()) {
+ // Checking visibleToMe here should be unnecessary, the
+ // query should have already performed it. But we don't
+ // want to trust the query rewriter that much yet.
+ //
+ if (visibleToMe.match(d)) {
+ results.add(d);
+ }
+ } else {
+ want.add(d.getId());
+ }
+ }
+
+ if (!want.isEmpty()) {
+ for (Change c : db.get().changes().get(want)) {
+ ChangeData d = new ChangeData(c);
+ if (visibleToMe.match(d)) {
+ results.add(d);
+ }
+ }
+ }
+
+ Collections.sort(results, new Comparator<ChangeData>() {
+ @Override
+ public int compare(ChangeData a, ChangeData b) {
+ return b.getChange().getSortKey().compareTo(
+ a.getChange().getSortKey());
+ }
+ });
+
+ int limit = limit(s);
+ if (limit < results.size()) {
+ results = results.subList(0, limit);
+ }
+
+ for (ChangeData d : results) {
+ ChangeAttribute c = eventFactory.asChangeAttribute(d.getChange());
+ eventFactory.extend(c, d.getChange());
+ eventFactory.addTrackingIds(c, d.trackingIds(db));
+
+ if (includePatchSets) {
+ eventFactory.addPatchSets(c, d.patches(db));
+ }
+
+ if (includeCurrentPatchSet) {
+ PatchSet current = d.currentPatchSet(db);
+ if (current != null) {
+ c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
+ eventFactory.addApprovals(c.currentPatchSet, //
+ d.approvalsFor(db, current.getId()));
+ }
+ }
+
+ show(c);
+ }
+
+ stats.rowCount = results.size();
+ stats.runTimeMilliseconds =
+ System.currentTimeMillis() - 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 int limit(Predicate<ChangeData> s) {
+ return queryBuilder.hasLimit(s) ? queryBuilder.getLimit(s) : defaultLimit;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Predicate<ChangeData> compileQuery(String queryString,
+ final Predicate<ChangeData> visibleToMe) throws QueryParseException {
+
+ Predicate<ChangeData> q = queryBuilder.parse(queryString);
+ if (!queryBuilder.hasLimit(q)) {
+ q = Predicate.and(q, queryBuilder.limit(defaultLimit));
+ }
+ if (!queryBuilder.hasSortKey(q)) {
+ q = Predicate.and(q, queryBuilder.sortkey_before("z"));
+ }
+ q = Predicate.and(q, visibleToMe);
+
+ Predicate<ChangeData> s = queryRewriter.rewrite(q);
+ if (!(s instanceof ChangeDataSource)) {
+ s = queryRewriter.rewrite(Predicate.and(queryBuilder.status_open(), q));
+ }
+
+ if (!(s instanceof ChangeDataSource)) {
+ throw new QueryParseException("cannot execute query: " + s);
+ }
+
+ return s;
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ indent(depth);
+ out.print(f.getName());
+ out.print(":");
+
+ if (val instanceof Long && isDateField(f.getName())) {
+ out.print(' ');
+ out.print(sdf.format(new Date(((Long) val) * 1000L)));
+ out.print('\n');
+ } else {
+ showTextValue(val, depth);
+ }
+ }
+ }
+
+ private void indent(int depth) {
+ for (int i = 0; i < depth; i++) {
+ out.print(" ");
+ }
+ }
+
+ @SuppressWarnings( {"cast", "unchecked"})
+ private void showTextValue(Object value, int depth) {
+ if (isPrimitive(value)) {
+ out.print(' ');
+ out.print(value);
+ out.print('\n');
+
+ } else if (value instanceof Collection) {
+ out.print('\n');
+ for (Object thing : ((Collection) value)) {
+ if (isPrimitive(thing)) {
+ out.print(' ');
+ out.print(value);
+ out.print('\n');
+ } else {
+ showText(thing, depth + 1);
+ out.print('\n');
+ }
+ }
+ } else {
+ out.print('\n');
+ showText(value, depth + 1);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ 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);
+ }
+
+ private List<Field> fieldsOf(Class<?> type) {
+ List<Field> r = new ArrayList<Field>();
+ 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/RefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
new file mode 100644
index 0000000000..f5f83e21bd
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
@@ -0,0 +1,44 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class RefPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+
+ RefPredicate(Provider<ReviewDb> dbProvider, String ref) {
+ super(ChangeQueryBuilder.FIELD_REF, ref);
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+ return getValue().equals(change.getDest().get());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
new file mode 100644
index 0000000000..6f159b8696
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
@@ -0,0 +1,55 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+import java.util.Collection;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+class RegexFilePredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> db;
+ private final PatchListCache cache;
+ private final Pattern pattern;
+
+ RegexFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String re) {
+ super(ChangeQueryBuilder.FIELD_FILE, re);
+ this.db = db;
+ this.cache = plc;
+ try {
+ this.pattern = Pattern.compile(re);
+ } catch (PatternSyntaxException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ Collection<String> files = object.currentFilePaths(db, cache);
+ if (files != null) {
+ for (String path : files) {
+ if (pattern.matcher(path).find()) {
+ return true;
+ }
+ }
+ return false;
+
+ } else {
+ // The ChangeData can't do expensive lookups right now. Bypass
+ // them and include the result anyway. We might be able to do
+ // a narrow later on to a smaller set.
+ //
+ return true;
+ }
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
new file mode 100644
index 0000000000..bcece94119
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -0,0 +1,52 @@
+// 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 com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class ReviewerPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final Account.Id id;
+
+ ReviewerPredicate(Provider<ReviewDb> dbProvider, Account.Id id) {
+ super(ChangeQueryBuilder.FIELD_REVIEWER, id.toString());
+ this.dbProvider = dbProvider;
+ this.id = id;
+ }
+
+ Account.Id getAccountId() {
+ return id;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ for (PatchSetApproval p : object.approvals(dbProvider)) {
+ if (id.equals(p.getAccountId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 2;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
new file mode 100644
index 0000000000..f8ab33f6ff
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
@@ -0,0 +1,55 @@
+// 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.server.query.change;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AuthConfig;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+final class SingleGroupUser extends CurrentUser {
+ private final Set<AccountGroup.Id> groups;
+
+ SingleGroupUser(AuthConfig authConfig, AccountGroup.Id groupId) {
+ this(authConfig, Collections.singleton(groupId));
+ }
+
+ SingleGroupUser(AuthConfig authConfig, Set<AccountGroup.Id> groups) {
+ super(AccessPath.UNKNOWN, authConfig);
+ this.groups = groups;
+ }
+
+ @Override
+ public Set<AccountGroup.Id> getEffectiveGroups() {
+ return groups;
+ }
+
+ @Override
+ public Set<Change.Id> getStarredChanges() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Collection<AccountProjectWatch> getNotificationFilters() {
+ return Collections.emptySet();
+ }
+}
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
new file mode 100644
index 0000000000..3ecc5969d8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
@@ -0,0 +1,47 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+abstract class SortKeyPredicate extends OperatorPredicate<ChangeData> {
+ protected final Provider<ReviewDb> dbProvider;
+
+ SortKeyPredicate(Provider<ReviewDb> dbProvider, String name, String value) {
+ super(name, value);
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+
+ static class Before extends SortKeyPredicate {
+ Before(Provider<ReviewDb> dbProvider, String value) {
+ super(dbProvider, "sortkey_before", value);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ Change change = cd.change(dbProvider);
+ return change != null && change.getSortKey().compareTo(getValue()) < 0;
+ }
+ }
+
+ static class After extends SortKeyPredicate {
+ After(Provider<ReviewDb> dbProvider, String value) {
+ super(dbProvider, "sortkey_after", value);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ Change change = cd.change(dbProvider);
+ return change != null && change.getSortKey().compareTo(getValue()) > 0;
+ }
+ }
+}
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
new file mode 100644
index 0000000000..7bc972d7c3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -0,0 +1,44 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+class TopicPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+
+ TopicPredicate(Provider<ReviewDb> dbProvider, String topic) {
+ super(ChangeQueryBuilder.FIELD_TOPIC, topic);
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+ return getValue().equals(change.getTopic());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
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
new file mode 100644
index 0000000000..eef568d874
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
@@ -0,0 +1,77 @@
+// 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 com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.TrackingId;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.client.impl.ListResultSet;
+import com.google.inject.Provider;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+class TrackingIdPredicate extends OperatorPredicate<ChangeData> implements
+ ChangeDataSource {
+ private final Provider<ReviewDb> db;
+
+ TrackingIdPredicate(Provider<ReviewDb> db, String trackingId) {
+ super(ChangeQueryBuilder.FIELD_TR, trackingId);
+ this.db = db;
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ for (TrackingId c : object.trackingIds(db)) {
+ if (getValue().equals(c.getTrackingId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ HashSet<Change.Id> ids = new HashSet<Change.Id>();
+ for (TrackingId sc : db.get().trackingIds() //
+ .byTrackingId(new TrackingId.Id(getValue()))) {
+ ids.add(sc.getChangeId());
+ }
+
+ ArrayList<ChangeData> r = new ArrayList<ChangeData>(ids.size());
+ for (Change.Id id : ids) {
+ r.add(new ChangeData(id));
+ }
+ return new ListResultSet<ChangeData>(r);
+ }
+
+ @Override
+ public boolean hasChange() {
+ return false;
+ }
+
+ @Override
+ public int getCardinality() {
+ return ChangeCosts.CARD_TRACKING_IDS;
+ }
+
+ @Override
+ public int getCost() {
+ return ChangeCosts.cost(ChangeCosts.TR_SCAN, getCardinality());
+ }
+}
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 da7d389dca..2f8d4ca86b 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
@@ -32,7 +32,7 @@ import java.util.List;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- private static final Class<? extends SchemaVersion> C = Schema_35.class;
+ private static final Class<? extends SchemaVersion> C = Schema_40.class;
public static class Module extends AbstractModule {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java
index 2f06f7027f..fa94146c9c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java
@@ -19,19 +19,33 @@ import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.RefRight.RefPattern;
-import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.project.RefControl.RefRightsForPattern;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class Schema_34 extends SchemaVersion {
+ private static final Comparator<String> DESCENDING_SORT =
+ new Comparator<String>() {
+
+ @Override
+ public int compare(String a, String b) {
+ int aLength = a.length();
+ int bLength = b.length();
+ if (bLength == aLength) {
+ return a.compareTo(b);
+ }
+ return bLength - aLength;
+ }
+ };
+
@Inject
Schema_34(Provider<Schema_33> prior) {
super(prior);
@@ -54,7 +68,7 @@ public class Schema_34 extends SchemaVersion {
ApprovalCategory.Id cat = right.getApprovalCategoryId();
if (r.get(cat) == null) {
Map<String, RefRightsForPattern> m =
- new TreeMap<String, RefRightsForPattern>(RefControl.DESCENDING_SORT);
+ new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
r.put(cat, m);
}
if (r.get(cat).get(right.getRefPattern()) == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java
new file mode 100644
index 0000000000..ba6b841f10
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java
@@ -0,0 +1,51 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectMySQL;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_36 extends SchemaVersion {
+ @Inject
+ Schema_36(Provider<Schema_35> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ if (((JdbcSchema) db).getDialect() instanceof DialectMySQL) {
+ stmt.execute("DROP INDEX account_project_watches_ntNew ON account_project_watches");
+ stmt.execute("DROP INDEX account_project_watches_ntCmt ON account_project_watches");
+ stmt.execute("DROP INDEX account_project_watches_ntSub ON account_project_watches");
+ } else {
+ stmt.execute("DROP INDEX account_project_watches_ntNew");
+ stmt.execute("DROP INDEX account_project_watches_ntCmt");
+ stmt.execute("DROP INDEX account_project_watches_ntSub");
+ }
+ stmt.execute("CREATE INDEX account_project_watches_byProject"
+ + " ON account_project_watches (project_name)");
+ } finally {
+ stmt.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java
new file mode 100644
index 0000000000..871f2e94bd
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java
@@ -0,0 +1,25 @@
+// 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.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_37 extends SchemaVersion {
+ @Inject
+ Schema_37(Provider<Schema_36> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java
new file mode 100644
index 0000000000..59d6fa24ea
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java
@@ -0,0 +1,67 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Schema_38 extends SchemaVersion {
+ @Inject
+ Schema_38(Provider<Schema_37> prior) {
+ super(prior);
+ }
+
+ /**
+ * Migrate the account.default_context column to account_diff_preferences.context column.
+ * <p>
+ * Other fields in account_diff_preferences will be filled in with their defaults as
+ * defined in the {@link AccountDiffPreference#createDefault(com.google.gerrit.reviewdb.Account.Id)}
+ */
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
+ SQLException {
+ List<AccountDiffPreference> newPrefs =
+ new ArrayList<AccountDiffPreference>();
+
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ ResultSet result =
+ stmt.executeQuery("SELECT account_id, default_context"
+ + " FROM accounts WHERE default_context <> 10");
+ while (result.next()) {
+ int accountId = result.getInt(1);
+ short defaultContext = result.getShort(2);
+ AccountDiffPreference diffPref = AccountDiffPreference.createDefault(new Account.Id(accountId));
+ diffPref.setContext(defaultContext);
+ newPrefs.add(diffPref);
+ }
+ } finally {
+ stmt.close();
+ }
+
+ db.accountDiffPreferences().insert(newPrefs);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java
new file mode 100644
index 0000000000..39ae226c38
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java
@@ -0,0 +1,25 @@
+// 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.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_39 extends SchemaVersion {
+ @Inject
+ Schema_39(Provider<Schema_38> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java
new file mode 100644
index 0000000000..7d3e4f53b5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java
@@ -0,0 +1,70 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectH2;
+import com.google.gwtorm.schema.sql.DialectMySQL;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.gwtorm.schema.sql.SqlDialect;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_40 extends SchemaVersion {
+ @Inject
+ Schema_40(Provider<Schema_39> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException,
+ OrmException {
+ // Set to "*" the filter field of the previously watched projects
+ //
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ stmt.execute("UPDATE account_project_watches" //
+ + " SET filter = '" + AccountProjectWatch.FILTER_ALL + "'" //
+ + " WHERE filter IS NULL OR filter = ''");
+
+ // Set the new primary key
+ //
+ final SqlDialect dialect = ((JdbcSchema) db).getDialect();
+ if (dialect instanceof DialectPostgreSQL) {
+ stmt.execute("ALTER TABLE account_project_watches "
+ + "DROP CONSTRAINT account_project_watches_pkey");
+ stmt.execute("ALTER TABLE account_project_watches "
+ + "ADD PRIMARY KEY (account_id, project_name, filter)");
+
+ } else if ((dialect instanceof DialectH2)
+ || (dialect instanceof DialectMySQL)) {
+ stmt.execute("ALTER TABLE account_project_watches DROP PRIMARY KEY");
+ stmt.execute("ALTER TABLE account_project_watches "
+ + "ADD PRIMARY KEY (account_id, project_name, filter)");
+
+ } else {
+ throw new OrmException("Unsupported dialect " + dialect);
+ }
+ } finally {
+ stmt.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
new file mode 100644
index 0000000000..589440dcea
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
@@ -0,0 +1,138 @@
+// 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.server.query;
+
+import static com.google.gerrit.server.query.Predicate.and;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class AndPredicateTest extends TestCase {
+ private static final class TestPredicate extends OperatorPredicate<String> {
+ private TestPredicate(String name, String value) {
+ super(name, value);
+ }
+
+ @Override
+ public boolean match(String object) {
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+ }
+
+ private static TestPredicate f(final String name, final String value) {
+ return new TestPredicate(name, value);
+ }
+
+ public void testChildren() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final Predicate<String> n = and(a, b);
+ assertEquals(2, n.getChildCount());
+ assertSame(a, n.getChild(0));
+ assertSame(b, n.getChild(1));
+ }
+
+ public void testChildrenUnmodifiable() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final Predicate<String> n = and(a, b);
+
+ try {
+ n.getChildren().clear();
+ } catch (RuntimeException e) {
+ }
+ assertChildren("clear", n, list(a, b));
+
+ try {
+ n.getChildren().remove(0);
+ } catch (RuntimeException e) {
+ }
+ assertChildren("remove(0)", n, list(a, b));
+
+ try {
+ n.getChildren().iterator().remove();
+ } catch (RuntimeException e) {
+ }
+ assertChildren("remove(0)", n, list(a, b));
+ }
+
+ private static void assertChildren(String o, Predicate<String> p,
+ final List<Predicate<String>> l) {
+ assertEquals(o + " did not affect child", l, p.getChildren());
+ }
+
+ public void testToString() {
+ final TestPredicate a = f("q", "alice");
+ final TestPredicate b = f("q", "bob");
+ final TestPredicate c = f("q", "charlie");
+ assertEquals("(q:alice q:bob)", and(a, b).toString());
+ assertEquals("(q:alice q:bob q:charlie)", and(a, b, c).toString());
+ }
+
+ public void testEquals() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final TestPredicate c = f("author", "charlie");
+
+ assertTrue(and(a, b).equals(and(a, b)));
+ assertTrue(and(a, b, c).equals(and(a, b, c)));
+
+ assertFalse(and(a, b).equals(and(b, a)));
+ assertFalse(and(a, c).equals(and(a, b)));
+
+ assertFalse(and(a, c).equals(a));
+ }
+
+ public void testHashCode() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final TestPredicate c = f("author", "charlie");
+
+ assertTrue(and(a, b).hashCode() == and(a, b).hashCode());
+ assertTrue(and(a, b, c).hashCode() == and(a, b, c).hashCode());
+ assertFalse(and(a, c).hashCode() == and(a, b).hashCode());
+ }
+
+ public void testCopy() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final TestPredicate c = f("author", "charlie");
+ final List<Predicate<String>> s2 = list(a, b);
+ final List<Predicate<String>> s3 = list(a, b, c);
+ final Predicate<String> n2 = and(a, b);
+
+ assertNotSame(n2, n2.copy(s2));
+ assertEquals(s2, n2.copy(s2).getChildren());
+ assertEquals(s3, n2.copy(s3).getChildren());
+
+ try {
+ n2.copy(Collections.<Predicate<String>> emptyList());
+ } catch (IllegalArgumentException e) {
+ assertEquals("Need at least two predicates", e.getMessage());
+ }
+ }
+
+ private static List<Predicate<String>> list(final Predicate<String>... predicates) {
+ return Arrays.asList(predicates);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/ChangeQueryBuilderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/ChangeQueryBuilderTest.java
deleted file mode 100644
index 4dc8ba5ad8..0000000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/ChangeQueryBuilderTest.java
+++ /dev/null
@@ -1,210 +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.server.query;
-
-import static com.google.gerrit.server.query.ChangeQueryBuilder.FIELD_CHANGE;
-import static com.google.gerrit.server.query.ChangeQueryBuilder.FIELD_COMMIT;
-import static com.google.gerrit.server.query.ChangeQueryBuilder.FIELD_OWNER;
-import static com.google.gerrit.server.query.ChangeQueryBuilder.FIELD_REVIEWER;
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
-import static com.google.gerrit.server.query.Predicate.or;
-
-import junit.framework.TestCase;
-
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-
-public class ChangeQueryBuilderTest extends TestCase {
- private static OperatorPredicate f(final String name, final String value) {
- return new OperatorPredicate(name, value);
- }
-
- private static Predicate owner(final String who) {
- return f(FIELD_OWNER, who);
- }
-
- private static Predicate reviewer(final String who) {
- return f(FIELD_REVIEWER, who);
- }
-
- private static Predicate commit(final String idstr) {
- final AbbreviatedObjectId id = AbbreviatedObjectId.fromString(idstr);
- return new ObjectIdPredicate(FIELD_COMMIT, id);
- }
-
- private static Predicate p(final String str) throws QueryParseException {
- return new ChangeQueryBuilder().parse(str);
- }
-
- public void testEmptyQuery() {
- try {
- p("");
- fail("expected exception");
- } catch (QueryParseException e) {
- assertEquals("line 0:-1 no viable alternative at input '<EOF>'", e
- .getMessage());
- }
- }
-
- public void testFailInvalidOperator() {
- final String op = "thiswillneverbeaqueryoperatoritistoolongtotype";
- final String val = "true";
- try {
- p(op + ":" + val);
- fail("expected exception");
- } catch (QueryParseException e) {
- assertEquals("Unsupported operator " + op + ":" + val, e.getMessage());
- }
- }
-
- public void testFailNestedOperator() {
- try {
- p("commit:(foo:bar whiz:bang)");
- fail("expected exception");
- } catch (QueryParseException e) {
- assertEquals("Nested operator not expected: foo", e.getMessage());
- }
- }
-
- // commit:
-
- public void testDefaultSHA1() throws QueryParseException {
- assertEquals(commit("6ea15"), p("6ea15"));
- assertEquals(commit("6ea15"), p("6EA15"));
- assertEquals(commit("6ea15b73668073fd9f70b2635efcb8cf8aabda22"),
- p("6ea15b73668073fd9f70b2635efcb8cf8aabda22"));
- }
-
- public void testCommitSHA1() throws QueryParseException {
- assertEquals(commit("6ea15"), p("commit:6ea15"));
- assertEquals(commit("6ea15"), p("commit:6EA15")); // note: forces lowercase
- assertEquals(commit("6ea15b73668073fd9f70b2635efcb8cf8aabda22"),
- p("commit:6ea15b73668073fd9f70b2635efcb8cf8aabda22"));
-
- try {
- p("commit:yonothash");
- } catch (QueryParseException e) {
- assertEquals("Error in operator commit:yonothash", e.getMessage());
- }
- }
-
- // change:
-
- public void testDefaultChangeID() throws QueryParseException {
- assertEquals(f(FIELD_CHANGE, "1234"), p("1234"));
- }
-
- public void testChangeID() throws QueryParseException {
- assertEquals(f(FIELD_CHANGE, "1234"), p("change:1234"));
- }
-
- // owner:
-
- public void testOwnerBare() throws QueryParseException {
- assertEquals(owner("bob"), p("owner:bob"));
- assertEquals(owner("Bob"), p("owner:Bob"));
- assertEquals(owner("bob@example.com"), p("owner:bob@example.com"));
-
- assertEquals(owner("bob"), p("owner: bob"));
- assertEquals(owner("Bob"), p("owner: Bob"));
- assertEquals(owner("bob@example.com"), p("owner: bob@example.com"));
-
- assertEquals(owner("bob"), p("owner:\tbob"));
- assertEquals(owner("Bob"), p("owner:\tBob"));
- assertEquals(owner("bob@example.com"), p("owner:\tbob@example.com"));
- }
-
- public void testOwnerQuoted() throws QueryParseException {
- assertEquals(owner("bob"), p("owner:\"bob\""));
- assertEquals(owner("bob@example.com"), p("owner:\"bob@example.com\""));
- assertEquals(owner("<bob@example.com>"), p("owner:\"<bob@example.com>\""));
- assertEquals(owner("A U Thor"), p("owner:\"A U Thor\""));
-
- assertEquals(owner("bob"), p("owner: \"bob\""));
- assertEquals(owner("bob@example.com"), p("owner: \"bob@example.com\""));
- assertEquals(owner("<bob@example.com>"), p("owner: \"<bob@example.com>\""));
- assertEquals(owner("A U Thor"), p("owner: \"A U Thor\""));
-
- assertEquals(owner("bob"), p("owner:\t\"bob\""));
- assertEquals(owner("bob@example.com"), p("owner:\t\"bob@example.com\""));
- assertEquals(owner("<bob@example.com>"), p("owner:\t\"<bob@example.com>\""));
- assertEquals(owner("A U Thor"), p("owner:\t\"A U Thor\""));
- }
-
- public void testOwner_NOT() throws QueryParseException {
- assertEquals(not(owner("bob")), p("-owner:bob"));
- assertEquals(not(owner("Bob")), p("-owner:Bob"));
- assertEquals(not(owner("bob@example.com")), p("-owner:bob@example.com"));
-
- assertEquals(not(owner("bob")), p("NOT owner:bob"));
- assertEquals(not(owner("Bob")), p("NOT owner:Bob"));
- assertEquals(not(owner("bob@example.com")), p("NOT owner:bob@example.com"));
- }
-
- // AND
-
- public void testAND_Styles2() throws QueryParseException {
- final Predicate exp = and(commit("6ea15"), owner("bob"));
- assertEquals(exp, p("6ea15 owner:bob"));
- assertEquals(exp, p("6ea15 AND owner:bob"));
- }
-
- public void testAND_Styles3() throws QueryParseException {
- final Predicate exp = and(commit("6ea15"), owner("bob"), reviewer("alice"));
- assertEquals(exp, p("6ea15 owner:bob reviewer:alice"));
- assertEquals(exp, p("6ea15 AND owner:bob reviewer:alice"));
- assertEquals(exp, p("6ea15 owner:bob AND reviewer:alice"));
- assertEquals(exp, p("6ea15 AND owner:bob AND reviewer:alice"));
- }
-
- public void testAND_ManyValuesOneOperator() throws QueryParseException {
- final Predicate exp =
- and(reviewer("alice"), reviewer("bob"), reviewer("charlie"));
- assertEquals(exp, p("reviewer:(alice bob charlie)"));
- assertEquals(exp, p("reviewer:(alice AND bob charlie)"));
- assertEquals(exp, p("reviewer:(alice bob AND charlie)"));
- assertEquals(exp, p("reviewer:(alice AND bob AND charlie)"));
- }
-
- public void testAND_FlattensOperators() throws QueryParseException {
- final Predicate exp =
- and(reviewer("alice"), reviewer("bob"), reviewer("charlie"));
- assertEquals(exp, p("reviewer:alice reviewer:(bob charlie)"));
- }
-
- // OR
-
- public void testOR_2() throws QueryParseException {
- final Predicate exp = or(commit("6ea15"), owner("bob"));
- assertEquals(exp, p("6ea15 OR owner:bob"));
- }
-
- public void testOR_3() throws QueryParseException {
- final Predicate exp = or(commit("6ea15"), owner("bob"), reviewer("alice"));
- assertEquals(exp, p("6ea15 OR owner:bob OR reviewer:alice"));
- }
-
- public void testOR_ManyValuesOneOperator() throws QueryParseException {
- final Predicate exp =
- or(reviewer("alice"), reviewer("bob"), reviewer("charlie"));
- assertEquals(exp, p("reviewer:(alice OR bob OR charlie)"));
- }
-
- public void testOR_FlattensOperators() throws QueryParseException {
- final Predicate exp =
- or(reviewer("alice"), reviewer("bob"), reviewer("charlie"));
- assertEquals(exp, p("reviewer:alice OR reviewer:(bob OR charlie)"));
- }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
index 63cf166951..a37a336828 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
@@ -16,9 +16,27 @@ package com.google.gerrit.server.query;
import junit.framework.TestCase;
+import java.util.Collections;
+
public class FieldPredicateTest extends TestCase {
- private static OperatorPredicate f(final String name, final String value) {
- return new OperatorPredicate(name, value);
+ private static final class TestPredicate extends OperatorPredicate<String> {
+ private TestPredicate(String name, String value) {
+ super(name, value);
+ }
+
+ @Override
+ public boolean match(String object) {
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+ }
+
+ private static TestPredicate f(final String name, final String value) {
+ return new TestPredicate(name, value);
}
public void testToString() {
@@ -42,9 +60,21 @@ public class FieldPredicateTest extends TestCase {
public void testNameValue() {
final String name = "author";
final String value = "alice";
- final OperatorPredicate f = f(name, value);
+ final OperatorPredicate<String> f = f(name, value);
assertSame(name, f.getOperator());
assertSame(value, f.getValue());
assertEquals(0, f.getChildren().size());
}
+
+ public void testCopy() {
+ final OperatorPredicate<String> f = f("author", "alice");
+ assertSame(f, f.copy(Collections.<Predicate<String>> emptyList()));
+ assertSame(f, f.copy(f.getChildren()));
+
+ try {
+ f.copy(Collections.singleton(f("owner", "bob")));
+ } catch (IllegalArgumentException e) {
+ assertEquals("Expected 0 children", e.getMessage());
+ }
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
index 23ba24ed81..8e86e95231 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
@@ -14,33 +14,53 @@
package com.google.gerrit.server.query;
+import static com.google.gerrit.server.query.Predicate.and;
import static com.google.gerrit.server.query.Predicate.not;
import junit.framework.TestCase;
+import java.util.Collections;
+import java.util.List;
+
public class NotPredicateTest extends TestCase {
- private static OperatorPredicate f(final String name, final String value) {
- return new OperatorPredicate(name, value);
+ private static final class TestPredicate extends OperatorPredicate<String> {
+ private TestPredicate(String name, String value) {
+ super(name, value);
+ }
+
+ @Override
+ public boolean match(String object) {
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+ }
+
+ private static TestPredicate f(final String name, final String value) {
+ return new TestPredicate(name, value);
}
public void testNotNot() {
- final OperatorPredicate p = f("author", "bob");
- final Predicate n = p.not();
+ final TestPredicate p = f("author", "bob");
+ final Predicate n = not(p);
assertTrue(n instanceof NotPredicate);
assertNotSame(p, n);
- assertSame(p, n.not());
+ assertSame(p, not(n));
}
public void testChildren() {
- final OperatorPredicate p = f("author", "bob");
- final Predicate n = p.not();
+ final TestPredicate p = f("author", "bob");
+ final Predicate n = not(p);
assertEquals(1, n.getChildCount());
assertSame(p, n.getChild(0));
}
public void testChildrenUnmodifiable() {
- final OperatorPredicate p = f("author", "bob");
- final Predicate n = p.not();
+ final TestPredicate p = f("author", "bob");
+ final Predicate n = not(p);
try {
n.getChildren().clear();
@@ -81,4 +101,30 @@ public class NotPredicateTest extends TestCase {
assertTrue(not(f("a", "b")).hashCode() == not(f("a", "b")).hashCode());
assertFalse(not(f("a", "b")).hashCode() == not(f("a", "a")).hashCode());
}
+
+ public void testCopy() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final List<TestPredicate> sa = Collections.singletonList(a);
+ final List<TestPredicate> sb = Collections.singletonList(b);
+ final Predicate n = not(a);
+
+ assertNotSame(n, n.copy(sa));
+ assertEquals(sa, n.copy(sa).getChildren());
+
+ assertNotSame(n, n.copy(sb));
+ assertEquals(sb, n.copy(sb).getChildren());
+
+ try {
+ n.copy(Collections.<Predicate> emptyList());
+ } catch (IllegalArgumentException e) {
+ assertEquals("Expected exactly one child", e.getMessage());
+ }
+
+ try {
+ n.copy(and(a, b).getChildren());
+ } catch (IllegalArgumentException e) {
+ assertEquals("Expected exactly one child", e.getMessage());
+ }
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
new file mode 100644
index 0000000000..be820b92b9
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
@@ -0,0 +1,138 @@
+// 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.server.query;
+
+import static com.google.gerrit.server.query.Predicate.or;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class OrPredicateTest extends TestCase {
+ private static final class TestPredicate extends OperatorPredicate<String> {
+ private TestPredicate(String name, String value) {
+ super(name, value);
+ }
+
+ @Override
+ public boolean match(String object) {
+ return false;
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+ }
+
+ private static TestPredicate f(final String name, final String value) {
+ return new TestPredicate(name, value);
+ }
+
+ public void testChildren() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final Predicate n = or(a, b);
+ assertEquals(2, n.getChildCount());
+ assertSame(a, n.getChild(0));
+ assertSame(b, n.getChild(1));
+ }
+
+ public void testChildrenUnmodifiable() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final Predicate n = or(a, b);
+
+ try {
+ n.getChildren().clear();
+ } catch (RuntimeException e) {
+ }
+ assertChildren("clear", n, list(a, b));
+
+ try {
+ n.getChildren().remove(0);
+ } catch (RuntimeException e) {
+ }
+ assertChildren("remove(0)", n, list(a, b));
+
+ try {
+ n.getChildren().iterator().remove();
+ } catch (RuntimeException e) {
+ }
+ assertChildren("remove(0)", n, list(a, b));
+ }
+
+ private static void assertChildren(String o, Predicate p,
+ final List<Predicate> l) {
+ assertEquals(o + " did not affect child", l, p.getChildren());
+ }
+
+ public void testToString() {
+ final TestPredicate a = f("q", "alice");
+ final TestPredicate b = f("q", "bob");
+ final TestPredicate c = f("q", "charlie");
+ assertEquals("(q:alice OR q:bob)", or(a, b).toString());
+ assertEquals("(q:alice OR q:bob OR q:charlie)", or(a, b, c).toString());
+ }
+
+ public void testEquals() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final TestPredicate c = f("author", "charlie");
+
+ assertTrue(or(a, b).equals(or(a, b)));
+ assertTrue(or(a, b, c).equals(or(a, b, c)));
+
+ assertFalse(or(a, b).equals(or(b, a)));
+ assertFalse(or(a, c).equals(or(a, b)));
+
+ assertFalse(or(a, c).equals(a));
+ }
+
+ public void testHashCode() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final TestPredicate c = f("author", "charlie");
+
+ assertTrue(or(a, b).hashCode() == or(a, b).hashCode());
+ assertTrue(or(a, b, c).hashCode() == or(a, b, c).hashCode());
+ assertFalse(or(a, c).hashCode() == or(a, b).hashCode());
+ }
+
+ public void testCopy() {
+ final TestPredicate a = f("author", "alice");
+ final TestPredicate b = f("author", "bob");
+ final TestPredicate c = f("author", "charlie");
+ final List<Predicate> s2 = list(a, b);
+ final List<Predicate> s3 = list(a, b, c);
+ final Predicate n2 = or(a, b);
+
+ assertNotSame(n2, n2.copy(s2));
+ assertEquals(s2, n2.copy(s2).getChildren());
+ assertEquals(s3, n2.copy(s3).getChildren());
+
+ try {
+ n2.copy(Collections.<Predicate> emptyList());
+ } catch (IllegalArgumentException e) {
+ assertEquals("Need at least two predicates", e.getMessage());
+ }
+ }
+
+ private static List<Predicate> list(final Predicate... predicates) {
+ return Arrays.asList(predicates);
+ }
+}
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 41a34af514..1644d22ed2 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
@@ -23,7 +23,7 @@ import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.workflow.NoOpFunction;
import com.google.gerrit.server.workflow.SubmitFunction;
-import com.google.gerrit.testutil.TestDatabase;
+import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -36,17 +36,17 @@ import java.util.HashSet;
public class SchemaCreatorTest extends TestCase {
private ApprovalCategory.Id codeReview = new ApprovalCategory.Id("CRVW");
- private TestDatabase db;
+ private InMemoryDatabase db;
@Override
protected void setUp() throws Exception {
super.setUp();
- db = new TestDatabase();
+ db = new InMemoryDatabase();
}
@Override
protected void tearDown() throws Exception {
- TestDatabase.drop(db);
+ InMemoryDatabase.drop(db);
super.tearDown();
}
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 1e01674699..a009f24bb4 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
@@ -17,7 +17,7 @@ package com.google.gerrit.server.schema;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.testutil.TestDatabase;
+import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.gwtorm.client.StatementExecutor;
@@ -33,17 +33,17 @@ import java.util.List;
import java.util.UUID;
public class SchemaUpdaterTest extends TestCase {
- private TestDatabase db;
+ private InMemoryDatabase db;
@Override
protected void setUp() throws Exception {
super.setUp();
- db = new TestDatabase();
+ db = new InMemoryDatabase();
}
@Override
protected void tearDown() throws Exception {
- TestDatabase.drop(db);
+ InMemoryDatabase.drop(db);
super.tearDown();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
index e6aecc8926..9e6604670e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.util;
-import static com.google.gerrit.server.util.SocketUtil.format;
import static com.google.gerrit.server.util.SocketUtil.hostname;
import static com.google.gerrit.server.util.SocketUtil.isIPv6;
import static com.google.gerrit.server.util.SocketUtil.parse;
@@ -48,21 +47,21 @@ public class SocketUtilTest extends TestCase {
}
public void testFormat() throws UnknownHostException {
- assertEquals("*:1234", format(new InetSocketAddress(1234), 80));
- assertEquals("*", format(new InetSocketAddress(80), 80));
+ assertEquals("*:1234", SocketUtil.format(new InetSocketAddress(1234), 80));
+ assertEquals("*", SocketUtil.format(new InetSocketAddress(80), 80));
- assertEquals("foo:1234", format(createUnresolved("foo", 1234), 80));
- assertEquals("foo", format(createUnresolved("foo", 80), 80));
+ assertEquals("foo:1234", SocketUtil.format(createUnresolved("foo", 1234), 80));
+ assertEquals("foo", SocketUtil.format(createUnresolved("foo", 80), 80));
assertEquals("[1:2:3:4:5:6:7:8]:1234",//
- format(new InetSocketAddress(getByName("1:2:3:4:5:6:7:8"), 1234), 80));
+ SocketUtil.format(new InetSocketAddress(getByName("1:2:3:4:5:6:7:8"), 1234), 80));
assertEquals("[1:2:3:4:5:6:7:8]",//
- format(new InetSocketAddress(getByName("1:2:3:4:5:6:7:8"), 80), 80));
+ SocketUtil.format(new InetSocketAddress(getByName("1:2:3:4:5:6:7:8"), 80), 80));
assertEquals("localhost:1234",//
- format(new InetSocketAddress("localhost", 1234), 80));
+ SocketUtil.format(new InetSocketAddress("localhost", 1234), 80));
assertEquals("localhost",//
- format(new InetSocketAddress("localhost", 80), 80));
+ SocketUtil. format(new InetSocketAddress("localhost", 80), 80));
}
public void testParse() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
index 1f71a917d5..fe138c6ef6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
@@ -43,10 +43,10 @@ import javax.sql.DataSource;
* <p>
* Test classes should create one instance of this class for each unique test
* database they want to use. When the tests needing this instance are complete,
- * ensure that {@link #drop(TestDatabase)} is called to free the resources so
+ * ensure that {@link #drop(InMemoryDatabase)} is called to free the resources so
* the JVM running the unit tests doesn't run out of heap space.
*/
-public class TestDatabase implements SchemaFactory<ReviewDb> {
+public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
private static int dbCnt;
private static synchronized DataSource newDataSource() throws SQLException {
@@ -58,7 +58,7 @@ public class TestDatabase implements SchemaFactory<ReviewDb> {
}
/** Drop the database from memory; does nothing if the instance was null. */
- public static void drop(final TestDatabase db) {
+ public static void drop(final InMemoryDatabase db) {
if (db != null) {
db.drop();
}
@@ -69,7 +69,7 @@ public class TestDatabase implements SchemaFactory<ReviewDb> {
private boolean created;
private SchemaVersion schemaVersion;
- public TestDatabase() throws OrmException {
+ public InMemoryDatabase() throws OrmException {
try {
final DataSource dataSource = newDataSource();
@@ -101,7 +101,7 @@ public class TestDatabase implements SchemaFactory<ReviewDb> {
}
/** Ensure the database schema has been created and initialized. */
- public TestDatabase create() throws OrmException {
+ public InMemoryDatabase create() throws OrmException {
if (!created) {
created = true;
final ReviewDb c = open();
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 004ad83c69..2bb4713c7e 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-sshd</artifactId>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java
deleted file mode 100644
index a346ab6087..0000000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.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.sshd;
-
-import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.server.AccessPath;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.sshd.SshScope.Context;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-import org.apache.mina.core.future.IoFuture;
-import org.apache.mina.core.future.IoFutureListener;
-import org.apache.sshd.server.PasswordAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
-
-import java.net.SocketAddress;
-
-/**
- * Authenticates by password through {@link AccountExternalId} entities.
- */
-@Singleton
-class DatabasePasswordAuth implements PasswordAuthenticator {
- private final AccountCache accountCache;
- private final SshLog log;
- private final IdentifiedUser.GenericFactory userFactory;
-
- @Inject
- DatabasePasswordAuth(final AccountCache ac, final SshLog l,
- final IdentifiedUser.GenericFactory uf) {
- accountCache = ac;
- log = l;
- userFactory = uf;
- }
-
- @Override
- public boolean authenticate(final String username, final String password,
- final ServerSession session) {
- final SshSession sd = session.getAttribute(SshSession.KEY);
-
- AccountState state = accountCache.getByUsername(username);
- if (state == null) {
- sd.authenticationError(username, "user-not-found");
- return false;
- }
-
- final String p = state.getPassword(username);
- if (p == null) {
- sd.authenticationError(username, "no-password");
- return false;
- }
-
- if (!p.equals(password)) {
- sd.authenticationError(username, "incorrect-password");
- return false;
- }
-
- if (sd.getCurrentUser() == null) {
- sd.authenticationSuccess(username, createUser(sd, state));
-
- // If this is the first time we've authenticated this
- // session, record a login event in the log and add
- // a close listener to record a logout event.
- //
- Context ctx = new Context(sd, null);
- Context old = SshScope.set(ctx);
- try {
- log.onLogin();
- } finally {
- SshScope.set(old);
- }
-
- session.getIoSession().getCloseFuture().addListener(
- new IoFutureListener<IoFuture>() {
- @Override
- public void operationComplete(IoFuture future) {
- final Context ctx = new Context(sd, null);
- final Context old = SshScope.set(ctx);
- try {
- log.onLogout();
- } finally {
- SshScope.set(old);
- }
- }
- });
- }
-
- return true;
- }
-
- private IdentifiedUser createUser(final SshSession sd,
- final AccountState state) {
- return userFactory.create(AccessPath.SSH_COMMAND,
- new Provider<SocketAddress>() {
- @Override
- public SocketAddress get() {
- return sd.getRemoteAddress();
- }
- }, state.getAccount().getId());
- }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
index e2797ef504..5839cf17dc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -22,14 +23,20 @@ import com.google.inject.Singleton;
@Singleton
class SshCurrentUserProvider implements Provider<CurrentUser> {
private final Provider<SshSession> session;
+ private final Provider<IdentifiedUser> identifiedProvider;
@Inject
- SshCurrentUserProvider(final Provider<SshSession> s) {
+ SshCurrentUserProvider(Provider<SshSession> s, Provider<IdentifiedUser> p) {
session = s;
+ identifiedProvider = p;
}
@Override
public CurrentUser get() {
+ final CurrentUser user = session.get().getCurrentUser();
+ if (user instanceof IdentifiedUser) {
+ return identifiedProvider.get();
+ }
return session.get().getCurrentUser();
}
}
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 35fd229c0f..2636ff2597 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
@@ -59,10 +59,8 @@ import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.ForwardingFilter;
-import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.UserAuth;
-import org.apache.sshd.server.auth.UserAuthPassword;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.channel.ChannelDirectTcpip;
import org.apache.sshd.server.channel.ChannelSession;
@@ -119,7 +117,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
@Inject
SshDaemon(final CommandFactory commandFactory,
- final PasswordAuthenticator passAuth,
final PublickeyAuthenticator userAuth,
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
@GerritServerConfig final Config cfg, final SshLog sshLog) {
@@ -141,7 +138,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
initForwardingFilter();
initSubsystems();
initCompression();
- initUserAuth(passAuth, userAuth);
+ initUserAuth(userAuth);
setKeyPairProvider(hostKeyProvider);
setCommandFactory(commandFactory);
setShellFactory(new NoShell());
@@ -459,11 +456,9 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
@SuppressWarnings("unchecked")
- private void initUserAuth(final PasswordAuthenticator pass,
- final PublickeyAuthenticator pubkey) {
- setUserAuthFactories(Arrays.<NamedFactory<UserAuth>> asList(
- new UserAuthPublicKey.Factory(), new UserAuthPassword.Factory()));
- setPasswordAuthenticator(pass);
+ private void initUserAuth(final PublickeyAuthenticator pubkey) {
+ setUserAuthFactories(Arrays
+ .<NamedFactory<UserAuth>> asList(new UserAuthPublicKey.Factory()));
setPublickeyAuthenticator(pubkey);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
index f3da92c39e..516b59cb43 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
@@ -25,17 +25,21 @@ import com.google.inject.Singleton;
@Singleton
class SshIdentifiedUserProvider implements Provider<IdentifiedUser> {
private final Provider<SshSession> session;
+ private final IdentifiedUser.RequestFactory factory;
@Inject
- SshIdentifiedUserProvider(final Provider<SshSession> s) {
+ SshIdentifiedUserProvider(Provider<SshSession> s,
+ IdentifiedUser.RequestFactory f) {
session = s;
+ factory = f;
}
@Override
public IdentifiedUser get() {
final CurrentUser user = session.get().getCurrentUser();
if (user instanceof IdentifiedUser) {
- return (IdentifiedUser) user;
+ return factory.create(user.getAccessPath(), //
+ ((IdentifiedUser) user).getAccountId());
}
throw new ProvisionException(NotSignedInException.MESSAGE,
new NotSignedInException());
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 7417cfcc7f..c5f64f746e 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
@@ -22,7 +22,7 @@ import com.google.gerrit.reviewdb.AccountSshKey;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
@@ -59,7 +59,7 @@ public class SshKeyCacheImpl implements SshKeyCache {
protected void configure() {
final TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>> type =
new TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>>() {};
- core(type, CACHE_NAME);
+ core(type, CACHE_NAME).populateWith(Loader.class);
bind(SshKeyCacheImpl.class);
bind(SshKeyCache.class).to(SshKeyCacheImpl.class);
}
@@ -71,33 +71,20 @@ public class SshKeyCacheImpl implements SshKeyCache {
.asList(new SshKeyCacheEntry[0]));
}
- private final SchemaFactory<ReviewDb> schema;
- private final SelfPopulatingCache<String, Iterable<SshKeyCacheEntry>> self;
+ private final Cache<String, Iterable<SshKeyCacheEntry>> cache;
@Inject
- SshKeyCacheImpl(final SchemaFactory<ReviewDb> schema,
- @Named(CACHE_NAME) final Cache<String, Iterable<SshKeyCacheEntry>> raw) {
- this.schema = schema;
- self = new SelfPopulatingCache<String, Iterable<SshKeyCacheEntry>>(raw) {
- @Override
- protected Iterable<SshKeyCacheEntry> createEntry(final String username)
- throws Exception {
- return lookup(username);
- }
-
- @Override
- protected Iterable<SshKeyCacheEntry> missing(final String username) {
- return Collections.emptyList();
- }
- };
+ SshKeyCacheImpl(
+ @Named(CACHE_NAME) final Cache<String, Iterable<SshKeyCacheEntry>> cache) {
+ this.cache = cache;
}
public Iterable<SshKeyCacheEntry> get(String username) {
- return self.get(username);
+ return cache.get(username);
}
public void evict(String username) {
- self.remove(username);
+ cache.remove(username);
}
@Override
@@ -120,52 +107,68 @@ public class SshKeyCacheImpl implements SshKeyCache {
}
}
- private Iterable<SshKeyCacheEntry> lookup(final String username)
- throws Exception {
- final ReviewDb db = schema.open();
- try {
- final AccountExternalId.Key key =
- new AccountExternalId.Key(SCHEME_USERNAME, username);
- final AccountExternalId user = db.accountExternalIds().get(key);
- if (user == null) {
- return NO_SUCH_USER;
- }
+ static class Loader extends EntryCreator<String, Iterable<SshKeyCacheEntry>> {
+ private final SchemaFactory<ReviewDb> schema;
- final List<SshKeyCacheEntry> kl = new ArrayList<SshKeyCacheEntry>(4);
- for (AccountSshKey k : db.accountSshKeys().byAccount(user.getAccountId())) {
- if (k.isValid()) {
- add(db, kl, k);
+ @Inject
+ Loader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public Iterable<SshKeyCacheEntry> createEntry(String username)
+ throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ final AccountExternalId.Key key =
+ new AccountExternalId.Key(SCHEME_USERNAME, username);
+ final AccountExternalId user = db.accountExternalIds().get(key);
+ if (user == null) {
+ return NO_SUCH_USER;
}
+
+ final List<SshKeyCacheEntry> kl = new ArrayList<SshKeyCacheEntry>(4);
+ for (AccountSshKey k : db.accountSshKeys().byAccount(
+ user.getAccountId())) {
+ if (k.isValid()) {
+ add(db, kl, k);
+ }
+ }
+ if (kl.isEmpty()) {
+ return NO_KEYS;
+ }
+ return Collections.unmodifiableList(kl);
+ } finally {
+ db.close();
}
- if (kl.isEmpty()) {
- return NO_KEYS;
- }
- return Collections.unmodifiableList(kl);
- } finally {
- db.close();
}
- }
- private void add(ReviewDb db, List<SshKeyCacheEntry> kl, AccountSshKey k) {
- try {
- kl.add(new SshKeyCacheEntry(k.getKey(), SshUtil.parse(k)));
- } catch (OutOfMemoryError e) {
- // This is the only case where we assume the problem has nothing
- // to do with the key object, and instead we must abort this load.
- //
- throw e;
- } catch (Throwable e) {
- markInvalid(db, k);
+ @Override
+ public Iterable<SshKeyCacheEntry> missing(String username) {
+ return Collections.emptyList();
}
- }
- private void markInvalid(final ReviewDb db, final AccountSshKey k) {
- try {
- log.info("Flagging SSH key " + k.getKey() + " invalid");
- k.setInvalid();
- db.accountSshKeys().update(Collections.singleton(k));
- } catch (OrmException e) {
- log.error("Failed to mark SSH key" + k.getKey() + " invalid", e);
+ private void add(ReviewDb db, List<SshKeyCacheEntry> kl, AccountSshKey k) {
+ try {
+ kl.add(new SshKeyCacheEntry(k.getKey(), SshUtil.parse(k)));
+ } catch (OutOfMemoryError e) {
+ // This is the only case where we assume the problem has nothing
+ // to do with the key object, and instead we must abort this load.
+ //
+ throw e;
+ } catch (Throwable e) {
+ markInvalid(db, k);
+ }
+ }
+
+ private void markInvalid(final ReviewDb db, final AccountSshKey k) {
+ try {
+ log.info("Flagging SSH key " + k.getKey() + " invalid");
+ k.setInvalid();
+ db.accountSshKeys().update(Collections.singleton(k));
+ } catch (OrmException e) {
+ log.error("Failed to mark SSH key" + k.getKey() + " invalid", e);
+ }
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 15fc093e76..40d271b975 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -40,14 +40,12 @@ import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.OptionHandlerFactory;
import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.inject.Key;
-import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryProvider;
import com.google.inject.servlet.RequestScoped;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.kohsuke.args4j.spi.OptionHandler;
@@ -78,7 +76,6 @@ public class SshModule extends FactoryModule {
bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
- bind(PasswordAuthenticator.class).to(DatabasePasswordAuth.class);
bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);
bind(TransferConfig.class);
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 a04ba159c3..cdcaf5620e 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
@@ -36,6 +36,7 @@ public class DefaultCommandModule extends CommandModule {
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
command(gerrit, "flush-caches").to(AdminFlushCaches.class);
command(gerrit, "ls-projects").to(ListProjects.class);
+ command(gerrit, "query").to(Query.class);
command(gerrit, "show-caches").to(AdminShowCaches.class);
command(gerrit, "show-connections").to(AdminShowConnections.class);
command(gerrit, "show-queue").to(ShowQueue.class);
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
new file mode 100644
index 0000000000..d2bf26cb4e
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -0,0 +1,71 @@
+// 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.sshd.commands;
+
+import com.google.gerrit.server.query.change.QueryProcessor;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.inject.Inject;
+
+import org.apache.sshd.server.Environment;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.util.List;
+
+class Query extends BaseCommand {
+ @Inject
+ private QueryProcessor processor;
+
+ @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
+ void setFormat(QueryProcessor.OutputFormat format) {
+ processor.setOutput(out, format);
+ }
+
+ @Option(name = "--current-patch-set", usage = "Include information about current patch set")
+ void setCurrentPatchSet(boolean on) {
+ processor.setIncludeCurrentPatchSet(on);
+ }
+
+ @Option(name = "--patch-sets", usage = "Include information about all patch sets")
+ void setPatchSets(boolean on) {
+ processor.setIncludePatchSets(on);
+ }
+
+ @Argument(index = 0, required = true, multiValued = true, metaVar = "QUERY", usage = "Query to execute")
+ private List<String> query;
+
+ @Override
+ public void start(Environment env) {
+ startThread(new CommandRunnable() {
+ @Override
+ public void run() throws Exception {
+ processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
+ parseCommandLine();
+ processor.query(join(query, " "));
+ }
+ });
+ }
+
+ private static String join(List<String> list, String sep) {
+ StringBuilder r = new StringBuilder();
+ for (int i = 0; i < list.size(); i++) {
+ if (i > 0) {
+ r.append(sep);
+ }
+ r.append(list.get(i));
+ }
+ return r.toString();
+ }
+}
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 4bc3686463..a0b9d252d4 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
@@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.CanSubmitResult;
@@ -96,6 +97,9 @@ public class ReviewCommand extends BaseCommand {
private MergeQueue merger;
@Inject
+ private MergeOp.Factory opFactory;
+
+ @Inject
private ApprovalTypes approvalTypes;
@Inject
@@ -166,7 +170,7 @@ public class ReviewCommand extends BaseCommand {
changeControl.canSubmit(patchSetId, db, approvalTypes,
functionStateFactory);
if (result == CanSubmitResult.OK) {
- ChangeUtil.submit(patchSetId, currentUser, db, merger);
+ ChangeUtil.submit(opFactory, patchSetId, currentUser, db, merger);
} else {
throw error(result.getMessage());
}
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 6e6e600aa9..e9e60152a5 100755
--- 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,8 +16,8 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.common.ChangeListener;
-import com.google.gerrit.common.ChangeHookRunner.ChangeEvent;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.ChangeEvent;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
import com.google.gerrit.sshd.BaseCommand;
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index a6d5cce959..20f28e10f0 100644
--- a/gerrit-util-cli/pom.xml
+++ b/gerrit-util-cli/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index 35f618822a..220ae47978 100644
--- a/gerrit-util-ssl/pom.xml
+++ b/gerrit-util-ssl/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index f6290851ca..4bfa17e266 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
</parent>
<artifactId>gerrit-war</artifactId>
@@ -155,6 +155,7 @@ limitations under the License.
<configuration>
<warName>gerrit-${project.version}</warName>
<archiveClasses>true</archiveClasses>
+ <attachClasses>true</attachClasses>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifestEntries>
diff --git a/pom.xml b/pom.xml
index 6123b50d8e..0ef6d2df0e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
<packaging>pom</packaging>
- <version>2.1.3</version>
+ <version>2.1.4-SNAPSHOT</version>
<name>Gerrit Code Review - Parent</name>
<url>http://code.google.com/p/gerrit/</url>
@@ -46,12 +46,12 @@ limitations under the License.
</issueManagement>
<properties>
- <jgitVersion>0.8.4</jgitVersion>
+ <jgitVersion>0.8.4.87-g395d236</jgitVersion>
<gwtormVersion>1.1.4</gwtormVersion>
<gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.1</gwtexpuiVersion>
- <gwtVersion>2.0.3</gwtVersion>
- <slf4jVersion>1.5.8</slf4jVersion>
+ <gwtVersion>2.0.4</gwtVersion>
+ <slf4jVersion>1.6.1</slf4jVersion>
<guiceVersion>2.0</guiceVersion>
<jettyVersion>7.0.2.v20100331</jettyVersion>
<keyappletVersion>1.0</keyappletVersion>
@@ -82,6 +82,7 @@ limitations under the License.
<module>gerrit-reviewdb</module>
<module>gerrit-server</module>
<module>gerrit-sshd</module>
+ <module>gerrit-gwtdebug</module>
<module>gerrit-war</module>
<module>gerrit-gwtui</module>
@@ -402,12 +403,6 @@ limitations under the License.
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
-
- <dependency>
- <groupId>org.easymock</groupId>
- <artifactId>easymockclassextension</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
<dependencyManagement>
@@ -554,6 +549,12 @@ limitations under the License.
</dependency>
<dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.5</version>
+ </dependency>
+
+ <dependency>
<groupId>eu.medsea.mimeutil</groupId>
<artifactId>mime-util</artifactId>
<version>2.1.3</version>
@@ -606,7 +607,7 @@ limitations under the License.
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
- <version>1.2.15</version>
+ <version>1.2.16</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
@@ -661,7 +662,7 @@ limitations under the License.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>3.8.2</version>
+ <version>4.8.1</version>
</dependency>
<dependency>
@@ -692,13 +693,7 @@ limitations under the License.
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
- <version>2.5.1</version>
- </dependency>
-
- <dependency>
- <groupId>org.easymock</groupId>
- <artifactId>easymockclassextension</artifactId>
- <version>2.4</version>
+ <version>3.0</version>
</dependency>
<dependency>
@@ -736,6 +731,12 @@ limitations under the License.
<artifactId>juniversalchardet</artifactId>
<version>1.0.3</version>
</dependency>
+
+ <dependency>
+ <groupId>dk.brics.automaton</groupId>
+ <artifactId>automaton</artifactId>
+ <version>1.11.2</version>
+ </dependency>
</dependencies>
</dependencyManagement>
@@ -769,5 +770,10 @@ limitations under the License.
<id>objectweb-repository</id>
<url>http://maven.objectweb.org/maven2/</url>
</repository>
+
+ <repository>
+ <id>clojars-repo</id>
+ <url>http://clojars.org/repo</url>
+ </repository>
</repositories>
</project>
diff --git a/tools/gwtui_dbg.launch b/tools/gwtui_dbg.launch
index e1b671619d..58f5ec8c55 100644
--- a/tools/gwtui_dbg.launch
+++ b/tools/gwtui_dbg.launch
@@ -2,19 +2,19 @@
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<stringAttribute key="bad_container_name" value="/gerrit-appja"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit-gwtdbug"/>
+<listEntry value="/gerrit-gwtdebug"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
<stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
-<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-common&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-httpd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-commonsnet&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-prettify&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-jgit&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-pgm&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-server&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-sshd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-cli&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-ssl&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-reviewdb&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtui&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtdbug&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;default/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
+<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-common&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-httpd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-commonsnet&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-prettify&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-jgit&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-pgm&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-server&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-sshd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-cli&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-ssl&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-reviewdb&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtui&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtdebug&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;default/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<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.6&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 path=&quot;3&quot; projectName=&quot;gerrit-gwtui&quot; type=&quot;1&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 path=&quot;3&quot; projectName=&quot;gerrit-gwtdbug&quot; type=&quot;1&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 path=&quot;3&quot; projectName=&quot;gerrit-gwtdebug&quot; type=&quot;1&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 path=&quot;3&quot; projectName=&quot;gerrit-war&quot; type=&quot;1&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 internalArchive=&quot;/gerrit-prettify/src/main/java&quot; path=&quot;3&quot; type=&quot;2&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 internalArchive=&quot;/gerrit-patch-jgit/src/main/java&quot; path=&quot;3&quot; type=&quot;2&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 internalArchive=&quot;/gerrit-reviewdb/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
@@ -24,12 +24,13 @@
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwtexpui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&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 internalArchive=&quot;/gwtjsonrpc/src/main/java&quot; path=&quot;3&quot; type=&quot;2&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 internalArchive=&quot;/gwtorm/src/main/java&quot; path=&quot;3&quot; type=&quot;2&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 internalArchive=&quot;/gerrit-gwtui/target/classes&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.maven.ide.eclipse.launchconfig.classpathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<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-gwtui/target}/gwt-hosted-mode&#10;-server com.google.gerrit.gwtdebug.GerritDebugLauncher&#10;com.google.gerrit.GerritGwtUI"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit-gwtdbug"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit-gwtdebug"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.maven.ide.eclipse.launchconfig.sourcepathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;&#10;-Dgerrit.site_path=${resource_loc:/gerrit-parent}/../test_site"/>
</launchConfiguration>