summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/access-control.txt17
-rw-r--r--Documentation/cmd-create-project.txt19
-rw-r--r--Documentation/cmd-query.txt8
-rw-r--r--Documentation/cmd-stream-events.txt17
-rw-r--r--Documentation/config-gerrit.txt148
-rw-r--r--Documentation/config-hooks.txt9
-rw-r--r--Documentation/config-mail.txt172
-rw-r--r--Documentation/config-replication.txt17
-rw-r--r--Documentation/index.txt1
-rw-r--r--Documentation/json.txt13
-rw-r--r--Documentation/licenses.txt1
-rw-r--r--Documentation/pgm-daemon.txt2
-rw-r--r--Documentation/user-search.txt31
-rw-r--r--gerrit-common/pom.xml2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java22
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java19
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java3
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java2
-rw-r--r--gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java26
-rw-r--r--gerrit-gwtdebug/pom.xml2
-rw-r--r--gerrit-gwtui/pom.xml2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java39
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java50
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelFull.java12
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java89
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java573
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java178
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java408
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java13
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties6
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java34
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java317
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java79
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java90
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java96
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/openid.css1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java4
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java1
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java45
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java14
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java13
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java43
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css16
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java71
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java2
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java84
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java52
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java51
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml20
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java32
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java36
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java9
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java40
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java66
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java3
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java211
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableAccountDiffPreference.java30
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java48
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java169
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.java25
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.properties5
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java122
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java59
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextSaveButtonListener.java71
-rw-r--r--gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Util.java21
-rw-r--r--gerrit-httpd/pom.xml2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java1
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java2
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java19
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java5
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java94
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java25
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java7
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java13
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java126
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java18
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java4
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java32
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java5
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java57
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java8
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java12
-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/ListBranches.java13
-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-jgit/pom.xml2
-rw-r--r--gerrit-patch-jgit/src/main/java/org/eclipse/jgit/storage/file/WindowCacheStatAccessor.java (renamed from gerrit-patch-jgit/src/main/java/org/eclipse/jgit/lib/WindowCacheStatAccessor.java)2
-rw-r--r--gerrit-pgm/pom.xml2
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java1
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java8
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java15
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java39
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java7
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java102
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java19
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java2
-rw-r--r--gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java2
-rw-r--r--gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java11
-rw-r--r--gerrit-prettify/pom.xml2
-rw-r--r--gerrit-reviewdb/pom.xml2
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java12
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java24
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGeneralPreferences.java91
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java25
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java15
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Patch.java24
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java24
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java11
-rw-r--r--gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java5
-rw-r--r--gerrit-server/pom.xml12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java74
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/common/CollectionsUtil.java43
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java71
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/account/AccountUserNameException.java28
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java31
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java53
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java7
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroups.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroups.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java50
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java28
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java28
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java22
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java21
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java65
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java66
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java117
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java102
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java144
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java (renamed from gerrit-sshd/src/main/java/com/google/gerrit/sshd/TransferConfig.java)13
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java18
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java137
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java42
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java6
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java17
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java86
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java74
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java91
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java37
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java77
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/CharText.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/CharTextComparator.java29
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java191
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java34
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java10
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java54
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java66
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java58
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java95
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java5
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java20
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java124
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java9
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java60
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java61
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java58
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java58
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java11
-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_41.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java62
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.java25
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java1
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/workflow/NoBlock.java34
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm44
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm51
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm37
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm47
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm44
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm44
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm52
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm49
-rw-r--r--gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm52
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java76
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java131
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java1
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java64
-rw-r--r--gerrit-sshd/pom.xml2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java5
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java157
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java45
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java105
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java8
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java56
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java7
-rw-r--r--gerrit-util-cli/pom.xml2
-rw-r--r--gerrit-util-ssl/pom.xml2
-rw-r--r--gerrit-war/pom.xml2
-rw-r--r--gerrit-war/src/main/resources/log4j.properties4
-rw-r--r--pom.xml12
233 files changed, 6841 insertions, 2272 deletions
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index b45853d696..7c9fcbf6df 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -10,7 +10,7 @@ be granted to individual users.
System Groups
-------------
-Gerrit comes with 3 system groups, with special access privileges
+Gerrit comes with 4 system groups, with special access privileges
and membership management. The identity of these groups is set
in the `system_config` table within the database, so the groups
can be renamed after installation if desired.
@@ -65,6 +65,21 @@ cause it to become approved or rejected.
Registered users are always permitted to make and publish comments
on any change in any project they have `Read Access` to.
+Project Owners
+~~~~~~~~~~~~~~
+
+Access rights assigned to this group are always evaluated within the
+context of a project and are resolved to access rights for all users
+which own the project.
+
+By assigning access rights to this group on a parent project Gerrit
+administrators can define a set of default access rights for project
+owners. Child projects inherit these access rights where they are
+resolved to the users that own the child project.
+Having default access rights for projects owners assigned on a parent
+project may avoid the need to initially configure access rights for
+newly created child projects.
+
Account Groups
--------------
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index 3da01c4015..f698d5c7ea 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -13,10 +13,13 @@ SYNOPSIS
[--branch <REF>] \
[\--owner <GROUP> ...] \
[\--parent <NAME>] \
+[\--permissions-only] \
[\--description <DESC>] \
[\--submit-type <TYPE>] \
+[\--use-content-merge] \
[\--use-contributor-agreements] \
[\--use-signed-off-by]
+[\--empty-commit]
DESCRIPTION
-----------
@@ -68,6 +71,11 @@ repository.*.createGroup will be used. If they don't exist,
through. If not specified, the parent is set to the default
project `\-- All Projects \--`.
+\--permissions-only::
+ Create the project only to serve as a parent for other
+ projects. The new project's Git repository will not be
+ initialized, and cannot be cloned.
+
\--description::
Initial description of the project. If not specified,
no description is stored.
@@ -89,6 +97,13 @@ Description values containing spaces should be quoted in single quotes
Defaults to MERGE_IF_NECESSARY. For more details see
link:project-setup.html#submit_type[Change Submit Actions].
+\--use-content-merge::
+ If enabled, Gerrit will try to perform a 3-way merge of text
+ file content when a file has been modified by both the
+ destination branch and the change being submitted. This
+ option only takes effect if submit type is not
+ FAST_FORWARD_ONLY. Disabled by default.
+
\--use-contributor-agreements::
If enabled, authors must complete a contributor agreement
on the site before pushing any commits or changes to this
@@ -99,6 +114,10 @@ link:project-setup.html#submit_type[Change Submit Actions].
from either the author or the uploader in the commit message.
Disabled by default.
+\--empty-commit:
+ Creates an initial empty commit for the Git repository of the
+ project that is newly created.
+
EXAMPLES
--------
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 76a9b7dc34..9047202505 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -11,7 +11,7 @@ SYNOPSIS
'ssh' -p <port> <host> 'gerrit query' \
[\--format {TEXT | JSON}] \
[\--current-patch-set] \
-[\--patch-sets] \
+[\--patch-sets|--all-approvals] \
[\--] \
<query> \
[limit:<n>] \
@@ -47,6 +47,12 @@ OPTIONS
the \--current-patch-set flag then the current patch set
information will be output twice, once in each field.
+\--all-approvals::
+ Include information about all patch sets along with the
+ approval information for each patch set. 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
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index aae908a2f7..fb54f67edb 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -42,7 +42,7 @@ SCHEMA
------
The JSON messages consist of nested objects referencing the *change*,
*patchset*, *account* involved, and other attributes as appropriate.
-The currently supported message types are *patchset-added*,
+The currently supported message types are *patchset-created*,
*comment-added*, *change-merged*, and *change-abandoned*.
Note that any field may be missing in the JSON messages, so consumers of
@@ -50,9 +50,9 @@ this JSON stream should deal with that appropriately.
Events
~~~~~~
-Patchset Added
-^^^^^^^^^^^^^^
-type:: "patchset-added"
+Patchset Created
+^^^^^^^^^^^^^^^^
+type:: "patchset-created"
change:: link:json.html#change[change attribute]
@@ -102,6 +102,15 @@ author:: link:json.html#account[account attribute]
comment:: Comment text author had written
+Ref Updated
+^^^^^^^^^^^
+type:: "ref-updated"
+
+submitter:: link:json.html#account[account attribute]
+
+refUpdate:: link:json.html#refupdate[refupdate attribute]
+
+
SEE ALSO
--------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index ebd1c7f017..864092eda3 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -56,6 +56,19 @@ from the user's account object in LDAP. The user's group membership
is also pulled from LDAP, making any LDAP groups that a user is a
member of available as groups in Gerrit.
+
+* `CLIENT_SSL_CERT_LDAP`
++
+This authentication type is actually kind of SSO. Gerrit will configure
+Jetty's SSL channel to request client's SSL certificate. For this
+authentication to work a Gerrit administrator has to import the root
+certificate of the trust chain used to issue the client's certificate
+into the <review-site>/etc/keystore.
+After the authentication is done Gerrit will obtain basic user
+registration (name and email) from LDAP, and some group memberships.
+Therefore, the "_LDAP" suffix in the name of this authentication type.
+This authentication type can only be used under hosted daemon mode, and
+the httpd.listenUrl must use https:// as the protocol.
++
* `LDAP`
+
Gerrit prompts the user to enter a username and a password, which
@@ -578,6 +591,27 @@ repository data manipulation.
+
Default on JGit is 128 file descriptors on all platforms.
+[[core.streamFileThreshold]]core.streamFileThreshold::
++
+Largest object size, in bytes, that JGit will allocate as a
+contiguous byte array. Any file revision larger than this threshold
+will have to be streamed, typically requiring the use of temporary
+files under '$GIT_DIR/objects' to implement psuedo-random access
+during delta decompression.
++
+Servers with very high traffic should set this to be larger than
+the size of their common big files. For example a server managing
+the Android platform typically has to deal with ~10-12 MiB XML
+files, so `15 m` would be a reasonable setting in that environment.
+Setting this too high may cause the JVM to run out of heap space
+when handling very big binary files, such as device firmware or
+CD-ROM ISO images.
++
+Default is 50 MiB on all platforms. Prior to Gerrit 2.1.6,
+this value was effectively 2047 MiB.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
[[core.packedGitMmap]]core.packedGitMmap::
+
When true, JGit will use `mmap()` rather than `malloc()+read()`
@@ -797,6 +831,11 @@ By default unset, as the git daemon must be configured externally
by the system administrator, and might not even be running on the
same host as Gerrit.
+[[gerrit.replicateOnStartup]]gerrit.replicateOnStartup::
++
+If true, replicates to all remotes on startup to ensure they are
+in-sync with this server. By default, true.
+
[[gitweb]]Section gitweb
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1079,8 +1118,9 @@ By default, 5 minutes.
~~~~~~~~~~~~~~~~~~~~
LDAP integration is only enabled if `auth.type` was set to
-`HTTP_LDAP` or `LDAP`. See above for a detailed description of
-the auth.type settings and their implications.
+`HTTP_LDAP`, `LDAP` or `CLIENT_SSL_CERT_LDAP`. See above for a
+detailed description of the auth.type settings and their
+implications.
An example LDAP configuration follows, and then discussion of
the parameters introduced here. Suitable defaults for most
@@ -1315,6 +1355,53 @@ Common examples:
safe = true
----
+
+[[pack]]Section pack
+~~~~~~~~~~~~~~~~~~~~
+Global settings controlling how Gerrit Code Review creates pack
+streams for Git clients running clone, fetch, or pull. Most of these
+variables are per-client request, and thus should be carefully set
+given the expected concurrent request load and available CPU and
+memory resources.
+
+[[pack.deltacompression]]pack.deltacompression::
++
+If true, delta compression between objects is enabled. This may
+result in a smaller overall transfer for the client, but requires
+more server memory and CPU time.
++
+False (off) by default, matching Gerrit Code Review 2.1.4.
+
+[[pack.threads]]pack.threads::
++
+Maximum number of threads to use for delta compression (if enabled).
+This is per-client request. If set to 0 then the number of CPUs is
+auto-detected and one thread per CPU is used, per client request.
++
+By default, 1.
+
+
+[[receive]]Section receive
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+Sets the group of users allowed to execute 'receive-pack' on the
+server, 'receive-pack' is what runs on the server during a user's
+push or repo upload command.
+
+----
+[receive]
+ allowGroup = GROUP_ALLOWED_TO_EXECUTE
+ allowGroup = YET_ANOTHER_GROUP_ALLOWED_TO_EXECUTE
+----
+
+[[receive.allowGroup]]receive.allowGroup::
++
+Name of the groups of users that are allowed to execute
+'receive-pack' on the server. One or more groups can be set.
++
+If no groups are added, any user will be allowed to execute
+'receive-pack' on the server.
+
+
[[repository]]Section repository
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Repositories in this sense are the same as projects.
@@ -1516,6 +1603,41 @@ pool by a simple FIFO scheduling system.
+
By default, 1 plus the number of CPUs available to the JVM.
+[[sshd.maxAuthTries]]sshd.maxAuthTries::
++
+Maximum number of authentication attempts before the server
+disconnects the client. Each public key that a client has loaded
+into its local agent counts as one auth request. Users can work
+around the server's limit by loading less keys into their agent,
+or selecting a specific key in their `~/.ssh/config` file with
+the `IdentityFile` option.
++
+By default, 6.
+
+[[sshd.loginGraceTime]]sshd.loginGraceTime::
++
+Time in seconds that a client has to authenticate before the server
+automatically terminates their connection. Values should use common
+unit suffixes to express their setting:
++
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+* d, day, days
+
++
+By default, 2 minutes.
+
+[[sshd.maxConnectionsPerUser]]sshd.maxConnectionsPerUser::
++
+Maximum number of concurrent SSH sessions that a user account
+may open at one time. This is the number of distinct SSH logins
+the each user may have active at one time, and is not related to
+the number of commands a user may issue over a single connection.
+If set to 0, there is no limit.
++
+By default, 64.
+
[[sshd.cipher]]sshd.cipher::
+
Available ciphers. To permit multiple ciphers, specify multiple
@@ -1650,6 +1772,28 @@ reasonable timeout value.
+
Defaults to 0 seconds, wait indefinitely.
+
+[[upload]]Section upload
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+Sets the group of users allowed to execute 'upload-pack' on the
+server, 'upload-pack' is what runs on the server during a user's
+fetch, clone or repo sync command.
+
+----
+[upload]
+ allowGroup = GROUP_ALLOWED_TO_EXECUTE
+ allowGroup = YET_ANOTHER_GROUP_ALLOWED_TO_EXECUTE
+----
+
+[[upload.allowGroup]]upload.allowGroup::
++
+Name of the groups of users that are allowed to execute 'upload-pack'
+on the server. One or more groups can be set.
++
+If no groups are added, any user will be allowed to execute
+'upload-pack' on the server.
+
+
[[user]] Section user
~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index e271ba8155..fd2ae82b93 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -66,6 +66,15 @@ Called whenever a change has been restored.
change-restored --change <change id> --change-url <change url> --project <project name> --branch <branch> --restorer <restorer> --reason <reason>
====
+ref-updated
+~~~~~~~~~~~
+
+Called whenever a ref has been updated.
+
+====
+ ref-updated --oldrev <old rev> --newrev <new rev> --refname <ref name> --project <project name> --submitter <submitter>
+====
+
Configuration Settings
----------------------
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
new file mode 100644
index 0000000000..168bbfe169
--- /dev/null
+++ b/Documentation/config-mail.txt
@@ -0,0 +1,172 @@
+Gerrit Code Review - Mail Templates
+===================================
+
+Gerrit uses velocity templates for the bulk of the standard mails it sends out.
+There are builtin default templates which are used if they are not overridden.
+These defaults are also provided as examples so that administrators may copy
+them and easily modify them to tweak their contents.
+
+
+Template Locations and Extensions:
+----------------------------------
+
+The default example templates reside under: `'$site_path'/etc/mail` and are
+terminated with the double extension `.vm.example`. Modifying these example
+files will have no effect on the behavior of Gerrit. However, copying an
+example template to an equivalently named file without the `.example` extension
+and modifying it will allow an administrator to customize the template.
+
+
+Supported Mail Templates:
+-------------------------
+
+Each mail that Gerrit sends out is controlled by at least one template, these
+are listed below. Change emails are influenced by two additional templates,
+one to set the subject line, and one to set the footer which gets appended to
+all the change emails (see `ChangeSubject.vm` and `ChangeFooter.vm` below.)
+
+Abandoned.vm
+~~~~~~~~~~~~
+
+The `Abandoned.vm` template will determine the contents of the email related
+to a change being abandoned. It is a `ChangeEmail`: see `ChangeSubject.vm` and
+`ChangeFooter.vm`.
+
+ChangeFooter.vm
+~~~~~~~~~~~~~~~
+
+The `ChangeFooter.vm` template will determine the contents of the footer
+text that will be appended to emails related to changes (all `ChangeEmails)`.
+
+ChangeSubject.vm
+~~~~~~~~~~~~~~~~
+
+The `ChangeSubject.vm` template will determine the contents of the email
+subject line for ALL emails related to changes.
+
+Comment.vm
+~~~~~~~~~~
+
+The `Comment.vm` template will determine the contents of the email related to
+a user submitting comments on changes. It is a `ChangeEmail`: see
+
+Merged.vm
+~~~~~~~~~
+
+The `Merged.vm` template will determine the contents of the email related to
+a change successfully merged to the head. It is a `ChangeEmail`: see
+`ChangeSubject.vm` and `ChangeFooter.vm`.
+
+MergeFail.vm
+~~~~~~~~~~~~
+
+The `MergeFail.vm` template will determine the contents of the email related
+to a failure upon attempting to merge a change to the head. It is a
+
+NewChange.vm
+~~~~~~~~~~~~
+
+The `NewChange.vm` template will determine the contents of the email related
+to a user submitting a new change for review. It is a `ChangeEmail`: see
+`ChangeSubject.vm` and `ChangeFooter.vm`.
+
+RegisterNewEmail.vm
+~~~~~~~~~~~~~~~~~~~
+
+The `RegisterNewEmail.vm` template will determine the contents of the email
+related to registering new email accounts.
+
+ReplacePatchSet.vm
+~~~~~~~~~~~~~~~~~~
+
+The `ReplacePatchSet.vm` template will determine the contents of the email
+related to a user submitting a new patchset for a change. It is a
+`ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
+
+
+Mail Variables and Methods
+--------------------------
+
+Mail templates can access and display objects currently made available to them
+via the velocity context. While the base objects are documented here, it is
+possible to call public methods on these objects from templates. Those methods
+are not documented here since they could change with every release. As these
+templates are meant to be modified only by a qualified sysadmin, it is accepted
+that writing templates for Gerrit emails is likely to require some basic
+knowledge of the class structure to be useful. Browsing the source code might
+be necessary for anything more than a minor formatting change.
+
+Warning
+~~~~~~~
+
+Be aware that modifying templates can cause them to fail to parse and therefor
+not send out the actual email, or worse, calling methods on the available
+objects could have internal side effects which would adversely affect the
+health of your Gerrit server and/or data.
+
+All OutgoingEmails
+~~~~~~~~~~~~~~~~~~
+
+All outgoing emails have the following variables available to them:
+
+$email::
++
+A reference to the class constructing the current `OutgoingEmail`. With this
+reference it is possible to call any public method on the OutgoingEmail class
+or the current child class inherited from it.
+
+$messageClass::
++
+A String containing the messageClass
+
+$StringUtils::
++
+A reference to the Apache `StringUtils` class. This can be very useful for
+formatting strings.
+
+Change Emails
+~~~~~~~~~~~~~
+
+All change related emails have the following additional variables available to them:
+
+$change::
++
+A reference to the current `Change` object
+
+$changeId::
++
+Id of the current change (a `Change.Key`)
+
+$coverLetter::
++
+The text of the `ChangeMessage`
+
+$branch::
++
+A reference to the branch of this change (a `Branch.NameKey`)
+
+$fromName::
++
+The name of the from user
+
+$projectName::
++
+The name of this change's project
+
+$patchSet::
++
+A reference to the current `PatchSet`
+
+$patchSetInfo::
++
+A reference to the current `PatchSetInfo`
+
+
+See Also
+--------
+
+* link:http://velocity.apache.org/[velocity]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/config-replication.txt b/Documentation/config-replication.txt
index abb6a9791c..1e20d2b9a2 100644
--- a/Documentation/config-replication.txt
+++ b/Documentation/config-replication.txt
@@ -42,8 +42,8 @@ different hosts:
url = mirror1.us.some.org:/pub/git/${name}.git
url = mirror2.us.some.org:/pub/git/${name}.git
url = mirror3.us.some.org:/pub/git/${name}.git
- push = +refs/heads/*
- push = +refs/tags/*
+ push = +refs/heads/*:refs/heads/*
+ push = +refs/tags/*:refs/tags/*
threads = 3
authGroup = Public Mirror Group
authGroup = Second Public Mirror Group
@@ -142,6 +142,19 @@ This is a Gerrit specific extension to the Git remote block.
+
By default, 15 seconds.
+[[remote.name.replicationRetry]]remote.<name>.replicationRetry::
++
+Number of minutes to wait before scheduling a remote push operation
+previously failed due to an offline remote server.
++
+If a remote push operation fails because a remote server was
+offline, all push operations to the same destination URL are
+blocked, and the remote push is continuously retried.
++
+This is a Gerrit specific extension to the Git remote block.
++
+By default, 1 minute.
+
[[remote.name.threads]]remote.<name>.threads::
+
Number of worker threads to dedicate to pushing to the repositories
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 1e13e8d8f5..e419455204 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -31,6 +31,7 @@ Configuration
* link:config-sso.html[Single Sign-On Systems]
* link:config-apache2.html[Apache 2 Reverse Proxy]
* link:config-hooks.html[Hooks]
+* link:config-mail.html[Mail Templates]
Developer Documentation
-----------------------
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 1c9a808dbe..99b158daae 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -107,6 +107,19 @@ was added or last updated.
by:: Reviewer of the patch set in <<account,account attribute>>.
+[[refupdate]]
+refupdate
+--------
+Information about a ref that was updated.
+
+oldRev:: The old value of the ref, prior to the update.
+
+newRev:: The new value the ref was updated to.
+
+project:: Project path in Gerrit
+
+refName:: Ref name within project.
+
SEE ALSO
--------
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 2c123f8841..25194a98ae 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -30,6 +30,7 @@ Apache Log4J <<apache2,Apache License 2.0>>
Apache MINA <<apache2,Apache License 2.0>>
Apache Tomact Servlet API <<apache2,Apache License 2.0>>
Apache SSHD <<apache2,Apache License 2.0>>, see also <<sshd,NOTICE>>
+Apache Velocity <<apache2,Apache License 2.0>>
Apache Xerces <<apache2,Apache License 2.0>>
OpenID4Java <<apache2,Apache License 2.0>>
Neko HTML <<apache2,Apache License 2.0>>
diff --git a/Documentation/pgm-daemon.txt b/Documentation/pgm-daemon.txt
index 228c60f859..0b2e72f153 100644
--- a/Documentation/pgm-daemon.txt
+++ b/Documentation/pgm-daemon.txt
@@ -49,7 +49,7 @@ OPTIONS
Run in slave mode, permitting only read operations
by clients. Commands which modify state such as
link:cmd-receive-pack.html[recieve-pack] (creates new changes
- or updates existing ones) or link:cmd-approve.html[approve]
+ or updates existing ones) or link:cmd-review.html[review]
(sets approve marks) are disabled.
+
This option automatically implies '\--disable-httpd \--enable-sshd'.
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index c62e894017..5b2f81e632 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -87,7 +87,8 @@ Changes where 'SHA1' is one of the patch sets of the change.
[[project]]
project:'PROJECT'::
+
-Changes occuring in 'PROJECT'.
+Changes occuring in 'PROJECT'. If 'PROJECT' starts with `^` it
+matches project names by regular expression.
[[branch]]
branch:'BRANCH'::
@@ -98,6 +99,9 @@ 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'.
++
+If 'BRANCH' starts with `^` it matches branch names by regular
+expression patterns.
[[topic]]
topic:'TOPIC'::
@@ -105,6 +109,9 @@ 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.
++
+If 'TOPIC' starts with `^` it matches topic names by regular
+expression patterns.
[[ref]]
ref:'REF'::
@@ -112,6 +119,9 @@ 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/'.
++
+If 'REF' starts with `^` it matches reference names by regular
+expression patterns.
[[tr]][[bug]]
tr:'ID', bug:'ID'::
@@ -130,12 +140,29 @@ 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.
+[[message]]
+message:'MESSAGE'::
++
+Changes that matches 'MESSAGE' arbitrary string in body commit messages.
+
[[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$`.
+`\^`. For example, to match all XML files use `file:^.*\.xml$`.
++
+The `\^` required at the beginning of the regular expression not only
+denotes a regular expression, but it also has the usual meaning of
+anchoring the match to the start of the string. To match all Java
+files, use `file:^.*\.java`.
++
+The entire regular expression pattern, including the `\^` character,
+should be double quoted when using more complex construction (like
+ones using a bracket expression). For example, to match all XML
+files named like 'name1.xml', 'name2.xml', and 'name3.xml' use
+`\file:"\^name[1-3].xml"`.
++
Currently this operator is only available on a watched project
and may not be used in the search bar.
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index adac3a8058..af03191364 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-common</artifactId>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java
index 56c31ad6b4..7341c47425 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java
@@ -19,35 +19,33 @@ import com.google.gerrit.reviewdb.Branch;
import java.util.List;
/**
- * It holds list of branches and boolean to indicate
- * if it is allowed to add new branches.
+ * It holds list of branches and boolean to indicate if it is allowed to add new
+ * branches.
*/
public final class ListBranchesResult {
+ protected boolean noRepository;
protected boolean canAdd;
-
protected List<Branch> branches;
protected ListBranchesResult() {
}
- public ListBranchesResult(final List<Branch> branches, boolean canAdd) {
+ public ListBranchesResult(List<Branch> branches, boolean canAdd,
+ boolean noRepository) {
this.branches = branches;
this.canAdd = canAdd;
+ this.noRepository = noRepository;
}
- public boolean getCanAdd() {
- return canAdd;
+ public boolean getNoRepository() {
+ return noRepository;
}
- public void setCanAdd(boolean canAdd) {
- this.canAdd = canAdd;
+ public boolean getCanAdd() {
+ return canAdd;
}
public List<Branch> getBranches() {
return branches;
}
-
- public void setBranches(List<Branch> branches) {
- this.branches = branches;
- }
}
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 048d4408cb..e24405fe53 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
@@ -34,10 +34,16 @@ public class PatchScript {
NONE, DIFF, IMG
}
+ public static enum FileMode {
+ FILE, SYMLINK, GITLINK
+ }
+
protected Change.Key changeId;
protected ChangeType changeType;
protected String oldName;
protected String newName;
+ protected FileMode oldMode;
+ protected FileMode newMode;
protected List<String> header;
protected AccountDiffPreference diffPrefs;
protected SparseFileContent a;
@@ -51,7 +57,8 @@ 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 AccountDiffPreference dp,
+ final String nn, final FileMode om, final FileMode nm,
+ 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,
@@ -60,6 +67,8 @@ public class PatchScript {
changeType = ct;
oldName = on;
newName = nn;
+ oldMode = om;
+ newMode = nm;
header = h;
diffPrefs = dp;
a = ca;
@@ -88,6 +97,14 @@ public class PatchScript {
return displayMethodB;
}
+ public FileMode getFileModeA() {
+ return oldMode;
+ }
+
+ public FileMode getFileModeB() {
+ return newMode;
+ }
+
public List<String> getPatchHeader() {
return header;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
index 19772fcf49..678ec79a7e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
@@ -50,6 +50,9 @@ public class ReviewerResult {
/** Name supplied does not match to a registered account. */
ACCOUNT_NOT_FOUND,
+ /** The account is inactive. */
+ ACCOUNT_INACTIVE,
+
/** The account is not permitted to see the change. */
CHANGE_NOT_VISIBLE,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
index 164df439fb..9dae169830 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
@@ -28,7 +28,7 @@ public interface SuggestService extends RemoteJsonService {
void suggestProjectNameKey(String query, int limit,
AsyncCallback<List<Project.NameKey>> callback);
- void suggestAccount(String query, int limit,
+ void suggestAccount(String query, Boolean enabled, int limit,
AsyncCallback<List<AccountInfo>> callback);
void suggestAccountGroup(String query, int limit,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java
new file mode 100644
index 0000000000..6ae5eb6d5d
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.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.common.errors;
+
+/** Error indicating the account is currently inactive. */
+public class InactiveAccountException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public static final String MESSAGE = "Account Inactive: ";
+
+ public InactiveAccountException(String who) {
+ super(MESSAGE + who);
+ }
+}
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index c3a07a4178..88246ac97b 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,7 +22,7 @@ limitations under the License.
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtdebug</artifactId>
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index 29c5b01cfd..9a27d12bea 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtui</artifactId>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 281c72b696..81a9dd5df3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -16,6 +16,7 @@ package com.google.gerrit.client;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gwt.i18n.client.DateTimeFormat;
import java.util.Date;
@@ -24,13 +25,29 @@ import java.util.Date;
public class FormatUtil {
private static final long ONE_YEAR = 182L * 24 * 60 * 60 * 1000;
- private static final DateTimeFormat sTime =
- DateTimeFormat.getShortTimeFormat();
- private static final DateTimeFormat sDate = DateTimeFormat.getFormat("MMM d");
- private static final DateTimeFormat mDate =
- DateTimeFormat.getMediumDateFormat();
- private static final DateTimeFormat dtfmt =
- DateTimeFormat.getFormat(mDate.getPattern() + " " + sTime.getPattern());
+ private static DateTimeFormat sTime = DateTimeFormat.getShortTimeFormat();
+ private static DateTimeFormat sDate = DateTimeFormat.getFormat("MMM d");
+ private static DateTimeFormat mDate = DateTimeFormat.getMediumDateFormat();
+ private static DateTimeFormat dtfmt;
+
+ public static void setPreferences(AccountGeneralPreferences pref) {
+ if (pref == null) {
+ if (Gerrit.isSignedIn()) {
+ pref = Gerrit.getUserAccount().getGeneralPreferences();
+ } else {
+ pref = new AccountGeneralPreferences();
+ pref.resetToDefaults();
+ }
+ }
+
+ String fmt_sTime = pref.getTimeFormat().getFormat();
+ String fmt_mDate = pref.getDateFormat().getLongFormat();
+
+ sTime = DateTimeFormat.getFormat(fmt_sTime);
+ sDate = DateTimeFormat.getFormat(pref.getDateFormat().getShortFormat());
+ mDate = DateTimeFormat.getFormat(fmt_mDate);
+ dtfmt = DateTimeFormat.getFormat(fmt_mDate + " " + fmt_sTime);
+ }
/** Format a date using a really short format. */
public static String shortFormat(Date dt) {
@@ -38,6 +55,7 @@ public class FormatUtil {
return "";
}
+ ensureInited();
final Date now = new Date();
dt = new Date(dt.getTime());
if (mDate.format(now).equals(mDate.format(dt))) {
@@ -62,9 +80,16 @@ public class FormatUtil {
if (dt == null) {
return "";
}
+ ensureInited();
return dtfmt.format(new Date(dt.getTime()));
}
+ private static void ensureInited() {
+ if (dtfmt == null) {
+ setPreferences(null);
+ }
+ }
+
/** Format an account as a name and email address. */
public static String nameEmail(final Account acct) {
return nameEmail(new AccountInfo(acct));
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 7bee0af174..d5ccdf9e10 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
@@ -481,6 +481,7 @@ public class Gerrit implements EntryPoint {
switch (cfg.getAuthType()) {
case HTTP:
case HTTP_LDAP:
+ case CLIENT_SSL_CERT_LDAP:
break;
case OPENID:
@@ -526,6 +527,7 @@ public class Gerrit implements EntryPoint {
if (siteFooter != null) {
siteFooter.setVisible(p.isShowSiteHeader());
}
+ FormatUtil.setPreferences(myAccount.getGeneralPreferences());
}
}
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 3f263d23fd..b32a32d061 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
@@ -46,6 +46,8 @@ public interface GerritConstants extends Constants {
String nameAlreadyUsedBody();
String noSuchAccountTitle();
+ String inactiveAccountBody();
+
String menuAll();
String menuAllOpen();
String menuAllMerged();
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 83a06e47de..33630150d3 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
@@ -29,6 +29,8 @@ notFoundBody = The page you requested was not found.
nameAlreadyUsedBody = The name is already in use.
noSuchAccountTitle = Code Review - Unknown User
+inactiveAccountBody = This user is currently inactive.
+
menuAll = All
menuAllOpen = Open
menuAllMerged = Merged
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 dd684790d8..ca9f248fcd 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
@@ -67,6 +67,7 @@ public interface GerritCss extends CssResource {
String commentPanelHeader();
String commentPanelLast();
String commentPanelMessage();
+ String commentPanelMenuBar();
String commentPanelSummary();
String commentPanelSummaryCell();
String complexHeader();
@@ -105,6 +106,7 @@ public interface GerritCss extends CssResource {
String fileLineCONTEXT();
String fileLineDELETE();
String fileLineINSERT();
+ String fileLineMode();
String fileLineNone();
String filePathCell();
String gerritTopMenu();
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 2d3d2e8cfb..db5094d6df 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
@@ -15,14 +15,11 @@
package com.google.gerrit.client;
import com.google.gerrit.client.changes.QueryScreen;
+import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.Change;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
@@ -35,7 +32,7 @@ import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.NpTextBox;
class SearchPanel extends Composite {
- private final NpTextBox searchBox;
+ private final HintTextBox searchBox;
private HandlerRegistration regFocus;
SearchPanel() {
@@ -43,39 +40,14 @@ class SearchPanel extends Composite {
initWidget(body);
setStyleName(Gerrit.RESOURCES.css().searchPanel());
- searchBox = new NpTextBox();
+ searchBox = new HintTextBox();
searchBox.setVisibleLength(70);
- searchBox.setText(Gerrit.C.searchHint());
- searchBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- searchBox.addFocusHandler(new FocusHandler() {
- @Override
- public void onFocus(FocusEvent event) {
- if (Gerrit.C.searchHint().equals(searchBox.getText())) {
- searchBox.setText("");
- searchBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
- searchBox.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(BlurEvent event) {
- if ("".equals(searchBox.getText())) {
- searchBox.setText(Gerrit.C.searchHint());
- searchBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
+ searchBox.setHintText(Gerrit.C.searchHint());
searchBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(final KeyPressEvent event) {
- switch (event.getCharCode()) {
- case KeyCodes.KEY_ENTER:
- doSearch();
- break;
- case KeyCodes.KEY_ESCAPE:
- searchBox.setText("");
- searchBox.setFocus(false);
- break;
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ doSearch();
}
}
});
@@ -93,13 +65,7 @@ class SearchPanel extends Composite {
}
void setText(final String query) {
- if (query == null || query.equals("")) {
- searchBox.setText(Gerrit.C.searchHint());
- searchBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- } else {
- searchBox.setText(query);
- searchBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
+ searchBox.setText(query);
}
@Override
@@ -129,7 +95,7 @@ class SearchPanel extends Composite {
private void doSearch() {
final String query = searchBox.getText().trim();
- if (query.length() == 0 || Gerrit.C.searchHint().equals(query)) {
+ if ("".equals(query)) {
return;
}
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 3756bedf73..276fc27d19 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -25,10 +25,12 @@ public interface AccountConstants extends Constants {
String accountId();
String maximumPageSizeFieldLabel();
+ String dateFormatLabel();
String contextWholeFile();
String showSiteHeader();
String useFlashClipboard();
String copySelfOnEmails();
+ String displayPatchSetsInReverseOrder();
String buttonSaveChanges();
String tabAccountSummary();
@@ -81,6 +83,10 @@ public interface AccountConstants extends Constants {
String buttonWatchProject();
String defaultProjectName();
String defaultFilter();
+ String buttonBrowseProjects();
+ String projects();
+ String projectsClose();
+ String projectListOpen();
String watchedProjectName();
String watchedProjectFilter();
String watchedProjectColumnEmailNotifications();
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 29ee14af9a..a5a652f34e 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
@@ -7,8 +7,10 @@ accountId = Account ID
showSiteHeader = Show Site Header
useFlashClipboard = Use Flash Clipboard Widget
copySelfOnEmails = CC Me On Comments I Write
+displayPatchSetsInReverseOrder = Display Patch Sets In Reverse Order
defaultContextFieldLabel = Default Context:
maximumPageSizeFieldLabel = Maximum Page Size:
+dateFormatLabel = Date/Time Format:
contextWholeFile = Whole File
buttonSaveChanges = Save Changes
@@ -62,6 +64,10 @@ sshJavaAppletNotAvailable = Open Key Unavailable: Java not enabled
buttonWatchProject = Watch
defaultProjectName = Project Name
defaultFilter = branch:name, or other search expression
+projects = All Watchable Projects
+projectsClose = Close
+buttonBrowseProjects = Browse
+projectListOpen = Watch Selected project
watchedProjectName = Project Name
watchedProjectFilter = Only If
watchedProjectColumnEmailNotifications = Email Notifications
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelFull.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelFull.java
index 3742beda92..b7a00ad506 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelFull.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelFull.java
@@ -15,7 +15,7 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.ui.TextSaveButtonListener;
+import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ContactInformation;
import com.google.gwt.user.client.ui.Grid;
@@ -80,11 +80,11 @@ class ContactPanelFull extends ContactPanelShort {
infoSecure.getCellFormatter().addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
infoSecure.getCellFormatter().addStyleName(3, 0, Gerrit.RESOURCES.css().bottomheader());
- final TextSaveButtonListener sbl = new TextSaveButtonListener(save);
- addressTxt.addKeyPressHandler(sbl);
- countryTxt.addKeyPressHandler(sbl);
- phoneTxt.addKeyPressHandler(sbl);
- faxTxt.addKeyPressHandler(sbl);
+ final OnEditEnabler sbl = new OnEditEnabler(save);
+ sbl.listenTo(addressTxt);
+ sbl.listenTo(countryTxt);
+ sbl.listenTo(phoneTxt);
+ sbl.listenTo(faxTxt);
}
@Override
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 21c9163f69..8fe4d5fe6f 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
@@ -16,7 +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.ui.TextSaveButtonListener;
+import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ContactInformation;
@@ -120,9 +120,8 @@ class ContactPanelShort extends Composite {
doSave();
}
});
+ new OnEditEnabler(save, nameTxt);
- final TextSaveButtonListener sbl = new TextSaveButtonListener(save);
- nameTxt.addKeyPressHandler(sbl);
emailPick.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(final ChangeEvent event) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
index b9fff040d4..437d438fb6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
@@ -26,7 +26,7 @@ public class MyGroupsScreen extends SettingsScreen {
@Override
protected void onInitUI() {
super.onInitUI();
- groups = new GroupTable(false /* do not hyperlink to admin */);
+ groups = new GroupTable(true /* hyperlink to admin */);
add(groups);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index bb81f3f85f..3f32195eed 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -26,18 +26,25 @@ import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.i18n.client.DateTimeFormat;
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.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwtjsonrpc.client.VoidResult;
+import java.util.Date;
+
public class MyPreferencesScreen extends SettingsScreen {
private CheckBox showSiteHeader;
private CheckBox useFlashClipboard;
private CheckBox copySelfOnEmails;
+ private CheckBox displayPatchSetsInReverseOrder;
private ListBox maximumPageSize;
+ private ListBox dateFormat;
+ private ListBox timeFormat;
private Button save;
@Override
@@ -66,21 +73,51 @@ public class MyPreferencesScreen extends SettingsScreen {
copySelfOnEmails = new CheckBox(Util.C.copySelfOnEmails());
copySelfOnEmails.addClickHandler(onClickSave);
+ displayPatchSetsInReverseOrder = new CheckBox(Util.C.displayPatchSetsInReverseOrder());
+ displayPatchSetsInReverseOrder.addClickHandler(onClickSave);
+
maximumPageSize = new ListBox();
for (final short v : PAGESIZE_CHOICES) {
maximumPageSize.addItem(Util.M.rowsPerPage(v), String.valueOf(v));
}
maximumPageSize.addChangeHandler(onChangeSave);
+ Date now = new Date();
+ dateFormat = new ListBox();
+ for (AccountGeneralPreferences.DateFormat fmt : AccountGeneralPreferences.DateFormat
+ .values()) {
+ StringBuilder r = new StringBuilder();
+ r.append(DateTimeFormat.getFormat(fmt.getShortFormat()).format(now));
+ r.append(" ; ");
+ r.append(DateTimeFormat.getFormat(fmt.getLongFormat()).format(now));
+ dateFormat.addItem(r.toString(), fmt.name());
+ }
+ dateFormat.addChangeHandler(onChangeSave);
+
+ timeFormat = new ListBox();
+ for (AccountGeneralPreferences.TimeFormat fmt : AccountGeneralPreferences.TimeFormat
+ .values()) {
+ StringBuilder r = new StringBuilder();
+ r.append(DateTimeFormat.getFormat(fmt.getFormat()).format(now));
+ timeFormat.addItem(r.toString(), fmt.name());
+ }
+ timeFormat.addChangeHandler(onChangeSave);
+
+ FlowPanel dateTimePanel = new FlowPanel();
+
final int labelIdx, fieldIdx;
if (LocaleInfo.getCurrentLocale().isRTL()) {
labelIdx = 1;
fieldIdx = 0;
+ dateTimePanel.add(timeFormat);
+ dateTimePanel.add(dateFormat);
} else {
labelIdx = 0;
fieldIdx = 1;
+ dateTimePanel.add(dateFormat);
+ dateTimePanel.add(timeFormat);
}
- final Grid formGrid = new Grid(4, 2);
+ final Grid formGrid = new Grid(6, 2);
int row = 0;
formGrid.setText(row, labelIdx, "");
@@ -95,10 +132,18 @@ public class MyPreferencesScreen extends SettingsScreen {
formGrid.setWidget(row, fieldIdx, copySelfOnEmails);
row++;
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, displayPatchSetsInReverseOrder);
+ row++;
+
formGrid.setText(row, labelIdx, Util.C.maximumPageSizeFieldLabel());
formGrid.setWidget(row, fieldIdx, maximumPageSize);
row++;
+ formGrid.setText(row, labelIdx, Util.C.dateFormatLabel());
+ formGrid.setWidget(row, fieldIdx, dateTimePanel);
+ row++;
+
add(formGrid);
save = new Button(Util.C.buttonSaveChanges());
@@ -126,21 +171,40 @@ public class MyPreferencesScreen extends SettingsScreen {
showSiteHeader.setEnabled(on);
useFlashClipboard.setEnabled(on);
copySelfOnEmails.setEnabled(on);
+ displayPatchSetsInReverseOrder.setEnabled(on);
maximumPageSize.setEnabled(on);
+ dateFormat.setEnabled(on);
+ timeFormat.setEnabled(on);
}
private void display(final AccountGeneralPreferences p) {
showSiteHeader.setValue(p.isShowSiteHeader());
useFlashClipboard.setValue(p.isUseFlashClipboard());
copySelfOnEmails.setValue(p.isCopySelfOnEmails());
+ displayPatchSetsInReverseOrder.setValue(p.isDisplayPatchSetsInReverseOrder());
setListBox(maximumPageSize, DEFAULT_PAGESIZE, p.getMaximumPageSize());
+ setListBox(dateFormat, AccountGeneralPreferences.DateFormat.STD, //
+ p.getDateFormat());
+ setListBox(timeFormat, AccountGeneralPreferences.TimeFormat.HHMM_12, //
+ p.getTimeFormat());
}
private void setListBox(final ListBox f, final short defaultValue,
final short currentValue) {
+ setListBox(f, String.valueOf(defaultValue), String.valueOf(currentValue));
+ }
+
+ private <T extends Enum<?>> void setListBox(final ListBox f,
+ final T defaultValue, final T currentValue) {
+ setListBox(f, defaultValue.name(), //
+ currentValue != null ? currentValue.name() : "");
+ }
+
+ private void setListBox(final ListBox f, final String defaultValue,
+ final String currentValue) {
final int n = f.getItemCount();
for (int i = 0; i < n; i++) {
- if (Short.parseShort(f.getValue(i)) == currentValue) {
+ if (f.getValue(i).equals(currentValue)) {
f.setSelectedIndex(i);
return;
}
@@ -158,12 +222,33 @@ public class MyPreferencesScreen extends SettingsScreen {
return defaultValue;
}
+ private <T extends Enum<?>> T getListBox(final ListBox f,
+ final T defaultValue, T[] all) {
+ final int idx = f.getSelectedIndex();
+ if (0 <= idx) {
+ String v = f.getValue(idx);
+ for (T t : all) {
+ if (t.name().equals(v)) {
+ return t;
+ }
+ }
+ }
+ return defaultValue;
+ }
+
private void doSave() {
final AccountGeneralPreferences p = new AccountGeneralPreferences();
p.setShowSiteHeader(showSiteHeader.getValue());
p.setUseFlashClipboard(useFlashClipboard.getValue());
p.setCopySelfOnEmails(copySelfOnEmails.getValue());
+ p.setDisplayPatchSetsInReverseOrder(displayPatchSetsInReverseOrder.getValue());
p.setMaximumPageSize(getListBox(maximumPageSize, DEFAULT_PAGESIZE));
+ p.setDateFormat(getListBox(dateFormat,
+ AccountGeneralPreferences.DateFormat.STD,
+ AccountGeneralPreferences.DateFormat.values()));
+ p.setTimeFormat(getListBox(timeFormat,
+ AccountGeneralPreferences.TimeFormat.HHMM_12,
+ AccountGeneralPreferences.TimeFormat.values()));
enable(false);
save.setEnabled(false);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
index 28099bf08f..224ff93050 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
@@ -17,179 +17,303 @@ 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.HintTextBox;
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
+import com.google.gerrit.client.ui.RPCSuggestOracle;
+import com.google.gerrit.client.ui.ProjectsTable;
+import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccountProjectWatchInfo;
-import com.google.gerrit.reviewdb.AccountProjectWatch;
-import com.google.gerrit.reviewdb.Change.Status;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gerrit.reviewdb.Project;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
-import com.google.gwt.user.client.DOM;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.CheckBox;
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.PopupPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
-import com.google.gwtjsonrpc.client.VoidResult;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.HidePopupPanelCommand;
+import com.google.gwtexpui.user.client.PluginSafeDialogBox;
-import java.util.HashSet;
import java.util.List;
-public class MyWatchedProjectsScreen extends SettingsScreen {
- private WatchTable watches;
-
+public class MyWatchedProjectsScreen extends SettingsScreen implements
+ ResizeHandler {
private Button addNew;
- private NpTextBox nameBox;
+ private HintTextBox nameBox;
private SuggestBox nameTxt;
- private NpTextBox filterTxt;
+ private HintTextBox filterTxt;
+ private MyWatchesTable watchesTab;
+ private Button browse;
+ private PluginSafeDialogBox popup;
+ private Button close;
+ private ProjectsTable projectsTab;
private Button delSel;
+
+ private PopupPanel.PositionCallback popupPosition;
+ private HandlerRegistration regWindowResize;
+
+ private int preferredPopupWidth = -1;
+
private boolean submitOnSelection;
+ private boolean firstPopupLoad = true;
+ private boolean popingUp;
+
+ private ScrollPanel sp;
@Override
protected void onInitUI() {
super.onInitUI();
+ createWidgets();
- {
- 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(nameBox.getText())) {
- nameBox.setText("");
- nameBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
- nameBox.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(BlurEvent event) {
- if ("".equals(nameBox.getText())) {
- nameBox.setText(Util.C.defaultProjectName());
- nameBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
+
+ /* top table */
+
+ 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);
+ fp.add(browse);
+ add(fp);
+
+
+ /* bottom table */
+
+ add(watchesTab);
+ add(delSel);
+
+
+ /* popup */
+
+ final FlowPanel pfp = new FlowPanel();
+ sp = new ScrollPanel(projectsTab);
+ pfp.add(sp);
+ pfp.add(close);
+ popup.setWidget(pfp);
+
+ popupPosition = new PopupPanel.PositionCallback() {
+ public void setPosition(int offsetWidth, int offsetHeight) {
+ if (preferredPopupWidth == -1) {
+ preferredPopupWidth = offsetWidth;
}
- });
- nameBox.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- submitOnSelection = false;
- if (event.getCharCode() == KeyCodes.KEY_ENTER) {
- if (nameTxt.isSuggestionListShowing()) {
- submitOnSelection = true;
- } else {
- doAddNew();
- }
- }
+ int top = grid.getAbsoluteTop() - 50; // under page header
+
+ // Try to place it to the right of everything else, but not
+ // right justified
+ int left = 5 + Math.max(
+ grid.getAbsoluteLeft() + grid.getOffsetWidth(),
+ watchesTab.getAbsoluteLeft() + watchesTab.getOffsetWidth() );
+
+ if (top + offsetHeight > Window.getClientHeight()) {
+ top = Window.getClientHeight() - offsetHeight;
}
- });
- nameTxt.addSelectionHandler(new SelectionHandler<Suggestion>() {
- @Override
- public void onSelection(SelectionEvent<Suggestion> event) {
- if (submitOnSelection) {
- submitOnSelection = false;
- doAddNew();
- }
+ if (left + offsetWidth > Window.getClientWidth()) {
+ left = Window.getClientWidth() - offsetWidth;
}
- });
-
- 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());
- }
+
+ if (top < 0) {
+ sp.setHeight((sp.getOffsetHeight() + top) + "px");
+ top = 0;
}
- });
- 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());
- }
+ if (left < 0) {
+ sp.setWidth((sp.getOffsetWidth() + left) + "px");
+ left = 0;
}
- });
- filterTxt.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+
+ popup.setPopupPosition(left, top);
+ }
+ };
+ }
+
+ @Override
+ public void onResize(final ResizeEvent event) {
+ sp.setSize("100%","100%");
+
+ // For some reason keeping track of preferredWidth keeps the width better,
+ // but using 100% for height works better.
+ popup.setHeight("100%");
+ popupPosition.setPosition(preferredPopupWidth, popup.getOffsetHeight());
+ }
+
+ protected void createWidgets() {
+ nameBox = new HintTextBox();
+ nameTxt = new SuggestBox(new ProjectNameSuggestOracle(), nameBox);
+ nameBox.setVisibleLength(50);
+ nameBox.setHintText(Util.C.defaultProjectName());
+ nameBox.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ submitOnSelection = false;
+
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ if (nameTxt.isSuggestionListShowing()) {
+ submitOnSelection = true;
+ } else {
doAddNew();
}
}
- });
+ }
+ });
+ nameTxt.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ @Override
+ public void onSelection(SelectionEvent<Suggestion> event) {
+ if (submitOnSelection) {
+ submitOnSelection = false;
+ doAddNew();
+ }
+ }
+ });
- addNew = new Button(Util.C.buttonWatchProject());
- addNew.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
+ filterTxt = new HintTextBox();
+ filterTxt.setVisibleLength(50);
+ filterTxt.setHintText(Util.C.defaultFilter());
+ filterTxt.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
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);
- add(fp);
- }
+ }
+ });
+
+ addNew = new Button(Util.C.buttonWatchProject());
+ addNew.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ doAddNew();
+ }
+ });
+
+ projectsTab = new ProjectsTable() {
+ {
+ keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.projectListOpen()));
+ keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
+ Util.C.projectListOpen()));
+ }
+
+ @Override
+ protected void movePointerTo(final int row, final boolean scroll) {
+ super.movePointerTo(row, scroll);
- watches = new WatchTable();
- add(watches);
+ // prevent user input from being overwritten by simply poping up
+ if (! popingUp || "".equals(nameBox.getText()) ) {
+ nameBox.setText(getRowItem(row).getName());
+ }
+ }
+
+ @Override
+ protected void onOpenRow(final int row) {
+ super.onOpenRow(row);
+ nameBox.setText(getRowItem(row).getName());
+ doAddNew();
+ }
+ };
+ projectsTab.setSavePointerId(PageLinks.SETTINGS_PROJECTS);
+
+ close = new Button(Util.C.projectsClose());
+ close.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ closePopup();
+ }
+ });
+
+ popup = new PluginSafeDialogBox();
+ popup.setModal(false);
+ popup.setText(Util.C.projects());
+
+ browse = new Button(Util.C.buttonBrowseProjects());
+ browse.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ displayPopup();
+ }
+ });
+
+ watchesTab = new MyWatchesTable();
delSel = new Button(Util.C.buttonDeleteSshKey());
delSel.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
- watches.deleteChecked();
+ watchesTab.deleteChecked();
}
});
- add(delSel);
}
- void doAddNew() {
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ populateWatches();
+ }
+
+ @Override
+ protected void onUnload() {
+ super.onUnload();
+ closePopup();
+ }
+
+ protected void displayPopup() {
+ popingUp = true;
+ if (firstPopupLoad) { // For sizing/positioning, delay display until loaded
+ populateProjects();
+ } else {
+ popup.setPopupPositionAndShow(popupPosition);
+
+ GlobalKey.dialog(popup);
+ GlobalKey.addApplication(popup, new HidePopupPanelCommand(0,
+ KeyCodes.KEY_ESCAPE, popup));
+ projectsTab.setRegisterKeys(true);
+
+ projectsTab.finishDisplay();
+
+ if (regWindowResize == null) {
+ regWindowResize = Window.addResizeHandler(this);
+ }
+
+ popingUp = false;
+ }
+ }
+
+ protected void closePopup() {
+ popup.hide();
+ if (regWindowResize != null) {
+ regWindowResize.removeHandler();
+ regWindowResize = null;
+ }
+ }
+
+ protected void doAddNew() {
final String projectName = nameTxt.getText();
- if (projectName == null || projectName.length() == 0
- || Util.C.defaultProjectName().equals(projectName)) {
+ if ("".equals(projectName)) {
return;
}
@@ -211,7 +335,7 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
filterTxt.setEnabled(true);
nameTxt.setText("");
- watches.insertWatch(result);
+ watchesTab.insertWatch(result);
}
@Override
@@ -225,190 +349,27 @@ public class MyWatchedProjectsScreen extends SettingsScreen {
});
}
- @Override
- protected void onLoad() {
- super.onLoad();
- Util.ACCOUNT_SVC
- .myProjectWatch(new ScreenLoadCallback<List<AccountProjectWatchInfo>>(
- this) {
- public void preDisplay(final List<AccountProjectWatchInfo> result) {
- watches.display(result);
- }
- });
- }
-
- private class WatchTable extends FancyFlexTable<AccountProjectWatchInfo> {
- WatchTable() {
- table.setWidth("");
- table.insertRow(1);
- table.setText(0, 2, Util.C.watchedProjectName());
- table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications());
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
- fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
- fmt.setRowSpan(0, 0, 2);
- fmt.setRowSpan(0, 1, 2);
- fmt.setRowSpan(0, 2, 2);
- DOM.setElementProperty(fmt.getElement(0, 3), "align", "center");
-
- fmt.setColSpan(0, 3, 3);
- table.setText(1, 0, Util.C.watchedProjectColumnNewChanges());
- table.setText(1, 1, Util.C.watchedProjectColumnAllComments());
- table.setText(1, 2, Util.C.watchedProjectColumnSubmittedChanges());
- fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(1, 1, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(1, 2, Gerrit.RESOURCES.css().dataHeader());
- }
-
- void deleteChecked() {
- final HashSet<AccountProjectWatch.Key> ids =
- new HashSet<AccountProjectWatch.Key>();
- for (int row = 1; row < table.getRowCount(); row++) {
- final AccountProjectWatchInfo k = getRowItem(row);
- if (k != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
- ids.add(k.getWatch().getKey());
- }
- }
- if (!ids.isEmpty()) {
- Util.ACCOUNT_SVC.deleteProjectWatches(ids,
- new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
- for (int row = 1; row < table.getRowCount();) {
- final AccountProjectWatchInfo k = getRowItem(row);
- if (k != null && ids.contains(k.getWatch().getKey())) {
- table.removeRow(row);
- } else {
- row++;
- }
- }
- }
- });
+ protected void populateWatches() {
+ Util.ACCOUNT_SVC.myProjectWatch(
+ new ScreenLoadCallback<List<AccountProjectWatchInfo>>(this) {
+ @Override
+ public void preDisplay(final List<AccountProjectWatchInfo> result) {
+ watchesTab.display(result);
}
- }
+ });
+ }
- void insertWatch(final AccountProjectWatchInfo k) {
- final String newName = k.getProject().getName();
- int row = 1;
- for (; row < table.getRowCount(); row++) {
- final AccountProjectWatchInfo i = getRowItem(row);
- if (i != null && i.getProject().getName().compareTo(newName) >= 0) {
- break;
+ protected void populateProjects() {
+ Util.PROJECT_SVC.visibleProjects(
+ new GerritCallback<List<Project>>() {
+ @Override
+ public void onSuccess(final List<Project> result) {
+ projectsTab.display(result);
+ if (firstPopupLoad) { // Display was delayed until table was loaded
+ firstPopupLoad = false;
+ displayPopup();
}
}
-
- table.insertRow(row);
- applyDataRowStyle(row);
- populate(row, k);
- }
-
- void display(final List<AccountProjectWatchInfo> result) {
- while (2 < table.getRowCount())
- table.removeRow(table.getRowCount() - 1);
-
- for (final AccountProjectWatchInfo k : result) {
- final int row = table.getRowCount();
- table.insertRow(row);
- applyDataRowStyle(row);
- populate(row, k);
- }
- }
-
- 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, fp);
- {
- final CheckBox notifyNewChanges = new CheckBox();
- notifyNewChanges.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- final boolean oldVal = k.getWatch().isNotifyNewChanges();
- k.getWatch().setNotifyNewChanges(notifyNewChanges.getValue());
- Util.ACCOUNT_SVC.updateProjectWatch(k.getWatch(),
- new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- k.getWatch().setNotifyNewChanges(oldVal);
- notifyNewChanges.setValue(oldVal);
- super.onFailure(caught);
- }
- });
- }
- });
- notifyNewChanges.setValue(k.getWatch().isNotifyNewChanges());
- table.setWidget(row, 3, notifyNewChanges);
- }
- {
- final CheckBox notifyAllComments = new CheckBox();
- notifyAllComments.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- final boolean oldVal = k.getWatch().isNotifyAllComments();
- k.getWatch().setNotifyAllComments(notifyAllComments.getValue());
- Util.ACCOUNT_SVC.updateProjectWatch(k.getWatch(),
- new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- k.getWatch().setNotifyAllComments(oldVal);
- notifyAllComments.setValue(oldVal);
- super.onFailure(caught);
- }
- });
- }
- });
- notifyAllComments.setValue(k.getWatch().isNotifyAllComments());
- table.setWidget(row, 4, notifyAllComments);
- }
- {
- final CheckBox notifySubmittedChanges = new CheckBox();
- notifySubmittedChanges.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- final boolean oldVal = k.getWatch().isNotifySubmittedChanges();
- k.getWatch().setNotifySubmittedChanges(
- notifySubmittedChanges.getValue());
- Util.ACCOUNT_SVC.updateProjectWatch(k.getWatch(),
- new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- k.getWatch().setNotifySubmittedChanges(oldVal);
- notifySubmittedChanges.setValue(oldVal);
- super.onFailure(caught);
- }
- });
- }
- });
- notifySubmittedChanges
- .setValue(k.getWatch().isNotifySubmittedChanges());
- table.setWidget(row, 5, notifySubmittedChanges);
- }
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
- fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 5, Gerrit.RESOURCES.css().dataCell());
-
- setRowItem(row, k);
- }
+ });
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
new file mode 100644
index 0000000000..be808fa57e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
@@ -0,0 +1,178 @@
+// 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.rpc.GerritCallback;
+import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gerrit.client.ui.ProjectLink;
+import com.google.gerrit.common.data.AccountProjectWatchInfo;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.Change.Status;
+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.CheckBox;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwtjsonrpc.client.VoidResult;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class MyWatchesTable extends FancyFlexTable<AccountProjectWatchInfo> {
+
+ public MyWatchesTable() {
+ table.setWidth("");
+ table.insertRow(1);
+ table.setText(0, 2, Util.C.watchedProjectName());
+ table.setText(0, 3, Util.C.watchedProjectColumnEmailNotifications());
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
+ fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
+ fmt.setRowSpan(0, 0, 2);
+ fmt.setRowSpan(0, 1, 2);
+ fmt.setRowSpan(0, 2, 2);
+ DOM.setElementProperty(fmt.getElement(0, 3), "align", "center");
+
+ fmt.setColSpan(0, 3, 3);
+ table.setText(1, 0, Util.C.watchedProjectColumnNewChanges());
+ table.setText(1, 1, Util.C.watchedProjectColumnAllComments());
+ table.setText(1, 2, Util.C.watchedProjectColumnSubmittedChanges());
+ fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(1, 1, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(1, 2, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ public void deleteChecked() {
+ final Set<AccountProjectWatch.Key> ids = getCheckedIds();
+ if (!ids.isEmpty()) {
+ Util.ACCOUNT_SVC.deleteProjectWatches(ids,
+ new GerritCallback<VoidResult>() {
+ public void onSuccess(final VoidResult result) {
+ remove(ids);
+ }
+ });
+ }
+ }
+
+ protected void remove(Set<AccountProjectWatch.Key> ids) {
+ for (int row = 1; row < table.getRowCount();) {
+ final AccountProjectWatchInfo k = getRowItem(row);
+ if (k != null && ids.contains(k.getWatch().getKey())) {
+ table.removeRow(row);
+ } else {
+ row++;
+ }
+ }
+ }
+
+ protected Set<AccountProjectWatch.Key> getCheckedIds() {
+ final Set<AccountProjectWatch.Key> ids =
+ new HashSet<AccountProjectWatch.Key>();
+ for (int row = 1; row < table.getRowCount(); row++) {
+ final AccountProjectWatchInfo k = getRowItem(row);
+ if (k != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
+ ids.add(k.getWatch().getKey());
+ }
+ }
+ return ids;
+ }
+
+ public void insertWatch(final AccountProjectWatchInfo k) {
+ final String newName = k.getProject().getName();
+ int row = 1;
+ for (; row < table.getRowCount(); row++) {
+ final AccountProjectWatchInfo i = getRowItem(row);
+ if (i != null && i.getProject().getName().compareTo(newName) >= 0) {
+ break;
+ }
+ }
+
+ table.insertRow(row);
+ applyDataRowStyle(row);
+ populate(row, k);
+ }
+
+ public void display(final List<AccountProjectWatchInfo> result) {
+ while (2 < table.getRowCount())
+ table.removeRow(table.getRowCount() - 1);
+
+ for (final AccountProjectWatchInfo k : result) {
+ final int row = table.getRowCount();
+ table.insertRow(row);
+ applyDataRowStyle(row);
+ populate(row, k);
+ }
+ }
+
+ protected void populate(final int row, final AccountProjectWatchInfo info) {
+ final FlowPanel fp = new FlowPanel();
+ fp.add(new ProjectLink(info.getProject().getNameKey(), Status.NEW));
+ if (info.getWatch().getFilter() != null) {
+ Label filter = new Label(info.getWatch().getFilter());
+ filter.setStyleName(Gerrit.RESOURCES.css().watchedProjectFilter());
+ fp.add(filter);
+ }
+
+ table.setWidget(row, 1, new CheckBox());
+ table.setWidget(row, 2, fp);
+
+ addNotifyButton(AccountProjectWatch.Type.NEW_CHANGES, info, row, 3);
+ addNotifyButton(AccountProjectWatch.Type.COMMENTS, info, row, 4);
+ addNotifyButton(AccountProjectWatch.Type.SUBMITS, info, row, 5);
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
+ fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 5, Gerrit.RESOURCES.css().dataCell());
+
+ setRowItem(row, info);
+ }
+
+ protected void addNotifyButton(final AccountProjectWatch.Type type,
+ final AccountProjectWatchInfo info, final int row, final int col) {
+ final CheckBox cbox = new CheckBox();
+
+ cbox.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ final boolean oldVal = info.getWatch().isNotify(type);
+ info.getWatch().setNotify(type, cbox.getValue());
+ Util.ACCOUNT_SVC.updateProjectWatch(info.getWatch(),
+ new GerritCallback<VoidResult>() {
+ public void onSuccess(final VoidResult result) {
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ info.getWatch().setNotify(type, oldVal);
+ cbox.setValue(oldVal);
+ super.onFailure(caught);
+ }
+ });
+ }
+ });
+
+ cbox.setValue(info.getWatch().isNotify(type));
+ table.setWidget(row, col, cbox);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
index a0aeced39f..15b0e4ec86 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
@@ -18,8 +18,8 @@ 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.AccountScreen;
+import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AgreementInfo;
import com.google.gerrit.reviewdb.AccountAgreement;
@@ -144,7 +144,7 @@ public class NewAgreementScreen extends AccountScreen {
});
finalGroup.add(submit);
formBody.add(finalGroup);
- new TextSaveButtonListener(yesIAgreeBox, submit);
+ new OnEditEnabler(submit, yesIAgreeBox);
final FormPanel form = new FormPanel();
form.add(formBody);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
index 93ccb7431a..73e784bd6b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -17,7 +17,7 @@ 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.client.ui.OnEditEnabler;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.reviewdb.Account;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -73,7 +73,7 @@ class UsernameField extends Composite {
doSetUserName();
}
});
- new TextSaveButtonListener(userNameTxt, setUserName);
+ new OnEditEnabler(setUserName, userNameTxt);
userNameLbl.setVisible(false);
body.add(userNameLbl);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
index 5ed79e6716..59e439d57c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
@@ -16,6 +16,8 @@ package com.google.gerrit.client.account;
import com.google.gerrit.common.data.AccountSecurity;
import com.google.gerrit.common.data.AccountService;
+import com.google.gerrit.common.data.ProjectAdminService;
+import com.google.gerrit.reviewdb.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwtjsonrpc.client.JsonUtil;
@@ -24,6 +26,7 @@ public class Util {
public static final AccountMessages M = GWT.create(AccountMessages.class);
public static final AccountService ACCOUNT_SVC;
public static final AccountSecurity ACCOUNT_SEC;
+ public static final ProjectAdminService PROJECT_SVC;
static {
ACCOUNT_SVC = GWT.create(AccountService.class);
@@ -31,5 +34,8 @@ public class Util {
ACCOUNT_SEC = GWT.create(AccountSecurity.class);
JsonUtil.bind(ACCOUNT_SEC, "rpc/AccountSecurity");
+
+ PROJECT_SVC = GWT.create(ProjectAdminService.class);
+ JsonUtil.bind(PROJECT_SVC, "rpc/ProjectAdminService");
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java
new file mode 100644
index 0000000000..762cc47141
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java
@@ -0,0 +1,408 @@
+// 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.admin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
+import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.RPCSuggestOracle;
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ProjectDetail;
+import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.ApprovalCategoryValue;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.RefRight;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+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.ListBox;
+import com.google.gwt.user.client.ui.SuggestBox;
+
+public class AccessRightEditor extends Composite
+ implements HasValueChangeHandlers<ProjectDetail> {
+ private Project.NameKey projectKey;
+ private ListBox catBox;
+ private HintTextBox nameTxt;
+ private SuggestBox nameSug;
+ private HintTextBox referenceTxt;
+ private ListBox topBox;
+ private ListBox botBox;
+ private Button addBut;
+ private Button clearBut;
+
+ public AccessRightEditor(final Project.NameKey key) {
+ projectKey = key;
+
+ initWidgets();
+ initCategories();
+
+ final Grid grid = new Grid(5, 2);
+ grid.setText(0, 0, Util.C.columnApprovalCategory() + ":");
+ grid.setWidget(0, 1, catBox);
+
+ grid.setText(1, 0, Util.C.columnGroupName() + ":");
+ grid.setWidget(1, 1, nameSug);
+
+ grid.setText(2, 0, Util.C.columnRefName() + ":");
+ grid.setWidget(2, 1, referenceTxt);
+
+ grid.setText(3, 0, Util.C.columnRightRange() + ":");
+ grid.setWidget(3, 1, topBox);
+
+ grid.setText(4, 0, "");
+ grid.setWidget(4, 1, botBox);
+
+ FlowPanel fp = new FlowPanel();
+ fp.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
+
+ fp.add(grid);
+ fp.add(addBut);
+ fp.add(clearBut);
+ initWidget(fp);
+ }
+
+ protected void initWidgets() {
+ catBox = new ListBox();
+ catBox.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(final ChangeEvent event) {
+ updateCategorySelection();
+ }
+ });
+
+ nameTxt = new HintTextBox();
+ nameSug = new SuggestBox(new RPCSuggestOracle(
+ new AccountGroupSuggestOracle()), nameTxt);
+ nameTxt.setVisibleLength(50);
+ nameTxt.setHintText(Util.C.defaultAccountGroupName());
+
+ referenceTxt = new HintTextBox();
+ referenceTxt.setVisibleLength(50);
+ referenceTxt.setText("");
+ referenceTxt.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ doAddNewRight();
+ }
+ }
+ });
+
+ topBox = new ListBox();
+ botBox = new ListBox();
+
+ addBut = new Button(Util.C.buttonAddProjectRight());
+ addBut.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ doAddNewRight();
+ }
+ });
+
+ clearBut = new Button(Util.C.buttonClearProjectRight());
+ clearBut.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ clear();
+ }
+ });
+ }
+
+ protected void initCategories() {
+ for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
+ .getApprovalTypes()) {
+ final ApprovalCategory c = at.getCategory();
+ catBox.addItem(c.getName(), c.getId().get());
+ }
+ for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
+ .getActionTypes()) {
+ final ApprovalCategory c = at.getCategory();
+ if (Gerrit.getConfig().getWildProject().equals(projectKey)
+ && ApprovalCategory.OWN.equals(c.getId())) {
+ // Giving out control of the WILD_PROJECT to other groups beyond
+ // Administrators is dangerous. Having control over WILD_PROJECT
+ // is about the same as having Administrator access as users are
+ // able to affect grants in all projects on the system.
+ //
+ continue;
+ }
+ catBox.addItem(c.getName(), c.getId().get());
+ }
+
+ if (catBox.getItemCount() > 0) {
+ catBox.setSelectedIndex(0);
+ updateCategorySelection();
+ }
+ }
+
+ public void enableForm(final boolean on) {
+ final boolean canAdd = on && catBox.getItemCount() > 0;
+ addBut.setEnabled(canAdd);
+ clearBut.setEnabled(canAdd);
+ nameTxt.setEnabled(canAdd);
+ referenceTxt.setEnabled(canAdd);
+ catBox.setEnabled(canAdd);
+ topBox.setEnabled(canAdd);
+ botBox.setEnabled(canAdd);
+ }
+
+ public void clear() {
+ setCat(null);
+ setName("");
+ setReference("");
+ }
+
+ public void load(final RefRight right, final AccountGroup group) {
+ final ApprovalType atype =
+ Gerrit.getConfig().getApprovalTypes().getApprovalType(
+ right.getApprovalCategoryId());
+
+ setCat(atype != null ? atype.getCategory().getName()
+ : right.getApprovalCategoryId().get() );
+
+ setName(group.getName());
+ setReference(right.getRefPatternForDisplay());
+
+ setRange(atype.getCategory().isRange() ? atype.getValue(right.getMinValue())
+ : null, atype.getValue(right.getMaxValue()) );
+ }
+
+ protected void doAddNewRight() {
+ final ApprovalType at = getApprovalType();
+ ApprovalCategoryValue min = getMin(at);
+ ApprovalCategoryValue max = getMax(at);
+
+ if (at == null || min == null || max == null) {
+ return;
+ }
+
+ final String groupName = nameSug.getText();
+ if ("".equals(groupName)
+ || Util.C.defaultAccountGroupName().equals(groupName)) {
+ return;
+ }
+
+ final String refPattern = referenceTxt.getText();
+
+ addBut.setEnabled(false);
+ Util.PROJECT_SVC.addRight(projectKey, at.getCategory().getId(),
+ groupName, refPattern, min.getValue(), max.getValue(),
+ new GerritCallback<ProjectDetail>() {
+ public void onSuccess(final ProjectDetail result) {
+ addBut.setEnabled(true);
+ nameSug.setText("");
+ referenceTxt.setText("");
+ ValueChangeEvent.fire(AccessRightEditor.this, result);
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ addBut.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ protected void updateCategorySelection() {
+ final ApprovalType at = getApprovalType();
+
+ if (at == null || at.getValues().isEmpty()) {
+ topBox.setEnabled(false);
+ botBox.setEnabled(false);
+ referenceTxt.setEnabled(false);
+ addBut.setEnabled(false);
+ clearBut.setEnabled(false);
+ return;
+ }
+
+ updateRanges(at);
+ }
+
+ protected void updateRanges(final ApprovalType at) {
+ ApprovalCategoryValue min = null, max = null, last = null;
+
+ topBox.clear();
+ botBox.clear();
+
+ for(final ApprovalCategoryValue v : at.getValues()) {
+ final int nval = v.getValue();
+ final String vStr = String.valueOf(nval);
+
+ String nStr = vStr + ": " + v.getName();
+ if (nval > 0) {
+ nStr = "+" + nStr;
+ }
+
+ topBox.addItem(nStr, vStr);
+ botBox.addItem(nStr, vStr);
+
+ if (min == null || nval < 0) {
+ min = v;
+ } else if (max == null && nval > 0) {
+ max = v;
+ }
+ last = v;
+ }
+
+ if (max == null) {
+ max = last;
+ }
+
+ if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
+ // Special case; for READ the most logical range is just
+ // +1 READ, so assume that as the default for both.
+ min = max;
+ }
+
+ if (! at.getCategory().isRange()) {
+ max = null;
+ }
+
+ setRange(min, max);
+ }
+
+ protected void setCat(final String cat) {
+ if (cat == null) {
+ catBox.setSelectedIndex(0);
+ } else {
+ setSelectedText(catBox, cat);
+ }
+ updateCategorySelection();
+ }
+
+ protected void setName(final String name) {
+ nameTxt.setText(name);
+ }
+
+ protected void setReference(final String ref) {
+ referenceTxt.setText(ref);
+ }
+
+ protected void setRange(final ApprovalCategoryValue min,
+ final ApprovalCategoryValue max) {
+ if (min == null || max == null) {
+ botBox.setVisible(false);
+ if (max != null) {
+ setSelectedValue(topBox, "" + max.getValue());
+ return;
+ }
+ } else {
+ botBox.setVisible(true);
+ setSelectedValue(botBox, "" + max.getValue());
+ }
+ setSelectedValue(topBox, "" + min.getValue());
+ }
+
+ private ApprovalType getApprovalType() {
+ int idx = catBox.getSelectedIndex();
+ if (idx < 0) {
+ return null;
+ }
+ return Gerrit.getConfig().getApprovalTypes().getApprovalType(
+ new ApprovalCategory.Id(catBox.getValue(idx)));
+ }
+
+ public ApprovalCategoryValue getMin(ApprovalType at) {
+ final ApprovalCategoryValue top = getTop(at);
+ final ApprovalCategoryValue bot = getBot(at);
+ if (bot == null) {
+ for (final ApprovalCategoryValue v : at.getValues()) {
+ if (0 <= v.getValue() && v.getValue() <= top.getValue()) {
+ return v;
+ }
+ }
+ return at.getMin();
+ }
+
+ if (top.getValue() > bot.getValue()) {
+ return bot;
+ }
+ return top;
+ }
+
+ public ApprovalCategoryValue getMax(ApprovalType at) {
+ final ApprovalCategoryValue top = getTop(at);
+ final ApprovalCategoryValue bot = getBot(at);
+ if (bot == null || bot.getValue() < top.getValue()) {
+ return top;
+ }
+ return bot;
+ }
+
+ protected ApprovalCategoryValue getTop(ApprovalType at) {
+ int idx = topBox.getSelectedIndex();
+ if (idx < 0) {
+ return null;
+ }
+ return at.getValue(Short.parseShort(topBox.getValue(idx)));
+ }
+
+ protected ApprovalCategoryValue getBot(ApprovalType at) {
+ int idx = botBox.getSelectedIndex();
+ if (idx < 0 || ! botBox.isVisible()) {
+ return null;
+ }
+ return at.getValue(Short.parseShort(botBox.getValue(idx)));
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ final ValueChangeHandler<ProjectDetail> handler) {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ public static boolean setSelectedText(ListBox box, String text) {
+ if (text == null) {
+ return false;
+ }
+ for (int i =0 ; i < box.getItemCount(); i++) {
+ if (text.equals(box.getItemText(i))) {
+ box.setSelectedIndex(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean setSelectedValue(ListBox box, String value) {
+ if (value == null) {
+ return false;
+ }
+ for (int i =0 ; i < box.getItemCount(); i++) {
+ if (value.equals(box.getValue(i))) {
+ box.setSelectedIndex(i);
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
index dd76875200..389f22b7b5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
@@ -22,8 +22,9 @@ import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gerrit.client.ui.AddMemberBox;
import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gerrit.client.ui.OnEditEnabler;
+import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.reviewdb.Account;
@@ -134,7 +135,7 @@ public class AccountGroupScreen extends AccountScreen {
groupNamePanel.add(saveName);
add(groupNamePanel);
- new TextSaveButtonListener(groupNameTxt, saveName);
+ new OnEditEnabler(saveName, groupNameTxt);
}
private void initOwner() {
@@ -143,7 +144,8 @@ public class AccountGroupScreen extends AccountScreen {
ownerTxtBox = new NpTextBox();
ownerTxtBox.setVisibleLength(60);
- ownerTxt = new SuggestBox(new AccountGroupSuggestOracle(), ownerTxtBox);
+ ownerTxt = new SuggestBox(new RPCSuggestOracle(
+ new AccountGroupSuggestOracle()), ownerTxtBox);
ownerPanel.add(ownerTxt);
saveOwner = new Button(Util.C.buttonChangeGroupOwner());
@@ -165,7 +167,7 @@ public class AccountGroupScreen extends AccountScreen {
ownerPanel.add(saveOwner);
add(ownerPanel);
- new TextSaveButtonListener(ownerTxtBox, saveOwner);
+ new OnEditEnabler(saveOwner, ownerTxtBox);
}
private void initDescription() {
@@ -194,7 +196,7 @@ public class AccountGroupScreen extends AccountScreen {
vp.add(saveDesc);
add(vp);
- new TextSaveButtonListener(descTxt, saveDesc);
+ new OnEditEnabler(saveDesc, descTxt);
}
private void initGroupType() {
@@ -223,6 +225,7 @@ public class AccountGroupScreen extends AccountScreen {
case HTTP_LDAP:
case LDAP:
case LDAP_BIND:
+ case CLIENT_SSL_CERT_LDAP:
break;
default:
return;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 20de3e336b..6f27ce5e99 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -31,14 +31,17 @@ public interface AdminConstants extends Constants {
String buttonChangeGroupType();
String buttonSelectGroup();
String buttonAddProjectRight();
+ String buttonClearProjectRight();
String buttonSaveChanges();
+ String useContentMerge();
String useContributorAgreements();
String useSignedOffBy();
+ String requireChangeID();
String headingOwner();
String headingParentProjectName();
String headingDescription();
- String headingSubmitType();
+ String headingProjectOptions();
String headingGroupType();
String headingMembers();
String headingExternalGroup();
@@ -87,4 +90,5 @@ public interface AdminConstants extends Constants {
String noGroupSelected();
String errorNoMatchingGroups();
+ String errorNoGitRepository();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 28917f1b8f..0874c37b74 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -12,14 +12,17 @@ buttonChangeGroupOwner = Change Owner
buttonChangeGroupType = Change Type
buttonSelectGroup = Select
buttonAddProjectRight = Add Access Right
+buttonClearProjectRight = Clear Form
buttonSaveChanges = Save Changes
+useContentMerge = Automatically resolve conflicts
useContributorAgreements = Require a valid contributor agreement to upload
useSignedOffBy = Require <a href="http://gerrit.googlecode.com/svn/documentation/2.0/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
+requireChangeID = Require <a href="http://gerrit.googlecode.com/svn/documentation/2.0/user-changeid.html" target="_blank"><code>Change-Id</code></a> in commit message
headingOwner = Owners
headingParentProjectName = Rights Inherit From
headingDescription = Description
-headingSubmitType = Change Submit Action
+headingProjectOptions = Project Options
headingGroupType = Group Type
headingMembers = Members
headingExternalGroup = Selected External Group
@@ -68,3 +71,4 @@ projectAdminTabAccess = Access
noGroupSelected = (No group selected)
errorNoMatchingGroups = No Matching Groups
+errorNoGitRepository = No Git Repository
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index 0586c8483d..9c51b77375 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -19,11 +19,16 @@ 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.AccountScreen;
+import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
@@ -78,14 +83,36 @@ public class GroupListScreen extends AccountScreen {
fp.add(addTxt);
addNew = new Button(Util.C.buttonCreateGroup());
+ addNew.setEnabled(false);
addNew.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
doCreateGroup();
}
});
+ addNew.addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ // unregister the keys for the 'groups' table so that pressing ENTER
+ // when the 'addNew' button has the focus triggers the button (if the
+ // keys for the 'groups' table would not be unregistered the 'addNew'
+ // button would not be triggered on ENTER but the group which is
+ // selected in the 'groups' table would be opened)
+ groups.setRegisterKeys(false);
+ }
+ });
+ addNew.addBlurHandler(new BlurHandler() {
+ @Override
+ public void onBlur(BlurEvent event) {
+ // re-register the keys for the 'groups' table when the 'addNew' button
+ // gets blurred
+ groups.setRegisterKeys(true);
+ }
+ });
fp.add(addNew);
add(fp);
+
+ new OnEditEnabler(addNew, addTxt);
}
@Override
@@ -100,10 +127,17 @@ public class GroupListScreen extends AccountScreen {
return;
}
+ addNew.setEnabled(false);
Util.GROUP_SVC.createGroup(newName, new GerritCallback<AccountGroup.Id>() {
public void onSuccess(final AccountGroup.Id result) {
History.newItem(Dispatcher.toAccountGroup(result));
}
+
+ @Override
+ public void onFailure(Throwable caught) {
+ super.onFailure(caught);
+ addNew.setEnabled(true);
+ }
});
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 14449f9cb8..5b863c76f0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -18,7 +18,6 @@ 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;
import com.google.gerrit.client.ui.SmallHeading;
@@ -27,31 +26,18 @@ import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.InheritedRefRight;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.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.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -65,14 +51,7 @@ public class ProjectAccessScreen extends ProjectScreen {
private RightsTable rights;
private Button delRight;
- private Button addRight;
- private ListBox catBox;
- private ListBox rangeMinBox;
- private ListBox rangeMaxBox;
- private NpTextBox nameTxtBox;
- private SuggestBox nameTxt;
- private NpTextBox referenceTxt;
- private FlowPanel addPanel;
+ private AccessRightEditor rightEditor;
public ProjectAccessScreen(final Project.NameKey toShow) {
super(toShow);
@@ -99,14 +78,7 @@ public class ProjectAccessScreen extends ProjectScreen {
private void enableForm(final boolean on) {
delRight.setEnabled(on);
-
- final boolean canAdd = on && catBox.getItemCount() > 0;
- addRight.setEnabled(canAdd);
- nameTxtBox.setEnabled(canAdd);
- referenceTxt.setEnabled(canAdd);
- catBox.setEnabled(canAdd);
- rangeMinBox.setEnabled(canAdd);
- rangeMaxBox.setEnabled(canAdd);
+ rightEditor.enableForm(on);
}
private void initParent() {
@@ -119,102 +91,6 @@ public class ProjectAccessScreen extends ProjectScreen {
}
private void initRights() {
- addPanel = new FlowPanel();
- addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
-
- final Grid addGrid = new Grid(5, 2);
-
- catBox = new ListBox();
- rangeMinBox = new ListBox();
- rangeMaxBox = new ListBox();
-
- catBox.addChangeHandler(new ChangeHandler() {
- @Override
- public void onChange(final ChangeEvent event) {
- updateCategorySelection();
- }
- });
- for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
- .getApprovalTypes()) {
- final ApprovalCategory c = at.getCategory();
- catBox.addItem(c.getName(), c.getId().get());
- }
- for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
- .getActionTypes()) {
- final ApprovalCategory c = at.getCategory();
- 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
- // is about the same as having Administrator access as users are
- // able to affect grants in all projects on the system.
- //
- continue;
- }
- catBox.addItem(c.getName(), c.getId().get());
- }
-
- addGrid.setText(0, 0, Util.C.columnApprovalCategory() + ":");
- addGrid.setWidget(0, 1, catBox);
-
- nameTxtBox = new NpTextBox();
- nameTxt = new SuggestBox(new AccountGroupSuggestOracle(), nameTxtBox);
- nameTxtBox.setVisibleLength(50);
- nameTxtBox.setText(Util.C.defaultAccountGroupName());
- nameTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- nameTxtBox.addFocusHandler(new FocusHandler() {
- @Override
- public void onFocus(FocusEvent event) {
- if (Util.C.defaultAccountGroupName().equals(nameTxtBox.getText())) {
- nameTxtBox.setText("");
- nameTxtBox.removeStyleName(Gerrit.RESOURCES.css()
- .inputFieldTypeHint());
- }
- }
- });
- nameTxtBox.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(BlurEvent event) {
- if ("".equals(nameTxtBox.getText())) {
- nameTxtBox.setText(Util.C.defaultAccountGroupName());
- nameTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
- addGrid.setText(1, 0, Util.C.columnGroupName() + ":");
- addGrid.setWidget(1, 1, nameTxt);
-
- referenceTxt = new NpTextBox();
- referenceTxt.setVisibleLength(50);
- referenceTxt.setText("");
- referenceTxt.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (event.getCharCode() == KeyCodes.KEY_ENTER) {
- doAddNewRight();
- }
- }
- });
-
- addGrid.setText(2, 0, Util.C.columnRefName() + ":");
- addGrid.setWidget(2, 1, referenceTxt);
-
- addGrid.setText(3, 0, Util.C.columnRightRange() + ":");
- addGrid.setWidget(3, 1, rangeMinBox);
-
- addGrid.setText(4, 0, "");
- addGrid.setWidget(4, 1, rangeMaxBox);
-
- addRight = new Button(Util.C.buttonAddProjectRight());
- addRight.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- doAddNewRight();
- }
- });
- addPanel.add(addGrid);
- addPanel.add(addRight);
-
rights = new RightsTable();
delRight = new Button(Util.C.buttonDeleteGroupMembers());
@@ -226,15 +102,18 @@ public class ProjectAccessScreen extends ProjectScreen {
}
});
+ rightEditor = new AccessRightEditor(getProjectKey());
+ rightEditor.addValueChangeHandler(new ValueChangeHandler<ProjectDetail>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<ProjectDetail> event) {
+ display(event.getValue());
+ }
+ });
+
add(new SmallHeading(Util.C.headingAccessRights()));
add(rights);
add(delRight);
- add(addPanel);
-
- if (catBox.getItemCount() > 0) {
- catBox.setSelectedIndex(0);
- updateCategorySelection();
- }
+ add(rightEditor);
}
void display(final ProjectDetail result) {
@@ -253,7 +132,7 @@ public class ProjectAccessScreen extends ProjectScreen {
rights.display(result.groups, result.rights);
- addPanel.setVisible(result.canModifyAccess);
+ rightEditor.setVisible(result.canModifyAccess);
delRight.setVisible(rights.getCanDelete());
}
@@ -270,144 +149,9 @@ public class ProjectAccessScreen extends ProjectScreen {
}
}
- private void doAddNewRight() {
- int idx = catBox.getSelectedIndex();
- final ApprovalType at;
- ApprovalCategoryValue min, max;
- if (idx < 0) {
- return;
- }
- at =
- Gerrit.getConfig().getApprovalTypes().getApprovalType(
- new ApprovalCategory.Id(catBox.getValue(idx)));
- if (at == null) {
- return;
- }
-
- idx = rangeMinBox.getSelectedIndex();
- if (idx < 0) {
- return;
- }
- min = at.getValue(Short.parseShort(rangeMinBox.getValue(idx)));
- if (min == null) {
- return;
- }
-
- if (at.getCategory().isRange()) {
- idx = rangeMaxBox.getSelectedIndex();
- if (idx < 0) {
- return;
- }
- max = at.getValue(Short.parseShort(rangeMaxBox.getValue(idx)));
- if (max == null) {
- return;
- }
- } else {
- // If its not a range, the maximum box was disabled. Use the min
- // value as the max, and select the min from the category values.
- //
- max = min;
- min = at.getMin();
- for (ApprovalCategoryValue v : at.getValues()) {
- if (0 <= v.getValue() && v.getValue() <= max.getValue()) {
- min = v;
- break;
- }
- }
- }
-
- final String groupName = nameTxt.getText();
- if ("".equals(groupName)
- || Util.C.defaultAccountGroupName().equals(groupName)) {
- return;
- }
-
- final String refPattern = referenceTxt.getText();
-
- if (min.getValue() > max.getValue()) {
- // If the user selects it backwards in the web UI, help them out
- // by reversing the order to what we would expect.
- //
- final ApprovalCategoryValue newMin = max;
- final ApprovalCategoryValue newMax = min;
- min = newMin;
- max = newMax;
- }
-
- addRight.setEnabled(false);
- 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);
- nameTxt.setText("");
- referenceTxt.setText("");
- display(result);
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- addRight.setEnabled(true);
- super.onFailure(caught);
- }
- });
- }
-
- private void updateCategorySelection() {
- final int idx = catBox.getSelectedIndex();
- final ApprovalType at;
- if (idx >= 0) {
- at =
- Gerrit.getConfig().getApprovalTypes().getApprovalType(
- new ApprovalCategory.Id(catBox.getValue(idx)));
- } else {
- at = null;
- }
-
- if (at == null || at.getValues().isEmpty()) {
- rangeMinBox.setEnabled(false);
- rangeMaxBox.setEnabled(false);
- referenceTxt.setEnabled(false);
- addRight.setEnabled(false);
- return;
- }
-
- int curIndex = 0, minIndex = -1, maxIndex = -1;
- rangeMinBox.clear();
- rangeMaxBox.clear();
- for (final ApprovalCategoryValue v : at.getValues()) {
- final String vStr = String.valueOf(v.getValue());
- String nStr = vStr + ": " + v.getName();
- if (v.getValue() > 0) {
- nStr = "+" + nStr;
- }
-
- rangeMinBox.addItem(nStr, vStr);
- rangeMaxBox.addItem(nStr, vStr);
-
- if (v.getValue() < 0) {
- minIndex = curIndex;
- }
- if (maxIndex < 0 && v.getValue() > 0) {
- maxIndex = curIndex;
- }
-
- curIndex++;
- }
- if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
- // Special case; for READ the most logical range is just
- // +1 READ, so assume that as the default for both.
- minIndex = maxIndex;
- }
- rangeMinBox.setSelectedIndex(minIndex >= 0 ? minIndex : 0);
- rangeMaxBox.setSelectedIndex(maxIndex >= 0 ? maxIndex : curIndex - 1);
- rangeMaxBox.setVisible(at.getCategory().isRange());
-
- addRight.setEnabled(true);
- }
-
private class RightsTable extends FancyFlexTable<RefRight> {
boolean canDelete;
+ Map<AccountGroup.Id, AccountGroup> groups;
RightsTable() {
table.setWidth("");
@@ -422,6 +166,13 @@ public class ProjectAccessScreen extends ProjectScreen {
fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 5, Gerrit.RESOURCES.css().dataHeader());
+
+ table.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ onOpenRow(table.getCellForEvent(event).getRowIndex());
+ }
+ });
}
HashSet<RefRight.Key> getRefRightIdsChecked() {
@@ -436,8 +187,9 @@ public class ProjectAccessScreen extends ProjectScreen {
return refRightIds;
}
- void display(final Map<AccountGroup.Id, AccountGroup> groups,
+ void display(final Map<AccountGroup.Id, AccountGroup> grps,
final List<InheritedRefRight> refRights) {
+ groups = grps;
canDelete = false;
while (1 < table.getRowCount())
@@ -447,12 +199,17 @@ public class ProjectAccessScreen extends ProjectScreen {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
- populate(row, groups, r);
+ populate(row, r);
+ }
+ }
+ protected void onOpenRow(final int row) {
+ if (row > 0) {
+ RefRight right = getRowItem(row);
+ rightEditor.load(right, groups.get(right.getAccountGroupId()));
}
}
- void populate(final int row, final Map<AccountGroup.Id, AccountGroup> groups,
- final InheritedRefRight r) {
+ void populate(final int row, final InheritedRefRight r) {
final GerritConfig config = Gerrit.getConfig();
final RefRight right = r.getRight();
final ApprovalType ar =
@@ -467,14 +224,12 @@ public class ProjectAccessScreen extends ProjectScreen {
canDelete = true;
}
- if (ar != null) {
- table.setText(row, 2, ar.getCategory().getName());
- } else {
- table.setText(row, 2, right.getApprovalCategoryId().get());
- }
+ table.setText(row, 2, ar != null ? ar.getCategory().getName()
+ : right.getApprovalCategoryId().get() );
if (group != null) {
- table.setText(row, 3, group.getName());
+ table.setWidget(row, 3, new Hyperlink(group.getName(), Dispatcher
+ .toAccountGroup(group.getId())));
} else {
table.setText(row, 3, Util.M.deletedGroup(right.getAccountGroupId()
.get()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 8bff3e5ff5..77c431a1da 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -20,18 +20,15 @@ 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.HintTextBox;
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;
import com.google.gerrit.reviewdb.Project;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
@@ -43,8 +40,8 @@ import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.RemoteJsonException;
import java.util.HashSet;
@@ -55,8 +52,8 @@ public class ProjectBranchesScreen extends ProjectScreen {
private BranchesTable branches;
private Button delBranch;
private Button addBranch;
- private NpTextBox nameTxtBox;
- private NpTextBox irevTxtBox;
+ private HintTextBox nameTxtBox;
+ private HintTextBox irevTxtBox;
private FlowPanel addPanel;
public ProjectBranchesScreen(final Project.NameKey toShow) {
@@ -68,10 +65,22 @@ public class ProjectBranchesScreen extends ProjectScreen {
super.onLoad();
Util.PROJECT_SVC.listBranches(getProjectKey(),
new ScreenLoadCallback<ListBranchesResult>(this) {
+ @Override
public void preDisplay(final ListBranchesResult result) {
- enableForm(true);
- display(result.getBranches());
- addPanel.setVisible(result.getCanAdd());
+ if (result.getNoRepository()) {
+ branches.setVisible(false);
+ addPanel.setVisible(false);
+ delBranch.setVisible(false);
+
+ Label no = new Label(Util.C.errorNoGitRepository());
+ no.setStyleName(Gerrit.RESOURCES.css().smallHeading());
+ add(no);
+
+ } else {
+ enableForm(true);
+ display(result.getBranches());
+ addPanel.setVisible(result.getCanAdd());
+ }
}
});
}
@@ -97,28 +106,9 @@ public class ProjectBranchesScreen extends ProjectScreen {
final Grid addGrid = new Grid(2, 2);
- nameTxtBox = new NpTextBox();
+ nameTxtBox = new HintTextBox();
nameTxtBox.setVisibleLength(50);
- nameTxtBox.setText(Util.C.defaultBranchName());
- nameTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- nameTxtBox.addFocusHandler(new FocusHandler() {
- @Override
- public void onFocus(FocusEvent event) {
- if (Util.C.defaultBranchName().equals(nameTxtBox.getText())) {
- nameTxtBox.setText("");
- nameTxtBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
- nameTxtBox.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(BlurEvent event) {
- if ("".equals(nameTxtBox.getText())) {
- nameTxtBox.setText(Util.C.defaultBranchName());
- nameTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
+ nameTxtBox.setHintText(Util.C.defaultBranchName());
nameTxtBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
@@ -130,28 +120,9 @@ public class ProjectBranchesScreen extends ProjectScreen {
addGrid.setText(0, 0, Util.C.columnBranchName() + ":");
addGrid.setWidget(0, 1, nameTxtBox);
- irevTxtBox = new NpTextBox();
+ irevTxtBox = new HintTextBox();
irevTxtBox.setVisibleLength(50);
- irevTxtBox.setText(Util.C.defaultRevisionSpec());
- irevTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- irevTxtBox.addFocusHandler(new FocusHandler() {
- @Override
- public void onFocus(FocusEvent event) {
- if (Util.C.defaultRevisionSpec().equals(irevTxtBox.getText())) {
- irevTxtBox.setText("");
- irevTxtBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
- irevTxtBox.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(BlurEvent event) {
- if ("".equals(irevTxtBox.getText())) {
- irevTxtBox.setText(Util.C.defaultRevisionSpec());
- irevTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
+ irevTxtBox.setHintText(Util.C.defaultRevisionSpec());
irevTxtBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
@@ -190,13 +161,13 @@ public class ProjectBranchesScreen extends ProjectScreen {
private void doAddNewBranch() {
String branchName = nameTxtBox.getText();
- if ("".equals(branchName) || Util.C.defaultBranchName().equals(branchName)) {
+ if ("".equals(branchName)) {
nameTxtBox.setFocus(true);
return;
}
String rev = irevTxtBox.getText();
- if ("".equals(rev) || Util.C.defaultRevisionSpec().equals(rev)) {
+ if ("".equals(rev)) {
irevTxtBox.setText("HEAD");
DeferredCommand.addCommand(new Command() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index d308fa6c1a..22fd8d9935 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -17,16 +17,15 @@ 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.OnEditEnabler;
import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.Project.SubmitType;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.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.ListBox;
@@ -37,8 +36,10 @@ import com.google.gwtexpui.globalkey.client.NpTextArea;
public class ProjectInfoScreen extends ProjectScreen {
private Project project;
- private Panel submitTypePanel;
+ private Panel projectOptionsPanel;
+ private CheckBox requireChangeID;
private ListBox submitType;
+ private CheckBox useContentMerge;
private Panel agreementsPanel;
private CheckBox useContributorAgreements;
@@ -47,6 +48,8 @@ public class ProjectInfoScreen extends ProjectScreen {
private NpTextArea descTxt;
private Button saveProject;
+ private OnEditEnabler saveEnabler;
+
public ProjectInfoScreen(final Project.NameKey toShow) {
super(toShow);
}
@@ -64,7 +67,7 @@ public class ProjectInfoScreen extends ProjectScreen {
});
initDescription();
- initSubmitType();
+ initProjectOptions();
initAgreements();
add(saveProject);
}
@@ -81,7 +84,6 @@ public class ProjectInfoScreen extends ProjectScreen {
result.canModifyAgreements ||
result.canModifyDescription ||
result.canModifyMergeType);
- saveProject.setEnabled(false);
display(result);
}
});
@@ -90,11 +92,11 @@ public class ProjectInfoScreen extends ProjectScreen {
private void enableForm(final boolean canModifyAgreements,
final boolean canModifyDescription, final boolean canModifyMergeType) {
submitType.setEnabled(canModifyMergeType);
+ useContentMerge.setEnabled(canModifyMergeType);
descTxt.setEnabled(canModifyDescription);
useContributorAgreements.setEnabled(canModifyAgreements);
useSignedOffBy.setEnabled(canModifyAgreements);
- saveProject.setEnabled(
- canModifyAgreements || canModifyDescription || canModifyMergeType);
+ requireChangeID.setEnabled(canModifyMergeType);
}
private void initDescription() {
@@ -107,12 +109,13 @@ public class ProjectInfoScreen extends ProjectScreen {
vp.add(descTxt);
add(vp);
- new TextSaveButtonListener(descTxt, saveProject);
+ saveEnabler = new OnEditEnabler(saveProject);
+ saveEnabler.listenTo(descTxt);
}
- private void initSubmitType() {
- submitTypePanel = new VerticalPanel();
- submitTypePanel.add(new SmallHeading(Util.C.headingSubmitType()));
+ private void initProjectOptions() {
+ projectOptionsPanel = new VerticalPanel();
+ projectOptionsPanel.add(new SmallHeading(Util.C.headingProjectOptions()));
submitType = new ListBox();
for (final Project.SubmitType type : Project.SubmitType.values()) {
@@ -120,46 +123,66 @@ public class ProjectInfoScreen extends ProjectScreen {
}
submitType.addChangeHandler(new ChangeHandler() {
@Override
- public void onChange(final ChangeEvent event) {
- saveProject.setEnabled(true);
+ public void onChange(ChangeEvent event) {
+ setEnabledForUseContentMerge();
}
});
- submitTypePanel.add(submitType);
- add(submitTypePanel);
+ saveEnabler.listenTo(submitType);
+ projectOptionsPanel.add(submitType);
+
+ useContentMerge = new CheckBox(Util.C.useContentMerge(), true);
+ saveEnabler.listenTo(useContentMerge);
+ projectOptionsPanel.add(useContentMerge);
+
+ requireChangeID = new CheckBox(Util.C.requireChangeID(), true);
+ saveEnabler.listenTo(requireChangeID);
+ projectOptionsPanel.add(requireChangeID);
+
+ add(projectOptionsPanel);
}
- private void initAgreements() {
- final ValueChangeHandler<Boolean> onChangeSave =
- new ValueChangeHandler<Boolean>() {
- @Override
- public void onValueChange(ValueChangeEvent<Boolean> event) {
- saveProject.setEnabled(true);
- }
- };
+ /**
+ * Enables the {@link #useContentMerge} checkbox if the selected submit type
+ * allows the usage of content merge.
+ * If the submit type (currently only 'Fast Forward Only') does not allow
+ * content merge the useContentMerge checkbox gets disabled.
+ */
+ private void setEnabledForUseContentMerge() {
+ if (SubmitType.FAST_FORWARD_ONLY.equals(Project.SubmitType
+ .valueOf(submitType.getValue(submitType.getSelectedIndex())))) {
+ useContentMerge.setEnabled(false);
+ useContentMerge.setValue(false);
+ } else {
+ useContentMerge.setEnabled(true);
+ }
+ }
+ private void initAgreements() {
agreementsPanel = new VerticalPanel();
agreementsPanel.add(new SmallHeading(Util.C.headingAgreements()));
useContributorAgreements = new CheckBox(Util.C.useContributorAgreements());
- useContributorAgreements.addValueChangeHandler(onChangeSave);
+ saveEnabler.listenTo(useContributorAgreements);
agreementsPanel.add(useContributorAgreements);
useSignedOffBy = new CheckBox(Util.C.useSignedOffBy(), true);
- useSignedOffBy.addValueChangeHandler(onChangeSave);
+ saveEnabler.listenTo(useSignedOffBy);
agreementsPanel.add(useSignedOffBy);
add(agreementsPanel);
}
private void setSubmitType(final Project.SubmitType newSubmitType) {
+ int index = -1;
if (submitType != null) {
for (int i = 0; i < submitType.getItemCount(); i++) {
if (newSubmitType.name().equals(submitType.getValue(i))) {
- submitType.setSelectedIndex(i);
- return;
+ index = i;
+ break;
}
}
- submitType.setSelectedIndex(-1);
+ submitType.setSelectedIndex(index);
+ setEnabledForUseContentMerge();
}
}
@@ -168,7 +191,7 @@ public class ProjectInfoScreen extends ProjectScreen {
final boolean isall =
Gerrit.getConfig().getWildProject().equals(project.getNameKey());
- submitTypePanel.setVisible(!isall);
+ projectOptionsPanel.setVisible(!isall);
agreementsPanel.setVisible(!isall);
useContributorAgreements.setVisible(Gerrit.getConfig()
.isUseContributorAgreements());
@@ -176,20 +199,25 @@ public class ProjectInfoScreen extends ProjectScreen {
descTxt.setText(project.getDescription());
useContributorAgreements.setValue(project.isUseContributorAgreements());
useSignedOffBy.setValue(project.isUseSignedOffBy());
+ useContentMerge.setValue(project.isUseContentMerge());
+ requireChangeID.setValue(project.isRequireChangeID());
setSubmitType(project.getSubmitType());
+
+ saveProject.setEnabled(false);
}
private void doSave() {
project.setDescription(descTxt.getText().trim());
project.setUseContributorAgreements(useContributorAgreements.getValue());
project.setUseSignedOffBy(useSignedOffBy.getValue());
+ project.setUseContentMerge(useContentMerge.getValue());
+ project.setRequireChangeID(requireChangeID.getValue());
if (submitType.getSelectedIndex() >= 0) {
project.setSubmitType(Project.SubmitType.valueOf(submitType
.getValue(submitType.getSelectedIndex())));
}
enableForm(false, false, false);
- saveProject.setEnabled(false);
Util.PROJECT_SVC.changeProjectSettings(project,
new GerritCallback<ProjectDetail>() {
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 e676718ee3..758f4dea46 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
@@ -18,23 +18,18 @@ 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.Hyperlink;
-import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.client.ui.ProjectsTable;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.Project;
-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.user.client.History;
import com.google.gwt.user.client.ui.VerticalPanel;
-import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwt.user.client.ui.HTMLTable.Cell;
import java.util.List;
public class ProjectListScreen extends Screen {
- private ProjectTable projects;
+ private ProjectsTable projects;
@Override
protected void onLoad() {
@@ -53,7 +48,26 @@ public class ProjectListScreen extends Screen {
super.onInitUI();
setPageTitle(Util.C.projectListTitle());
- projects = new ProjectTable();
+ projects = new ProjectsTable() {
+ @Override
+ protected void onOpenRow(final int row) {
+ History.newItem(link(getRowItem(row)));
+ }
+
+ private String link(final Project item) {
+ return Dispatcher.toProjectAdmin(item.getNameKey(), ProjectScreen.INFO);
+ }
+
+ @Override
+ protected void populate(final int row, final Project k) {
+ table.setWidget(row, 1, new Hyperlink(k.getName(), link(k)));
+ table.setText(row, 2, k.getDescription());
+
+ setRowItem(row, k);
+ }
+ };
+ projects.setSavePointerId(PageLinks.ADMIN_PROJECTS);
+
add(projects);
final VerticalPanel fp = new VerticalPanel();
@@ -66,70 +80,4 @@ public class ProjectListScreen extends Screen {
super.registerKeys();
projects.setRegisterKeys(true);
}
-
- private class ProjectTable extends NavigationTable<Project> {
- ProjectTable() {
- setSavePointerId(PageLinks.ADMIN_PROJECTS);
- keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.projectListPrev()));
- keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.projectListNext()));
- keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.projectListOpen()));
- keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER, Util.C
- .projectListOpen()));
-
- table.setText(0, 1, Util.C.columnProjectName());
- table.setText(0, 2, Util.C.columnProjectDescription());
- table.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- final Cell cell = table.getCellForEvent(event);
- if (cell != null && cell.getCellIndex() != 1
- && getRowItem(cell.getRowIndex()) != null) {
- movePointerTo(cell.getRowIndex());
- }
- }
- });
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
- }
-
- @Override
- protected Object getRowItemKey(final Project item) {
- return item.getNameKey();
- }
-
- @Override
- protected void onOpenRow(final int row) {
- History.newItem(link(getRowItem(row)));
- }
-
- private String link(final Project item) {
- return Dispatcher.toProjectAdmin(item.getNameKey(), ProjectScreen.INFO);
- }
-
- void display(final List<Project> result) {
- while (1 < table.getRowCount())
- table.removeRow(table.getRowCount() - 1);
-
- for (final Project k : result) {
- final int row = table.getRowCount();
- table.insertRow(row);
- applyDataRowStyle(row);
- populate(row, k);
- }
- }
-
- void populate(final int row, final Project k) {
- table.setWidget(row, 1, new Hyperlink(k.getName(), link(k)));
- table.setText(row, 2, k.getDescription());
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().cPROJECT());
- fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
-
- setRowItem(row, k);
- }
- }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/openid.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/openid.css
index 07133918fe..1e475ee6c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/openid.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/openid.css
@@ -23,7 +23,6 @@
}
.logo {
- width: 98%;
text-align: right;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java
index 46e16bb05d..c4709a5c3c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/userpass/UserPassSignInDialog.java
@@ -149,6 +149,7 @@ public class UserPassSignInDialog extends SignInDialog {
buttons.add(login);
close = new Button();
+ DOM.setStyleAttribute(close.getElement(), "marginLeft", "45px");
close.setText(Gerrit.C.signInDialogClose());
close.addClickHandler(new ClickHandler() {
@Override
@@ -226,7 +227,7 @@ public class UserPassSignInDialog extends SignInDialog {
@Override
public void onFailure(final Throwable caught) {
super.onFailure(caught);
- enable(false);
+ enable(true);
}
});
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index f850a33eda..23dd635719 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -188,6 +188,10 @@ public class ApprovalTable extends Composite {
r.append(Util.M.accountNotFound(e.getName()));
break;
+ case ACCOUNT_INACTIVE:
+ r.append(Util.M.accountInactive(e.getName()));
+ break;
+
case CHANGE_NOT_VISIBLE:
r.append(Util.M.changeNotVisibleTo(e.getName()));
break;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index 6c68ba92fd..312cc106c0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -49,6 +49,7 @@ public interface ChangeMessages extends Messages {
String changeQueryPageTitle(String query);
String accountNotFound(String who);
+ String accountInactive(String who);
String changeNotVisibleTo(String who);
String anonymousDownload(String protocol);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index a4a1c3165b..625a9ecb8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -1,6 +1,6 @@
accountDashboardTitle = Code Review Dashboard for {0}
changesStartedBy = Started by {0}
-changesReviewableBy = Reviewable by {0}
+changesReviewableBy = Review Requests for {0}
changesOpenInProject = Open Changes In {0}
changesMergedInProject = Merged Changes In {0}
changesAbandonedInProject = Abandoned Changes In {0}
@@ -30,6 +30,7 @@ changeQueryWindowTitle = {0}
changeQueryPageTitle = Search for {0}
accountNotFound = {0} is not a registered user.
+accountInactive = {0} is not an active user.
changeNotVisibleTo = {0} cannot access the change.
anonymousDownload = Anonymous {0}
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 83870c0a54..4db387f9ce 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
@@ -29,9 +29,9 @@ import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.Change.Status;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
-import com.google.gerrit.reviewdb.Change.Status;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -39,6 +39,7 @@ import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
@@ -115,7 +116,6 @@ public class ChangeScreen extends Screen {
super.registerKeys();
regNavigation = GlobalKey.add(this, keysNavigation);
regAction = GlobalKey.add(this, keysAction);
- patchSetsBlock.setRegisterKeys(true);
}
public void refresh() {
@@ -125,6 +125,11 @@ public class ChangeScreen extends Screen {
protected void preDisplay(final ChangeDetail r) {
display(r);
}
+
+ @Override
+ protected void postDisplay() {
+ patchSetsBlock.setRegisterKeys(true);
+ }
});
}
@@ -178,7 +183,7 @@ public class ChangeScreen extends Screen {
dependencies = new ChangeTable() {
{
- table.setWidth("98%");
+ table.setWidth("auto");
}
};
dependsOn = new ChangeTable.Section(Util.C.changeScreenDependsOn());
@@ -188,7 +193,6 @@ public class ChangeScreen extends Screen {
dependenciesPanel = new DisclosurePanel(Util.C.changeScreenDependencies());
dependenciesPanel.setContent(dependencies);
- dependenciesPanel.setWidth("95%");
add(dependenciesPanel);
patchSetsBlock = new PatchSetsBlock(this);
@@ -217,7 +221,12 @@ public class ChangeScreen extends Screen {
setPageTitle(titleBuf.toString());
}
- void display(final ChangeDetail detail) {
+ void update(final ChangeDetail detail) {
+ display(detail);
+ patchSetsBlock.setRegisterKeys(true);
+ }
+
+ private void display(final ChangeDetail detail) {
displayTitle(detail.getChange().getKey(), detail.getChange().getSubject());
if (starChange != null) {
@@ -263,15 +272,17 @@ public class ChangeScreen extends Screen {
private void addComments(final ChangeDetail detail) {
comments.clear();
- final Label hdr = new Label(Util.C.changeScreenComments());
- hdr.setStyleName(Gerrit.RESOURCES.css().blockHeader());
- comments.add(hdr);
-
final AccountInfoCache accts = detail.getAccounts();
final List<ChangeMessage> msgList = detail.getMessages();
+
+ HorizontalPanel title = new HorizontalPanel();
+ title.setWidth("100%");
+ title.add(new Label(Util.C.changeScreenComments()));
if (msgList.size() > 1) {
- comments.add(messagesMenuBar());
+ title.add(messagesMenuBar());
}
+ title.setStyleName(Gerrit.RESOURCES.css().blockHeader());
+ comments.add(title);
final long AGE = 7 * 24 * 60 * 60 * 1000L;
final Timestamp aged = new Timestamp(System.currentTimeMillis() - AGE);
@@ -307,24 +318,22 @@ public class ChangeScreen extends Screen {
comments.add(cp);
}
- if (msgList.size() > 1) {
- comments.add(messagesMenuBar());
- }
comments.setVisible(msgList.size() > 0);
}
private LinkMenuBar messagesMenuBar() {
final Panel c = comments;
- final LinkMenuBar m = new LinkMenuBar();
- m.addItem(Util.C.messageExpandRecent(), new ExpandAllCommand(c, true) {
+ final LinkMenuBar menuBar = new LinkMenuBar();
+ menuBar.addItem(Util.C.messageExpandRecent(), new ExpandAllCommand(c, true) {
@Override
protected void expand(final CommentPanel w) {
w.setOpen(w.isRecent());
}
});
- m.addItem(Util.C.messageExpandAll(), new ExpandAllCommand(c, true));
- m.addItem(Util.C.messageCollapseAll(), new ExpandAllCommand(c, false));
- return m;
+ menuBar.addItem(Util.C.messageExpandAll(), new ExpandAllCommand(c, true));
+ menuBar.addItem(Util.C.messageCollapseAll(), new ExpandAllCommand(c, false));
+ menuBar.addStyleName(Gerrit.RESOURCES.css().commentPanelMenuBar());
+ return menuBar;
}
private void toggleStar() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 6a57feb165..248e57de72 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -25,8 +25,9 @@ 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;
+import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
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;
@@ -34,8 +35,6 @@ import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.UserIdentity;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadCommand;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -48,9 +47,9 @@ import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import java.util.Collections;
@@ -167,7 +166,6 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
}
private void displayDownload() {
- final Branch.NameKey branchKey = changeDetail.getChange().getDest();
final Project.NameKey projectKey = changeDetail.getChange().getProject();
final String projectName = projectKey.get();
final CopyableLabel copyLabel = new CopyableLabel("");
@@ -405,7 +403,7 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
new AbandonChangeDialog(patchSet.getId(),
new AsyncCallback<ChangeDetail>() {
public void onSuccess(ChangeDetail result) {
- changeScreen.display(result);
+ changeScreen.update(result);
}
public void onFailure(Throwable caught) {
@@ -425,7 +423,7 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
new RestoreChangeDialog(patchSet.getId(),
new AsyncCallback<ChangeDetail>() {
public void onSuccess(ChangeDetail result) {
- changeScreen.display(result);
+ changeScreen.update(result);
}
public void onFailure(Throwable caught) {
@@ -516,7 +514,7 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
new SubmitFailureDialog(result, msg).center();
}
}
- changeScreen.display(result);
+ changeScreen.update(result);
}
public PatchSet getPatchSet() {
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
index b0f06a4e3e..edfeb235e6 100644
--- 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
@@ -16,6 +16,7 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -30,8 +31,10 @@ import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Composite that displays the patch sets of a change. This composite ensures
@@ -39,7 +42,7 @@ import java.util.List;
*/
public class PatchSetsBlock extends Composite {
- private final HashMap<PatchSet.Id, PatchSetComplexDisclosurePanel> patchSetPanels =
+ private final Map<PatchSet.Id, PatchSetComplexDisclosurePanel> patchSetPanels =
new HashMap<PatchSet.Id, PatchSetComplexDisclosurePanel>();
private final ChangeScreen parent;
@@ -72,6 +75,14 @@ public class PatchSetsBlock extends Composite {
currentPatchSetId = currps.getId();
patchSets = detail.getPatchSets();
+ if (Gerrit.isSignedIn()) {
+ final AccountGeneralPreferences p =
+ Gerrit.getUserAccount().getGeneralPreferences();
+ if (p.isDisplayPatchSetsInReverseOrder()) {
+ Collections.reverse(patchSets);
+ }
+ }
+
for (final PatchSet ps : patchSets) {
if (ps == currps) {
add(new PatchSetComplexDisclosurePanel(parent, detail, detail
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 34695ddaaa..33b37c8b24 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
@@ -17,10 +17,12 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.patches.PatchScreen;
import com.google.gerrit.client.ui.InlineHyperlink;
+import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
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.Patch.ChangeType;
import com.google.gerrit.reviewdb.Patch.Key;
import com.google.gerrit.reviewdb.Patch.PatchType;
import com.google.gwt.core.client.GWT;
@@ -53,16 +55,22 @@ public class PatchTable extends Composite {
private MyTable myTable;
private String savePointerId;
private List<Patch> patchList;
+ private ListenableAccountDiffPreference listenablePrefs;
private List<ClickHandler> clickHandlers;
private boolean active;
private boolean registerKeys;
- public PatchTable() {
+ public PatchTable(ListenableAccountDiffPreference prefs) {
+ listenablePrefs = prefs;
myBody = new FlowPanel();
initWidget(myBody);
}
+ public PatchTable() {
+ this(new ListenableAccountDiffPreference());
+ }
+
public int indexOf(Patch.Key patch) {
for (int i = 0; i < patchList.size(); i++) {
if (patchList.get(i).getKey().equals(patch)) {
@@ -166,9 +174,13 @@ public class PatchTable extends Composite {
* @return a link to the previous file in this patch set, or null.
*/
public InlineHyperlink getPreviousPatchLink(int index, PatchScreen.Type patchType) {
- if (0 < index)
- return createLink(index - 1, patchType, SafeHtml.asis(Util.C
+ for(index--; index > -1; index--) {
+ InlineHyperlink link = createLink(index, patchType, SafeHtml.asis(Util.C
.prevPatchLinkIcon()), null);
+ if (link != null) {
+ return link;
+ }
+ }
return null;
}
@@ -176,9 +188,13 @@ public class PatchTable extends Composite {
* @return a link to the next file in this patch set, or null.
*/
public InlineHyperlink getNextPatchLink(int index, PatchScreen.Type patchType) {
- if (index < patchList.size() - 1)
- return createLink(index + 1, patchType, null, SafeHtml.asis(Util.C
+ for(index++; index < patchList.size(); index++) {
+ InlineHyperlink link = createLink(index, patchType, null, SafeHtml.asis(Util.C
.nextPatchLinkIcon()));
+ if (link != null) {
+ return link;
+ }
+ }
return null;
}
@@ -192,6 +208,14 @@ public class PatchTable extends Composite {
private PatchLink createLink(int index, PatchScreen.Type patchType,
SafeHtml before, SafeHtml after) {
Patch patch = patchList.get(index);
+ if (( listenablePrefs.get().isSkipDeleted() &&
+ patch.getChangeType().equals(ChangeType.DELETED) )
+ ||
+ ( listenablePrefs.get().isSkipUncommented() &&
+ patch.getCommentCount() == 0 ) ) {
+ return null;
+ }
+
Key thisKey = patch.getKey();
PatchLink link;
if (patchType == PatchScreen.Type.SIDE_BY_SIDE
@@ -240,6 +264,14 @@ public class PatchTable extends Composite {
}
}
+ public ListenableAccountDiffPreference getPreferences() {
+ return listenablePrefs;
+ }
+
+ public void setPreferences(ListenableAccountDiffPreference prefs) {
+ listenablePrefs = prefs;
+ }
+
private class MyTable extends NavigationTable<Patch> {
private static final int C_PATH = 2;
private static final int C_DRAFT = 3;
@@ -756,5 +788,4 @@ public class PatchTable extends Composite {
return System.currentTimeMillis() - start > 200;
}
}
-
}
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 b184064153..c8f16d0afc 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
@@ -23,7 +23,7 @@
@external .gwt-TextBox;
@external .gwt-Hyperlink;
@external .gwt-CheckBox;
-@external .gwt-DisclosurePanel, .header;
+@external .gwt-DisclosurePanel, .header, .content;
@external .gwt-InlineLabel;
@external .gwt-InlineHyperlink;
@external .gwt-RadioButton;
@@ -69,7 +69,6 @@
font-size: 11pt;
padding-left: 5px;
padding-right: 5px;
- width: 98%;
}
.version,
@@ -199,6 +198,9 @@
padding-left: 0.5em;
padding-right: 0.5em;
}
+.commentPanelMenuBar {
+ float: right;
+}
.commentPanelMessage p {
margin-top: 0px;
margin-bottom: 0px;
@@ -674,6 +676,9 @@
background: #eeeeee;
border-bottom: 1px solid #eeeeee;
}
+.fileLineMode {
+ font-weight: bold;
+}
.fileLineDELETE,
.fileLineDELETE .wdc {
background: #ffeeee;
@@ -753,6 +758,10 @@
margin-bottom: 10px;
}
+.gwt-DisclosurePanel .content {
+ margin-left: 10px;
+}
+
.changeScreenDescription {
white-space: pre;
font-family: mono-font;
@@ -764,8 +773,7 @@
}
.changeComments {
- margin-left: 0.5em;
- margin-right: 0.5em;
+ padding-top: 1em;
width: 60em;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index 050dcb85d0..6452e08624 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -30,8 +30,12 @@ import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.shared.HandlerRegistration;
@@ -52,7 +56,7 @@ import java.util.ArrayList;
import java.util.List;
public abstract class AbstractPatchContentTable extends NavigationTable<Object>
- implements CommentEditorContainer {
+ implements CommentEditorContainer, FocusHandler, BlurHandler {
protected PatchTable fileList;
protected AccountInfoCache accountCache = AccountInfoCache.empty();
protected Patch.Key patchKey;
@@ -62,6 +66,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
private final KeyCommandSet keysComment;
private HandlerRegistration regComment;
+ private final KeyCommandSet keysOpenByEnter;
+ private HandlerRegistration regOpenByEnter;
protected AbstractPatchContentTable() {
keysNavigation.add(new PrevKeyCommand(0, 'k', PatchUtil.C.linePrev()));
@@ -72,8 +78,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
keysNavigation.add(new NextCommentCmd(0, 'N', PatchUtil.C.commentNext()));
keysAction.add(new OpenKeyCommand(0, 'o', PatchUtil.C.expandComment()));
- keysAction.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER, PatchUtil.C
- .expandComment()));
+ keysOpenByEnter = new KeyCommandSet(Gerrit.C.sectionNavigation());
+ keysOpenByEnter.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER, PatchUtil.C.expandComment()));
if (Gerrit.isSignedIn()) {
keysAction.add(new InsertCommentCommand(0, 'c', PatchUtil.C
@@ -86,8 +92,6 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
keysComment = new KeyCommandSet(PatchUtil.C.commentEditorSet());
keysComment.add(new NoOpKeyCommand(KeyCommand.M_CTRL, 's', PatchUtil.C
.commentSaveDraft()));
- keysComment.add(new NoOpKeyCommand(KeyCommand.M_CTRL, 'd', PatchUtil.C
- .commentDiscard()));
keysComment.add(new NoOpKeyCommand(0, KeyCodes.KEY_ESCAPE, PatchUtil.C
.commentCancelEdit()));
} else {
@@ -149,6 +153,13 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
regComment.removeHandler();
regComment = null;
}
+
+ if (on && keysOpenByEnter != null && regOpenByEnter == null) {
+ regOpenByEnter = GlobalKey.add(this, keysOpenByEnter);
+ } else if (!on && regOpenByEnter != null) {
+ regOpenByEnter.removeHandler();
+ regOpenByEnter = null;
+ }
}
public void display(final Patch.Key k, final PatchSet.Id a,
@@ -157,9 +168,6 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
idSideA = a;
idSideB = b;
- final String pathName = patchKey.get();
- int ext = pathName.lastIndexOf('.');
-
render(s);
}
@@ -408,6 +416,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
}
final CommentEditorPanel ed = new CommentEditorPanel(newComment);
+ ed.addFocusHandler(this);
+ ed.addBlurHandler(this);
boolean isCommentRow = false;
boolean needInsert = false;
if (row < table.getRowCount()) {
@@ -522,6 +532,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
final PatchLineComment line, final boolean isLast) {
if (line.getStatus() == PatchLineComment.Status.DRAFT) {
final CommentEditorPanel plc = new CommentEditorPanel(line);
+ plc.addFocusHandler(this);
+ plc.addBlurHandler(this);
table.setWidget(row, col, plc);
styleLastCommentCell(row, col);
@@ -529,6 +541,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
final AccountInfo author = accountCache.get(line.getAuthor());
final PublishedCommentPanel panel =
new PublishedCommentPanel(author, line);
+ panel.addFocusHandler(this);
+ panel.addBlurHandler(this);
table.setWidget(row, col, panel);
styleLastCommentCell(row, col);
@@ -544,6 +558,29 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
styleCommentRow(row);
}
+ @Override
+ public void onFocus(FocusEvent event) {
+ // when the comment panel gets focused (actually when a button inside the
+ // comment panel gets focused) we have to unregister the key binding for
+ // ENTER that expands/collapses the comment panel, if we don't do this the
+ // focused button in the comment panel cannot be triggered by pressing ENTER
+ // since ENTER would then be already consumed by this key binding
+ if (regOpenByEnter != null) {
+ regOpenByEnter.removeHandler();
+ regOpenByEnter = null;
+ }
+ }
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ // when the comment panel gets blurred (actually when a button inside the
+ // comment panel gets blurred) we have to re-register the key binding for
+ // ENTER that expands/collapses the comment panel
+ if (keysOpenByEnter != null && regOpenByEnter == null) {
+ regOpenByEnter = GlobalKey.add(this, keysOpenByEnter);
+ }
+ }
+
private void styleCommentRow(final int row) {
final CellFormatter fmt = table.getCellFormatter();
final Element iconCell = fmt.getElement(row, 0);
@@ -705,11 +742,11 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
reply = new Button(PatchUtil.C.buttonReply());
reply.addClickHandler(this);
- getButtonPanel().add(reply);
+ addButton(reply);
replyDone = new Button(PatchUtil.C.buttonReplyDone());
replyDone.addClickHandler(this);
- getButtonPanel().add(replyDone);
+ addButton(replyDone);
}
@Override
@@ -738,18 +775,18 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
final PatchLineComment newComment = newComment();
newComment.setMessage(message);
- enable(false);
+ enableButtons(false);
PatchUtil.DETAIL_SVC.saveDraft(newComment,
new GerritCallback<PatchLineComment>() {
public void onSuccess(final PatchLineComment result) {
- enable(true);
+ enableButtons(true);
notifyDraftDelta(1);
createEditor(result).setOpen(false);
}
@Override
public void onFailure(Throwable caught) {
- enable(true);
+ enableButtons(true);
super.onFailure(caught);
}
});
@@ -775,13 +812,5 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
newComment.setSide(comment.getSide());
return newComment;
}
-
- private void enable(boolean on) {
- for (Widget w : getButtonPanel()) {
- if (w instanceof Button) {
- ((Button) w).setEnabled(on);
- }
- }
- }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
index 73a3ec2279..98f0c95997 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
@@ -102,18 +102,6 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
event.preventDefault();
onSave(NULL_CALLBACK);
return;
-
- case 'd':
- case 'D':
- event.preventDefault();
- if (isNew()) {
- onDiscard();
- } else if (Window.confirm(PatchUtil.C.confirmDiscard())) {
- onDiscard();
- } else {
- text.setFocus(true);
- }
- return;
}
}
@@ -125,22 +113,22 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
edit = new Button();
edit.setText(PatchUtil.C.buttonEdit());
edit.addClickHandler(this);
- getButtonPanel().add(edit);
+ addButton(edit);
save = new Button();
save.setText(PatchUtil.C.buttonSave());
save.addClickHandler(this);
- getButtonPanel().add(save);
+ addButton(save);
cancel = new Button();
cancel.setText(PatchUtil.C.buttonCancel());
cancel.addClickHandler(this);
- getButtonPanel().add(cancel);
+ addButton(cancel);
discard = new Button();
discard.setText(PatchUtil.C.buttonDiscard());
discard.addClickHandler(this);
- getButtonPanel().add(discard);
+ addButton(discard);
setOpen(true);
if (isNew()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
index f7f99e1e45..cb85c2448e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
@@ -38,7 +38,7 @@ class HistoryTable extends FancyFlexTable<Patch> {
HistoryTable(final PatchScreen parent) {
setStyleName(Gerrit.RESOURCES.css().patchHistoryTable());
screen = parent;
- table.setWidth("98%");
+ table.setWidth("auto");
table.addStyleName(Gerrit.RESOURCES.css().changeTable());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
index 6aae855f55..b8c52d8cb6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/NavLinks.java
@@ -30,16 +30,31 @@ import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import com.google.gwtexpui.safehtml.client.SafeHtml;
class NavLinks extends Composite {
+ public enum Nav {
+ PREV (0, '[', PatchUtil.C.previousFileHelp(), 0),
+ NEXT (2, ']', PatchUtil.C.nextFileHelp(), 1);
+
+ public int col; // Table Cell column to display link in
+ public int key; // key code shortcut to activate link
+ public String help; // help string for '?' popup
+ public int cmd; // index into cmds array
+
+ Nav(int c, int k, String h, int i) {
+ this.col = c;
+ this.key = k;
+ this.help = h;
+ this.cmd = i;
+ }
+ }
+
+ private final Change.Id changeId;
private final KeyCommandSet keys;
private final Grid table;
- private InlineHyperlink prev;
- private InlineHyperlink next;
-
- private KeyCommand prevKey;
- private KeyCommand nextKey;
+ private KeyCommand cmds[] = new KeyCommand[2];
NavLinks(KeyCommandSet kcs, Change.Id forChange) {
+ changeId = forChange;
keys = kcs;
table = new Grid(1, 3);
initWidget(table);
@@ -50,56 +65,49 @@ class NavLinks extends Composite {
fmt.setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_CENTER);
fmt.setHorizontalAlignment(0, 2, HasHorizontalAlignment.ALIGN_RIGHT);
- final ChangeLink up = new ChangeLink("", forChange);
+ final ChangeLink up = new ChangeLink("", changeId);
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);
+ setupNav(Nav.PREV, fileList.getPreviousPatchLink(patchIndex, type));
+ setupNav(Nav.NEXT, fileList.getNextPatchLink(patchIndex, type));
} else {
- prev = null;
- next = null;
+ setupNav(Nav.PREV, null);
+ setupNav(Nav.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);
+ protected void setupNav(final Nav nav, final InlineHyperlink link) {
+
+ /* setup the cells */
+ if (link != null) {
+ table.setWidget(0, nav.col, link);
} else {
- if (keys != null && prevKey != null) {
- keys.remove(prevKey);
- prevKey = null;
- }
- table.clearCell(0, 0);
+ table.clearCell(0, nav.col);
}
- if (next != null) {
- if (keys != null && nextKey == null) {
- nextKey = new KeyCommand(0, ']', PatchUtil.C.nextFileHelp()) {
+ /* setup the keys */
+ if (keys != null) {
+
+ if (cmds[nav.cmd] != null) {
+ keys.remove(cmds[nav.cmd]);
+ }
+
+ if (link != null) {
+ cmds[nav.cmd] = new KeyCommand(0, nav.key, nav.help) {
@Override
public void onKeyPress(KeyPressEvent event) {
- next.go();
+ link.go();
}
};
- keys.add(nextKey);
+ } else {
+ cmds[nav.cmd] = new UpToChangeCommand(changeId, 0, nav.key);
}
- table.setWidget(0, 2, next);
- } else {
- if (keys != null && nextKey != null) {
- keys.remove(nextKey);
- nextKey = null;
- }
- table.clearCell(0, 2);
+
+ keys.add(cmds[nav.cmd]);
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index 3b7c445373..93e8deb44a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -24,7 +24,6 @@ public interface PatchConstants extends Constants {
String buttonSave();
String buttonCancel();
String buttonDiscard();
- String confirmDiscard();
String noDifference();
String patchHeaderOld();
@@ -46,7 +45,6 @@ public interface PatchConstants extends Constants {
String commentEditorSet();
String commentInsert();
String commentSaveDraft();
- String commentDiscard();
String commentCancelEdit();
String whitespaceIGNORE_NONE();
@@ -62,4 +60,7 @@ public interface PatchConstants extends Constants {
String buttonReplyDone();
String cannedReplyDone();
+
+ String fileTypeSymlink();
+ String fileTypeGitlink();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 9826aba61f..90def1d307 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -7,7 +7,6 @@ buttonEdit = Edit
buttonSave = Save
buttonCancel = Cancel
buttonDiscard = Discard
-confirmDiscard = Discard this comment?
noDifference = No Differences
patchHeaderOld = Old Version
@@ -28,7 +27,6 @@ expandComment = Expand or collapse comment
commentEditorSet = Comment Editing
commentInsert = Create a new inline comment
commentSaveDraft = Save draft comment
-commentDiscard = Discard draft comment
commentCancelEdit = Cancel comment edit
whitespaceIGNORE_NONE=None
@@ -41,3 +39,6 @@ nextFileHelp = Next file
reviewed = Reviewed
download = (Download)
+
+fileTypeSymlink = Type: Symbolic Link
+fileTypeGitlink = Type: Git Commit in Subproject
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 238a201d2b..812a74ee72 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
@@ -17,15 +17,13 @@ package com.google.gerrit.client.patches;
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.InlineHyperlink;
+import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
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.PatchSetDetail;
import com.google.gerrit.prettify.client.ClientSideFormatter;
@@ -137,17 +135,12 @@ public abstract class PatchScreen extends Screen implements
/** The index of the file we are currently looking at among the fileList */
private int patchIndex;
+ private ListenableAccountDiffPreference prefs;
/** Keys that cause an action on this screen */
private KeyCommandSet keysNavigation;
private HandlerRegistration regNavigation;
- /** Link to the screen for the previous file, null if not applicable */
- private InlineHyperlink previousFileLink;
-
- /** Link to the screen for the next file, null if not applicable */
- private InlineHyperlink nextFileLink;
-
/**
* How this patch should be displayed in the patch screen.
*/
@@ -175,14 +168,17 @@ public abstract class PatchScreen extends Screen implements
idSideB = diffSideB != null ? diffSideB : id.getParentKey();
this.patchIndex = patchIndex;
- settingsPanel = new PatchScriptSettingsPanel();
- settingsPanel
- .addValueChangeHandler(new ValueChangeHandler<AccountDiffPreference>() {
+ prefs = fileList != null ? fileList.getPreferences() :
+ new ListenableAccountDiffPreference();
+ prefs.addValueChangeHandler(
+ new ValueChangeHandler<AccountDiffPreference>() {
@Override
public void onValueChange(ValueChangeEvent<AccountDiffPreference> event) {
update(event.getValue());
}
});
+
+ settingsPanel = new PatchScriptSettingsPanel(prefs);
settingsPanel.getReviewedCheckBox().addValueChangeHandler(
new ValueChangeHandler<Boolean>() {
@Override
@@ -252,8 +248,9 @@ public abstract class PatchScreen extends Screen implements
protected void onInitUI() {
super.onInitUI();
+ final Change.Id ck = patchKey.getParentKey().getParentKey();
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
- keysNavigation.add(new UpToChangeCommand(0, 'u', PatchUtil.C.upToChange()));
+ keysNavigation.add(new UpToChangeCommand(ck, 0, 'u'));
keysNavigation.add(new FileListCmd(0, 'f', PatchUtil.C.fileList()));
historyTable = new HistoryTable(this);
@@ -333,11 +330,9 @@ public abstract class PatchScreen extends Screen implements
public void onSuccess(PatchSetDetail result) {
patchSetDetail = result;
if (fileList == null) {
- fileList = new PatchTable();
+ fileList = new PatchTable(prefs);
fileList.display(result);
patchIndex = fileList.indexOf(patchKey);
- topNav.display(patchIndex, getPatchScreenType(), fileList);
- bottomNav.display(patchIndex, getPatchScreenType(), fileList);
}
refresh(true);
}
@@ -360,6 +355,10 @@ public abstract class PatchScreen extends Screen implements
public void registerKeys() {
super.registerKeys();
contentTable.setRegisterKeys(contentTable.isVisible());
+ if (regNavigation != null) {
+ regNavigation.removeHandler();
+ regNavigation = null;
+ }
regNavigation = GlobalKey.add(this, keysNavigation);
}
@@ -391,6 +390,7 @@ public abstract class PatchScreen extends Screen implements
}
private void onResult(final PatchScript script, final boolean isFirst) {
+
final Change.Key cid = script.getChangeId();
final String path = PatchTable.getDisplayFileName(patchKey);
String fileName = path;
@@ -411,6 +411,7 @@ public abstract class PatchScreen extends Screen implements
new GerritCallback<PatchSetDetail>() {
@Override
public void onSuccess(PatchSetDetail result) {
+ commitMessageBlock.setVisible(true);
commitMessageBlock.display(result.getInfo().getMessage());
}
});
@@ -450,6 +451,11 @@ public abstract class PatchScreen extends Screen implements
settingsPanel.setEnabled(true);
lastScript = script;
+ if (fileList != null) {
+ topNav.display(patchIndex, getPatchScreenType(), fileList);
+ bottomNav.display(patchIndex, getPatchScreenType(), fileList);
+ }
+
// Mark this file reviewed as soon we display the diff screen
if (Gerrit.isSignedIn() && isFirst) {
settingsPanel.getReviewedCheckBox().setValue(true);
@@ -473,18 +479,6 @@ public abstract class PatchScreen extends Screen implements
diffSideB = patchSetId;
}
- public class UpToChangeCommand extends KeyCommand {
- public UpToChangeCommand(int mask, int key, String help) {
- super(mask, key, help);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- final Change.Id ck = patchKey.getParentKey().getParentKey();
- Gerrit.display(PageLinks.toChange(ck), new ChangeScreen(ck));
- }
- }
-
public class FileListCmd extends KeyCommand {
public FileListCmd(int mask, int key, String help) {
super(mask, key, help);
@@ -494,7 +488,7 @@ public abstract class PatchScreen extends Screen implements
public void onKeyPress(final KeyPressEvent event) {
if (fileList == null || fileList.isAttached()) {
final PatchSet.Id psid = patchKey.getParentKey();
- fileList = new PatchTable();
+ fileList = new PatchTable(prefs);
fileList.setSavePointerId("PatchTable " + psid);
Util.DETAIL_SVC.patchSetDetail(psid,
new GerritCallback<PatchSetDetail>() {
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 758b0f0ad0..3550229f6f 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
@@ -17,6 +17,7 @@ package com.google.gerrit.client.patches;
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.ListenableAccountDiffPreference;
import com.google.gerrit.client.ui.NpIntTextBox;
import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
@@ -48,7 +49,7 @@ public class PatchScriptSettingsPanel extends Composite implements
interface MyUiBinder extends UiBinder<Widget, PatchScriptSettingsPanel> {
}
- private AccountDiffPreference value;
+ private ListenableAccountDiffPreference listenablePrefs;
private boolean enableIntralineDifference = true;
private boolean enableSmallFileFeatures = true;
@@ -80,6 +81,13 @@ public class PatchScriptSettingsPanel extends Composite implements
CheckBox reviewed;
@UiField
+ CheckBox skipDeleted;
+
+ @UiField
+ CheckBox skipUncommented;
+
+
+ @UiField
Button update;
/**
@@ -96,7 +104,8 @@ public class PatchScriptSettingsPanel extends Composite implements
*/
private int setEnabledCounter;
- public PatchScriptSettingsPanel() {
+ public PatchScriptSettingsPanel(ListenableAccountDiffPreference prefs) {
+ listenablePrefs = prefs;
initWidget(uiBinder.createAndBindUi(this));
initIgnoreWhitespace(ignoreWhitespace);
initContext(context);
@@ -115,11 +124,7 @@ public class PatchScriptSettingsPanel extends Composite implements
tabWidth.addKeyPressHandler(onEnter);
colWidth.addKeyPressHandler(onEnter);
- if (Gerrit.isSignedIn() && Gerrit.getAccountDiffPreference() != null) {
- setValue(Gerrit.getAccountDiffPreference());
- } else {
- setValue(AccountDiffPreference.createDefault(null));
- }
+ display();
}
@Override
@@ -147,7 +152,7 @@ public class PatchScriptSettingsPanel extends Composite implements
public void setEnableSmallFileFeatures(final boolean on) {
enableSmallFileFeatures = on;
if (enableSmallFileFeatures) {
- syntaxHighlighting.setValue(value.isSyntaxHighlighting());
+ syntaxHighlighting.setValue(getValue().isSyntaxHighlighting());
} else {
syntaxHighlighting.setValue(false);
}
@@ -157,7 +162,7 @@ public class PatchScriptSettingsPanel extends Composite implements
public void setEnableIntralineDifference(final boolean on) {
enableIntralineDifference = on;
if (enableIntralineDifference) {
- intralineDifference.setValue(value.isIntralineDifference());
+ intralineDifference.setValue(getValue().isIntralineDifference());
} else {
intralineDifference.setValue(false);
}
@@ -178,10 +183,16 @@ public class PatchScriptSettingsPanel extends Composite implements
}
public AccountDiffPreference getValue() {
- return value;
+ return listenablePrefs.get();
}
public void setValue(final AccountDiffPreference dp) {
+ listenablePrefs.set(dp);
+ display();
+ }
+
+ protected void display() {
+ final AccountDiffPreference dp = getValue();
setIgnoreWhitespace(dp.getIgnoreWhitespace());
if (enableSmallFileFeatures) {
syntaxHighlighting.setValue(dp.isSyntaxHighlighting());
@@ -195,8 +206,8 @@ public class PatchScriptSettingsPanel extends Composite implements
intralineDifference.setValue(dp.isIntralineDifference());
whitespaceErrors.setValue(dp.isShowWhitespaceErrors());
showTabs.setValue(dp.isShowTabs());
-
- value = dp;
+ skipDeleted.setValue(dp.isSkipDeleted());
+ skipUncommented.setValue(dp.isSkipUncommented());
}
@UiHandler("update")
@@ -205,7 +216,7 @@ public class PatchScriptSettingsPanel extends Composite implements
}
private void update() {
- AccountDiffPreference dp = new AccountDiffPreference(value);
+ AccountDiffPreference dp = new AccountDiffPreference(getValue());
dp.setIgnoreWhitespace(getIgnoreWhitespace());
dp.setContext(getContext());
dp.setTabSize(tabWidth.getIntValue());
@@ -214,9 +225,10 @@ public class PatchScriptSettingsPanel extends Composite implements
dp.setIntralineDifference(intralineDifference.getValue());
dp.setShowWhitespaceErrors(whitespaceErrors.getValue());
dp.setShowTabs(showTabs.getValue());
+ dp.setSkipDeleted(skipDeleted.getValue());
+ dp.setSkipUncommented(skipUncommented.getValue());
- value = dp;
- fireEvent(new ValueChangeEvent<AccountDiffPreference>(dp) {});
+ listenablePrefs.set(dp);
if (Gerrit.isSignedIn()) {
persistDiffPreferences();
@@ -225,10 +237,11 @@ public class PatchScriptSettingsPanel extends Composite implements
private void persistDiffPreferences() {
setEnabled(false);
- Util.ACCOUNT_SVC.changeDiffPreferences(value, new GerritCallback<VoidResult>() {
+ Util.ACCOUNT_SVC.changeDiffPreferences(getValue(),
+ new GerritCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
- Gerrit.setAccountDiffPreference(value);
+ Gerrit.setAccountDiffPreference(getValue());
setEnabled(true);
}
@@ -267,7 +280,7 @@ public class PatchScriptSettingsPanel extends Composite implements
if (0 <= sel) {
return Whitespace.valueOf(ignoreWhitespace.getValue(sel));
}
- return value.getIgnoreWhitespace();
+ return getValue().getIgnoreWhitespace();
}
private void setIgnoreWhitespace(Whitespace s) {
@@ -285,7 +298,7 @@ public class PatchScriptSettingsPanel extends Composite implements
if (0 <= sel) {
return Short.parseShort(context.getValue(sel));
}
- return (short) value.getContext();
+ return (short) getValue().getContext();
}
private void setContext(int ctx) {
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 7bbc8fe3ab..f0f1b4dcaa 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
@@ -115,12 +115,28 @@ limitations under the License.
</g:CheckBox>
</td>
+ <td rowspan='2'>
+ <g:CheckBox
+ ui:field='skipUncommented'
+ text='Skip Uncommented Files'
+ tabIndex='9'>
+ <ui:attribute name='text'/>
+ </g:CheckBox>
+ <br/>
+ <g:CheckBox
+ ui:field='skipDeleted'
+ text='Skip Deleted Files'
+ tabIndex='10'>
+ <ui:attribute name='text'/>
+ </g:CheckBox>
+ </td>
+
<td valign='bottom' rowspan='2'>
<g:Button
ui:field='update'
text='Update'
styleName='{style.updateButton}'
- tabIndex='9'>
+ tabIndex='11'>
<ui:attribute name='text'/>
</g:Button>
</td>
@@ -129,7 +145,7 @@ limitations under the License.
<g:CheckBox
ui:field='reviewed'
text='Reviewed'
- tabIndex='10'>
+ tabIndex='12'>
<ui:attribute name='text'/>
</g:CheckBox>
</td>
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 1bc4dd5c14..2939e714f2 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
@@ -22,6 +22,7 @@ import static com.google.gerrit.client.patches.PatchLine.Type.REPLACE;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.common.data.PatchScript.FileMode;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseHtmlFile;
import com.google.gerrit.reviewdb.Patch;
@@ -81,6 +82,14 @@ public class SideBySideTable extends AbstractPatchContentTable {
appendHeader(script, nc);
lines.add(null);
+ if(script.getFileModeA()!=FileMode.FILE||script.getFileModeB()!=FileMode.FILE){
+ openLine(nc);
+ appendModeLine(nc, script.getFileModeA());
+ appendModeLine(nc, script.getFileModeB());
+ closeLine(nc);
+ lines.add(null);
+ }
+
int lastB = 0;
final boolean ignoreWS = script.isIgnoreWhitespace();
for (final EditList.Hunk hunk : script.getHunks()) {
@@ -153,6 +162,29 @@ public class SideBySideTable extends AbstractPatchContentTable {
}
}
+ private void appendModeLine(final SafeHtmlBuilder nc, final FileMode mode) {
+ nc.openTd();
+ nc.setStyleName(Gerrit.RESOURCES.css().lineNumber());
+ nc.nbsp();
+ nc.closeTd();
+
+ nc.openTd();
+ nc.addStyleName(Gerrit.RESOURCES.css().fileLine());
+ nc.addStyleName(Gerrit.RESOURCES.css().fileLineMode());
+ switch(mode){
+ case FILE:
+ nc.nbsp();
+ break;
+ case SYMLINK:
+ nc.append(PatchUtil.C.fileTypeSymlink());
+ break;
+ case GITLINK:
+ nc.append(PatchUtil.C.fileTypeGitlink());
+ break;
+ }
+ nc.closeTd();
+ }
+
@Override
public void display(final CommentDetail cd) {
if (cd.isEmpty()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java
new file mode 100644
index 0000000000..709065d3d9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java
@@ -0,0 +1,36 @@
+// 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.ChangeScreen;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
+
+class UpToChangeCommand extends KeyCommand {
+ private final Change.Id changeId;
+
+ UpToChangeCommand(Change.Id changeId, int mask, int key) {
+ super(mask, key, PatchUtil.C.upToChange());
+ this.changeId = changeId;
+ }
+
+ @Override
+ public void onKeyPress(final KeyPressEvent event) {
+ Gerrit.display(PageLinks.toChange(changeId), new ChangeScreen(changeId));
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index fa028d75ba..f29742ad92 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -17,6 +17,7 @@ package com.google.gerrit.client.rpc;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.NotSignedInDialog;
+import com.google.gerrit.common.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
@@ -37,6 +38,9 @@ public abstract class GerritCallback<T> implements AsyncCallback<T> {
} else if (isNoSuchEntity(caught)) {
new ErrorDialog(Gerrit.C.notFoundBody()).center();
+ } else if (isInactiveAccount(caught)) {
+ new ErrorDialog(Gerrit.C.inactiveAccountBody()).center();
+
} else if (isNoSuchAccount(caught)) {
final String msg = caught.getMessage();
final String who = msg.substring(NoSuchAccountException.MESSAGE.length());
@@ -71,6 +75,11 @@ public abstract class GerritCallback<T> implements AsyncCallback<T> {
&& caught.getMessage().equals(NoSuchEntityException.MESSAGE);
}
+ protected static boolean isInactiveAccount(final Throwable caught) {
+ return caught instanceof RemoteJsonException
+ && caught.getMessage().startsWith(InactiveAccountException.MESSAGE);
+ }
+
private static boolean isNoSuchAccount(final Throwable caught) {
return caught instanceof RemoteJsonException
&& caught.getMessage().startsWith(NoSuchAccountException.MESSAGE);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
index 441878f41a..bcf6438ec8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
@@ -30,7 +30,8 @@ public class AccountSuggestOracle extends HighlightSuggestOracle {
public void onRequestSuggestions(final Request req, final Callback callback) {
RpcStatus.hide(new Runnable() {
public void run() {
- SuggestUtil.SVC.suggestAccount(req.getQuery(), req.getLimit(),
+ SuggestUtil.SVC.suggestAccount(req.getQuery(), Boolean.TRUE,
+ req.getLimit(),
new GerritCallback<List<AccountInfo>>() {
public void onSuccess(final List<AccountInfo> result) {
final ArrayList<AccountSuggestion> r =
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java
index b5e36558bf..b2d25e439f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AddMemberBox.java
@@ -16,12 +16,10 @@ package com.google.gerrit.client.ui;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.admin.Util;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
@@ -32,42 +30,23 @@ import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
public class AddMemberBox extends Composite {
private final FlowPanel addPanel;
private final Button addMember;
- private final NpTextBox nameTxtBox;
+ private final HintTextBox nameTxtBox;
private final SuggestBox nameTxt;
private boolean submitOnSelection;
public AddMemberBox() {
addPanel = new FlowPanel();
addMember = new Button(Util.C.buttonAddGroupMember());
- nameTxtBox = new NpTextBox();
- nameTxt = new SuggestBox(new AccountSuggestOracle(), nameTxtBox);
+ nameTxtBox = new HintTextBox();
+ nameTxt = new SuggestBox(new RPCSuggestOracle(
+ new AccountSuggestOracle()), nameTxtBox);
nameTxtBox.setVisibleLength(50);
- nameTxtBox.setText(Util.C.defaultAccountName());
- nameTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- nameTxtBox.addFocusHandler(new FocusHandler() {
- @Override
- public void onFocus(final FocusEvent event) {
- if (Util.C.defaultAccountName().equals(nameTxtBox.getText())) {
- nameTxtBox.setText("");
- nameTxtBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
- nameTxtBox.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(final BlurEvent event) {
- if ("".equals(nameTxtBox.getText())) {
- nameTxtBox.setText(Util.C.defaultAccountName());
- nameTxtBox.addStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
- }
- }
- });
+ nameTxtBox.setHintText(Util.C.defaultAccountName());
nameTxtBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
@@ -108,10 +87,7 @@ public class AddMemberBox extends Composite {
public String getText() {
String s = nameTxtBox.getText();
- if (s == null || s.equals(Util.C.defaultAccountName())) {
- s = "";
- }
- return s;
+ return s == null ? "" : s;
}
public void setEnabled(boolean enabled) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
index b23bdba5a3..06c17e5c5b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
@@ -17,12 +17,20 @@ package com.google.gerrit.client.ui;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.AccountInfo;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
@@ -37,8 +45,10 @@ import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.Date;
-public class CommentPanel extends Composite implements HasDoubleClickHandlers {
+public class CommentPanel extends Composite implements HasDoubleClickHandlers,
+ HasFocusHandlers, FocusHandler, HasBlurHandlers, BlurHandler {
private static final int SUMMARY_LENGTH = 75;
+ private final HandlerManager handlerManager = new HandlerManager(this);
private final FlexTable header;
private final InlineLabel messageSummary;
private final FlowPanel content;
@@ -132,7 +142,39 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers {
}
}
- protected Panel getButtonPanel() {
+ /**
+ * Registers a {@link FocusHandler} for this comment panel.
+ * The comment panel is considered as being focused whenever any button in the
+ * comment panel gets focused.
+ *
+ * @param handler the focus handler to be registered
+ */
+ @Override
+ public HandlerRegistration addFocusHandler(final FocusHandler handler) {
+ return handlerManager.addHandler(FocusEvent.getType(), handler);
+ }
+
+ /**
+ * Registers a {@link BlurHandler} for this comment panel.
+ * The comment panel is considered as being blurred whenever any button in the
+ * comment panel gets blurred.
+ *
+ * @param handler the blur handler to be registered
+ */
+ @Override
+ public HandlerRegistration addBlurHandler(final BlurHandler handler) {
+ return handlerManager.addHandler(BlurEvent.getType(), handler);
+ }
+
+ protected void addButton(final Button button) {
+ // register focus and blur handler for each button, so that we can fire
+ // focus and blur events for the comment panel
+ button.addFocusHandler(this);
+ button.addBlurHandler(this);
+ getButtonPanel().add(button);
+ }
+
+ private Panel getButtonPanel() {
if (buttons == null) {
buttons = new FlowPanel();
buttons.setStyleName(Gerrit.RESOURCES.css().commentPanelButtons());
@@ -141,6 +183,26 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers {
return buttons;
}
+ @Override
+ public void onFocus(final FocusEvent event) {
+ // a button was focused -> fire focus event for the comment panel
+ handlerManager.fireEvent(event);
+ }
+
+ @Override
+ public void onBlur(final BlurEvent event) {
+ // a button was blurred -> fire blur event for the comment panel
+ handlerManager.fireEvent(event);
+ }
+
+ protected void enableButtons(final boolean on) {
+ for (Widget w : getButtonPanel()) {
+ if (w instanceof Button) {
+ ((Button) w).setEnabled(on);
+ }
+ }
+ }
+
private static String summarize(final String message) {
if (message.length() < SUMMARY_LENGTH) {
return message;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
index c303e1cf45..c93969db87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
@@ -38,7 +38,8 @@ public class ComplexDisclosurePanel extends Composite implements
// other modification of its header. We're stuck with injecting
// into the DOM directly.
//
- main = new DisclosurePanel(text, isOpen);
+ main = new DisclosurePanel(text);
+ main.setOpen(isOpen);
final Element headerParent;
{
final Element table = main.getElement();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java
new file mode 100644
index 0000000000..6575b61d2d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/HintTextBox.java
@@ -0,0 +1,211 @@
+// 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.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.Widget;
+
+
+public class HintTextBox extends NpTextBox {
+ private HandlerRegistration hintFocusHandler;
+ private HandlerRegistration hintBlurHandler;
+ private HandlerRegistration keyDownHandler;
+
+ private String hintText;
+ private String hintStyleName = Gerrit.RESOURCES.css().inputFieldTypeHint();
+
+ private String prevText;
+
+ private boolean hintOn;
+ private boolean isFocused;
+
+
+ public String getText() {
+ if (hintOn) {
+ return "";
+ }
+ return super.getText();
+ }
+
+ public void setText(String text) {
+ focusHint();
+
+ super.setText(text);
+ prevText = text;
+
+ if (! isFocused) {
+ blurHint();
+ }
+ }
+
+ public String getHintText() {
+ return hintText;
+ }
+
+ public void setHintText(String text) {
+ if (text == null) {
+ if (hintText == null) { // was not set, still not set, no change.
+ return;
+ }
+
+ // Clearing a previously set Hint
+ hintFocusHandler.removeHandler();
+ hintFocusHandler = null;
+ hintBlurHandler.removeHandler();
+ hintBlurHandler = null;
+ keyDownHandler.removeHandler();
+ keyDownHandler = null;
+ hintText = null;
+ focusHint();
+
+ return;
+ }
+
+ // Setting Hints
+
+ if (hintText == null) { // first time (was not already set)
+ hintText = text;
+
+ hintFocusHandler = addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ focusHint();
+ prevText = getText();
+ isFocused = true;
+ }
+ });
+
+ hintBlurHandler = addBlurHandler(new BlurHandler() {
+ @Override
+ public void onBlur(BlurEvent event) {
+ blurHint();
+ isFocused = false;
+ }
+ });
+
+ /*
+ * There seems to be a strange bug (at least on firefox 3.5.9 ubuntu) with
+ * the textbox under the following circumstances:
+ * 1) The field is not focused with BText in it.
+ * 2) The field receives focus and a focus listener changes the text to FText
+ * 3) The ESC key is pressed and the value of the field has not changed
+ * (ever) from FText
+ * 4) BUG: The text value gets reset to BText!
+ *
+ * A counter to this bug seems to be to force setFocus(false) on ESC.
+ */
+
+ /* Chrome does not create a KeyPressEvent on ESC, so use KeyDownEvents */
+ keyDownHandler = addKeyDownHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(final KeyDownEvent event) {
+ onKey(event.getNativeKeyCode());
+ }
+ });
+
+ } else { // Changing an already set Hint
+
+ focusHint();
+ hintText = text;
+ }
+
+ if (! isFocused) {
+ blurHint();
+ }
+ }
+
+ private void onKey(int key) {
+ if (key == KeyCodes.KEY_ESCAPE) {
+ setText(prevText);
+
+ Widget p = getParent();
+ if (p instanceof SuggestBox) {
+ // Since the text was changed, ensure that the SuggestBox is
+ // aware of this change so that it will refresh properly on
+ // the next keystroke. Without this, if the first keystroke
+ // recreates the same string as before ESC was pressed, the
+ // SuggestBox will think that the string has not changed, and
+ // it will not yet provide any Suggestions.
+ ((SuggestBox)p).showSuggestionList();
+
+ // The suggestion list lingers if we don't hide it.
+ ((SuggestBox)p).hideSuggestionList();
+ }
+
+ setFocus(false);
+ }
+ }
+
+ public void setHintStyleName(String styleName) {
+ if (hintStyleName != null && hintOn) {
+ removeStyleName(hintStyleName);
+ }
+
+ hintStyleName = styleName;
+
+ if (styleName != null && hintOn) {
+ addStyleName(styleName);
+ }
+ }
+
+ public String getHintStyleName() {
+ return hintStyleName;
+ }
+
+ protected void blurHint() {
+ if (! hintOn && getHintText() != null && "".equals(super.getText())) {
+ hintOn = true;
+ super.setText(getHintText());
+ if (getHintStyleName() != null) {
+ addStyleName(getHintStyleName());
+ }
+ }
+ }
+
+ protected void focusHint() {
+ if (hintOn) {
+ super.setText("");
+ if (getHintStyleName() != null) {
+ removeStyleName(getHintStyleName());
+ }
+ hintOn = false;
+ }
+ }
+
+ public void setFocus(boolean focus) {
+ super.setFocus(focus);
+
+ if (focus != isFocused) {
+ if (focus) {
+ focusHint();
+ } else {
+ blurHint();
+ }
+ }
+
+ isFocused = focus;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableAccountDiffPreference.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableAccountDiffPreference.java
new file mode 100644
index 0000000000..195adf5e8b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableAccountDiffPreference.java
@@ -0,0 +1,30 @@
+// 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.gerrit.reviewdb.AccountDiffPreference;
+
+public class ListenableAccountDiffPreference
+ extends ListenableValue<AccountDiffPreference> {
+
+ public ListenableAccountDiffPreference() {
+ if (Gerrit.isSignedIn() && Gerrit.getAccountDiffPreference() != null) {
+ set(Gerrit.getAccountDiffPreference());
+ } else {
+ set(AccountDiffPreference.createDefault(null));
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
new file mode 100644
index 0000000000..6dad875b60
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.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.client.ui;
+
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+
+
+public class ListenableValue<T> implements HasValueChangeHandlers<T> {
+
+ private HandlerManager manager = new HandlerManager(this);
+
+ private T value;
+
+ public T get() {
+ return value;
+ }
+
+ public void set(final T value) {
+ this.value = value;
+ fireEvent(new ValueChangeEvent<T>(value) {});
+ }
+
+ public void fireEvent(GwtEvent<?> event) {
+ manager.fireEvent(event);
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<T> handler) {
+ return manager.addHandler(ValueChangeEvent.getType(), handler);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
new file mode 100644
index 0000000000..932fb5e274
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
@@ -0,0 +1,169 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBoxBase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/** Enables a FocusWidget (e.g. a Button) if an edit is detected from any
+ * registered input widget.
+ */
+public class OnEditEnabler implements KeyPressHandler, KeyDownHandler,
+ MouseUpHandler, ChangeHandler, ValueChangeHandler {
+
+ private final FocusWidget widget;
+ private Map<TextBoxBase, String> strings = new HashMap<TextBoxBase, String>();
+
+
+ // The first parameter to the contructors must be the FocusWidget to enable,
+ // subsequent parameters are widgets to listenTo.
+
+ public OnEditEnabler(final FocusWidget w, final TextBoxBase tb) {
+ this(w);
+ listenTo(tb);
+ }
+
+ public OnEditEnabler(final FocusWidget w, final ListBox lb) {
+ this(w);
+ listenTo(lb);
+ }
+
+ public OnEditEnabler(final FocusWidget w, final CheckBox cb) {
+ this(w);
+ listenTo(cb);
+ }
+
+ public OnEditEnabler(final FocusWidget w) {
+ widget = w;
+ }
+
+
+ // Register input widgets to be listened to
+
+ public void listenTo(final TextBoxBase tb) {
+ strings.put(tb, tb.getText());
+ tb.addKeyPressHandler(this);
+
+ // Is there another way to capture middle button X11 pastes in browsers
+ // which do not yet support ONPASTE events (Firefox)?
+ tb.addMouseUpHandler(this);
+
+ // Resetting the "original text" on focus ensures that we are
+ // up to date with non-user updates of the text (calls to
+ // setText()...) and also up to date with user changes which
+ // occured after enabling "widget".
+ tb.addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ strings.put(tb, tb.getText());
+ }
+ });
+
+ // CTRL-V Pastes in Chrome seem only detectable via BrowserEvents or
+ // KeyDownEvents, the latter is better.
+ tb.addKeyDownHandler(this);
+ }
+
+ public void listenTo(final ListBox lb) {
+ lb.addChangeHandler(this);
+ }
+
+ public void listenTo(final CheckBox cb) {
+ cb.addValueChangeHandler(this);
+ }
+
+
+ // Handlers
+
+ @Override
+ public void onKeyPress(final KeyPressEvent e) {
+ on(e);
+ }
+
+ @Override
+ public void onKeyDown(final KeyDownEvent e) {
+ on(e);
+ }
+
+ @Override
+ public void onMouseUp(final MouseUpEvent e) {
+ on(e);
+ }
+
+ @Override
+ public void onChange(final ChangeEvent e) {
+ on(e);
+ }
+
+ @Override
+ public void onValueChange(final ValueChangeEvent e) {
+ on(e);
+ }
+
+ private void on(final GwtEvent e) {
+ if (widget.isEnabled() ||
+ ! (e.getSource() instanceof FocusWidget) ||
+ ! ((FocusWidget) e.getSource()).isEnabled() ) {
+ return;
+ }
+
+ if (e.getSource() instanceof TextBoxBase) {
+ onTextBoxBase((TextBoxBase) e.getSource());
+ } else {
+ // For many widgets, we can assume that a change is an edit. If
+ // a widget does not work that way, it should be special cased
+ // above.
+ widget.setEnabled(true);
+ }
+ }
+
+ private void onTextBoxBase(final TextBoxBase tb) {
+ // The text appears to not get updated until the handlers complete.
+ DeferredCommand.add(new Command() {
+ @Override
+ public void execute() {
+ String orig = strings.get(tb);
+ if (orig == null) {
+ orig = "";
+ }
+ if (! orig.equals(tb.getText())) {
+ widget.setEnabled(true);
+ }
+ }
+ });
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.java
new file mode 100644
index 0000000000..8b13392e1a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.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.client.ui;
+
+import com.google.gwt.i18n.client.Constants;
+
+public interface ProjectConstants extends Constants {
+ String projectName();
+ String projectDescription();
+ String projectListOpen();
+ String projectListPrev();
+ String projectListNext();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.properties
new file mode 100644
index 0000000000..15de117e9a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectConstants.properties
@@ -0,0 +1,5 @@
+projectName = Project Name
+projectDescription = Project Description
+projectListOpen = Select project
+projectListPrev = Previous project
+projectListNext = Next project
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
new file mode 100644
index 0000000000..96089b902b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
@@ -0,0 +1,122 @@
+// 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.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+
+import java.util.List;
+
+public class ProjectsTable extends NavigationTable<Project> {
+
+ public ProjectsTable() {
+ keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.projectListPrev()));
+ keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.projectListNext()));
+ keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.projectListOpen()));
+ keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
+ Util.C.projectListOpen()));
+
+ table.setText(0, 1, Util.C.projectName());
+ table.setText(0, 2, Util.C.projectDescription());
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ protected MyFlexTable createFlexTable() {
+ MyFlexTable table = new MyFlexTable() {
+ @Override
+ public void onBrowserEvent(final Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONCLICK: {
+ // Find out which cell was actually clicked.
+ final Element td = getEventTargetCell(event);
+ if (td == null) {
+ break;
+ }
+ final int row = rowOf(td);
+ if (getRowItem(row) != null) {
+ ProjectsTable.this.movePointerTo(row);
+ return;
+ }
+ break;
+ }
+ case Event.ONDBLCLICK: {
+ // Find out which cell was actually clicked.
+ Element td = getEventTargetCell(event);
+ if (td == null) {
+ return;
+ }
+ onOpenRow(rowOf(td));
+ return;
+ }
+ }
+ super.onBrowserEvent(event);
+ }
+ };
+
+ table.sinkEvents(Event.ONDBLCLICK | Event.ONCLICK);
+ return table;
+ }
+
+ @Override
+ protected Object getRowItemKey(final Project item) {
+ return item.getNameKey();
+ }
+
+ @Override
+ protected void onOpenRow(final int row) {
+ if (row > 0) {
+ movePointerTo(row);
+ }
+ }
+
+ public void display(final List<Project> projects) {
+ while (1 < table.getRowCount())
+ table.removeRow(table.getRowCount() - 1);
+
+ for (final Project k : projects)
+ insert(table.getRowCount(), k);
+
+ finishDisplay();
+ }
+
+ protected void insert(final int row, final Project k) {
+ table.insertRow(row);
+
+ applyDataRowStyle(row);
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().cPROJECT());
+ fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
+
+ populate(row, k);
+ }
+
+ protected void populate(final int row, final Project k) {
+ table.setText(row, 1, k.getName());
+ table.setText(row, 2, k.getDescription());
+
+ setRowItem(row, k);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java
new file mode 100644
index 0000000000..cc3d510ef7
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RPCSuggestOracle.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.gwt.user.client.ui.SuggestOracle.Callback;
+import com.google.gwt.user.client.ui.SuggestOracle.Request;
+import com.google.gwt.user.client.ui.SuggestOracle.Response;
+
+/** This class will proxy SuggestOracle requests to another SuggestOracle
+ * while keeping track of the latest request. Any repsonse that belongs
+ * to a request which is not the latest request will be dropped to prevent
+ * invalid deliveries.
+ */
+
+public class RPCSuggestOracle extends SuggestOracle {
+
+ private SuggestOracle oracle;
+ private SuggestOracle.Request request;
+ private SuggestOracle.Callback callback;
+ private SuggestOracle.Callback myCallback = new SuggestOracle.Callback() {
+ public void onSuggestionsReady(SuggestOracle.Request req,
+ SuggestOracle.Response response) {
+ if (request == req) {
+ callback.onSuggestionsReady(req, response);
+ request = null;
+ callback = null;
+ }
+ }
+ };
+
+
+ public RPCSuggestOracle(SuggestOracle ora) {
+ oracle = ora;
+ }
+
+ public void requestSuggestions(SuggestOracle.Request req,
+ SuggestOracle.Callback cb) {
+ request = req;
+ callback = cb;
+ oracle.requestSuggestions(req, myCallback);
+ }
+
+ public boolean isDisplayStringHTML() {
+ return oracle.isDisplayStringHTML();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextSaveButtonListener.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextSaveButtonListener.java
deleted file mode 100644
index 63270df8fa..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/TextSaveButtonListener.java
+++ /dev/null
@@ -1,71 +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.ui;
-
-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.FocusWidget;
-import com.google.gwt.user.client.ui.TextBoxBase;
-
-/** Enables an action (e.g. a Button) if the text box is modified. */
-public class TextSaveButtonListener implements KeyPressHandler {
- private final FocusWidget descAction;
-
- public TextSaveButtonListener(final FocusWidget action) {
- descAction = action;
- }
-
- public TextSaveButtonListener(final TextBoxBase text, final FocusWidget action) {
- this(action);
- text.addKeyPressHandler(this);
- }
-
- @Override
- public void onKeyPress(final KeyPressEvent e) {
- if (descAction.isEnabled()) {
- // Do nothing, its already enabled.
- } else if (e.isControlKeyDown() || e.isAltKeyDown() || e.isMetaKeyDown()) {
- switch (e.getCharCode()) {
- case 'v':
- case 'x':
- on(e);
- break;
- }
- } else {
- switch (e.getCharCode()) {
- case KeyCodes.KEY_UP:
- case KeyCodes.KEY_DOWN:
- case KeyCodes.KEY_LEFT:
- case KeyCodes.KEY_RIGHT:
- case KeyCodes.KEY_HOME:
- case KeyCodes.KEY_END:
- case KeyCodes.KEY_PAGEUP:
- case KeyCodes.KEY_PAGEDOWN:
- case KeyCodes.KEY_ALT:
- case KeyCodes.KEY_CTRL:
- case KeyCodes.KEY_SHIFT:
- break;
- default:
- on(e);
- break;
- }
- }
- }
-
- private void on(final KeyPressEvent e) {
- descAction.setEnabled(((TextBoxBase) e.getSource()).isEnabled());
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Util.java
new file mode 100644
index 0000000000..ebed764472
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Util.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.client.ui;
+
+import com.google.gwt.core.client.GWT;
+
+public class Util {
+ public static final ProjectConstants C = GWT.create(ProjectConstants.class);
+}
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index b02b142562..39b2788736 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.4-SNAPSHOT</version>
+ <version>2.1-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
index b79971c03a..97debbceb1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
@@ -70,6 +70,7 @@ public class ChangeQueryServlet extends HttpServlet {
p.setIncludeCurrentPatchSet(get(req, "current-patch-set", false));
p.setIncludePatchSets(get(req, "patch-sets", false));
+ p.setIncludeApprovals(get(req, "all-approvals", false));
p.setOutput(rsp.getOutputStream(), format);
p.query(get(req, "q", "status:open"));
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index 9d9daec2f2..f67f12fd37 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -133,7 +133,7 @@ class ProjectDigestFilter implements Filter {
}
final AccountState who = accountCache.getByUsername(username);
- if (who == null) {
+ if (who == null || ! who.getAccount().isActive()) {
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
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 3f7f68d527..09e2cce8ef 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
@@ -23,6 +23,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@@ -40,6 +41,7 @@ import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.http.server.resolver.UploadPackFactory;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.UploadPack;
import org.slf4j.Logger;
@@ -175,19 +177,26 @@ public class ProjectServlet extends GitServlet {
static class Upload implements UploadPackFactory {
private final Provider<ReviewDb> db;
+ private final PackConfig packConfig;
@Inject
- Upload(final Provider<ReviewDb> db) {
+ Upload(final Provider<ReviewDb> db, final TransferConfig tc) {
this.db = db;
+ this.packConfig = tc.getPackConfig();
}
@Override
public UploadPack create(HttpServletRequest req, Repository repo)
- throws ServiceNotEnabledException {
+ throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+ ProjectControl pc = getProjectControl(req);
+ if (!pc.canRunUploadPack()) {
+ throw new ServiceNotAuthorizedException();
+ }
+
// The Resolver above already checked READ access for us.
//
- ProjectControl pc = getProjectControl(req);
UploadPack up = new UploadPack(repo);
+ up.setPackConfig(packConfig);
if (!pc.allRefsAreVisible()) {
up.setRefFilter(new VisibleRefFilter(repo, pc, db.get()));
}
@@ -207,6 +216,10 @@ public class ProjectServlet extends GitServlet {
public ReceivePack create(HttpServletRequest req, Repository db)
throws ServiceNotEnabledException, ServiceNotAuthorizedException {
final ProjectControl pc = getProjectControl(req);
+ if (!pc.canRunReceivePack()) {
+ throw new ServiceNotAuthorizedException();
+ }
+
if (pc.getCurrentUser() instanceof IdentifiedUser) {
final IdentifiedUser user = (IdentifiedUser) pc.getCurrentUser();
final ReceiveCommits rc = factory.create(pc, db);
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 93b6d09a0d..cc2e144742 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
@@ -19,6 +19,7 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
import com.google.gerrit.httpd.auth.container.HttpAuthModule;
+import com.google.gerrit.httpd.auth.container.HttpsClientSslCertModule;
import com.google.gerrit.httpd.auth.ldap.LdapAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.gitweb.GitWebModule;
@@ -101,6 +102,10 @@ public class WebModule extends FactoryModule {
install(new HttpAuthModule());
break;
+ case CLIENT_SSL_CERT_LDAP:
+ install(new HttpsClientSslCertModule());
+ break;
+
case LDAP:
case LDAP_BIND:
install(new LdapAuthModule());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
new file mode 100644
index 0000000000..381daa8c87
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
@@ -0,0 +1,94 @@
+// 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.auth.container;
+
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+@Singleton
+class HttpsClientSslCertAuthFilter implements Filter {
+
+ private static final Pattern REGEX_USERID = Pattern.compile("CN=([^,]*),.*");
+ private static final Logger log =
+ LoggerFactory.getLogger(HttpsClientSslCertAuthFilter.class);
+
+ private final Provider<WebSession> webSession;
+ private final AccountManager accountManager;
+
+ @Inject
+ HttpsClientSslCertAuthFilter(final Provider<WebSession> webSession,
+ final AccountManager accountManager) {
+ this.webSession = webSession;
+ this.accountManager = accountManager;
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse rsp,
+ FilterChain chain) throws IOException, ServletException {
+ X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+ if (certs == null || certs.length == 0) {
+ throw new ServletException(
+ "Couldn't get the attribute javax.servlet.request.X509Certificate from the request");
+ }
+ String name = certs[0].getSubjectDN().getName();
+ Matcher m = REGEX_USERID.matcher(name);
+ String userName;
+ if (m.matches()) {
+ userName = m.group(1);
+ } else {
+ throw new ServletException("Couldn't extract username from your certificate");
+ }
+ final AuthRequest areq = AuthRequest.forUser(userName);
+ final AuthResult arsp;
+ try {
+ arsp = accountManager.authenticate(areq);
+ } catch (AccountException e) {
+ String err = "Unable to authenticate user \"" + userName + "\"";
+ log.error(err, e);
+ throw new ServletException(err, e);
+ }
+ webSession.get().login(arsp, true);
+ chain.doFilter(req, rsp);
+ }
+
+ @Override
+ public void init(FilterConfig arg0) throws ServletException {
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java
new file mode 100644
index 0000000000..f0976f3ed5
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.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.httpd.auth.container;
+
+import com.google.inject.servlet.ServletModule;
+
+/** Servlets and support related to CLIENT_SSL_CERT_LDAP authentication. */
+public class HttpsClientSslCertModule extends ServletModule {
+ @Override
+ protected void configureServlets() {
+ filter("/").through(HttpsClientSslCertAuthFilter.class);
+ }
+}
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 a59d013188..e4577c1fc6 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
@@ -19,6 +19,7 @@ import com.google.gerrit.common.auth.userpass.UserPassAuthService;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AccountUserNameException;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -55,6 +56,12 @@ class UserPassAuthServiceImpl implements UserPassAuthService {
final AuthResult res;
try {
res = accountManager.authenticate(req);
+ } catch (AccountUserNameException e) {
+ // entered user name and password were correct, but user name could not be
+ // set for the newly created account and this is why the login fails,
+ // error screen with error message should be shown to the user
+ callback.onFailure(e);
+ return;
} catch (AccountException e) {
result.success = false;
callback.onSuccess(result);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index a704476471..8991ea9b06 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -344,11 +344,14 @@ class GitWebServlet extends HttpServlet {
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
-
- rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
- rsp.setHeader("Pragma", "no-cache");
- rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
- exec(req, rsp, project, repo);
+ try {
+ rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
+ rsp.setHeader("Pragma", "no-cache");
+ rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
+ exec(req, rsp, project, repo);
+ } finally {
+ repo.close();
+ }
}
private static Map<String, String> getParameters(final HttpServletRequest req)
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
index 3ecfa627c7..832ae991e5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -33,14 +33,16 @@ import eu.medsea.mimeutil.MimeType;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.NB;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
@@ -162,45 +164,50 @@ public class CatServlet extends HttpServlet {
return;
}
- final byte[] blobData;
+ final ObjectLoader blobLoader;
final RevCommit fromCommit;
final String suffix;
final String path = patchKey.getFileName();
try {
- final RevWalk rw = new RevWalk(repo);
- final RevCommit c;
- final TreeWalk tw;
-
- c = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- if (side == 0) {
- fromCommit = c;
- suffix = "new";
-
- } else if (1 <= side && side - 1 < c.getParentCount()) {
- fromCommit = rw.parseCommit(c.getParent(side - 1));
- if (c.getParentCount() == 1) {
- suffix = "old";
+ final ObjectReader reader = repo.newObjectReader();
+ try {
+ final RevWalk rw = new RevWalk(reader);
+ final RevCommit c;
+ final TreeWalk tw;
+
+ c = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
+ if (side == 0) {
+ fromCommit = c;
+ suffix = "new";
+
+ } else if (1 <= side && side - 1 < c.getParentCount()) {
+ fromCommit = rw.parseCommit(c.getParent(side - 1));
+ if (c.getParentCount() == 1) {
+ suffix = "old";
+ } else {
+ suffix = "old" + side;
+ }
+
} else {
- suffix = "old" + side;
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
}
- } else {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
-
- tw = TreeWalk.forPath(repo, path, fromCommit.getTree());
- if (tw == null) {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
+ tw = TreeWalk.forPath(reader, path, fromCommit.getTree());
+ if (tw == null) {
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
- if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) {
- blobData = repo.openBlob(tw.getObjectId(0)).getCachedBytes();
+ if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) {
+ blobLoader = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
- } else {
- rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- return;
+ } else {
+ rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ } finally {
+ reader.release();
}
} catch (IOException e) {
getServletContext().log("Cannot read repository", e);
@@ -214,14 +221,20 @@ public class CatServlet extends HttpServlet {
repo.close();
}
+ final byte[] raw =
+ blobLoader.isLarge() ? null : blobLoader.getCachedBytes();
final long when = fromCommit.getCommitTime() * 1000L;
- MimeType contentType = registry.getMimeType(path, blobData);
- final byte[] outData;
- if (registry.isSafeInline(contentType)) {
- outData = blobData;
+ rsp.setDateHeader("Last-Modified", when);
+ rsp.setDateHeader("Expires", 0L);
+ rsp.setHeader("Pragma", "no-cache");
+ rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
+
+ OutputStream out;
+ ZipOutputStream zo;
- } else {
+ final MimeType contentType = registry.getMimeType(path, raw);
+ if (!registry.isSafeInline(contentType)) {
// The content may not be safe to transmit inline, as a browser might
// interpret it as HTML or JavaScript hosted by this site. Such code
// might then run in the site's security domain, and may be able to use
@@ -230,31 +243,38 @@ public class CatServlet extends HttpServlet {
// Usually, wrapping the content into a ZIP file forces the browser to
// save the content to the local system instead.
//
- final ByteArrayOutputStream zip = new ByteArrayOutputStream();
- final ZipOutputStream zo = new ZipOutputStream(zip);
+
+ rsp.setContentType(ZIP.toString());
+ rsp.setHeader("Content-Disposition", "attachment; filename=\""
+ + safeFileName(path, suffix) + ".zip" + "\"");
+
+ zo = new ZipOutputStream(rsp.getOutputStream());
+
final ZipEntry e = new ZipEntry(safeFileName(path, rand(req, suffix)));
e.setComment(fromCommit.name() + ":" + path);
- e.setSize(blobData.length);
+ e.setSize(blobLoader.getSize());
e.setTime(when);
zo.putNextEntry(e);
- zo.write(blobData);
- zo.closeEntry();
- zo.close();
+ out = zo;
+
+ } else {
+ rsp.setContentType(contentType.toString());
+ rsp.setHeader("Content-Length", "" + blobLoader.getSize());
- outData = zip.toByteArray();
- contentType = ZIP;
+ out = rsp.getOutputStream();
+ zo = null;
+ }
- rsp.setHeader("Content-Disposition", "attachment; filename=\""
- + safeFileName(path, suffix) + ".zip" + "\"");
+ if (raw != null) {
+ out.write(raw);
+ } else {
+ blobLoader.copyTo(out);
}
- rsp.setContentType(contentType.toString());
- rsp.setContentLength(outData.length);
- rsp.setDateHeader("Last-Modified", when);
- rsp.setDateHeader("Expires", 0L);
- rsp.setHeader("Pragma", "no-cache");
- rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
- rsp.getOutputStream().write(outData);
+ if (zo != null) {
+ zo.closeEntry();
+ }
+ out.close();
}
private static String safeFileName(String fileName, final String suffix) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index f9721f8647..864c550ded 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -33,6 +33,7 @@ import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
class SuggestServiceImpl extends BaseServiceImplementation implements
SuggestService {
@@ -74,8 +75,8 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
});
}
- public void suggestAccount(final String query, final int limit,
- final AsyncCallback<List<AccountInfo>> callback) {
+ public void suggestAccount(final String query, final Boolean active,
+ final int limit, final AsyncCallback<List<AccountInfo>> callback) {
run(callback, new Action<List<AccountInfo>>() {
public List<AccountInfo> run(final ReviewDb db) throws OrmException {
final String a = query;
@@ -86,12 +87,12 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
final LinkedHashMap<Account.Id, AccountInfo> r =
new LinkedHashMap<Account.Id, AccountInfo>();
for (final Account p : db.accounts().suggestByFullName(a, b, n)) {
- r.put(p.getId(), new AccountInfo(p));
+ addSuggestion(r, p, new AccountInfo(p), active);
}
if (r.size() < n) {
for (final Account p : db.accounts().suggestByPreferredEmail(a, b,
n - r.size())) {
- r.put(p.getId(), new AccountInfo(p));
+ addSuggestion(r, p, new AccountInfo(p), active);
}
}
if (r.size() < n) {
@@ -101,7 +102,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
final Account p = accountCache.get(e.getAccountId()).getAccount();
final AccountInfo info = new AccountInfo(p);
info.setPreferredEmail(e.getEmailAddress());
- r.put(e.getAccountId(), info);
+ addSuggestion(r, p, info, active);
}
}
}
@@ -110,6 +111,13 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
});
}
+ private void addSuggestion(Map map, Account account, AccountInfo info,
+ Boolean active) {
+ if (active == null || active == account.isActive()) {
+ map.put(account.getId(), info);
+ }
+ }
+
public void suggestAccountGroup(final String query, final int limit,
final AsyncCallback<List<AccountGroupName>> callback) {
run(callback, new Action<List<AccountGroupName>>() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
index 4e4f82e7f8..870d77ce91 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -16,6 +16,7 @@ package com.google.gerrit.httpd.rpc.account;
import com.google.gerrit.common.data.GroupAdminService;
import com.google.gerrit.common.data.GroupDetail;
+import com.google.gerrit.common.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
@@ -221,6 +222,9 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
}
final Account a = findAccount(nameOrEmail);
+ if (!a.isActive()) {
+ throw new Failure(new InactiveAccountException(a.getFullName()));
+ }
if (!control.canAdd(a.getId())) {
throw new Failure(new NoSuchEntityException());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
index ec0f2255a4..7d6f764777 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
@@ -80,22 +80,26 @@ class IncludedInDetailFactory extends Handler<IncludedInDetail> {
repoManager.openRepository(control.getProject().getName());
try {
final RevWalk rw = new RevWalk(repo);
- rw.setRetainBody(false);
-
- final RevCommit rev;
try {
- rev = rw.parseCommit(ObjectId.fromString(patch.getRevision().get()));
- } catch (IncorrectObjectTypeException err) {
- throw new InvalidRevisionException();
- } catch (MissingObjectException err) {
- throw new InvalidRevisionException();
+ rw.setRetainBody(false);
+
+ final RevCommit rev;
+ try {
+ rev = rw.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+ } catch (IncorrectObjectTypeException err) {
+ throw new InvalidRevisionException();
+ } catch (MissingObjectException err) {
+ throw new InvalidRevisionException();
+ }
+
+ detail = new IncludedInDetail();
+ detail.setBranches(includedIn(repo, rw, rev, Constants.R_HEADS));
+ detail.setTags(includedIn(repo, rw, rev, Constants.R_TAGS));
+
+ return detail;
+ } finally {
+ rw.release();
}
-
- detail = new IncludedInDetail();
- detail.setBranches(includedIn(repo, rw, rev, Constants.R_HEADS));
- detail.setTags(includedIn(repo, rw, rev, Constants.R_TAGS));
-
- return detail;
} finally {
repo.close();
}
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 ae36388dc9..9d508e2fcf 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
@@ -94,6 +94,11 @@ class AddReviewer extends Handler<ReviewerResult> {
ReviewerResult.Error.Type.ACCOUNT_NOT_FOUND, nameOrEmail));
continue;
}
+ if (!account.isActive()) {
+ result.addError(new ReviewerResult.Error(
+ ReviewerResult.Error.Type.ACCOUNT_INACTIVE, nameOrEmail));
+ continue;
+ }
final IdentifiedUser user = identifiedUserFactory.create(account.getId());
if (!control.forUser(user).isVisible()) {
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 0f557ee630..730d2699da 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
@@ -24,7 +24,6 @@ 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;
import com.google.gerrit.server.patch.Text;
@@ -40,7 +39,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -64,6 +63,7 @@ class PatchScriptBuilder {
};
private Repository db;
+ private ObjectReader reader;
private Change change;
private AccountDiffPreference diffPrefs;
private boolean againstParent;
@@ -112,16 +112,17 @@ class PatchScriptBuilder {
PatchScript toPatchScript(final PatchListEntry content,
final boolean intralineDifference, final CommentDetail comments,
final List<Patch> history) throws IOException {
- if (content.getPatchType() == PatchType.N_WAY) {
- // For a diff --cc format we don't support converting it into
- // a patch script. Instead treat everything as a file header.
- //
- return new PatchScript(change.getKey(), content.getChangeType(), content
- .getOldName(), content.getNewName(), content.getHeaderLines(),
- diffPrefs, a.dst, b.dst, Collections.<Edit> emptyList(),
- a.displayMethod, b.displayMethod, comments, history, false, false);
+ reader = db.newObjectReader();
+ try {
+ return build(content, intralineDifference, comments, history);
+ } finally {
+ reader.release();
}
+ }
+ private PatchScript build(final PatchListEntry content,
+ final boolean intralineDifference, final CommentDetail comments,
+ final List<Patch> history) throws IOException {
a.path = oldName(content);
b.path = newName(content);
@@ -167,9 +168,9 @@ class PatchScriptBuilder {
}
return new PatchScript(change.getKey(), content.getChangeType(), content
- .getOldName(), content.getNewName(), content.getHeaderLines(),
- diffPrefs, a.dst, b.dst, edits, a.displayMethod, b.displayMethod,
- comments, history, hugeFile, intralineDifference);
+ .getOldName(), content.getNewName(), a.fileMode, b.fileMode, content
+ .getHeaderLines(), diffPrefs, a.dst, b.dst, edits, a.displayMethod,
+ b.displayMethod, comments, history, hugeFile, intralineDifference);
}
private static String oldName(final PatchListEntry entry) {
@@ -318,14 +319,14 @@ class PatchScriptBuilder {
for (final EditList.Hunk hunk : list.getHunks()) {
while (hunk.next()) {
if (hunk.isContextLine()) {
- final String lineA = a.src.getLine(hunk.getCurA());
+ final String lineA = a.src.getString(hunk.getCurA());
a.dst.addLine(hunk.getCurA(), lineA);
if (ignoredWhitespace) {
// If we ignored whitespace in some form, also get the line
// from b when it does not exactly match the line from a.
//
- final String lineB = b.src.getLine(hunk.getCurB());
+ final String lineB = b.src.getString(hunk.getCurB());
if (!lineA.equals(lineB)) {
b.dst.addLine(hunk.getCurB(), lineB);
}
@@ -355,6 +356,7 @@ class PatchScriptBuilder {
Text src;
MimeType mimeType = MimeUtil2.UNKNOWN_MIME_TYPE;
DisplayMethod displayMethod = DisplayMethod.DIFF;
+ PatchScript.FileMode fileMode = PatchScript.FileMode.FILE;
final SparseFileContent dst = new SparseFileContent();
int size() {
@@ -362,7 +364,7 @@ class PatchScriptBuilder {
}
void addLine(int line) {
- dst.addLine(line, src.getLine(line));
+ dst.addLine(line, src.getString(line));
}
void resolve(final Side other, final ObjectId within) throws IOException {
@@ -377,7 +379,7 @@ class PatchScriptBuilder {
displayMethod = DisplayMethod.NONE;
} else {
id = within;
- src = Text.forCommit(db, within);
+ src = Text.forCommit(db, reader, within);
srcContent = src.getContent();
if (src == Text.EMPTY) {
mode = FileMode.MISSING;
@@ -399,14 +401,7 @@ class PatchScriptBuilder {
srcContent = other.srcContent;
} else if (mode.getObjectType() == Constants.OBJ_BLOB) {
- final ObjectLoader ldr = db.openObject(id);
- if (ldr == null) {
- throw new MissingObjectException(id, Constants.TYPE_BLOB);
- }
- srcContent = ldr.getCachedBytes();
- if (ldr.getType() != Constants.OBJ_BLOB) {
- throw new IncorrectObjectTypeException(id, Constants.TYPE_BLOB);
- }
+ srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB));
} else {
srcContent = Text.NO_BYTES;
@@ -443,6 +438,12 @@ class PatchScriptBuilder {
}
dst.setSize(size());
dst.setPath(path);
+
+ if (mode == FileMode.SYMLINK) {
+ fileMode = PatchScript.FileMode.SYMLINK;
+ } else if (mode == FileMode.GITLINK) {
+ fileMode = PatchScript.FileMode.GITLINK;
+ }
} catch (IOException err) {
throw new IOException("Cannot read " + within.name() + ":" + path, err);
}
@@ -450,12 +451,12 @@ class PatchScriptBuilder {
private TreeWalk find(final ObjectId within) throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
- if (path == null) {
+ if (path == null || within == null) {
return null;
}
- final RevWalk rw = new RevWalk(db);
+ final RevWalk rw = new RevWalk(reader);
final RevTree tree = rw.parseTree(within);
- return TreeWalk.forPath(db, path, tree);
+ return TreeWalk.forPath(reader, path, tree);
}
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
index 0472dd4c4f..d4698c296a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc.project;
+import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.InvalidRevisionException;
@@ -58,6 +59,7 @@ class AddBranch extends Handler<ListBranchesResult> {
private final IdentifiedUser identifiedUser;
private final GitRepositoryManager repoManager;
private final ReplicationQueue replication;
+ private final ChangeHookRunner hooks;
private final Project.NameKey projectName;
private final String branchName;
@@ -69,6 +71,7 @@ class AddBranch extends Handler<ListBranchesResult> {
final IdentifiedUser identifiedUser,
final GitRepositoryManager repoManager,
final ReplicationQueue replication,
+ final ChangeHookRunner hooks,
@Assisted Project.NameKey projectName,
@Assisted("branchName") String branchName,
@@ -78,6 +81,7 @@ class AddBranch extends Handler<ListBranchesResult> {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.replication = replication;
+ this.hooks = hooks;
this.projectName = projectName;
this.branchName = branchName;
@@ -136,11 +140,9 @@ class AddBranch extends Handler<ListBranchesResult> {
case NEW:
case NO_CHANGE:
replication.scheduleUpdate(name.getParentKey(), refname);
+ hooks.doRefUpdatedHook(name, u, identifiedUser.getAccount());
break;
default: {
- final String msg =
- "Cannot create branch " + name + ": " + result.name();
- log.error(msg);
throw new IOException(result.name());
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
index e8c8904e16..fffc1261ae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
@@ -14,9 +14,11 @@
package com.google.gerrit.httpd.rpc.project;
+import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -46,6 +48,8 @@ class DeleteBranches extends Handler<Set<Branch.NameKey>> {
private final ProjectControl.Factory projectControlFactory;
private final GitRepositoryManager repoManager;
private final ReplicationQueue replication;
+ private final IdentifiedUser identifiedUser;
+ private final ChangeHookRunner hooks;
private final Project.NameKey projectName;
private final Set<Branch.NameKey> toRemove;
@@ -54,11 +58,15 @@ class DeleteBranches extends Handler<Set<Branch.NameKey>> {
DeleteBranches(final ProjectControl.Factory projectControlFactory,
final GitRepositoryManager repoManager,
final ReplicationQueue replication,
+ final IdentifiedUser identifiedUser,
+ final ChangeHookRunner hooks,
@Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
this.projectControlFactory = projectControlFactory;
this.repoManager = repoManager;
this.replication = replication;
+ this.identifiedUser = identifiedUser;
+ this.hooks = hooks;
this.projectName = name;
this.toRemove = toRemove;
@@ -85,8 +93,9 @@ class DeleteBranches extends Handler<Set<Branch.NameKey>> {
for (final Branch.NameKey branchKey : toRemove) {
final String refname = branchKey.get();
final RefUpdate.Result result;
+ final RefUpdate u;
try {
- final RefUpdate u = r.updateRef(refname);
+ u = r.updateRef(refname);
u.setForceUpdate(true);
result = u.delete();
} catch (IOException e) {
@@ -101,6 +110,7 @@ class DeleteBranches extends Handler<Set<Branch.NameKey>> {
case FORCED:
deleted.add(branchKey);
replication.scheduleUpdate(projectName, refname);
+ hooks.doRefUpdatedHook(branchKey, u, identifiedUser.getAccount());
break;
case REJECTED_CURRENT_BRANCH:
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 92154c4cbd..ae8b98b2cd 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
@@ -23,7 +23,6 @@ import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -71,8 +70,12 @@ class DeleteRefRights extends Handler<ProjectDetail> {
if (!projectName.equals(k.getProjectNameKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
- if (!projectControl.controlForRef(k.getRefPattern()).isOwner()) {
- throw new NoSuchRefException(k.getRefPattern());
+ String refPattern = k.getRefPattern();
+ if (refPattern.startsWith("-")) {
+ refPattern = refPattern.substring(1);
+ }
+ if (!projectControl.controlForRef(refPattern).isOwner()) {
+ throw new NoSuchRefException(refPattern);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
index c02af37ccf..7b22de097f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
@@ -60,15 +60,20 @@ class ListBranches extends Handler<ListBranchesResult> {
}
@Override
- public ListBranchesResult call() throws NoSuchProjectException,
- RepositoryNotFoundException {
+ public ListBranchesResult call() throws NoSuchProjectException {
final ProjectControl pctl = projectControlFactory.validateFor( //
projectName, //
ProjectControl.OWNER | ProjectControl.VISIBLE);
final List<Branch> branches = new ArrayList<Branch>();
Branch headBranch = null;
- final Repository db = repoManager.openRepository(projectName.get());
+
+ final Repository db;
+ try {
+ db = repoManager.openRepository(projectName.get());
+ } catch (RepositoryNotFoundException noGitRepository) {
+ return new ListBranchesResult(branches, false, true);
+ }
try {
final Map<String, Ref> all = db.getAllRefs();
@@ -139,7 +144,7 @@ class ListBranches extends Handler<ListBranchesResult> {
if (headBranch != null) {
branches.add(0, headBranch);
}
- return new ListBranchesResult(branches, pctl.canAddRefs());
+ return new ListBranchesResult(branches, pctl.canAddRefs(), false);
}
private Branch createBranch(final String name) {
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index 1000777281..3c9c979618 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index 10cb14aec7..a46aa4ff8a 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-main</artifactId>
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index df64d13874..5bbae03533 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index 68a08c3d16..f30eacede2 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-jgit</artifactId>
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/lib/WindowCacheStatAccessor.java b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/storage/file/WindowCacheStatAccessor.java
index 7d12e47e3a..7e29536139 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/lib/WindowCacheStatAccessor.java
+++ b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/storage/file/WindowCacheStatAccessor.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
// Hack to obtain visibility to package level methods only.
// These aren't yet part of the public JGit API.
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index 66c5349799..f658f52153 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-pgm</artifactId>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 445c10aad6..b10e1db7b4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -52,7 +52,6 @@ import com.google.inject.spi.Message;
import org.kohsuke.args4j.Option;
-import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
index e62fe40050..16c72d4859 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
@@ -140,8 +140,12 @@ public class ScanTrackingIds extends SiteProgram {
private RevCommit parse(final Repository git, PatchSet ps)
throws MissingObjectException, IncorrectObjectTypeException, IOException {
- return new RevWalk(git).parseCommit(ObjectId.fromString(ps.getRevision()
- .get()));
+ RevWalk rw = new RevWalk(git);
+ try {
+ return rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ } finally {
+ rw.release();
+ }
}
private Change next() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index e9d0f2e995..7ab8d30254 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -19,7 +19,9 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.reviewdb.AuthType;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
@@ -139,6 +141,7 @@ public class JettyServer {
final URI[] listenUrls = listenURLs(cfg);
final boolean reuseAddress = cfg.getBoolean("httpd", "reuseaddress", true);
final int acceptors = cfg.getInt("httpd", "acceptorThreads", 2);
+ final AuthType authType = ConfigUtil.getEnum(cfg, "auth", null, "type", AuthType.OPENID);
reverseProxy = true;
final Connector[] connectors = new Connector[listenUrls.length];
@@ -147,11 +150,17 @@ public class JettyServer {
final int defaultPort;
final SelectChannelConnector c;
+ if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType) && ! "https".equals(u.getScheme())) {
+ throw new IllegalArgumentException("Protocol '" + u.getScheme()
+ + "' " + " not supported in httpd.listenurl '" + u
+ + "' when auth.type = '" + AuthType.CLIENT_SSL_CERT_LDAP.name()
+ + "'; only 'https' is supported");
+ }
+
if ("http".equals(u.getScheme())) {
reverseProxy = false;
defaultPort = 80;
c = new SelectChannelConnector();
-
} else if ("https".equals(u.getScheme())) {
final SslSelectChannelConnector ssl = new SslSelectChannelConnector();
final File keystore = getFile(cfg, "sslkeystore", "etc/keystore");
@@ -164,6 +173,10 @@ public class JettyServer {
ssl.setKeyPassword(password);
ssl.setTrustPassword(password);
+ if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType)) {
+ ssl.setNeedClientAuth(true);
+ }
+
reverseProxy = false;
defaultPort = 443;
c = ssl;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
index ef95d87b7f..7063f5440f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -14,7 +14,6 @@
package com.google.gerrit.pgm.init;
-import static com.google.gerrit.pgm.init.InitUtil.copy;
import static com.google.gerrit.pgm.init.InitUtil.die;
import static com.google.gerrit.pgm.init.InitUtil.username;
@@ -24,10 +23,14 @@ import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.util.FS;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.OutputStream;
/** Initialize the {@code container} configuration section. */
@Singleton
@@ -84,7 +87,39 @@ class InitContainer implements InitStep {
System.err.format("Copying gerrit.war to %s", siteWar.getPath());
System.err.println();
}
- copy(siteWar, new FileInputStream(myWar));
+
+ FileInputStream in = new FileInputStream(myWar);
+ try {
+ siteWar.getParentFile().mkdirs();
+
+ LockFile lf = new LockFile(siteWar, FS.DETECTED);
+ if (!lf.lock()) {
+ throw new IOException("Cannot lock " + siteWar);
+ }
+
+ try {
+ final OutputStream out = lf.getOutputStream();
+ try {
+ final byte[] tmp = new byte[4096];
+ for (;;) {
+ int n = in.read(tmp);
+ if (n < 0) {
+ break;
+ }
+ out.write(tmp, 0, n);
+ }
+ } finally {
+ out.close();
+ }
+ if (!lf.commit()) {
+ throw new IOException("Cannot commit " + siteWar);
+ }
+ } finally {
+ lf.unlock();
+ }
+ } finally {
+ in.close();
+ }
}
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
index 5ca7e4131f..992c616cbe 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
@@ -19,7 +19,8 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
import java.io.IOException;
@@ -40,8 +41,8 @@ public class InitFlags {
@Inject
InitFlags(final SitePaths site) throws IOException, ConfigInvalidException {
- cfg = new FileBasedConfig(site.gerrit_config);
- sec = new FileBasedConfig(site.secure_config);
+ cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+ sec = new FileBasedConfig(site.secure_config, FS.DETECTED);
cfg.load();
sec.load();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
index 17f014673e..1382f65067 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
@@ -17,19 +17,23 @@ package com.google.gerrit.pgm.init;
import com.google.gerrit.pgm.util.Die;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileBasedConfig;
-import org.eclipse.jgit.lib.LockFile;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.SystemReader;
-import java.io.OutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
/** Utility functions to help initialize a site. */
class InitUtil {
@@ -42,25 +46,41 @@ class InitUtil {
}
static void savePublic(final FileBasedConfig sec) throws IOException {
- sec.save();
+ if (modified(sec)) {
+ sec.save();
+ }
}
static void saveSecure(final FileBasedConfig sec) throws IOException {
- final byte[] out = Constants.encode(sec.toText());
- final File path = sec.getFile();
- final LockFile lf = new LockFile(path);
- if (!lf.lock()) {
- throw new IOException("Cannot lock " + path);
+ if (modified(sec)) {
+ final byte[] out = Constants.encode(sec.toText());
+ final File path = sec.getFile();
+ final LockFile lf = new LockFile(path, FS.DETECTED);
+ if (!lf.lock()) {
+ throw new IOException("Cannot lock " + path);
+ }
+ try {
+ chmod(0600, new File(path.getParentFile(), path.getName() + ".lock"));
+ lf.write(out);
+ if (!lf.commit()) {
+ throw new IOException("Cannot commit write to " + path);
+ }
+ } finally {
+ lf.unlock();
+ }
}
+ }
+
+ private static boolean modified(FileBasedConfig cfg) throws IOException {
+ byte[] curVers;
try {
- chmod(0600, new File(path.getParentFile(), path.getName() + ".lock"));
- lf.write(out);
- if (!lf.commit()) {
- throw new IOException("Cannot commit write to " + path);
- }
- } finally {
- lf.unlock();
+ curVers = IO.readFully(cfg.getFile());
+ } catch (FileNotFoundException notFound) {
+ return true;
}
+
+ byte[] newVers = Constants.encode(cfg.toText());
+ return !Arrays.equals(curVers, newVers);
}
static void mkdir(final File path) {
@@ -144,7 +164,8 @@ class InitUtil {
final String name) throws IOException {
final InputStream in = open(sibling, name);
if (in != null) {
- copy(dst, in);
+ ByteBuffer buf = IO.readWholeStream(in, 8192);
+ copy(dst, buf);
}
}
@@ -165,34 +186,41 @@ class InitUtil {
return in;
}
- static void copy(final File dst, final InputStream in)
+ static void copy(final File dst, final ByteBuffer buf)
throws FileNotFoundException, IOException {
+ // If the file already has the content we want to put there,
+ // don't attempt to overwrite the file.
+ //
try {
- dst.getParentFile().mkdirs();
- LockFile lf = new LockFile(dst);
- if (!lf.lock()) {
- throw new IOException("Cannot lock " + dst);
+ if (buf.equals(ByteBuffer.wrap(IO.readFully(dst)))) {
+ return;
}
- try {
+ } catch (FileNotFoundException notFound) {
+ // Fall through and write the file.
+ }
- final OutputStream out = lf.getOutputStream();
- try {
- final byte[] buf = new byte[4096];
- int n;
- while (0 < (n = in.read(buf))) {
- out.write(buf, 0, n);
- }
- } finally {
- out.close();
- }
- if (!lf.commit()) {
- throw new IOException("Cannot commit " + dst);
+ dst.getParentFile().mkdirs();
+ LockFile lf = new LockFile(dst, FS.DETECTED);
+ if (!lf.lock()) {
+ throw new IOException("Cannot lock " + dst);
+ }
+ try {
+ final OutputStream out = lf.getOutputStream();
+ try {
+ final byte[] tmp = new byte[4096];
+ while (0 < buf.remaining()) {
+ int n = Math.min(buf.remaining(), tmp.length);
+ buf.get(tmp, 0, n);
+ out.write(tmp, 0, n);
}
} finally {
- lf.unlock();
+ out.close();
+ }
+ if (!lf.commit()) {
+ throw new IOException("Cannot commit " + dst);
}
} finally {
- in.close();
+ lf.unlock();
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 74e754830c..dae08934ad 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -25,11 +25,13 @@ import static com.google.gerrit.pgm.init.InitUtil.version;
import com.google.gerrit.pgm.Init;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.mail.OutgoingEmail;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -66,6 +68,7 @@ public class SitePathInitializer {
mkdir(site.etc_dir);
mkdir(site.lib_dir);
mkdir(site.logs_dir);
+ mkdir(site.mail_dir);
mkdir(site.static_dir);
for (InitStep step : steps) {
@@ -82,11 +85,27 @@ public class SitePathInitializer {
extract(site.gerrit_sh, Init.class, "gerrit.sh");
chmod(0755, site.gerrit_sh);
+ extractMailExample("Abandoned.vm");
+ extractMailExample("ChangeFooter.vm");
+ extractMailExample("ChangeSubject.vm");
+ extractMailExample("Comment.vm");
+ extractMailExample("Merged.vm");
+ extractMailExample("MergeFail.vm");
+ extractMailExample("NewChange.vm");
+ extractMailExample("RegisterNewEmail.vm");
+ extractMailExample("ReplacePatchSet.vm");
+
if (!ui.isBatch()) {
System.err.println();
}
}
+ private void extractMailExample(String orig) throws Exception {
+ File ex = new File(site.mail_dir, orig + ".example");
+ extract(ex, OutgoingEmail.class, orig);
+ chmod(0444, ex);
+ }
+
private static List<InitStep> stepsOf(final Injector injector) {
final ArrayList<InitStep> r = new ArrayList<InitStep>();
for (Binding<InitStep> b : all(injector)) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
index fad587884f..9f62fc5faa 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -26,7 +26,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import java.io.File;
import java.io.FileInputStream;
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java
index 2964f50fb0..4d7370bfb7 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java
@@ -21,6 +21,6 @@ import java.io.IOException;
public abstract class InitTestCase extends LocalDiskRepositoryTestCase {
protected File newSitePath() throws IOException {
- return new File(createWorkRepository().getWorkDir(), "test_site");
+ return new File(createWorkRepository().getWorkTree(), "test_site");
}
}
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 72b02d5dd6..0018558108 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
@@ -24,7 +24,8 @@ import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
import java.io.File;
@@ -50,7 +51,9 @@ public class UpgradeFrom2_0_xTest extends InitTestCase {
}
}
- FileBasedConfig old = new FileBasedConfig(new File(p, "gerrit.config"));
+ FileBasedConfig old =
+ new FileBasedConfig(new File(p, "gerrit.config"), FS.DETECTED);
+
old.setString("ldap", null, "username", "ldap.user");
old.setString("ldap", null, "password", "ldap.s3kr3t");
@@ -84,8 +87,8 @@ public class UpgradeFrom2_0_xTest extends InitTestCase {
new String(IO.readFully(new File(site.etc_dir, n)), "UTF-8"));
}
- FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config);
- FileBasedConfig sec = new FileBasedConfig(site.secure_config);
+ FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+ FileBasedConfig sec = new FileBasedConfig(site.secure_config, FS.DETECTED);
cfg.load();
sec.load();
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index 99eece6076..060ffdda0d 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-prettify</artifactId>
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index 5626739adf..d81b068b0c 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.4-SNAPSHOT</version>
+ <version>2.1-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 f428a22790..43b7b17ba7 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
@@ -129,6 +129,10 @@ public final class Account {
@Column(id = 6, name = Column.NONE)
protected AccountGeneralPreferences generalPreferences;
+ /** Is this user active */
+ @Column(id = 7)
+ protected boolean inactive;
+
/** <i>computed</i> the username selected from the identities. */
protected String userName;
@@ -198,6 +202,14 @@ public final class Account {
contactFiledOn = new Timestamp(System.currentTimeMillis());
}
+ public boolean isActive() {
+ return ! inactive;
+ }
+
+ public void setActive(boolean active) {
+ inactive = ! active;
+ }
+
/** @return the computed user name for this account */
public String getUserName() {
return userName;
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
index 4a3dd18120..38a23593b1 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
@@ -96,6 +96,12 @@ public class AccountDiffPreference {
@Column(id = 9)
protected short context;
+ @Column(id = 10)
+ protected boolean skipDeleted;
+
+ @Column(id = 11)
+ protected boolean skipUncommented;
+
protected AccountDiffPreference() {
}
@@ -112,6 +118,8 @@ public class AccountDiffPreference {
this.showWhitespaceErrors = p.showWhitespaceErrors;
this.intralineDifference = p.intralineDifference;
this.showTabs = p.showTabs;
+ this.skipDeleted = p.skipDeleted;
+ this.skipUncommented = p.skipUncommented;
this.context = p.context;
}
@@ -185,4 +193,20 @@ public class AccountDiffPreference {
assert 0 <= context || context == WHOLE_FILE_CONTEXT;
this.context = context;
}
+
+ public boolean isSkipDeleted() {
+ return skipDeleted;
+ }
+
+ public void setSkipDeleted(boolean skip) {
+ skipDeleted = skip;
+ }
+
+ public boolean isSkipUncommented() {
+ return skipUncommented;
+ }
+
+ public void setSkipUncommented(boolean skip) {
+ skipUncommented = skip;
+ }
}
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 c69e785513..b90129cd82 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
@@ -35,6 +35,51 @@ public final class AccountGeneralPreferences {
REPO_DOWNLOAD, PULL, CHECKOUT, CHERRY_PICK, FORMAT_PATCH;
}
+ public static enum DateFormat {
+ /** US style dates: Apr 27, Feb 14, 2010 */
+ STD("MMM d", "MMM d, yyyy"),
+
+ /** US style dates: 04/27, 02/14/10 */
+ US("MM/dd", "MM/dd/yy"),
+
+ /** ISO style dates: 2010-02-14 */
+ ISO("MM-dd", "yyyy-MM-dd");
+
+ private final String shortFormat;
+ private final String longFormat;
+
+ DateFormat(String shortFormat, String longFormat) {
+ this.shortFormat = shortFormat;
+ this.longFormat = longFormat;
+ }
+
+ public String getShortFormat() {
+ return shortFormat;
+ }
+
+ public String getLongFormat() {
+ return longFormat;
+ }
+ }
+
+ public static enum TimeFormat {
+ /** 12-hour clock: 1:15 am, 2:13 pm */
+ HHMM_12("h:mm a"),
+
+ /** 24-hour clock: 01:15, 14:13 */
+ HHMM_24("HH:mm");
+
+ private final String format;
+
+ TimeFormat(String format) {
+ this.format = format;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+ }
+
/** Number of changes to show in a screen. */
@Column(id = 2)
protected short maximumPageSize;
@@ -59,6 +104,19 @@ public final class AccountGeneralPreferences {
@Column(id = 7)
protected boolean copySelfOnEmail;
+ @Column(id = 8, length = 10, notNull = false)
+ protected String dateFormat;
+
+ @Column(id = 9, length = 10, notNull = false)
+ protected String timeFormat;
+
+ /**
+ * If true display the patch sets in the ChangeScreen in reverse order
+ * (show latest patch set on top).
+ */
+ @Column(id = 10)
+ protected boolean displayPatchSetsInReverseOrder;
+
public AccountGeneralPreferences() {
}
@@ -124,12 +182,45 @@ public final class AccountGeneralPreferences {
copySelfOnEmail = includeSelfOnEmail;
}
+ public boolean isDisplayPatchSetsInReverseOrder() {
+ return displayPatchSetsInReverseOrder;
+ }
+
+ public void setDisplayPatchSetsInReverseOrder(final boolean displayPatchSetsInReverseOrder) {
+ this.displayPatchSetsInReverseOrder = displayPatchSetsInReverseOrder;
+ }
+
+ public DateFormat getDateFormat() {
+ if (dateFormat == null) {
+ return DateFormat.STD;
+ }
+ return DateFormat.valueOf(dateFormat);
+ }
+
+ public void setDateFormat(DateFormat fmt) {
+ dateFormat = fmt.name();
+ }
+
+ public TimeFormat getTimeFormat() {
+ if (timeFormat == null) {
+ return TimeFormat.HHMM_12;
+ }
+ return TimeFormat.valueOf(timeFormat);
+ }
+
+ public void setTimeFormat(TimeFormat fmt) {
+ timeFormat = fmt.name();
+ }
+
public void resetToDefaults() {
maximumPageSize = DEFAULT_PAGESIZE;
showSiteHeader = true;
useFlashClipboard = true;
copySelfOnEmail = false;
+ displayPatchSetsInReverseOrder = false;
downloadUrl = null;
downloadCommand = null;
+ dateFormat = null;
+ timeFormat = 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 52bef2ba9c..6713d8f8f6 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
@@ -20,6 +20,11 @@ import com.google.gwtorm.client.StringKey;
/** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch {
+
+ public enum Type {
+ NEW_CHANGES, SUBMITS, COMMENTS
+ }
+
public static final String FILTER_ALL = "*";
public static class Key extends CompoundKey<Account.Id> {
@@ -142,4 +147,24 @@ public final class AccountProjectWatch {
public void setNotifySubmittedChanges(final boolean a) {
notifySubmittedChanges = a;
}
+
+ public boolean isNotify(final Type type) {
+ switch(type) {
+ case NEW_CHANGES: return notifySubmittedChanges;
+ case SUBMITS: return notifyNewChanges;
+ case COMMENTS: return notifyAllComments;
+ }
+ return false;
+ }
+
+ public void setNotify(final Type type, final boolean v) {
+ switch(type) {
+ case NEW_CHANGES: notifySubmittedChanges = v;
+ break;
+ case SUBMITS: notifyNewChanges = v;
+ break;
+ case COMMENTS: notifyAllComments = v;
+ break;
+ }
+ }
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java
index 46b435c6fe..5d69e21da8 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AuthType.java
@@ -40,6 +40,21 @@ public enum AuthType {
HTTP_LDAP,
/**
+ * Login via client SSL certificate.
+ * <p>
+ * This authentication type is actually kind of SSO. Gerrit will configure
+ * Jetty's SSL channel to request client's SSL certificate. For this
+ * authentication to work a Gerrit administrator has to import the root
+ * certificate of the trust chain used to issue the client's certificate
+ * into the <review-site>/etc/keystore.
+ * <p>
+ * After the authentication is done Gerrit will obtain basic user
+ * registration (name and email) from LDAP, and some group memberships.
+ * Therefore, the "_LDAP" suffix in the name of this authentication type.
+ */
+ CLIENT_SSL_CERT_LDAP,
+
+ /**
* Login collects username and password through a web form, and binds to LDAP.
* <p>
* Unlike {@link #HTTP_LDAP}, Gerrit presents a sign-in dialog to the user and
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Patch.java
index 3a922fb12f..0153755a4b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Patch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Patch.java
@@ -136,29 +136,7 @@ public final class Patch {
* the only information it can display is the old and new file content
* hashes.
*/
- BINARY('B'),
-
- /**
- * Difference of three or more textual contents.
- *
- * <p>
- * Git can produce an n-way unified diff, showing how a merge conflict was
- * resolved when two or more conflicting branches were merged together in a
- * single merge commit.
- *
- * <p>
- * This type of patch can only appear if there are two or more
- * {@link PatchSetAncestor} entities for the same parent {@link PatchSet},
- * as that denotes that the patch set is a merge commit.
- *
- * <p>
- * Gerrit can only render an N_WAY file in a PatchScreen.Unified view, as it
- * does not have code to split the n-way unified diff into multiple edit
- * lists, one per pre-image. However, a logical way to display this format
- * would be an n-way table, with n+1 columns displayed (n pre-images, +1
- * post-image).
- */
- N_WAY('N');
+ BINARY('B');
private final char code;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java
index e37c78d663..409547a9bd 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java
@@ -99,6 +99,12 @@ public final class Project {
@Column(id = 6, notNull = false, name = "parent_name")
protected NameKey parent;
+ @Column(id = 7)
+ protected boolean requireChangeID;
+
+ @Column(id = 8)
+ protected boolean useContentMerge;
+
protected Project() {
}
@@ -136,10 +142,26 @@ public final class Project {
return useSignedOffBy;
}
+ public boolean isUseContentMerge() {
+ return useContentMerge;
+ }
+
+ public boolean isRequireChangeID() {
+ return requireChangeID;
+ }
+
public void setUseSignedOffBy(final boolean sbo) {
useSignedOffBy = sbo;
}
+ public void setUseContentMerge(final boolean cm) {
+ useContentMerge = cm;
+ }
+
+ public void setRequireChangeID(final boolean cid) {
+ requireChangeID = cid;
+ }
+
public SubmitType getSubmitType() {
return SubmitType.forCode(submitType);
}
@@ -152,6 +174,8 @@ public final class Project {
description = update.description;
useContributorAgreements = update.useContributorAgreements;
useSignedOffBy = update.useSignedOffBy;
+ useContentMerge = update.useContentMerge;
+ requireChangeID = update.requireChangeID;
submitType = update.submitType;
}
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 ec70051aed..97ee219946 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
@@ -96,6 +96,10 @@ public final class RefRight {
return refPattern.get();
}
+ public void setGroupId(AccountGroup.Id groupId) {
+ this.groupId = groupId;
+ }
+
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {refPattern, categoryId,
@@ -119,6 +123,13 @@ public final class RefRight {
this.key = key;
}
+ public RefRight(final RefRight refRight, final AccountGroup.Id groupId) {
+ this(new RefRight.Key(refRight.getKey().projectName,
+ refRight.getKey().refPattern, refRight.getKey().categoryId, groupId));
+ setMinValue(refRight.getMinValue());
+ setMaxValue(refRight.getMaxValue());
+ }
+
public RefRight.Key getKey() {
return key;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
index f9b6a2dfbf..6ff23ed7ea 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
@@ -14,7 +14,6 @@
package com.google.gerrit.reviewdb;
-import com.google.gerrit.reviewdb.AccountGroup.Id;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.StringKey;
@@ -83,6 +82,10 @@ public final class SystemConfig {
@Column(id = 8)
public AccountGroup.Id batchUsersGroupId;
+ /** Identity of the owner group, which permits any project owner. */
+ @Column(id = 9)
+ public AccountGroup.Id ownerGroupId;
+
protected SystemConfig() {
}
}
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index db8093cc20..0a9ece3eda 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-server</artifactId>
@@ -34,6 +34,16 @@ limitations under the License.
<dependencies>
<dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</dependency>
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 d29bc23964..c7c51b6cf1 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
@@ -19,6 +19,7 @@ import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
+import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
@@ -35,6 +36,7 @@ import com.google.gerrit.server.events.ChangeRestoreEvent;
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.events.RefUpdatedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.project.ProjectCache;
@@ -45,6 +47,8 @@ import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -96,6 +100,9 @@ public class ChangeHookRunner {
/** Filename of the change abandoned hook. */
private final File changeRestoredHook;
+ /** Filename of the ref updated hook. */
+ private final File refUpdatedHook;
+
/** Repository Manager. */
private final GitRepositoryManager repoManager;
@@ -141,6 +148,7 @@ public class ChangeHookRunner {
changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath());
changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath());
changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath());
+ refUpdatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdatedHook", "ref-updated")).getPath());
}
public void addChangeListener(ChangeListener listener, IdentifiedUser user) {
@@ -172,7 +180,16 @@ public class ChangeHookRunner {
* @return Repository or null.
*/
private Repository openRepository(final Change change) {
- Project.NameKey name = change.getProject();
+ return openRepository(change.getProject());
+ }
+
+ /**
+ * Get the Repository for the given project name, or null on error.
+ *
+ * @param name Project to get repo for,
+ * @return Repository or null.
+ */
+ private Repository openRepository(final Project.NameKey name) {
try {
return repoManager.openRepository(name.get());
} catch (RepositoryNotFoundException err) {
@@ -335,6 +352,44 @@ public class ChangeHookRunner {
runHook(openRepository(change), changeRestoredHook, args);
}
+ /**
+ * Fire the Ref Updated Hook
+ * @param project The project the ref update occured on
+ * @param refUpdate An actual RefUpdate object
+ * @param account The gerrit user who moved the ref
+ */
+ public void doRefUpdatedHook(final Branch.NameKey refName, final RefUpdate refUpdate, final Account account) {
+ doRefUpdatedHook(refName, refUpdate.getOldObjectId(), refUpdate.getNewObjectId(), account);
+ }
+
+ /**
+ * Fire the Ref Updated Hook
+ * @param refName The Branch.NameKey of the ref that was updated
+ * @param oldId The ref's old id
+ * @param newId The ref's new id
+ * @param account The gerrit user who moved the ref
+ */
+ public void doRefUpdatedHook(final Branch.NameKey refName, final ObjectId oldId, final ObjectId newId, final Account account) {
+ final RefUpdatedEvent event = new RefUpdatedEvent();
+
+ if (account != null) {
+ event.submitter = eventFactory.asAccountAttribute(account);
+ }
+ event.refUpdate = eventFactory.asRefUpdateAttribute(oldId, newId, refName);
+ fireEvent(refName, event);
+
+ final List<String> args = new ArrayList<String>();
+ addArg(args, "--oldrev", event.refUpdate.oldRev);
+ addArg(args, "--newrev", event.refUpdate.newRev);
+ addArg(args, "--refname", event.refUpdate.refName);
+ addArg(args, "--project", event.refUpdate.project);
+ if (account != null) {
+ addArg(args, "--submitter", getDisplayName(account));
+ }
+
+ runHook(openRepository(refName.getParentKey()), refUpdatedHook, args);
+ }
+
private void fireEvent(final Change change, final ChangeEvent event) {
for (ChangeListenerHolder holder : listeners.values()) {
if (isVisibleTo(change, holder.user)) {
@@ -343,6 +398,14 @@ public class ChangeHookRunner {
}
}
+ private void fireEvent(Branch.NameKey branchName, final ChangeEvent event) {
+ for (ChangeListenerHolder holder : listeners.values()) {
+ if (isVisibleTo(branchName, holder.user)) {
+ holder.listener.onChangeEvent(event);
+ }
+ }
+ }
+
private boolean isVisibleTo(Change change, IdentifiedUser user) {
final ProjectState pe = projectCache.get(change.getProject());
if (pe == null) {
@@ -352,6 +415,15 @@ public class ChangeHookRunner {
return pc.controlFor(change).isVisible();
}
+ private boolean isVisibleTo(Branch.NameKey branchName, IdentifiedUser user) {
+ final ProjectState pe = projectCache.get(branchName.getParentKey());
+ if (pe == null) {
+ return false;
+ }
+ final ProjectControl pc = pe.controlFor(user);
+ return pc.controlForRef(branchName).isVisible();
+ }
+
/**
* Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
* @param approval
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/CollectionsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/common/CollectionsUtil.java
new file mode 100644
index 0000000000..6e635cb2c2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/CollectionsUtil.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.common;
+
+import java.util.Collection;
+
+/** Utilities for manipulating Collections . */
+public class CollectionsUtil {
+ /**
+ * Checks if any of the elements in the first collection can be found in the
+ * second collection.
+ *
+ * @param findAnyOfThese which elements to look for.
+ * @param inThisCollection where to look for them.
+ * @param <E> type of the elements in question.
+ * @return {@code true} if any of the elements in {@code findAnyOfThese} can
+ * be found in {@code inThisCollection}, {@code false} otherwise.
+ */
+ public static <E> boolean isAnyIncludedIn(Collection<E> findAnyOfThese,
+ Collection<E> inThisCollection) {
+ for (E findThisItem : findAnyOfThese) {
+ if (inThisCollection.contains(findThisItem)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private CollectionsUtil() {
+ }
+}
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 32933241d8..5c44bfe58c 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
@@ -17,7 +17,6 @@ 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;
@@ -47,13 +46,7 @@ public class ReplicationUser extends CurrentUser {
effectiveGroups = EVERYTHING_VISIBLE;
} else if (authGroups.isEmpty()) {
- // Only include the registered groups if no specific groups
- // were provided. This allows an administrator to configure
- // a replication user with a narrower view of the system than
- // all other users, such as when replicating from an internal
- // company server to a public open source distribution site.
- //
- effectiveGroups = authConfig.getRegisteredGroups();
+ effectiveGroups = Collections.emptySet();
} else {
effectiveGroups = copy(authGroups);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 24ddd27f39..5cb8f36bf1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -100,7 +100,7 @@ public class AccountManager {
* @param who identity of the user, with any details we received about them.
* @return the result of authenticating the user.
* @throws AccountException the account does not exist, and cannot be created,
- * or exists, but cannot be located.
+ * or exists, but cannot be located, or is inactive.
*/
public AuthResult authenticate(AuthRequest who) throws AccountException {
who = realm.authenticate(who);
@@ -114,9 +114,14 @@ public class AccountManager {
//
return create(db, who);
- } else {
- // Account exists, return the identity to the caller.
- //
+ } else { // Account exists
+
+ Account act = db.accounts().get(id.getAccountId());
+ if (act == null || !act.isActive()) {
+ throw new AccountException("Authentication error, account inactive");
+ }
+
+ // return the identity to the caller.
update(db, who, id);
return new AuthResult(id.getAccountId(), key, false);
}
@@ -285,13 +290,18 @@ public class AccountManager {
try {
changeUserNameFactory.create(db, user, who.getUserName()).call();
} catch (NameAlreadyUsedException e) {
- log.error("Cannot assign user name \"" + who.getUserName()
- + "\" to account " + newId + "; name already in use.");
+ final String message =
+ "Cannot assign user name \"" + who.getUserName() + "\" to account "
+ + newId + "; name already in use.";
+ handleSettingUserNameFailure(db, account, extId, message, e, false);
} catch (InvalidUserNameException e) {
- log.error("Cannot assign user name \"" + who.getUserName()
- + "\" to account " + newId + "; name does not conform.");
+ final String message =
+ "Cannot assign user name \"" + who.getUserName() + "\" to account "
+ + newId + "; name does not conform.";
+ handleSettingUserNameFailure(db, account, extId, message, e, false);
} catch (OrmException e) {
- log.error("Cannot assign user name", e);
+ final String message = "Cannot assign user name";
+ handleSettingUserNameFailure(db, account, extId, message, e, true);
}
}
@@ -300,6 +310,49 @@ public class AccountManager {
return new AuthResult(newId, extId.getKey(), true);
}
+ /**
+ * This method handles an exception that occurred during the setting of the
+ * user name for a newly created account. If the realm does not allow the user
+ * to set a user name manually this method deletes the newly created account
+ * and throws an {@link AccountUserNameException}. In any case the error
+ * message is logged.
+ *
+ * @param db the database
+ * @param account the newly created account
+ * @param extId the newly created external id
+ * @param errorMessage the error message
+ * @param e the exception that occurred during the setting of the user name
+ * for the new account
+ * @param logException flag that decides whether the exception should be
+ * included into the log
+ * @throws AccountUserNameException thrown if the realm does not allow the
+ * user to manually set the user name
+ * @throws OrmException thrown if cleaning the database failed
+ */
+ private void handleSettingUserNameFailure(final ReviewDb db,
+ final Account account, final AccountExternalId extId,
+ final String errorMessage, final Exception e, final boolean logException)
+ throws AccountUserNameException, OrmException {
+ if (logException) {
+ log.error(errorMessage, e);
+ } else {
+ log.error(errorMessage);
+ }
+ if (!realm.allowsEdit(Account.FieldName.USER_NAME)) {
+ // setting the given user name has failed, but the realm does not
+ // allow the user to manually set a user name,
+ // this means we would end with an account without user name
+ // (without 'username:<USERNAME>' entry in
+ // account_external_ids table),
+ // such an account cannot be used for uploading changes,
+ // this is why the best we can do here is to fail early and cleanup
+ // the database
+ db.accounts().delete(Collections.singleton(account));
+ db.accountExternalIds().delete(Collections.singleton(extId));
+ throw new AccountUserNameException(errorMessage, e);
+ }
+ }
+
private static AccountExternalId createId(final Account.Id newId,
final AuthRequest who) {
final String ext = who.getExternalId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountUserNameException.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountUserNameException.java
new file mode 100644
index 0000000000..1cf8be80e4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountUserNameException.java
@@ -0,0 +1,28 @@
+// 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;
+
+/**
+ * Thrown by {@link AccountManager} if the user name for a newly created account
+ * could not be set and the realm does not allow the user to set a user name
+ * manually.
+ */
+public class AccountUserNameException extends AccountException {
+ private static final long serialVersionUID = 1L;
+
+ public AccountUserNameException(final String message, final Throwable why) {
+ super(message, why);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 6f6a4d440f..675202cc4d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -33,7 +33,9 @@ import java.util.List;
import java.util.Properties;
import java.util.Set;
+import javax.naming.CompositeName;
import javax.naming.Context;
+import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
@@ -196,8 +198,9 @@ import javax.net.ssl.SSLSocketFactory;
// Recursively identify the groups it is a member of.
//
try {
+ final Name compositeGroupName = new CompositeName().add(groupDN);
final Attribute in =
- ctx.getAttributes(groupDN).get(schema.accountMemberField);
+ ctx.getAttributes(compositeGroupName).get(schema.accountMemberField);
if (in != null) {
final NamingEnumeration<?> groups = in.getAll();
while (groups.hasMore()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index e66746da9d..6396431876 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -146,6 +146,7 @@ public class AuthConfig {
case HTTP_LDAP:
case LDAP:
case LDAP_BIND:
+ case CLIENT_SSL_CERT_LDAP:
// Its safe to assume yes for an HTTP authentication type, as the
// only way in is through some external system that the admin trusts
//
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 73320ade7a..927e88eeec 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
@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
+
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
@@ -31,6 +32,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class ConfigUtil {
/**
@@ -226,7 +229,7 @@ public class ConfigUtil {
/**
* Parse a numerical time unit, such as "1 minute", from a string.
*
- * @param s the string to parse.
+ * @param valueString 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
@@ -235,26 +238,16 @@ public class ConfigUtil {
* @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) {
- unitName = s.substring(sp + 1).trim();
- s = s.substring(0, sp);
- } else {
- final char last = s.charAt(s.length() - 1);
- if ('0' <= last && last <= '9') {
- unitName = "";
- } else {
- unitName = String.valueOf(last);
- s = s.substring(0, s.length() - 1).trim();
- }
- }
- if (s.length() == 0) {
+ public static long getTimeUnit(final String valueString, long defaultValue,
+ TimeUnit wantUnit) {
+ Matcher m = Pattern.compile("^([1-9][0-9]*)\\s*(.*)$").matcher(valueString);
+ if (!m.matches()) {
return defaultValue;
}
+ String digits = m.group(1);
+ String unitName = m.group(2).trim();
+
TimeUnit inputUnit;
int inputMul;
@@ -299,7 +292,7 @@ public class ConfigUtil {
}
try {
- return wantUnit.convert(Long.parseLong(s) * inputMul, inputUnit);
+ return wantUnit.convert(Long.parseLong(digits) * inputMul, inputUnit);
} catch (NumberFormatException nfe) {
throw notTimeUnit(valueString);
}
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 fa2aaad0a4..f77550e446 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
@@ -17,6 +17,7 @@ package com.google.gerrit.server.config;
import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AuthType;
@@ -37,6 +38,7 @@ 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.config.SitePaths;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.git.ChangeMergeQueue;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -46,6 +48,7 @@ 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.TransferConfig;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.mail.FromAddressGenerator;
@@ -53,25 +56,66 @@ import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
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.AccessControlModule;
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.project.RefControl;
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.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeConstants;
+
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
+import java.util.Properties;
import java.util.Set;
+
/** Starts global state with standard dependencies. */
public class GerritGlobalModule extends FactoryModule {
private final AuthType loginType;
+ public static class VelocityLifecycle implements LifecycleListener {
+ private final SitePaths site;
+
+ @Inject
+ VelocityLifecycle(final SitePaths site) {
+ this.site = site;
+ }
+
+ @Override
+ public void start() {
+ String rl = "resource.loader";
+ String pkg = "org.apache.velocity.runtime.resource.loader";
+ Properties p = new Properties();
+
+ p.setProperty(rl, "file, class");
+ p.setProperty("file." + rl + ".class", pkg + ".FileResourceLoader");
+ p.setProperty("file." + rl + ".path", site.mail_dir.getAbsolutePath());
+ p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
+ p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
+ "org.apache.velocity.runtime.log.SimpleLog4JLogSystem" );
+ p.setProperty("runtime.log.logsystem.log4j.category", "velocity");
+
+ try {
+ Velocity.init(p);
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ }
+ }
+
@Inject
GerritGlobalModule(final AuthConfig authConfig,
@GerritServerConfig final Config config) {
@@ -84,6 +128,7 @@ public class GerritGlobalModule extends FactoryModule {
case HTTP_LDAP:
case LDAP:
case LDAP_BIND:
+ case CLIENT_SSL_CERT_LDAP:
install(new LdapModule());
break;
@@ -94,10 +139,6 @@ public class GerritGlobalModule extends FactoryModule {
bind(Project.NameKey.class).annotatedWith(WildProjectName.class)
.toProvider(WildProjectNameProvider.class).in(SINGLETON);
- bind(new TypeLiteral<Set<AccountGroup.Id>>(){}).annotatedWith(ProjectCreatorGroups.class)
- .toProvider(ProjectCreatorGroupsProvider.class).in(SINGLETON);
- bind(new TypeLiteral<Set<AccountGroup.Id>>(){}).annotatedWith(ProjectOwnerGroups.class)
- .toProvider(ProjectOwnerGroupsProvider.class).in(SINGLETON);
bind(ApprovalTypes.class).toProvider(ApprovalTypesProvider.class).in(
SINGLETON);
bind(EmailExpander.class).toProvider(EmailExpanderProvider.class).in(
@@ -114,15 +155,18 @@ public class GerritGlobalModule extends FactoryModule {
install(GroupCacheImpl.module());
install(PatchListCacheImpl.module());
install(ProjectCacheImpl.module());
+ install(new AccessControlModule());
factory(AccountInfoCacheFactory.Factory.class);
factory(ProjectState.Factory.class);
+ factory(RefControl.Factory.class);
bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(WorkQueue.class);
bind(ToolsCatalog.class);
bind(EventFactory.class);
+ bind(TransferConfig.class);
bind(ReplicationQueue.class).to(PushReplication.class).in(SINGLETON);
factory(PushAllProjectsOp.Factory.class);
@@ -147,6 +191,7 @@ public class GerritGlobalModule extends FactoryModule {
listener().to(LocalDiskRepositoryManager.Lifecycle.class);
listener().to(CachePool.Lifecycle.class);
listener().to(WorkQueue.Lifecycle.class);
+ listener().to(VelocityLifecycle.class);
}
});
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
index 6592ac774d..92a2614702 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
@@ -20,7 +20,8 @@ import com.google.inject.ProvisionException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +41,7 @@ class GerritServerConfigProvider implements Provider<Config> {
@Override
public Config get() {
- FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config);
+ FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
if (!cfg.getFile().exists()) {
log.info("No " + site.gerrit_config.getAbsolutePath()
@@ -57,7 +58,7 @@ class GerritServerConfigProvider implements Provider<Config> {
}
if (site.secure_config.exists()) {
- cfg = new FileBasedConfig(cfg, site.secure_config);
+ cfg = new FileBasedConfig(cfg, site.secure_config, FS.DETECTED);
try {
cfg.load();
} catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroups.java
new file mode 100644
index 0000000000..35ea9e6c2f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroups.java
@@ -0,0 +1,37 @@
+// 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.config;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Used to populate the groups of users that are allowed to run
+ * receive-pack on the server.
+ *
+ * Gerrit.config example:
+ *
+ * <pre>
+ * [receive]
+ * allowGroup = RECEIVE_GROUP_ALLOWED
+ * </pre>
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface GitReceivePackGroups {
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
new file mode 100644
index 0000000000..9af6d629b6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -0,0 +1,42 @@
+// 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.config;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Collections;
+import java.util.HashSet;
+
+public class GitReceivePackGroupsProvider extends GroupSetProvider {
+ @Inject
+ public GitReceivePackGroupsProvider(@GerritServerConfig Config config,
+ AuthConfig authConfig, SchemaFactory<ReviewDb> db) {
+ super(config, db, "receive", null, "allowGroup");
+
+ // If no group was set, default to "registered users"
+ //
+ if (groupIds.isEmpty()) {
+ HashSet<AccountGroup.Id> all = new HashSet<AccountGroup.Id>();
+ all.addAll(authConfig.getRegisteredGroups());
+ all.removeAll(authConfig.getAnonymousGroups());
+ groupIds = Collections.unmodifiableSet(all);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroups.java
new file mode 100644
index 0000000000..fa8ccb7244
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroups.java
@@ -0,0 +1,37 @@
+// 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.config;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Used to populate the groups of users that are allowed to run
+ * upload-pack on the server.
+ *
+ * Gerrit.config example:
+ *
+ * <pre>
+ * [upload]
+ * allowGroup = UPLOAD_GROUP_ALLOWED
+ * </pre>
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface GitUploadPackGroups {
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
new file mode 100644
index 0000000000..bfb09a51bc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -0,0 +1,42 @@
+// 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.config;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Collections;
+import java.util.HashSet;
+
+public class GitUploadPackGroupsProvider extends GroupSetProvider {
+ @Inject
+ public GitUploadPackGroupsProvider(@GerritServerConfig Config config,
+ AuthConfig authConfig, SchemaFactory<ReviewDb> db) {
+ super(config, db, "upload", null, "allowGroup");
+
+ // If no group was set, default to "registered users" and "anonymous"
+ //
+ if (groupIds.isEmpty()) {
+ HashSet<AccountGroup.Id> all = new HashSet<AccountGroup.Id>();
+ all.addAll(authConfig.getRegisteredGroups());
+ all.addAll(authConfig.getAnonymousGroups());
+ groupIds = Collections.unmodifiableSet(all);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
new file mode 100644
index 0000000000..373fdb56e1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -0,0 +1,50 @@
+// 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.config;
+
+import static com.google.gerrit.server.config.ConfigUtil.groupsFor;
+import static java.util.Collections.unmodifiableSet;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+public abstract class GroupSetProvider implements
+ Provider<Set<AccountGroup.Id>> {
+ private static final Logger log =
+ LoggerFactory.getLogger(GroupSetProvider.class);
+
+ protected Set<AccountGroup.Id> groupIds;
+
+ @Inject
+ protected GroupSetProvider(@GerritServerConfig Config config,
+ SchemaFactory<ReviewDb> db, String section, String subsection, String name) {
+ String[] groupNames = config.getStringList(section, subsection, name);
+ groupIds = unmodifiableSet(groupsFor(db, groupNames, log));
+ }
+
+ @Override
+ public Set<AccountGroup.Id> get() {
+ return groupIds;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java
index d2c50056a5..26c76c5878 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java
@@ -20,6 +20,8 @@ import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.ReloadSubmitQueueOp;
import com.google.inject.Inject;
+import org.eclipse.jgit.lib.Config;
+
import java.util.concurrent.TimeUnit;
/** Configuration for a master node in a cluster of servers. */
@@ -32,17 +34,24 @@ public class MasterNodeStartup extends LifecycleModule {
static class OnStart implements LifecycleListener {
private final PushAllProjectsOp.Factory pushAll;
private final ReloadSubmitQueueOp.Factory submit;
+ private final boolean replicateOnStartup;
@Inject
OnStart(final PushAllProjectsOp.Factory pushAll,
- final ReloadSubmitQueueOp.Factory submit) {
+ final ReloadSubmitQueueOp.Factory submit,
+ final @GerritServerConfig Config cfg) {
this.pushAll = pushAll;
this.submit = submit;
+
+ replicateOnStartup = cfg.getBoolean("gerrit", "replicateOnStartup", true);
}
@Override
public void start() {
- pushAll.create(null).start(30, TimeUnit.SECONDS);
+ if (replicateOnStartup) {
+ pushAll.create(null).start(30, TimeUnit.SECONDS);
+ }
+
submit.create().start(15, TimeUnit.SECONDS);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java
index 616e691149..381c914238 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java
@@ -14,19 +14,14 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.util.Collections;
-import java.util.Set;
/**
* Provider of the group(s) which are allowed to create new projects. Currently
@@ -39,27 +34,14 @@ import java.util.Set;
* createGroup = Administrators
* </pre>
*/
-public class ProjectCreatorGroupsProvider implements
- Provider<Set<AccountGroup.Id>> {
- private static final Logger log =
- LoggerFactory.getLogger(ProjectCreatorGroupsProvider.class);
-
- private final Set<AccountGroup.Id> groupIds;
-
+public class ProjectCreatorGroupsProvider extends GroupSetProvider {
@Inject
- ProjectCreatorGroupsProvider(@GerritServerConfig final Config config,
- SchemaFactory<ReviewDb> db, final SystemConfig systemConfig) {
- String[] names = config.getStringList("repository", "*", "createGroup");
- Set<AccountGroup.Id> createGroups = ConfigUtil.groupsFor(db, names, log);
+ public ProjectCreatorGroupsProvider(@GerritServerConfig final Config config,
+ final SystemConfig systemConfig, final SchemaFactory<ReviewDb> db) {
+ super(config, db, "repository", "*", "createGroup");
- if (createGroups.isEmpty()) {
+ if (groupIds.isEmpty()) {
groupIds = Collections.singleton(systemConfig.adminGroupId);
- } else {
- groupIds = createGroups;
}
}
-
- public Set<AccountGroup.Id> get() {
- return groupIds;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
index 30234c0bfb..c457d734a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -18,11 +18,8 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.util.Set;
@@ -37,28 +34,15 @@ import java.util.Set;
* ownerGroup = Administrators
* </pre>
*/
-public class ProjectOwnerGroupsProvider implements
- Provider<Set<AccountGroup.Id>> {
- private static final Logger log =
- LoggerFactory.getLogger(ProjectOwnerGroupsProvider.class);
-
- private final Set<AccountGroup.Id> groupIds;
-
+public class ProjectOwnerGroupsProvider extends GroupSetProvider {
@Inject
- ProjectOwnerGroupsProvider(@GerritServerConfig final Config config,
- SchemaFactory<ReviewDb> db,
- @ProjectCreatorGroups Set<AccountGroup.Id> creatorGroups) {
- String[] names = config.getStringList("repository", "*", "ownerGroup");
- Set<AccountGroup.Id> ownerGroups = ConfigUtil.groupsFor(db, names, log);
+ public ProjectOwnerGroupsProvider(
+ @ProjectCreatorGroups final Set<AccountGroup.Id> creatorGroups,
+ @GerritServerConfig final Config config, final SchemaFactory<ReviewDb> db) {
+ super(config, db, "repository", "*", "ownerGroup");
- if (ownerGroups.isEmpty()) {
+ if (groupIds.isEmpty()) {
groupIds = creatorGroups;
- } else {
- groupIds = ownerGroups;
}
}
-
- public Set<AccountGroup.Id> get() {
- return groupIds;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index 1faa672a95..c3a5fb7abb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -28,6 +28,7 @@ public final class SitePaths {
public final File etc_dir;
public final File lib_dir;
public final File logs_dir;
+ public final File mail_dir;
public final File hooks_dir;
public final File static_dir;
@@ -61,6 +62,7 @@ public final class SitePaths {
etc_dir = new File(site_path, "etc");
lib_dir = new File(site_path, "lib");
logs_dir = new File(site_path, "logs");
+ mail_dir = new File(etc_dir, "mail");
hooks_dir = new File(site_path, "hooks");
static_dir = new File(site_path, "static");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 3ad397e0b0..573e6bb250 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -17,6 +17,7 @@ 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.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
@@ -28,8 +29,11 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.internal.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Map;
@Singleton
public class EventFactory {
@@ -67,6 +71,23 @@ public class EventFactory {
}
/**
+ * Create a RefUpdateAttribute for the given old ObjectId, new ObjectId, and
+ * branch that is suitable for serialization to JSON.
+ *
+ * @param refUpdate
+ * @param refName
+ * @return object suitable for serialization to JSON
+ */
+ public RefUpdateAttribute asRefUpdateAttribute(final ObjectId oldId, final ObjectId newId, final Branch.NameKey refName) {
+ RefUpdateAttribute ru = new RefUpdateAttribute();
+ ru.newRev = newId != null ? newId.getName() : ObjectId.zeroId().getName();
+ ru.oldRev = oldId != null ? oldId.getName() : ObjectId.zeroId().getName();
+ ru.project = refName.getParentKey().get();
+ ru.refName = refName.getShortName();
+ return ru;
+ }
+
+ /**
* Extend the existing ChangeAttribute with additional fields.
*
* @param a
@@ -89,10 +110,19 @@ public class EventFactory {
}
public void addPatchSets(ChangeAttribute a, Collection<PatchSet> ps) {
+ addPatchSets(a, ps, null);
+ }
+
+ public void addPatchSets(ChangeAttribute ca, Collection<PatchSet> ps,
+ Map<PatchSet.Id,Collection<PatchSetApproval>> approvals) {
if (!ps.isEmpty()) {
- a.patchSets = new ArrayList<PatchSetAttribute>(ps.size());
+ ca.patchSets = new ArrayList<PatchSetAttribute>(ps.size());
for (PatchSet p : ps) {
- a.patchSets.add(asPatchSetAttribute(p));
+ PatchSetAttribute psa = asPatchSetAttribute(p);
+ if (approvals != null) {
+ addApprovals(psa, p.getId(), approvals);
+ }
+ ca.patchSets.add(psa);
}
}
}
@@ -120,6 +150,14 @@ public class EventFactory {
return p;
}
+ public void addApprovals(PatchSetAttribute p, PatchSet.Id id,
+ Map<PatchSet.Id,Collection<PatchSetApproval>> all) {
+ Collection<PatchSetApproval> list = all.get(id);
+ if (list != null) {
+ addApprovals(p, list);
+ }
+ }
+
public void addApprovals(PatchSetAttribute p,
Collection<PatchSetApproval> list) {
if (!list.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java
new file mode 100644
index 0000000000..e4d715a3f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.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 RefUpdateAttribute {
+ public String oldRev;
+ public String newRev;
+ public String refName;
+ public String project;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java
new file mode 100644
index 0000000000..f90bc81018
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.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 RefUpdatedEvent extends ChangeEvent {
+ public final String type = "ref-updated";
+ public AccountAttribute submitter;
+ public RefUpdateAttribute refUpdate;
+}
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 676e4b68e6..33661ab46b 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
@@ -102,6 +102,8 @@ public class GitProjectImporter {
p.setSubmitType(SubmitType.MERGE_IF_NECESSARY);
p.setUseContributorAgreements(false);
p.setUseSignedOffBy(false);
+ p.setUseContentMerge(false);
+ p.setRequireChangeID(false);
db.projects().insert(Collections.singleton(p));
} else if (f.isDirectory()) {
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 e8df230e00..c644ca8f45 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
@@ -23,12 +23,12 @@ import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.LockFile;
import org.eclipse.jgit.lib.Repository;
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.storage.file.LockFile;
+import org.eclipse.jgit.storage.file.WindowCache;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
@@ -135,25 +135,29 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
public String getProjectDescription(final String name)
throws RepositoryNotFoundException, IOException {
final Repository e = openRepository(name);
- final File d = new File(e.getDirectory(), "description");
-
- String description;
try {
- description = RawParseUtils.decode(IO.readFully(d));
- } catch (FileNotFoundException err) {
- return null;
- }
+ final File d = new File(e.getDirectory(), "description");
- if (description != null) {
- description = description.trim();
- if (description.isEmpty()) {
- description = null;
+ String description;
+ try {
+ description = RawParseUtils.decode(IO.readFully(d));
+ } catch (FileNotFoundException err) {
+ return null;
}
- if (UNNAMED.equals(description)) {
- description = null;
+
+ if (description != null) {
+ description = description.trim();
+ if (description.isEmpty()) {
+ description = null;
+ }
+ if (UNNAMED.equals(description)) {
+ description = null;
+ }
}
+ return description;
+ } finally {
+ e.close();
}
- return description;
}
public void setProjectDescription(final String name, final String description) {
@@ -164,21 +168,24 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
final LockFile f;
e = openRepository(name);
- f = new LockFile(new File(e.getDirectory(), "description"));
- if (f.lock()) {
- String d = description;
- if (d != null) {
- d = d.trim();
- if (d.length() > 0) {
- d += "\n";
+ try {
+ f = new LockFile(new File(e.getDirectory(), "description"), FS.DETECTED);
+ if (f.lock()) {
+ String d = description;
+ if (d != null) {
+ d = d.trim();
+ if (d.length() > 0) {
+ d += "\n";
+ }
+ } else {
+ d = "";
}
- } else {
- d = "";
+ f.write(Constants.encode(d));
+ f.commit();
}
- f.write(Constants.encode(d));
- f.commit();
+ } finally {
+ e.close();
}
- e.close();
} catch (RepositoryNotFoundException e) {
log.error("Cannot update description for " + name, e);
} catch (IOException e) {
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 a5b739e166..2efb1f4db1 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
@@ -56,9 +56,10 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Commit;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
@@ -76,6 +77,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
@@ -202,6 +204,9 @@ public class MergeOp {
try {
mergeImpl();
} finally {
+ if (rw != null) {
+ rw.release();
+ }
if (db != null) {
db.close();
}
@@ -267,7 +272,7 @@ public class MergeOp {
branchTip = null;
}
- for (final Ref r : rw.getRepository().getAllRefs().values()) {
+ for (final Ref r : db.getAllRefs().values()) {
if (r.getName().startsWith(Constants.R_HEADS)
|| r.getName().startsWith(Constants.R_TAGS)) {
try {
@@ -431,7 +436,18 @@ public class MergeOp {
}
private void mergeOneCommit(final CodeReviewCommit n) throws MergeException {
- final Merger m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
+ final ThreeWayMerger m;
+ if (destProject.isUseContentMerge()) {
+ // Settings for this project allow us to try and
+ // automatically resolve conflicts within files if needed.
+ // Use ResolveMerge and instruct to operate in core.
+ m = MergeStrategy.RESOLVE.newMerger(db, true);
+ } else {
+ // No auto conflict resolving allowed. If any of the
+ // affected files was modified, merge will fail.
+ m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
+ }
+
try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
writeMergeCommit(m, n);
@@ -547,15 +563,14 @@ public class MergeOp {
authorIdent = myIdent;
}
- final Commit mergeCommit = new Commit(db);
+ final CommitBuilder mergeCommit = new CommitBuilder();
mergeCommit.setTreeId(m.getResultTreeId());
- mergeCommit.setParentIds(new ObjectId[] {mergeTip, n});
+ mergeCommit.setParentIds(mergeTip, n);
mergeCommit.setAuthor(authorIdent);
mergeCommit.setCommitter(myIdent);
mergeCommit.setMessage(msgbuf.toString());
- final ObjectId id = m.getObjectWriter().writeCommit(mergeCommit);
- mergeTip = (CodeReviewCommit) rw.parseCommit(id);
+ mergeTip = (CodeReviewCommit) rw.parseCommit(commit(m, mergeCommit));
}
private void markCleanMerges() throws MergeException {
@@ -602,7 +617,17 @@ public class MergeOp {
final CodeReviewCommit n = toMerge.remove(0);
final ThreeWayMerger m;
- m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
+ if (destProject.isUseContentMerge()) {
+ // Settings for this project allow us to try and
+ // automatically resolve conflicts within files if needed.
+ // Use ResolveMerge and instruct to operate in core.
+ m = MergeStrategy.RESOLVE.newMerger(db, true);
+ } else {
+ // No auto conflict resolving allowed. If any of the
+ // affected files was modified, merge will fail.
+ m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
+ }
+
try {
if (mergeTip == null) {
// The branch is unborn. Take a fast-forward resolution to
@@ -789,14 +814,14 @@ public class MergeOp {
log.error("Can't read approval records for " + n.patchsetId, e);
}
- final Commit mergeCommit = new Commit(db);
+ final CommitBuilder mergeCommit = new CommitBuilder();
mergeCommit.setTreeId(m.getResultTreeId());
- mergeCommit.setParentIds(new ObjectId[] {mergeTip});
+ mergeCommit.setParentId(mergeTip);
mergeCommit.setAuthor(n.getAuthorIdent());
mergeCommit.setCommitter(toCommitterIdent(submitAudit));
mergeCommit.setMessage(msgbuf.toString());
- final ObjectId id = m.getObjectWriter().writeCommit(mergeCommit);
+ final ObjectId id = commit(m, mergeCommit);
final CodeReviewCommit newCommit = (CodeReviewCommit) rw.parseCommit(id);
newCommit.copyFrom(n);
newCommit.statusCode = CommitMergeStatus.CLEAN_PICK;
@@ -806,6 +831,18 @@ public class MergeOp {
setRefLogIdent(submitAudit);
}
+ private ObjectId commit(final Merger m, final CommitBuilder mergeCommit)
+ throws IOException, UnsupportedEncodingException {
+ ObjectInserter oi = m.getObjectInserter();
+ try {
+ ObjectId id = oi.insert(mergeCommit);
+ oi.flush();
+ return id;
+ } finally {
+ oi.release();
+ }
+ }
+
private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
for (final FooterLine line : footers) {
if (line.matches(key) && val.equals(line.getValue())) {
@@ -844,6 +881,13 @@ public class MergeOp {
case FAST_FORWARD:
replication.scheduleUpdate(destBranch.getParentKey(), branchUpdate
.getName());
+
+ Account account = null;
+ final PatchSetApproval submitter = getSubmitter(mergeTip.patchsetId);
+ if (submitter != null) {
+ account = accountCache.get(submitter.getAccountId()).getAccount();
+ }
+ hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
break;
default:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
index ac53830097..1c100dfc9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
@@ -78,6 +78,13 @@ class PushOp implements ProjectRunnable {
private Repository db;
+ /**
+ * It indicates if the current instance is in fact retrying to push.
+ */
+ private boolean retrying;
+
+ private boolean canceled;
+
@Inject
PushOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> s,
final PushReplication.ReplicationConfig p, final RemoteConfig c,
@@ -90,6 +97,22 @@ class PushOp implements ProjectRunnable {
uri = u;
}
+ public boolean isRetrying() {
+ return retrying;
+ }
+
+ public void setToRetry() {
+ retrying = true;
+ }
+
+ public void cancel() {
+ canceled = true;
+ }
+
+ public boolean wasCanceled() {
+ return canceled;
+ }
+
URIish getURI() {
return uri;
}
@@ -103,45 +126,73 @@ class PushOp implements ProjectRunnable {
}
}
+ public Set<String> getRefs() {
+ final Set<String> refs;
+
+ if (mirror) {
+ refs = new HashSet<String>(1);
+ refs.add(MIRROR_ALL);
+ } else {
+ refs = delta;
+ }
+
+ return refs;
+ }
+
+ public void addRefs(Set<String> refs) {
+ if (!mirror) {
+ for (String ref : refs) {
+ addRef(ref);
+ }
+ }
+ }
+
public void run() {
- try {
- // Lock the queue, and remove ourselves, so we can't be modified once
- // we start replication (instead a new instance, with the same URI, is
- // created and scheduled for a future point in time.)
- //
- pool.notifyStarting(this);
- db = repoManager.openRepository(projectName.get());
- runImpl();
- } catch (RepositoryNotFoundException e) {
- log.error("Cannot replicate " + projectName + "; " + e.getMessage());
-
- } catch (NoRemoteRepositoryException e) {
- log.error("Cannot replicate to " + uri + "; repository not found");
-
- } catch (NotSupportedException e) {
- log.error("Cannot replicate to " + uri, e);
-
- } catch (TransportException e) {
- final Throwable cause = e.getCause();
- if (cause instanceof JSchException
- && cause.getMessage().startsWith("UnknownHostKey:")) {
- log.error("Cannot replicate to " + uri + ": " + cause.getMessage());
- } else {
+ // Lock the queue, and remove ourselves, so we can't be modified once
+ // we start replication (instead a new instance, with the same URI, is
+ // created and scheduled for a future point in time.)
+ //
+ pool.notifyStarting(this);
+
+ // It should only verify if it was canceled after calling notifyStarting,
+ // since the canceled flag would be set locking the queue.
+ if (!canceled) {
+ try {
+ db = repoManager.openRepository(projectName.get());
+ runImpl();
+ } catch (RepositoryNotFoundException e) {
+ log.error("Cannot replicate " + projectName + "; " + e.getMessage());
+
+ } catch (NoRemoteRepositoryException e) {
+ log.error("Cannot replicate to " + uri + "; repository not found");
+
+ } catch (NotSupportedException e) {
log.error("Cannot replicate to " + uri, e);
- }
- } catch (IOException e) {
- log.error("Cannot replicate to " + uri, e);
+ } catch (TransportException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof JSchException
+ && cause.getMessage().startsWith("UnknownHostKey:")) {
+ log.error("Cannot replicate to " + uri + ": " + cause.getMessage());
+ } else {
+ log.error("Cannot replicate to " + uri, e);
+ }
+
+ // The remote push operation should be retried.
+ pool.reschedule(this);
+ } catch (IOException e) {
+ log.error("Cannot replicate to " + uri, e);
- } catch (RuntimeException e) {
- log.error("Unexpected error during replication to " + uri, e);
+ } catch (RuntimeException e) {
+ log.error("Unexpected error during replication to " + uri, e);
- } catch (Error e) {
- log.error("Unexpected error during replication to " + uri, e);
+ } catch (Error e) {
+ log.error("Unexpected error during replication to " + uri, e);
- } finally {
- if (db != null) {
- db.close();
+ } finally {
+ if (db != null) {
+ db.close();
+ }
}
}
}
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 f26bf65301..382a7390d1 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
@@ -37,7 +37,7 @@ import com.jcraft.jsch.Session;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
@@ -55,7 +55,6 @@ import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -129,7 +128,8 @@ public class PushReplication implements ReplicationQueue {
private List<ReplicationConfig> allConfigs(final SitePaths site)
throws ConfigInvalidException, IOException {
- final FileBasedConfig cfg = new FileBasedConfig(site.replication_config);
+ final FileBasedConfig cfg =
+ new FileBasedConfig(site.replication_config, FS.DETECTED);
if (!cfg.getFile().exists()) {
log.warn("No " + cfg.getFile() + "; not replicating");
@@ -306,6 +306,7 @@ public class PushReplication implements ReplicationQueue {
static class ReplicationConfig {
private final RemoteConfig remote;
private final int delay;
+ private final int retryDelay;
private final WorkQueue.Executor pool;
private final Map<URIish, PushOp> pending = new HashMap<URIish, PushOp>();
private final PushOp.Factory opFactory;
@@ -317,6 +318,7 @@ public class PushReplication implements ReplicationQueue {
remote = rc;
delay = Math.max(0, getInt(rc, cfg, "replicationdelay", 15));
+ retryDelay = Math.max(0, getInt(rc, cfg, "replicationretry", 1));
final int poolSize = Math.max(0, getInt(rc, cfg, "threads", 1));
final String poolName = "ReplicateTo-" + rc.getName();
@@ -382,6 +384,96 @@ public class PushReplication implements ReplicationQueue {
}
}
+ /**
+ * It schedules again a PushOp instance.
+ * <p>
+ * It is assumed to be previously scheduled and found a
+ * transport exception. It will schedule it as a push
+ * operation to be retried after the minutes count
+ * determined by class attribute retryDelay.
+ * <p>
+ * In case the PushOp instance to be scheduled has same
+ * URI than one also pending for retry, it adds to the one
+ * pending the refs list of the parameter instance.
+ * <p>
+ * In case the PushOp instance to be scheduled has same
+ * URI than one pending, but not pending for retry, it
+ * indicates the one pending should be canceled when it
+ * starts executing, removes it from pending list, and
+ * adds its refs to the parameter instance. The parameter
+ * instance is scheduled for retry.
+ * <p>
+ * Notice all operations to indicate a PushOp should be
+ * canceled, or it is retrying, or remove/add it from/to
+ * pending Map should be protected by the lock on pending
+ * Map class instance attribute.
+ *
+ * @param pushOp The PushOp instance to be scheduled.
+ */
+ void reschedule(final PushOp pushOp) {
+ try {
+ if (!controlFor(pushOp.getProjectNameKey()).isVisible()) {
+ return;
+ }
+ } catch (NoSuchProjectException e1) {
+ log.error("Internal error: project " + pushOp.getProjectNameKey()
+ + " not found during replication");
+ return;
+ }
+
+ // It locks access to pending variable.
+ synchronized (pending) {
+ PushOp pendingPushOp = pending.get(pushOp.getURI());
+
+ if (pendingPushOp != null) {
+ // There is one PushOp instance already pending to same URI.
+
+ if (pendingPushOp.isRetrying()) {
+ // The one pending is one already retrying, so it should
+ // maintain it and add to it the refs of the one passed
+ // as parameter to the method.
+
+ // This scenario would happen if a PushOp has started running
+ // and then before it failed due transport exception, another
+ // one to same URI started. The first one would fail and would
+ // be rescheduled, being present in pending list. When the
+ // second one fails, it will also be rescheduled and then,
+ // here, find out replication to its URI is already pending
+ // for retry (blocking).
+ pendingPushOp.addRefs(pushOp.getRefs());
+
+ } else {
+ // The one pending is one that is NOT retrying, it was just
+ // scheduled believing no problem would happen. The one pending
+ // should be canceled, and this is done by setting its canceled
+ // flag, removing it from pending list, and adding its refs to
+ // the pushOp instance that should then, later, in this method,
+ // be scheduled for retry.
+
+ // Notice that the PushOp found pending will start running and,
+ // when notifying it is starting (with pending lock protection),
+ // it will see it was canceled and then it will do nothing with
+ // pending list and it will not execute its run implementation.
+
+ pendingPushOp.cancel();
+ pending.remove(pendingPushOp);
+
+ pushOp.addRefs(pendingPushOp.getRefs());
+ }
+ }
+
+ if (pendingPushOp == null || !pendingPushOp.isRetrying()) {
+ // The PushOp method param instance should be scheduled for retry.
+ // Remember when retrying it should be used different delay.
+
+ pushOp.setToRetry();
+
+ pending.put(pushOp.getURI(), pushOp);
+ pool.schedule(pushOp, retryDelay, TimeUnit.MINUTES);
+ }
+ }
+ }
+
ProjectControl controlFor(final Project.NameKey project)
throws NoSuchProjectException {
return projectControlFactory.controlFor(project);
@@ -389,7 +481,9 @@ public class PushReplication implements ReplicationQueue {
void notifyStarting(final PushOp op) {
synchronized (pending) {
- pending.remove(op.getURI());
+ if (!op.wasCanceled()) {
+ pending.remove(op.getURI());
+ }
}
}
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 a3140ec8a6..8b6f39782c 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,9 +55,12 @@ 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.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
@@ -225,11 +228,33 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
/** Determine if the user can upload commits. */
public Capable canUpload() {
- if (!projectControl.canUploadToAtLeastOneRef()) {
+ if (!projectControl.canPushToAtLeastOneRef()) {
String reqName = project.getName();
return new Capable("Upload denied for project '" + reqName + "'");
}
+ // Don't permit receive-pack to be executed if a refs/for/branch_name
+ // reference exists in the destination repository. These block the
+ // client from being able to even send us a pack file, as it is very
+ // unlikely the user passed the --force flag and the new commit is
+ // probably not going to fast-forward the branch.
+ //
+ Map<String, Ref> blockingFors;
+ try {
+ blockingFors = repo.getRefDatabase().getRefs("refs/for/");
+ } catch (IOException err) {
+ String projName = project.getName();
+ log.warn("Cannot scan refs in '" + projName + "'", err);
+ return new Capable("Server process cannot read '" + projName + "'");
+ }
+ if (!blockingFors.isEmpty()) {
+ String projName = project.getName();
+ log.error("Repository '" + projName
+ + "' needs the following refs removed to receive changes: "
+ + blockingFors.keySet());
+ return new Capable("One or more refs/for/ names blocks change upload");
+ }
+
if (project.isUseContributorAgreements()) {
try {
return verifyActiveContributorAgreement();
@@ -277,6 +302,8 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
// Change refs are scheduled when they are created.
//
replication.scheduleUpdate(project.getNameKey(), c.getRefName());
+ Branch.NameKey destBranch = new Branch.NameKey(project.getNameKey(), c.getRefName());
+ hooks.doRefUpdatedHook(destBranch, c.getOldId(), c.getNewId(), currentUser.getAccount());
}
}
}
@@ -537,18 +564,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
}
private void parseRewind(final ReceiveCommand cmd) {
- final RevObject oldObject, newObject;
- try {
- oldObject = rp.getRevWalk().parseAny(cmd.getOldId());
- } catch (IOException err) {
- log.error("Invalid object " + cmd.getOldId().name() + " for "
- + cmd.getRefName() + " forced update", err);
- reject(cmd, "invalid object");
- return;
- }
-
+ RevCommit newObject;
try {
- newObject = rp.getRevWalk().parseAny(cmd.getNewId());
+ newObject = rp.getRevWalk().parseCommit(cmd.getNewId());
+ } catch (IncorrectObjectTypeException notCommit) {
+ newObject = null;
} catch (IOException err) {
log.error("Invalid object " + cmd.getNewId().name() + " for "
+ cmd.getRefName() + " forced update", err);
@@ -557,9 +577,14 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
}
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
- if (oldObject instanceof RevCommit && newObject instanceof RevCommit
- && ctl.canForceUpdate()) {
+ if (newObject != null) {
validateNewCommits(ctl, cmd);
+ if (cmd.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+ return;
+ }
+ }
+
+ if (ctl.canForceUpdate()) {
// Let the core receive process handle it
} else {
cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD);
@@ -1061,19 +1086,30 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
if (priorPatchSet.equals(ps.getId()) && c.getTree() == prior.getTree()) {
rp.getRevWalk().parseBody(prior);
final boolean messageEq =
- c.getFullMessage().equals(prior.getFullMessage());
+ eq(c.getFullMessage(), prior.getFullMessage());
final boolean parentsEq = parentsEqual(c, prior);
+ final boolean authorEq = authorEqual(c, prior);
- if (messageEq && parentsEq) {
+ if (messageEq && parentsEq && authorEq) {
reject(request.cmd, "no changes made");
return null;
} else {
- rp.sendMessage("(W) " + c.abbreviate(repo, 6).name() + ":" //
- + " no files changed, but" //
- + (!messageEq ? " message updated" : "") //
- + (!messageEq && !parentsEq ? " and" : "") //
- + (!parentsEq ? " was rebased" : "") //
- );
+ ObjectReader reader = rp.getRevWalk().getObjectReader();
+ StringBuilder msg = new StringBuilder();
+ msg.append("(W) ");
+ msg.append(reader.abbreviate(c).name());
+ msg.append(":");
+ msg.append(" no files changed");
+ if (!authorEq) {
+ msg.append(", author changed");
+ }
+ if (!messageEq) {
+ msg.append(", message updated");
+ }
+ if (!parentsEq) {
+ msg.append(", was rebased");
+ }
+ rp.sendMessage(msg.toString());
}
}
} catch (IOException e) {
@@ -1256,6 +1292,30 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
return true;
}
+ static boolean authorEqual(RevCommit a, RevCommit b) {
+ PersonIdent aAuthor = a.getAuthorIdent();
+ PersonIdent bAuthor = b.getAuthorIdent();
+
+ if (aAuthor == null && bAuthor == null) {
+ return true;
+ } else if (aAuthor == null || bAuthor == null) {
+ return false;
+ }
+
+ return eq(aAuthor.getName(), bAuthor.getName())
+ && eq(aAuthor.getEmailAddress(), bAuthor.getEmailAddress());
+ }
+
+ static boolean eq(String a, String b) {
+ if (a == null && b == null) {
+ return true;
+ } else if (a == null || b == null) {
+ return false;
+ } else {
+ return a.equals(b);
+ }
+ }
+
private void insertDummyApproval(final ReplaceResult result,
final Account.Id forAccount, final ApprovalCategory.Id catId,
final ReviewDb db) throws OrmException {
@@ -1402,12 +1462,38 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
}
}
+ if (project.isRequireChangeID()) {
+ final List<String> idList = c.getFooterLines(CHANGE_ID);
+ if (idList.isEmpty()) {
+ reject(cmd, "missing Change-Id in commit message ");
+ return false;
+ }
+
+ if (idList.size() > 1) {
+ reject(cmd, "multiple Change-Id lines in commit message ");
+ return false;
+ }
+
+ final String v = idList.get(idList.size() - 1).trim();
+ if (!v.matches("^I[0-9a-f]{8,}.*$")) {
+ reject(cmd, "invalid Change-Id line format in commit message ");
+ return false;
+ }
+ }
+
return true;
}
private void warnMalformedMessage(RevCommit c) {
+ ObjectReader reader = rp.getRevWalk().getObjectReader();
if (65 < c.getShortMessage().length()) {
- rp.sendMessage("(W) " + c.abbreviate(repo, 6).name()
+ AbbreviatedObjectId id;
+ try {
+ id = reader.abbreviate(c);
+ } catch (IOException err) {
+ id = c.abbreviate(6);
+ }
+ rp.sendMessage("(W) " + id.name() //
+ ": commit subject >65 characters; use shorter first paragraph");
}
@@ -1422,7 +1508,13 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
}
if (0 < longLineCnt && 33 < longLineCnt * 100 / nonEmptyCnt) {
- rp.sendMessage("(W) " + c.abbreviate(repo, 6).name()
+ AbbreviatedObjectId id;
+ try {
+ id = reader.abbreviate(c);
+ } catch (IOException err) {
+ id = c.abbreviate(6);
+ }
+ rp.sendMessage("(W) " + id.name() //
+ ": commit message lines >70 characters; manually wrap lines");
}
}
@@ -1462,10 +1554,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
final PatchSet.Id psi = doReplace(req);
if (psi != null) {
closeChange(req.cmd, psi, req.newCommit);
- } else {
- log.warn("Replacement of Change-Id " + req.ontoChange
- + " with commit " + req.newCommit.name()
- + " did not import the new patch set.");
}
}
} catch (IOException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/TransferConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java
index c976e7fcf3..de4130dca5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/TransferConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd;
+package com.google.gerrit.server.git;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -20,21 +20,32 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.pack.PackConfig;
import java.util.concurrent.TimeUnit;
@Singleton
public class TransferConfig {
private final int timeout;
+ private final PackConfig packConfig;
@Inject
TransferConfig(@GerritServerConfig final Config cfg) {
timeout = (int) ConfigUtil.getTimeUnit(cfg, "transfer", null, "timeout", //
0, TimeUnit.SECONDS);
+
+ packConfig = new PackConfig();
+ packConfig.setDeltaCompress(false);
+ packConfig.setThreads(1);
+ packConfig.fromConfig(cfg);
}
/** @return configured timeout, in seconds. 0 if the timeout is infinite. */
public int getTimeout() {
return timeout;
}
+
+ public PackConfig getPackConfig() {
+ return packConfig;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 05da2bede0..ffc3fd8e19 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -118,16 +118,20 @@ public class VisibleRefFilter implements RefFilter {
private void addVisibleTags(final Map<String, Ref> result,
final List<Ref> tags) {
final RevWalk rw = new RevWalk(db);
- final RevFlag VISIBLE = rw.newFlag("VISIBLE");
- final List<RevCommit> starts;
+ try {
+ final RevFlag VISIBLE = rw.newFlag("VISIBLE");
+ final List<RevCommit> starts;
- rw.carry(VISIBLE);
- starts = lookupVisibleCommits(result, rw, VISIBLE);
+ rw.carry(VISIBLE);
+ starts = lookupVisibleCommits(result, rw, VISIBLE);
- for (Ref tag : tags) {
- if (isTagVisible(rw, VISIBLE, starts, tag)) {
- result.put(tag.getName(), tag);
+ for (Ref tag : tags) {
+ if (isTagVisible(rw, VISIBLE, starts, tag)) {
+ result.put(tag.getName(), tag);
+ }
}
+ } finally {
+ rw.release();
}
}
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 d2b5c29c3c..e67984978a 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
@@ -30,7 +30,7 @@ public class AbandonedSender extends ReplyToChangeSender {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
ccAllApprovals();
@@ -39,10 +39,7 @@ public class AbandonedSender extends ReplyToChangeSender {
}
@Override
- protected void formatChange() {
- appendText(getNameFor(fromId));
- appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n");
- appendText("\n");
- formatCoverLetter();
+ protected void formatChange() throws EmailException {
+ appendText(velocifyFile("Abandoned.vm"));
}
}
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 be62ba070a..e5437cfac0 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
@@ -32,7 +32,7 @@ public class AddReviewerSender extends NewChangeSender {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
ccExistingReviewers();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index b3ddaeb92b..71712af953 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -54,7 +54,6 @@ public abstract class ChangeEmail extends OutgoingEmail {
private ProjectState projectState;
protected ChangeData changeData;
- private boolean inFooter;
protected ChangeEmail(EmailArguments ea, final Change c, final String mc) {
super(ea, mc);
@@ -76,31 +75,9 @@ public abstract class ChangeEmail extends OutgoingEmail {
}
/** Format the message body by calling {@link #appendText(String)}. */
- protected void format() {
+ protected void format() throws EmailException {
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");
-
+ appendText(velocifyFile("ChangeFooter.vm"));
try {
HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
@@ -121,11 +98,10 @@ public abstract class ChangeEmail extends OutgoingEmail {
}
/** Format the message body by calling {@link #appendText(String)}. */
- protected abstract void formatChange();
+ protected abstract void formatChange() throws EmailException;
/** Setup the message headers and envelope (TO, CC, BCC). */
- protected void init() {
- super.init();
+ protected void init() throws EmailException {
if (args.projectCache != null) {
projectState = args.projectCache.get(change.getProject());
projectName =
@@ -151,6 +127,8 @@ public abstract class ChangeEmail extends OutgoingEmail {
}
}
+ super.init();
+
if (changeMessage != null && changeMessage.getWrittenOn() != null) {
setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
}
@@ -159,27 +137,21 @@ public abstract class ChangeEmail extends OutgoingEmail {
setListIdHeader();
setChangeUrlHeader();
setCommitIdHeader();
-
- inFooter = false;
}
- private void setListIdHeader() {
+ private void setListIdHeader() throws EmailException {
// 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('@', '.') + ">");
+ setVHeader("Mailing-List", "list $email.listId");
+ setVHeader("List-Id", "<$email.listId.replace('@', '.')>");
if (getSettingsUrl() != null) {
- setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
+ setVHeader("List-Unsubscribe", "<$email.settingsUrl>");
}
}
+ public String getListId() throws EmailException {
+ return velocify("gerrit-$projectName.replace('/', '-')@$email.gerritHost");
+ }
+
private void setChangeUrlHeader() {
final String u = getChangeUrl();
if (u != null) {
@@ -195,27 +167,12 @@ public abstract class ChangeEmail extends OutgoingEmail {
}
}
- 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());
+ private void setChangeSubjectHeader() throws EmailException {
+ setHeader("Subject", velocifyFile("ChangeSubject.vm"));
}
/** Get a link to the change; null if the server doesn't know its own address. */
- protected String getChangeUrl() {
+ public String getChangeUrl() {
if (change != null && getGerritUrl() != null) {
final StringBuilder r = new StringBuilder();
r.append(getGerritUrl());
@@ -225,25 +182,9 @@ public abstract class ChangeEmail extends OutgoingEmail {
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");
- }
+ public String getChangeMessageThreadId() throws EmailException {
+ return velocify("<gerrit.${change.createdOn.time}.$change.key.get()" +
+ "@$email.gerritHost>");
}
/** Format the sender's "cover letter", {@link #getCoverLetter()}. */
@@ -256,7 +197,7 @@ public abstract class ChangeEmail extends OutgoingEmail {
}
/** Get the text of the "cover letter", from {@link ChangeMessage}. */
- protected String getCoverLetter() {
+ public String getCoverLetter() {
if (changeMessage != null) {
final String txt = changeMessage.getMessage();
if (txt != null) {
@@ -268,34 +209,41 @@ public abstract class ChangeEmail extends OutgoingEmail {
/** Format the change message and the affected file list. */
protected void formatChangeDetail() {
+ appendText(getChangeDetail());
+ }
+
+ /** Create the change message and the affected file list. */
+ public String getChangeDetail() {
+ StringBuilder detail = new StringBuilder();
+
if (patchSetInfo != null) {
- appendText(patchSetInfo.getMessage().trim());
- appendText("\n");
+ detail.append(patchSetInfo.getMessage().trim() + "\n");
} else {
- appendText(change.getSubject().trim());
- appendText("\n");
+ detail.append(change.getSubject().trim() + "\n");
}
if (patchSet != null) {
- appendText("---\n");
+ detail.append("---\n");
PatchList patchList = getPatchList();
for (PatchListEntry p : patchList.getPatches()) {
if (Patch.COMMIT_MSG.equals(p.getNewName())) {
continue;
}
- appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
+ detail.append(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
}
- appendText(MessageFormat.format("" //
+ detail.append(MessageFormat.format("" //
+ "{0,choice,0#0 files|1#1 file|1<{0} files} changed, " //
+ "{1,choice,0#0 insertions|1#1 insertion|1<{1} insertions}(+), " //
+ "{2,choice,0#0 deletions|1#1 deletion|1<{2} deletions}(-)" //
+ "\n", patchList.getPatches().size() - 1, //
patchList.getInsertions(), //
patchList.getDeletions()));
- appendText("\n");
+ detail.append("\n");
}
+ return detail.toString();
}
+
/** Get the patch list corresponding to this patch set. */
protected PatchList getPatchList() {
if (patchSet != null) {
@@ -441,4 +389,17 @@ public abstract class ChangeEmail extends OutgoingEmail {
|| projectState.controlFor(args.identifiedUserFactory.create(to))
.controlFor(change).isVisible();
}
+
+ @Override
+ protected void setupVelocityContext() {
+ super.setupVelocityContext();
+ velocityContext.put("change", change);
+ velocityContext.put("changeId", change.getKey());
+ velocityContext.put("coverLetter", getCoverLetter());
+ velocityContext.put("branch", change.getDest());
+ velocityContext.put("fromName", getNameFor(fromId));
+ velocityContext.put("projectName", projectName);
+ velocityContext.put("patchSet", patchSet);
+ velocityContext.put("patchSetInfo", patchSetInfo);
+ }
}
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 e501a3e690..0425121eb7 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
@@ -61,7 +61,7 @@ public class CommentSender extends ReplyToChangeSender {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
ccAllApprovals();
@@ -70,20 +70,13 @@ public class CommentSender extends ReplyToChangeSender {
}
@Override
- protected void formatChange() {
- if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) {
- appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n");
- appendText("\n");
- formatCoverLetter();
- formatInlineComments();
- if (getChangeUrl() != null) {
- appendText("To respond, visit " + getChangeUrl() + "\n");
- appendText("\n");
- }
- }
+ public void formatChange() throws EmailException {
+ appendText(velocifyFile("Comment.vm"));
}
- private void formatInlineComments() {
+ public String getInlineComments() {
+ StringBuilder cmts = new StringBuilder();
+
final Repository repo = getRepository();
try {
final PatchList patchList = repo != null ? getPatchList() : null;
@@ -96,13 +89,13 @@ public class CommentSender extends ReplyToChangeSender {
final short side = c.getSide();
if (!pk.equals(currentFileKey)) {
- appendText("....................................................\n");
+ cmts.append("....................................................\n");
if (Patch.COMMIT_MSG.equals(pk.get())) {
- appendText("Commit Message\n");
+ cmts.append("Commit Message\n");
} else {
- appendText("File ");
- appendText(pk.get());
- appendText("\n");
+ cmts.append("File ");
+ cmts.append(pk.get());
+ cmts.append("\n");
}
currentFileKey = pk;
@@ -118,26 +111,27 @@ public class CommentSender extends ReplyToChangeSender {
}
}
- appendText("Line " + lineNbr);
+ cmts.append("Line " + lineNbr);
if (currentFileData != null) {
try {
final String lineStr = currentFileData.getLine(side, lineNbr);
- appendText(": ");
- appendText(lineStr);
+ cmts.append(": ");
+ cmts.append(lineStr);
} catch (Throwable cce) {
// Don't quote the line if we can't safely convert it.
}
}
- appendText("\n");
+ cmts.append("\n");
- appendText(c.getMessage().trim());
- appendText("\n\n");
+ cmts.append(c.getMessage().trim());
+ cmts.append("\n\n");
}
} finally {
if (repo != null) {
repo.close();
}
}
+ return cmts.toString();
}
private Repository getRepository() {
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 ea57cde1e5..18bfe976c7 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
@@ -40,7 +40,7 @@ public class CreateChangeSender extends NewChangeSender {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
bccWatchers();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index 8a7ffd6b70..f2ab9fa4a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -20,6 +20,7 @@ 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.SitePaths;
import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
@@ -47,6 +48,7 @@ class EmailArguments {
final ChangeQueryBuilder.Factory queryBuilder;
final Provider<ChangeQueryRewriter> queryRewriter;
final Provider<ReviewDb> db;
+ final SitePaths site;
@Inject
EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
@@ -57,7 +59,8 @@ class EmailArguments {
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
@WildProjectName Project.NameKey wildProject,
ChangeQueryBuilder.Factory queryBuilder,
- Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db) {
+ Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db,
+ SitePaths site) {
this.server = server;
this.projectCache = projectCache;
this.accountCache = accountCache;
@@ -71,5 +74,6 @@ class EmailArguments {
this.queryBuilder = queryBuilder;
this.queryRewriter = queryRewriter;
this.db = db;
+ this.site = site;
}
}
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 00750ef306..19fb48b640 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
@@ -26,27 +26,18 @@ public class MergeFailSender extends ReplyToChangeSender {
@Inject
public MergeFailSender(EmailArguments ea, @Assisted Change c) {
- super(ea, c, "comment");
+ super(ea, c, "merge-failed");
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
ccExistingReviewers();
}
@Override
- protected void formatChange() {
- appendText("Change " + change.getKey().abbreviate());
- if (patchSetInfo != null && patchSetInfo.getAuthor() != null
- && patchSetInfo.getAuthor().getName() != null) {
- appendText(" by ");
- appendText(patchSetInfo.getAuthor().getName());
- }
- appendText(" FAILED to submit to ");
- appendText(change.getDest().getShortName());
- appendText(".\n\n");
- formatCoverLetter();
+ protected void formatChange() throws EmailException {
+ appendText(velocifyFile("MergeFail.vm"));
}
}
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 caf19e472e..40f479075f 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
@@ -51,7 +51,7 @@ public class MergedSender extends ReplyToChangeSender {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
ccAllApprovals();
@@ -61,58 +61,47 @@ public class MergedSender extends ReplyToChangeSender {
}
@Override
- protected void formatChange() {
- appendText("Change " + change.getKey().abbreviate());
- if (patchSetInfo != null && patchSetInfo.getAuthor() != null
- && patchSetInfo.getAuthor().getName() != null) {
- appendText(" by ");
- appendText(patchSetInfo.getAuthor().getName());
- }
- appendText(" submitted to ");
- appendText(dest.getShortName());
- appendText(":\n\n");
- formatChangeDetail();
- formatApprovals();
+ protected void formatChange() throws EmailException {
+ appendText(velocifyFile("Merged.vm"));
}
- private void formatApprovals() {
- if (patchSet != null) {
- try {
- final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
- new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
-
- final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
- new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
-
- for (PatchSetApproval ca : args.db.get().patchSetApprovals()
- .byPatchSet(patchSet.getId())) {
- if (ca.getValue() > 0) {
- insert(pos, ca);
- } else if (ca.getValue() < 0) {
- insert(neg, ca);
- }
+ public String getApprovals() {
+ try {
+ final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
+ new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
+
+ final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
+ new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
+
+ for (PatchSetApproval ca : args.db.get().patchSetApprovals()
+ .byPatchSet(patchSet.getId())) {
+ if (ca.getValue() > 0) {
+ insert(pos, ca);
+ } else if (ca.getValue() < 0) {
+ insert(neg, ca);
}
-
- format("Approvals", pos);
- format("Objections", neg);
- } catch (OrmException err) {
- // Don't list the approvals
}
+
+ return format("Approvals", pos) + format("Objections", neg);
+ } catch (OrmException err) {
+ // Don't list the approvals
}
+ return "";
}
- private void format(final String type,
+ private String format(final String type,
final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> list) {
+ StringBuilder txt = new StringBuilder();
if (list.isEmpty()) {
- return;
+ return "";
}
- appendText(type + ":\n");
+ txt.append(type + ":\n");
for (final Map.Entry<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> ent : list
.entrySet()) {
final Map<ApprovalCategory.Id, PatchSetApproval> l = ent.getValue();
- appendText(" ");
- appendText(getNameFor(ent.getKey()));
- appendText(": ");
+ txt.append(" ");
+ txt.append(getNameFor(ent.getKey()));
+ txt.append(": ");
boolean first = true;
for (ApprovalType at : approvalTypes.getApprovalTypes()) {
final PatchSetApproval ca = l.get(at.getCategory().getId());
@@ -123,24 +112,25 @@ public class MergedSender extends ReplyToChangeSender {
if (first) {
first = false;
} else {
- appendText("; ");
+ txt.append("; ");
}
final ApprovalCategoryValue v = at.getValue(ca);
if (v != null) {
- appendText(v.getName());
+ txt.append(v.getName());
} else {
- appendText(at.getCategory().getName());
- appendText("=");
+ txt.append(at.getCategory().getName());
+ txt.append("=");
if (ca.getValue() > 0) {
- appendText("+");
+ txt.append("+");
}
- appendText("" + ca.getValue());
+ txt.append("" + ca.getValue());
}
}
- appendText("\n");
+ txt.append("\n");
}
- appendText("\n");
+ txt.append("\n");
+ return txt.toString();
}
private void insert(
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 dc8c2c2b33..9e78cabf30 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
@@ -20,6 +20,7 @@ import com.google.gerrit.server.ssh.SshInfo;
import com.jcraft.jsch.HostKey;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@@ -46,7 +47,7 @@ public abstract class NewChangeSender extends ChangeEmail {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
setHeader("Message-ID", getChangeMessageThreadId());
@@ -57,74 +58,19 @@ public abstract class NewChangeSender extends ChangeEmail {
}
@Override
- protected void formatChange() {
- formatSalutation();
- formatChangeDetail();
-
- appendText("\n");
- appendText(" " + getPullUrl() + "\n");
+ protected void formatChange() throws EmailException {
+ appendText(velocifyFile("NewChange.vm"));
}
- private void formatSalutation() {
- final String changeUrl = getChangeUrl();
-
+ public List<String> getReviewerNames() {
if (reviewers.isEmpty()) {
- formatDest();
- if (changeUrl != null) {
- appendText("\n");
- appendText(" " + changeUrl + "\n");
- appendText("\n");
- }
- appendText("\n");
-
- } else {
- appendText("Hello");
- for (final Iterator<Account.Id> i = reviewers.iterator(); i.hasNext();) {
- appendText(" ");
- appendText(getNameFor(i.next()));
- appendText(",");
- }
- appendText("\n");
- appendText("\n");
-
- appendText("I'd like you to do a code review.");
- if (changeUrl != null) {
- appendText(" Please visit\n");
- appendText("\n");
- appendText(" " + changeUrl + "\n");
- appendText("\n");
- appendText("to review the following change:\n");
- }
- appendText("\n");
-
- formatDest();
- appendText("\n");
+ return null;
}
- }
-
- private void formatDest() {
- appendText("Change " + change.getKey().abbreviate());
- appendText(" for ");
- appendText(change.getDest().getShortName());
- appendText(" in ");
- appendText(projectName);
- appendText(":\n");
- }
-
- private String getPullUrl() {
- final String host = getSshHost();
- if (host == null) {
- return "";
+ List<String> names = new ArrayList<String>();
+ for (Account.Id id : reviewers) {
+ names.add(getNameFor(id));
}
-
- final StringBuilder r = new StringBuilder();
- r.append("git pull ssh://");
- r.append(host);
- r.append("/");
- r.append(projectName);
- r.append(" ");
- r.append(patchSet.getRefName());
- return r.toString();
+ return names;
}
public String getSshHost() {
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 2136c658c4..02e500082a 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
@@ -19,10 +19,15 @@ import com.google.gerrit.reviewdb.UserIdentity;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.mail.EmailHeader.AddressList;
+import org.apache.commons.lang.StringUtils;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.ResourceNotFoundException;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
@@ -47,6 +52,7 @@ public abstract class OutgoingEmail {
private final List<Address> smtpRcptTo = new ArrayList<Address>();
private Address smtpFromAddress;
private StringBuilder body;
+ protected VelocityContext velocityContext;
protected final EmailArguments args;
protected Account.Id fromId;
@@ -113,10 +119,12 @@ public abstract class OutgoingEmail {
}
/** Format the message body by calling {@link #appendText(String)}. */
- protected abstract void format();
+ protected abstract void format() throws EmailException;
/** Setup the message headers and envelope (TO, CC, BCC). */
- protected void init() {
+ protected void init() throws EmailException {
+ setupVelocityContext();
+
smtpFromAddress = args.fromAddressGenerator.from(fromId);
setHeader("Date", new Date());
headers.put("From", new EmailHeader.AddressList(smtpFromAddress));
@@ -140,25 +148,31 @@ public abstract class OutgoingEmail {
body = new StringBuilder();
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();
-
- if ((name != null && !name.isEmpty())
- || (email != null && !email.isEmpty())) {
- body.append("From");
- if (name != null && !name.isEmpty()) {
- body.append(" ").append(name);
- }
- if (email != null && !email.isEmpty()) {
- body.append(" <").append(email).append(">");
- }
- body.append(":\n\n");
+ appendText(getFromLine());
+ }
+ }
+
+ protected String getFromLine() {
+ final Account account = args.accountCache.get(fromId).getAccount();
+ final String name = account.getFullName();
+ final String email = account.getPreferredEmail();
+ StringBuilder f = new StringBuilder();
+
+ if ((name != null && !name.isEmpty())
+ || (email != null && !email.isEmpty())) {
+ f.append("From");
+ if (name != null && !name.isEmpty()) {
+ f.append(" ").append(name);
+ }
+ if (email != null && !email.isEmpty()) {
+ f.append(" <").append(email).append(">");
}
+ f.append(":\n\n");
}
+ return f.toString();
}
- protected String getGerritHost() {
+ public String getGerritHost() {
if (getGerritUrl() != null) {
try {
return new URL(getGerritUrl()).getHost();
@@ -184,10 +198,16 @@ public abstract class OutgoingEmail {
return null;
}
- protected String getGerritUrl() {
+ public String getGerritUrl() {
return args.urlProvider.get();
}
+ /** Set a header in the outgoing message using a template. */
+ protected void setVHeader(final String name, final String value) throws
+ EmailException {
+ setHeader(name, velocify(value));
+ }
+
/** Set a header in the outgoing message. */
protected void setHeader(final String name, final String value) {
headers.put(name, new EmailHeader.String(value));
@@ -221,7 +241,7 @@ public abstract class OutgoingEmail {
return name;
}
- protected String getNameEmailFor(Account.Id accountId) {
+ public String getNameEmailFor(Account.Id accountId) {
AccountState who = args.accountCache.get(accountId);
String name = who.getAccount().getFullName();
String email = who.getAccount().getPreferredEmail();
@@ -310,9 +330,40 @@ public abstract class OutgoingEmail {
private Address toAddress(final Account.Id id) {
final Account a = args.accountCache.get(id).getAccount();
final String e = a.getPreferredEmail();
- if (e == null) {
+ if (!a.isActive() || e == null) {
return null;
}
return new Address(a.getFullName(), e);
}
+
+ protected void setupVelocityContext() {
+ velocityContext = new VelocityContext();
+
+ velocityContext.put("email", this);
+ velocityContext.put("messageClass", messageClass);
+ velocityContext.put("StringUtils", StringUtils.class);
+ }
+
+ protected String velocify(String template) throws EmailException {
+ try {
+ StringWriter w = new StringWriter();
+ Velocity.evaluate(velocityContext, w, "OutgoingEmail", template);
+ return w.toString();
+ } catch(Exception e) {
+ throw new EmailException("Velocity template " + template, e);
+ }
+ }
+
+ protected String velocifyFile(String name) throws EmailException {
+ if (!Velocity.resourceExists(name)) {
+ name = "com/google/gerrit/server/mail/" + name;
+ }
+ try {
+ StringWriter w = new StringWriter();
+ Velocity.mergeTemplate(name, "UTF-8", velocityContext, w);
+ return w.toString();
+ } catch(Exception e) {
+ throw new EmailException("Velocity template " + name + ".\n", e);
+ }
+ }
}
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 9b201fd4b5..2c779994b6 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
@@ -40,7 +40,7 @@ public class RegisterNewEmailSender extends OutgoingEmail {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
setHeader("Subject", "[Gerrit Code Review] Email Verification");
add(RecipientType.TO, new Address(addr));
@@ -52,39 +52,8 @@ public class RegisterNewEmailSender extends OutgoingEmail {
}
@Override
- protected void format() {
- final StringBuilder url = new StringBuilder();
- url.append(getGerritUrl());
- url.append("#VE,");
- url.append(getEmailRegistrationToken());
-
- appendText("Welcome to Gerrit Code Review at ");
- appendText(getGerritHost());
- appendText(".\n");
-
- appendText("\n");
- appendText("To add a verified email address to your user account, please\n");
- appendText("click on the following link:\n");
- appendText("\n");
- appendText(url.toString());
- appendText("\n");
-
- appendText("\n");
- appendText("If you have received this mail in error,"
- + " you do not need to take any\n");
- appendText("action to cancel the account."
- + " The account will not be activated, and\n");
- appendText("you will not receive any further emails.\n");
-
- appendText("\n");
- appendText("If clicking the link above does not work,"
- + " copy and paste the URL in a\n");
- appendText("new browser window instead.\n");
-
- appendText("\n");
- appendText("This is a send-only email address."
- + " Replies to this message will not\n");
- appendText("be read or answered.\n");
+ protected void format() throws EmailException {
+ appendText(velocifyFile("RegisterNewEmail.vm"));
}
public String getEmailRegistrationToken() {
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 841aa355e0..0c8ca9ae2b 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
@@ -22,6 +22,7 @@ import com.google.inject.assistedinject.Assisted;
import com.jcraft.jsch.HostKey;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@@ -53,7 +54,7 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
if (fromId != null) {
@@ -67,77 +68,19 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
}
@Override
- protected void formatChange() {
- formatSalutation();
- formatChangeDetail();
-
- appendText("\n");
- appendText(" " + getPullUrl() + "\n");
+ protected void formatChange() throws EmailException {
+ appendText(velocifyFile("ReplacePatchSet.vm"));
}
- private void formatSalutation() {
- final String changeUrl = getChangeUrl();
-
+ public List<String> getReviewerNames() {
if (reviewers.isEmpty()) {
- formatDest();
- if (changeUrl != null) {
- appendText("\n");
- appendText(" " + changeUrl + "\n");
- appendText("\n");
- }
- appendText("\n");
-
- } else {
- appendText("Hello");
- for (final Iterator<Account.Id> i = reviewers.iterator(); i.hasNext();) {
- appendText(" ");
- appendText(getNameFor(i.next()));
- appendText(",");
- }
- appendText("\n");
- appendText("\n");
-
- appendText("I'd like you to reexamine change "
- + change.getKey().abbreviate() + ".");
- if (changeUrl != null) {
- appendText(" Please visit\n");
- appendText("\n");
- appendText(" " + changeUrl + "\n");
- appendText("\n");
- appendText("to look at patch set " + patchSet.getPatchSetId());
- appendText(":\n");
- }
- appendText("\n");
-
- formatDest();
- appendText("\n");
+ return null;
}
- }
-
- private void formatDest() {
- appendText("Change " + change.getKey().abbreviate());
- appendText(" (patch set " + patchSet.getPatchSetId() + ")");
- appendText(" for ");
- appendText(change.getDest().getShortName());
- appendText(" in ");
- appendText(projectName);
- appendText(":\n");
- }
-
- private String getPullUrl() {
- final String host = getSshHost();
- if (host == null) {
- return "";
+ List<String> names = new ArrayList<String>();
+ for (Account.Id id : reviewers) {
+ names.add(getNameFor(id));
}
-
- final StringBuilder r = new StringBuilder();
- r.append("git pull ssh://");
- r.append(host);
- r.append("/");
- r.append(projectName);
- r.append(" ");
- r.append(patchSet.getRefName());
- return r.toString();
+ return names;
}
public String getSshHost() {
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 05d2753dee..4c3ed76465 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
@@ -23,7 +23,7 @@ public abstract class ReplyToChangeSender extends ChangeEmail {
}
@Override
- protected void init() {
+ protected void init() throws EmailException {
super.init();
final String threadId = getChangeMessageThreadId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharText.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharText.java
index fd6208633b..2a0cd2f5c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharText.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharText.java
@@ -16,11 +16,11 @@ package com.google.gerrit.server.patch;
import org.eclipse.jgit.diff.Sequence;
-class CharText implements Sequence {
+class CharText extends Sequence {
private final String content;
CharText(Text text, int s, int e) {
- content = text.getLines(s, e, false /* keep LF */);
+ content = text.getString(s, e, false /* keep LF */);
}
char charAt(int idx) {
@@ -41,11 +41,6 @@ class CharText implements Sequence {
}
@Override
- public boolean equals(int a, Sequence other, int b) {
- return content.charAt(a) == ((CharText) other).content.charAt(b);
- }
-
- @Override
public int size() {
return content.length();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharTextComparator.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharTextComparator.java
new file mode 100644
index 0000000000..811931380e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/CharTextComparator.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.patch;
+
+import org.eclipse.jgit.diff.SequenceComparator;
+
+class CharTextComparator extends SequenceComparator<CharText> {
+ @Override
+ public boolean equals(CharText a, int ai, CharText b, int bi) {
+ return a.charAt(ai) == b.charAt(bi);
+ }
+
+ @Override
+ public int hash(CharText seq, int ptr) {
+ return seq.charAt(ptr);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
index 22be063ebb..4feccd05a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
@@ -23,7 +23,7 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
@@ -49,29 +49,34 @@ public class PatchFile {
this.repo = repo;
this.entry = patchList.get(fileName);
- final RevWalk rw = new RevWalk(repo);
- final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
+ final ObjectReader reader = repo.newObjectReader();
+ try {
+ final RevWalk rw = new RevWalk(reader);
+ final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
- if (Patch.COMMIT_MSG.equals(fileName)) {
- if (patchList.isAgainstParent()) {
- a = Text.EMPTY;
- } else {
- a = Text.forCommit(repo, patchList.getOldId());
- }
- b = Text.forCommit(repo, bCommit);
+ if (Patch.COMMIT_MSG.equals(fileName)) {
+ if (patchList.isAgainstParent()) {
+ a = Text.EMPTY;
+ } else {
+ a = Text.forCommit(repo, reader, patchList.getOldId());
+ }
+ b = Text.forCommit(repo, reader, bCommit);
- aTree = null;
- bTree = null;
+ aTree = null;
+ bTree = null;
- } else {
- if (patchList.getOldId() != null) {
- aTree = rw.parseTree(patchList.getOldId());
} else {
- final RevCommit p = bCommit.getParent(0);
- rw.parseHeaders(p);
- aTree = p.getTree();
+ if (patchList.getOldId() != null) {
+ aTree = rw.parseTree(patchList.getOldId());
+ } else {
+ final RevCommit p = bCommit.getParent(0);
+ rw.parseHeaders(p);
+ aTree = p.getTree();
+ }
+ bTree = bCommit.getTree();
}
- bTree = bCommit.getTree();
+ } finally {
+ reader.release();
}
}
@@ -93,13 +98,13 @@ public class PatchFile {
if (a == null) {
a = load(aTree, entry.getOldName());
}
- return a.getLine(line - 1);
+ return a.getString(line - 1);
case 1:
if (b == null) {
b = load(bTree, entry.getNewName());
}
- return b.getLine(line - 1);
+ return b.getString(line - 1);
default:
throw new NoSuchEntityException();
@@ -119,11 +124,6 @@ public class PatchFile {
if (tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) {
return Text.EMPTY;
}
- final ObjectId id = tw.getObjectId(0);
- final ObjectLoader ldr = repo.openObject(id);
- if (ldr == null) {
- throw new MissingObjectException(id, Constants.TYPE_BLOB);
- }
- return new Text(ldr.getCachedBytes());
+ return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB));
}
}
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 bb96237226..e5c3b72e35 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
@@ -84,17 +84,16 @@ import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
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.RawTextComparator;
import org.eclipse.jgit.diff.ReplaceEdit;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
@@ -187,81 +186,85 @@ public class PatchListCacheImpl implements PatchListCache {
final Repository repo) throws IOException {
// TODO(jeffschu) correctly handle merge commits
- RawText.Factory rawTextFactory;
+ RawTextComparator cmp;
switch (key.getWhitespace()) {
case IGNORE_ALL_SPACE:
- rawTextFactory = RawTextIgnoreAllWhitespace.FACTORY;
+ cmp = RawTextComparator.WS_IGNORE_ALL;
break;
case IGNORE_SPACE_AT_EOL:
- rawTextFactory = RawTextIgnoreTrailingWhitespace.FACTORY;
+ cmp = RawTextComparator.WS_IGNORE_TRAILING;
break;
case IGNORE_SPACE_CHANGE:
- rawTextFactory = RawTextIgnoreWhitespaceChange.FACTORY;
+ cmp = RawTextComparator.WS_IGNORE_CHANGE;
break;
case IGNORE_NONE:
default:
- rawTextFactory = RawText.FACTORY;
+ cmp = RawTextComparator.DEFAULT;
break;
}
- final RevWalk rw = new RevWalk(repo);
- final RevCommit b = rw.parseCommit(key.getNewId());
- final RevObject a = aFor(key, repo, rw, b);
+ final ObjectReader reader = repo.newObjectReader();
+ try {
+ final RevWalk rw = new RevWalk(reader);
+ final RevCommit b = rw.parseCommit(key.getNewId());
+ final RevObject a = aFor(key, repo, rw, b);
- if (a == null) {
- // This is a merge commit, compared to its ancestor.
- //
- final PatchListEntry[] entries = new PatchListEntry[1];
- entries[0] = newCommitMessage(rawTextFactory, repo, null, b);
- return new PatchList(a, b, computeIntraline, true, entries);
- }
+ if (a == null) {
+ // This is a merge commit, compared to its ancestor.
+ //
+ final PatchListEntry[] entries = new PatchListEntry[1];
+ entries[0] = newCommitMessage(cmp, repo, reader, null, b);
+ return new PatchList(a, b, computeIntraline, true, entries);
+ }
- final boolean againstParent =
- b.getParentCount() > 0 && b.getParent(0) == a;
-
- RevCommit aCommit;
- RevTree aTree;
- if (a instanceof RevCommit) {
- aCommit = (RevCommit) a;
- aTree = aCommit.getTree();
- } else if (a instanceof RevTree) {
- aCommit = null;
- aTree = (RevTree) a;
- } else {
- throw new IOException("Unexpected type: " + a.getClass());
- }
+ final boolean againstParent =
+ b.getParentCount() > 0 && b.getParent(0) == a;
+
+ RevCommit aCommit;
+ RevTree aTree;
+ if (a instanceof RevCommit) {
+ aCommit = (RevCommit) a;
+ aTree = aCommit.getTree();
+ } else if (a instanceof RevTree) {
+ aCommit = null;
+ aTree = (RevTree) a;
+ } else {
+ throw new IOException("Unexpected type: " + a.getClass());
+ }
- 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);
- df.setRawTextFactory(rawTextFactory);
-
- RenameDetector rd = new RenameDetector(repo);
- rd.addAll(DiffEntry.scan(walk));
- List<DiffEntry> diffEntries = rd.compute();
-
- final int cnt = diffEntries.size();
- final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
- entries[0] = newCommitMessage(rawTextFactory, repo, //
- againstParent ? null : aCommit, b);
- for (int i = 0; i < cnt; i++) {
- FileHeader fh = df.createFileHeader(diffEntries.get(i));
- entries[1 + i] = newEntry(repo, aTree, bTree, fh);
+ RevTree bTree = b.getTree();
+
+ final TreeWalk walk = new TreeWalk(reader);
+ 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);
+ df.setDiffComparator(cmp);
+ df.setDetectRenames(true);
+ List<DiffEntry> diffEntries = df.scan(aTree, bTree);
+
+ final int cnt = diffEntries.size();
+ final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
+ entries[0] = newCommitMessage(cmp, repo, reader, //
+ againstParent ? null : aCommit, b);
+ for (int i = 0; i < cnt; i++) {
+ FileHeader fh = df.toFileHeader(diffEntries.get(i));
+ entries[1 + i] = newEntry(reader, aTree, bTree, fh);
+ }
+ return new PatchList(a, b, computeIntraline, againstParent, entries);
+ } finally {
+ reader.release();
}
- return new PatchList(a, b, computeIntraline, againstParent, entries);
}
private PatchListEntry newCommitMessage(
- final RawText.Factory rawTextFactory, final Repository repo,
- final RevCommit aCommit, final RevCommit bCommit) throws IOException {
+ final RawTextComparator cmp, final Repository db,
+ final ObjectReader reader, final RevCommit aCommit,
+ final RevCommit bCommit) throws IOException {
StringBuilder hdr = new StringBuilder();
hdr.append("diff --git");
@@ -280,18 +283,18 @@ public class PatchListCacheImpl implements PatchListCache {
}
hdr.append("+++ b/" + Patch.COMMIT_MSG + "\n");
- Text aText = aCommit != null ? Text.forCommit(repo, aCommit) : Text.EMPTY;
- Text bText = Text.forCommit(repo, bCommit);
+ Text aText = aCommit != null ? Text.forCommit(db, reader, aCommit) : Text.EMPTY;
+ Text bText = Text.forCommit(db, reader, bCommit);
byte[] rawHdr = hdr.toString().getBytes("UTF-8");
- RawText aRawText = rawTextFactory.create(aText.getContent());
- RawText bRawText = rawTextFactory.create(bText.getContent());
- EditList edits = new MyersDiff(aRawText, bRawText).getEdits();
+ RawText aRawText = new RawText(aText.getContent());
+ RawText bRawText = new RawText(bText.getContent());
+ EditList edits = MyersDiff.INSTANCE.diff(cmp, aRawText, bRawText);
FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
- return newEntry(repo, aText, bText, edits, null, null, fh);
+ return newEntry(reader, aText, bText, edits, null, null, fh);
}
- private PatchListEntry newEntry(Repository repo, RevTree aTree,
+ private PatchListEntry newEntry(ObjectReader reader, RevTree aTree,
RevTree bTree, FileHeader fileHeader) throws IOException {
final FileMode oldMode = fileHeader.getOldMode();
final FileMode newMode = fileHeader.getNewMode();
@@ -320,10 +323,10 @@ public class PatchListCacheImpl implements PatchListCache {
return new PatchListEntry(fileHeader, edits);
}
- return newEntry(repo, null, null, edits, aTree, bTree, fileHeader);
+ return newEntry(reader, null, null, edits, aTree, bTree, fileHeader);
}
- private PatchListEntry newEntry(Repository repo, Text aContent,
+ private PatchListEntry newEntry(ObjectReader reader, Text aContent,
Text bContent, List<Edit> edits, RevTree aTree, RevTree bTree,
FileHeader fileHeader) throws IOException {
for (int i = 0; i < edits.size(); i++) {
@@ -332,8 +335,8 @@ public class PatchListCacheImpl implements PatchListCache {
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);
+ aContent = read(reader, fileHeader.getOldPath(), aTree);
+ bContent = read(reader, fileHeader.getNewPath(), bTree);
combineLineEdits(edits, aContent, bContent);
i = -1; // restart the entire scan after combining lines.
continue;
@@ -341,8 +344,9 @@ public class PatchListCacheImpl implements PatchListCache {
CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
+ CharTextComparator cmp = new CharTextComparator();
- List<Edit> wordEdits = new MyersDiff(a, b).getEdits();
+ List<Edit> wordEdits = MyersDiff.INSTANCE.diff(cmp, a, b);
// Combine edits that are really close together. If they are
// just a few characters apart we tend to get better results
@@ -407,11 +411,11 @@ public class PatchListCacheImpl implements PatchListCache {
// 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)) {
+ while (ab < ae && bb < be && cmp.equals(a, ab, b, bb)) {
ab++;
bb++;
}
- while (ab < ae && bb < be && a.equals(ae - 1, b, be - 1)) {
+ while (ab < ae && bb < be && cmp.equals(a, ae - 1, b, be - 1)) {
ae--;
be--;
}
@@ -429,7 +433,7 @@ public class PatchListCacheImpl implements PatchListCache {
int nb = lf + 1;
int p = 0;
while (p < ae - ab) {
- if (a.equals(ab + p, a, ab + p))
+ if (cmp.equals(a, ab + p, a, ab + p))
p++;
else
break;
@@ -443,12 +447,12 @@ public class PatchListCacheImpl implements PatchListCache {
}
if (aShift) {
while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
- && a.equals(ab - 1, a, ae - 1)) {
+ && cmp.equals(a, ab - 1, a, ae - 1)) {
ab--;
ae--;
}
if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
- while (ab < ae && ae < a.size() && a.equals(ab, a, ae)) {
+ while (ab < ae && ae < a.size() && cmp.equals(a, ab, a, ae)) {
ab++;
ae++;
if (a.charAt(ae - 1) == '\n') {
@@ -465,7 +469,7 @@ public class PatchListCacheImpl implements PatchListCache {
int nb = lf + 1;
int p = 0;
while (p < be - bb) {
- if (b.equals(bb + p, b, bb + p))
+ if (cmp.equals(b, bb + p, b, bb + p))
p++;
else
break;
@@ -479,12 +483,12 @@ public class PatchListCacheImpl implements PatchListCache {
}
if (bShift) {
while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
- && b.equals(bb - 1, b, be - 1)) {
+ && cmp.equals(b, 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)) {
+ while (bb < be && be < b.size() && cmp.equals(b, bb, b, be)) {
bb++;
be++;
if (b.charAt(be - 1) == '\n') {
@@ -551,7 +555,7 @@ public class PatchListCacheImpl implements PatchListCache {
private static boolean isBlankLineGap(Text a, int b, int e) {
for (; b < e; b++) {
- if (!BLANK_LINE_RE.matcher(a.getLine(b)).matches()) {
+ if (!BLANK_LINE_RE.matcher(a.getString(b)).matches()) {
return false;
}
}
@@ -559,7 +563,7 @@ public class PatchListCacheImpl implements PatchListCache {
}
private static boolean isControlBlockStart(Text a, int idx) {
- final String l = a.getLine(idx);
+ final String l = a.getString(idx);
return CONTROL_BLOCK_START_RE.matcher(l).find();
}
@@ -590,17 +594,19 @@ public class PatchListCacheImpl implements PatchListCache {
return b < e;
}
- private static Text read(Repository repo, String path, RevTree tree)
+ private static Text read(ObjectReader reader, String path, RevTree tree)
throws IOException {
- TreeWalk tw = TreeWalk.forPath(repo, path, tree);
+ TreeWalk tw = TreeWalk.forPath(reader, 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) {
+ ObjectLoader ldr;
+ try {
+ ldr = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
+ } catch (MissingObjectException notFound) {
return Text.EMPTY;
}
- return new Text(ldr.getCachedBytes());
+ return new Text(ldr);
}
private static RevObject aFor(final PatchListKey key,
@@ -625,7 +631,14 @@ public class PatchListCacheImpl implements PatchListCache {
}
private static ObjectId emptyTree(final Repository repo) throws IOException {
- return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
+ ObjectInserter oi = repo.newObjectInserter();
+ try {
+ ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
+ oi.flush();
+ return id;
+ } finally {
+ oi.release();
+ }
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index fd7da082c5..fac8ec4350 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -69,19 +69,19 @@ public class PatchListEntry {
switch (changeType) {
case DELETED:
oldName = null;
- newName = hdr.getOldName();
+ newName = hdr.getOldPath();
break;
case ADDED:
case MODIFIED:
oldName = null;
- newName = hdr.getNewName();
+ newName = hdr.getNewPath();
break;
case COPIED:
case RENAMED:
- oldName = hdr.getOldName();
- newName = hdr.getNewName();
+ oldName = hdr.getOldPath();
+ newName = hdr.getNewPath();
break;
default:
@@ -286,21 +286,17 @@ public class PatchListEntry {
private static PatchType toPatchType(final FileHeader hdr) {
PatchType pt;
- if (hdr instanceof CombinedFileHeader) {
- pt = Patch.PatchType.N_WAY;
- } else {
- switch (hdr.getPatchType()) {
- case UNIFIED:
- pt = Patch.PatchType.UNIFIED;
- break;
- case GIT_BINARY:
- case BINARY:
- pt = Patch.PatchType.BINARY;
- break;
- default:
- throw new IllegalArgumentException("Unsupported type "
- + hdr.getPatchType());
- }
+ switch (hdr.getPatchType()) {
+ case UNIFIED:
+ pt = Patch.PatchType.UNIFIED;
+ break;
+ case GIT_BINARY:
+ case BINARY:
+ pt = Patch.PatchType.BINARY;
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported type "
+ + hdr.getPatchType());
}
if (pt != PatchType.BINARY) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 961717297b..840e76f971 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -79,9 +79,13 @@ public class PatchSetInfoFactory {
final String projectName = projectKey.get();
repo = repoManager.openRepository(projectName);
final RevWalk rw = new RevWalk(repo);
- final RevCommit src =
- rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- return get(src, patchSetId);
+ try {
+ final RevCommit src =
+ rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
+ return get(src, patchSetId);
+ } finally {
+ rw.release();
+ }
} catch (OrmException e) {
throw new PatchSetInfoNotAvailableException(e);
} catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
index 860e4b9512..97f53fc09a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
@@ -15,11 +15,16 @@
package com.google.gerrit.server.patch;
import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.RawParseUtils;
import org.mozilla.universalchardet.UniversalDetector;
import org.slf4j.Logger;
@@ -34,13 +39,14 @@ import java.text.SimpleDateFormat;
public class Text extends RawText {
private static final Logger log = LoggerFactory.getLogger(Text.class);
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
+ private static final int bigFileThreshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
public static final byte[] NO_BYTES = {};
public static final Text EMPTY = new Text(NO_BYTES);
- public static Text forCommit(Repository db, AnyObjectId commitId)
- throws IOException {
- RevWalk rw = new RevWalk(db);
+ public static Text forCommit(Repository db, ObjectReader reader,
+ AnyObjectId commitId) throws IOException {
+ RevWalk rw = new RevWalk(reader);
RevCommit c;
if (commitId instanceof RevCommit) {
c = (RevCommit) commitId;
@@ -56,7 +62,7 @@ public class Text extends RawText {
RevCommit p = c.getParent(0);
rw.parseBody(p);
b.append("Parent: ");
- b.append(p.abbreviate(db, 8).name());
+ b.append(reader.abbreviate(p, 8).name());
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
@@ -67,7 +73,7 @@ public class Text extends RawText {
RevCommit p = c.getParent(i);
rw.parseBody(p);
b.append(i == 0 ? "Merge Of: " : " ");
- b.append(p.abbreviate(db, 8).name());
+ b.append(reader.abbreviate(p, 8).name());
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
@@ -103,8 +109,9 @@ public class Text extends RawText {
}
}
- public static String asString(byte[] content, String encoding) {
- return new String(content, charset(content, encoding));
+ public static byte[] asByteArray(ObjectLoader ldr)
+ throws MissingObjectException, LargeObjectException, IOException {
+ return ldr.getCachedBytes(bigFileThreshold);
}
private static Charset charset(byte[] content, String encoding) {
@@ -136,39 +143,20 @@ public class Text extends RawText {
super(r);
}
- public byte[] getContent() {
- return content;
- }
-
- public String getLine(final int i) {
- return getLines(i, i + 1, true);
+ public Text(ObjectLoader ldr) throws MissingObjectException,
+ LargeObjectException, IOException {
+ this(asByteArray(ldr));
}
- public String getLines(final int begin, final int end, boolean dropLF) {
- if (begin == end) {
- return "";
- }
-
- final int s = getLineStart(begin);
- int e = getLineEnd(end - 1);
- if (dropLF && content[e - 1] == '\n') {
- e--;
- }
- return decode(s, e);
+ public byte[] getContent() {
+ return content;
}
- private String decode(final int s, int e) {
+ @Override
+ protected String decode(final int s, int e) {
if (charset == null) {
charset = charset(content, null);
}
return RawParseUtils.decode(charset, content, s, e);
}
-
- private int getLineStart(final int i) {
- return lines.get(i + 1);
- }
-
- private int getLineEnd(final int i) {
- return lines.get(i + 2);
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
new file mode 100644
index 0000000000..1e2e7f4382
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.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.project;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GitReceivePackGroups;
+import com.google.gerrit.server.config.GitReceivePackGroupsProvider;
+import com.google.gerrit.server.config.GitUploadPackGroups;
+import com.google.gerrit.server.config.GitUploadPackGroupsProvider;
+import com.google.gerrit.server.config.ProjectCreatorGroups;
+import com.google.gerrit.server.config.ProjectCreatorGroupsProvider;
+import com.google.gerrit.server.config.ProjectOwnerGroups;
+import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
+import com.google.inject.TypeLiteral;
+
+import java.util.Set;
+
+public class AccessControlModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ .annotatedWith(ProjectCreatorGroups.class) //
+ .toProvider(ProjectCreatorGroupsProvider.class).in(SINGLETON);
+
+ bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ .annotatedWith(ProjectOwnerGroups.class) //
+ .toProvider(ProjectOwnerGroupsProvider.class).in(SINGLETON);
+
+ bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ .annotatedWith(GitUploadPackGroups.class) //
+ .toProvider(GitUploadPackGroupsProvider.class).in(SINGLETON);
+
+ bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ .annotatedWith(GitReceivePackGroups.class) //
+ .toProvider(GitReceivePackGroupsProvider.class).in(SINGLETON);
+
+ factory(ProjectControl.AssistedFactory.class);
+ }
+}
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 ff788dd089..25778a6fb8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import static com.google.gerrit.common.CollectionsUtil.*;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
@@ -22,8 +23,11 @@ import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser;
+import com.google.gerrit.server.config.GitReceivePackGroups;
+import com.google.gerrit.server.config.GitUploadPackGroups;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
import java.util.HashSet;
import java.util.Set;
@@ -93,10 +97,25 @@ public class ProjectControl {
}
}
+ interface AssistedFactory {
+ ProjectControl create(CurrentUser who, ProjectState ps);
+ }
+
+ private final Set<AccountGroup.Id> uploadGroups;
+ private final Set<AccountGroup.Id> receiveGroups;
+
+ private final RefControl.Factory refControlFactory;
private final CurrentUser user;
private final ProjectState state;
- ProjectControl(final CurrentUser who, final ProjectState ps) {
+ @Inject
+ ProjectControl(@GitUploadPackGroups Set<AccountGroup.Id> uploadGroups,
+ @GitReceivePackGroups Set<AccountGroup.Id> receiveGroups,
+ final RefControl.Factory refControlFactory,
+ @Assisted CurrentUser who, @Assisted ProjectState ps) {
+ this.uploadGroups = uploadGroups;
+ this.receiveGroups = receiveGroups;
+ this.refControlFactory = refControlFactory;
user = who;
state = ps;
}
@@ -118,7 +137,7 @@ public class ProjectControl {
}
public RefControl controlForRef(String refName) {
- return new RefControl(this, refName);
+ return refControlFactory.create(this, refName);
}
public CurrentUser getCurrentUser() {
@@ -169,33 +188,25 @@ public class ProjectControl {
}
/** @return true if the user can upload to at least one reference */
- public boolean canUploadToAtLeastOneRef() {
- return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2);
+ public boolean canPushToAtLeastOneRef() {
+ return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2)
+ || canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, (short) 1)
+ || canPerformOnAnyRef(ApprovalCategory.PUSH_TAG, (short) 1);
}
+ // TODO (anatol.pomazau): Try to merge this method with similar RefRightsForPattern#canPerform
private boolean canPerformOnAnyRef(ApprovalCategory.Id actionId,
short requireValue) {
final Set<AccountGroup.Id> groups = user.getEffectiveGroups();
- int val = Integer.MIN_VALUE;
-
- for (final RefRight pr : state.getLocalRights(actionId)) {
- if (groups.contains(pr.getAccountGroupId())) {
- val = Math.max(pr.getMaxValue(), val);
- }
- }
- if (val >= requireValue) {
- return true;
- }
- if (actionId.canInheritFromWildProject()) {
- for (final RefRight pr : state.getInheritedRights(actionId)) {
- if (groups.contains(pr.getAccountGroupId())) {
- val = Math.max(pr.getMaxValue(), val);
- }
+ for (final RefRight pr : state.getAllRights(actionId, true)) {
+ if (groups.contains(pr.getAccountGroupId())
+ && pr.getMaxValue() >= requireValue) {
+ return true;
}
}
- return val >= requireValue;
+ return false;
}
private boolean canPerformOnAllRefs(ApprovalCategory.Id actionId,
@@ -220,14 +231,17 @@ public class ProjectControl {
private Set<String> allRefPatterns(ApprovalCategory.Id actionId) {
final Set<String> all = new HashSet<String>();
- for (final RefRight pr : state.getLocalRights(actionId)) {
+ for (final RefRight pr : state.getAllRights(actionId, true)) {
all.add(pr.getRefPattern());
}
- if (actionId.canInheritFromWildProject()) {
- for (final RefRight pr : state.getInheritedRights(actionId)) {
- all.add(pr.getRefPattern());
- }
- }
return all;
}
+
+ public boolean canRunUploadPack() {
+ return isAnyIncludedIn(uploadGroups, user.getEffectiveGroups());
+ }
+
+ public boolean canRunReceivePack() {
+ return isAnyIncludedIn(receiveGroups, user.getEffectiveGroups());
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 330fc6efa8..567ecfeeb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -28,6 +28,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -40,6 +42,7 @@ public class ProjectState {
private final AnonymousUser anonymousUser;
private final Project.NameKey wildProject;
private final ProjectCache projectCache;
+ private final ProjectControl.AssistedFactory projectControlFactory;
private final Project project;
private final Collection<RefRight> localRights;
@@ -51,11 +54,13 @@ public class ProjectState {
protected ProjectState(final AnonymousUser anonymousUser,
final ProjectCache projectCache,
@WildProjectName final Project.NameKey wildProject,
+ final ProjectControl.AssistedFactory projectControlFactory,
@Assisted final Project project,
@Assisted final Collection<RefRight> rights) {
this.anonymousUser = anonymousUser;
this.projectCache = projectCache;
this.wildProject = wildProject;
+ this.projectControlFactory = projectControlFactory;
this.project = project;
this.localRights = rights;
@@ -134,16 +139,59 @@ public class ProjectState {
}
/**
- * Get the rights this project inherits from the wild project.
+ * Utility class that is needed to filter overridden refrights
+ */
+ private static class Grant {
+ final AccountGroup.Id group;
+ final String pattern;
+
+ private Grant(AccountGroup.Id group, String pattern) {
+ this.group = group;
+ this.pattern = pattern;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ Grant grant = (Grant) o;
+ return group.equals(grant.group) && pattern.equals(grant.pattern);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = group.hashCode();
+ result = 31 * result + pattern.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * Get the rights this project has and inherits from the wild project.
*
* @param action the category requested.
+ * @param dropOverridden whether to remove inherited permissions in case if we have a
+ * local one that matches (action,group,ref)
* @return immutable collection of rights for the requested category.
*/
- public Collection<RefRight> getInheritedRights(ApprovalCategory.Id action) {
+ public Collection<RefRight> getAllRights(ApprovalCategory.Id action, boolean dropOverridden) {
+ Collection<RefRight> rights = new LinkedList<RefRight>(getLocalRights(action));
if (action.canInheritFromWildProject()) {
- return filter(getInheritedRights(), action);
+ rights.addAll(filter(getInheritedRights(), action));
+ }
+ if (dropOverridden) {
+ Set<Grant> grants = new HashSet<Grant>();
+ Iterator<RefRight> iter = rights.iterator();
+ while (iter.hasNext()) {
+ RefRight right = iter.next();
+
+ Grant grant = new Grant(right.getAccountGroupId(), right.getRefPattern());
+ if (grants.contains(grant)) {
+ iter.remove();
+ } else {
+ grants.add(grant);
+ }
+ }
}
- return Collections.emptyList();
+ return Collections.unmodifiableCollection(rights);
}
/** Is this the special wild project which manages inherited rights? */
@@ -160,7 +208,7 @@ public class ProjectState {
}
public ProjectControl controlFor(final CurrentUser user) {
- return new ProjectControl(user, this);
+ return projectControlFactory.create(user, this);
}
private static Collection<RefRight> filter(Collection<RefRight> all,
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 46fd90e7c7..7a27835460 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
@@ -32,8 +32,11 @@ 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.reviewdb.SystemConfig;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
import dk.brics.automaton.RegExp;
@@ -50,6 +53,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -60,13 +64,22 @@ import java.util.regex.Pattern;
/** Manages access control for Git references (aka branches, tags). */
public class RefControl {
+ public interface Factory {
+ RefControl create(ProjectControl projectControl, String ref);
+ }
+
+ private final SystemConfig systemConfig;
private final ProjectControl projectControl;
private final String refName;
private Boolean canForgeAuthor;
private Boolean canForgeCommitter;
- RefControl(final ProjectControl projectControl, String ref) {
+ @Inject
+ protected RefControl(final SystemConfig systemConfig,
+ @Assisted final ProjectControl projectControl,
+ @Assisted String ref) {
+ this.systemConfig = systemConfig;
if (isRE(ref)) {
ref = shortestExample(ref);
@@ -302,38 +315,35 @@ public class RefControl {
* @param groups The groups of the user
* @return The allowed value for this ref for all the specified groups
*/
- public int allowedValueForRef(Set<AccountGroup.Id> groups) {
- int val = Integer.MIN_VALUE;
+ private boolean allowedValueForRef(Set<AccountGroup.Id> groups, short level) {
for (RefRight right : rights) {
- if (groups.contains(right.getAccountGroupId())) {
- val = Math.max(right.getMaxValue(), val);
+ if (groups.contains(right.getAccountGroupId())
+ && right.getMaxValue() >= level) {
+ return true;
}
}
- return val;
+ return false;
}
}
boolean canPerform(ApprovalCategory.Id actionId, short level) {
final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups();
- int val = Integer.MIN_VALUE;
List<RefRight> allRights = new ArrayList<RefRight>();
- allRights.addAll(getLocalRights(actionId));
-
- if (actionId.canInheritFromWildProject()) {
- allRights.addAll(getInheritedRights(actionId));
- }
+ allRights.addAll(getAllRights(actionId));
SortedMap<String, RefRightsForPattern> perPatternRights =
sortedRightsByPattern(allRights);
for (RefRightsForPattern right : perPatternRights.values()) {
- val = Math.max(val, right.allowedValueForRef(groups));
- if (val >= level || right.containsExclusive()) {
- return val >= level;
+ if (right.allowedValueForRef(groups, level)) {
+ return true;
+ }
+ if (right.containsExclusive() && !actionId.equals(OWN)) {
+ break;
}
}
- return val >= level;
+ return false;
}
/**
@@ -464,12 +474,9 @@ public class RefControl {
return rights;
}
- private List<RefRight> getLocalRights(ApprovalCategory.Id actionId) {
- return filter(getProjectState().getLocalRights(actionId));
- }
-
- private List<RefRight> getInheritedRights(ApprovalCategory.Id actionId) {
- return filter(getProjectState().getInheritedRights(actionId));
+ private List<RefRight> getAllRights(ApprovalCategory.Id actionId) {
+ final List<RefRight> allRefRights = filter(getProjectState().getAllRights(actionId, true));
+ return resolveOwnerGroups(allRefRights);
}
/**
@@ -484,8 +491,7 @@ public class RefControl {
*/
public List<RefRight> getApplicableRights(final ApprovalCategory.Id id) {
List<RefRight> l = new ArrayList<RefRight>();
- l.addAll(getLocalRights(id));
- l.addAll(getInheritedRights(id));
+ l.addAll(getAllRights(id));
SortedMap<String, RefRightsForPattern> perPatternRights =
sortedRightsByPattern(l);
List<RefRight> applicable = new ArrayList<RefRight>();
@@ -498,6 +504,47 @@ public class RefControl {
return Collections.unmodifiableList(applicable);
}
+ /**
+ * Resolves all refRights which assign privileges to the 'Project Owners'
+ * group. All other refRights stay unchanged.
+ *
+ * @param refRights refRights to be resolved
+ * @return the resolved refRights
+ */
+ private List<RefRight> resolveOwnerGroups(final List<RefRight> refRights) {
+ final List<RefRight> resolvedRefRights =
+ new ArrayList<RefRight>(refRights.size());
+ for (final RefRight refRight : refRights) {
+ resolvedRefRights.addAll(resolveOwnerGroups(refRight));
+ }
+ return resolvedRefRights;
+ }
+
+ /**
+ * Checks if the given refRight assigns privileges to the 'Project Owners'
+ * group.
+ * If yes, resolves the 'Project Owners' group to the concrete groups that
+ * own the project and creates new refRights for the concrete owner groups
+ * which are returned.
+ * If no, the given refRight is returned unchanged.
+ *
+ * @param refRight refRight
+ * @return the resolved refRights
+ */
+ private Set<RefRight> resolveOwnerGroups(final RefRight refRight) {
+ final Set<RefRight> resolvedRefRights = new HashSet<RefRight>();
+ if (refRight.getAccountGroupId().equals(systemConfig.ownerGroupId)) {
+ for (final AccountGroup.Id ownerGroup : getProjectState().getOwners()) {
+ if (!ownerGroup.equals(systemConfig.ownerGroupId)) {
+ resolvedRefRights.add(new RefRight(refRight, ownerGroup));
+ }
+ }
+ } else {
+ resolvedRefRights.add(refRight);
+ }
+ return resolvedRefRights;
+ }
+
private List<RefRight> filter(Collection<RefRight> all) {
List<RefRight> mine = new ArrayList<RefRight>(all.size());
for (RefRight right : all) {
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
index ae48fdc6f9..1083d6ae30 100644
--- 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
@@ -28,7 +28,7 @@ class BranchPredicate extends OperatorPredicate<ChangeData> {
BranchPredicate(Provider<ReviewDb> dbProvider, String branch) {
super(ChangeQueryBuilder.FIELD_BRANCH, branch);
this.dbProvider = dbProvider;
- this.shortName = new Branch.NameKey(null, branch).getShortName();
+ this.shortName = branch;
}
@Override
@@ -37,7 +37,8 @@ class BranchPredicate extends OperatorPredicate<ChangeData> {
if (change == null) {
return false;
}
- return shortName.equals(change.getDest().getShortName());
+ return change.getDest().get().startsWith(Branch.R_HEADS)
+ && shortName.equals(change.getDest().getShortName());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 930b2d6960..b4965f040e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -32,13 +32,16 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ChangeData {
private final Change.Id legacyId;
private Change change;
private Collection<PatchSet> patches;
private Collection<PatchSetApproval> approvals;
+ private Map<PatchSet.Id,Collection<PatchSetApproval>> approvalsMap;
private Collection<PatchSetApproval> currentApprovals;
private String[] currentFiles;
private Collection<PatchLineComment> comments;
@@ -175,6 +178,23 @@ public class ChangeData {
return approvals;
}
+ public Map<PatchSet.Id,Collection<PatchSetApproval>> approvalsMap(Provider<ReviewDb> db)
+ throws OrmException {
+ if (approvalsMap == null) {
+ Collection<PatchSetApproval> all = approvals(db);
+ approvalsMap = new HashMap<PatchSet.Id,Collection<PatchSetApproval>>(all.size());
+ for (PatchSetApproval psa : all) {
+ Collection<PatchSetApproval> c = approvalsMap.get(psa.getPatchSetId());
+ if (c == null) {
+ c = new ArrayList<PatchSetApproval>();
+ approvalsMap.put(psa.getPatchSetId(), c);
+ }
+ c.add(psa);
+ }
+ }
+ return approvalsMap;
+ }
+
public Collection<PatchLineComment> comments(Provider<ReviewDb> db)
throws OrmException {
if (comments == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index eccb2e8239..5cd05a8ee2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -27,6 +27,7 @@ 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.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.query.IntPredicate;
@@ -75,6 +76,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
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_MESSAGE = "message";
public static final String FIELD_OWNER = "owner";
public static final String FIELD_PROJECT = "project";
public static final String FIELD_REF = "ref";
@@ -102,6 +104,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
final ApprovalTypes approvalTypes;
final Project.NameKey wildProjectName;
final PatchListCache patchListCache;
+ final GitRepositoryManager repoManager;
@Inject
Arguments(Provider<ReviewDb> dbProvider,
@@ -112,7 +115,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
AccountResolver accountResolver, GroupCache groupCache,
AuthConfig authConfig, ApprovalTypes approvalTypes,
@WildProjectName Project.NameKey wildProjectName,
- PatchListCache patchListCache) {
+ PatchListCache patchListCache,
+ GitRepositoryManager repoManager) {
this.dbProvider = dbProvider;
this.rewriter = rewriter;
this.userFactory = userFactory;
@@ -124,6 +128,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
this.approvalTypes = approvalTypes;
this.wildProjectName = wildProjectName;
this.patchListCache = patchListCache;
+ this.repoManager = repoManager;
}
}
@@ -238,21 +243,29 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> project(String name) {
+ if (name.startsWith("^"))
+ return new RegexProjectPredicate(args.dbProvider, name);
return new ProjectPredicate(args.dbProvider, name);
}
@Operator
public Predicate<ChangeData> branch(String name) {
+ if (name.startsWith("^"))
+ return new RegexBranchPredicate(args.dbProvider, name);
return new BranchPredicate(args.dbProvider, name);
}
@Operator
public Predicate<ChangeData> topic(String name) {
+ if (name.startsWith("^"))
+ return new RegexTopicPredicate(args.dbProvider, name);
return new TopicPredicate(args.dbProvider, name);
}
@Operator
public Predicate<ChangeData> ref(String ref) {
+ if (ref.startsWith("^"))
+ return new RegexRefPredicate(args.dbProvider, ref);
return new RefPredicate(args.dbProvider, ref);
}
@@ -276,6 +289,11 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
}
@Operator
+ public Predicate<ChangeData> message(String text) {
+ return new MessagePredicate(args.dbProvider, args.repoManager, text);
+ }
+
+ @Operator
public Predicate<ChangeData> starredby(String who)
throws QueryParseException, OrmException {
Account account = args.accountResolver.find(who);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index 904bc3cb70..6fbd060125 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -38,7 +38,7 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
new ChangeQueryBuilder.Arguments( //
new InvalidProvider<ReviewDb>(), //
new InvalidProvider<ChangeQueryRewriter>(), //
- null, null, null, null, null, null, null, null, null), null));
+ null, null, null, null, null, null, null, null, null, null), null));
private final Provider<ReviewDb> dbProvider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
new file mode 100644
index 0000000000..0490127a37
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -0,0 +1,124 @@
+// 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.Project;
+import com.google.gerrit.reviewdb.RevId;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.MessageRevFilter;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * Predicate to match changes that contains specified text in commit messages
+ * body.
+ */
+public class MessagePredicate extends OperatorPredicate<ChangeData> {
+
+ private static final Logger log =
+ LoggerFactory.getLogger(MessagePredicate.class);
+
+ private final Provider<ReviewDb> db;
+ private final GitRepositoryManager repoManager;
+ private final RevFilter rFilter;
+
+ public MessagePredicate(Provider<ReviewDb> db,
+ GitRepositoryManager repoManager, String text) {
+ super(ChangeQueryBuilder.FIELD_MESSAGE, text);
+ this.db = db;
+ this.repoManager = repoManager;
+ this.rFilter = MessageRevFilter.create(text);
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ final PatchSet patchSet = object.currentPatchSet(db);
+
+ if (patchSet == null) {
+ return false;
+ }
+
+ final RevId revision = patchSet.getRevision();
+
+ if (revision == null) {
+ return false;
+ }
+
+ final AnyObjectId objectId = ObjectId.fromString(revision.get());
+
+ if (objectId == null) {
+ return false;
+ }
+
+ final Change change = object.change(db);
+
+ if (change == null) {
+ return false;
+ }
+
+ final Project.NameKey projectName = change.getProject();
+
+ if (projectName == null) {
+ return false;
+ }
+
+ try {
+ final Repository repo = repoManager.openRepository(projectName.get());
+ try {
+ final RevWalk rw = new RevWalk(repo);
+ try {
+ return rFilter.include(rw, rw.parseCommit(objectId));
+ } finally {
+ rw.release();
+ }
+ } finally {
+ repo.close();
+ }
+ } catch (RepositoryNotFoundException e) {
+ log.error("Repository \"" + projectName.get() + "\" unknown.", e);
+ } catch (MissingObjectException e) {
+ log.error(projectName.get() + "\" commit does not exist.", e);
+ } catch (IncorrectObjectTypeException e) {
+ log.error(projectName.get() + "\" revision is not a commit.", e);
+ } catch (IOException e) {
+ log.error("Could not search for commit message in \"" + projectName.get()
+ + "\" repository.", e);
+ }
+
+ return false;
+ }
+
+ @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
index 3433fa9a8c..6c9fbc0379 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -20,6 +20,7 @@ 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.PatchSetAttribute;
import com.google.gerrit.server.events.QueryStats;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
@@ -69,6 +70,7 @@ public class QueryProcessor {
private OutputFormat outputFormat = OutputFormat.TEXT;
private boolean includePatchSets;
private boolean includeCurrentPatchSet;
+ private boolean includeApprovals;
private OutputStream outputStream = DisabledOutputStream.INSTANCE;
private PrintWriter out;
@@ -91,6 +93,10 @@ public class QueryProcessor {
includeCurrentPatchSet = on;
}
+ public void setIncludeApprovals(boolean on) {
+ includeApprovals = on;
+ }
+
public void setOutput(OutputStream out, OutputFormat fmt) {
this.outputStream = out;
this.outputFormat = fmt;
@@ -151,7 +157,8 @@ public class QueryProcessor {
eventFactory.addTrackingIds(c, d.trackingIds(db));
if (includePatchSets) {
- eventFactory.addPatchSets(c, d.patches(db));
+ eventFactory.addPatchSets(c, d.patches(db),
+ includeApprovals ? d.approvalsMap(db) : null);
}
if (includeCurrentPatchSet) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java
new file mode 100644
index 0000000000..a18d43ae9b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java
@@ -0,0 +1,60 @@
+// 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;
+
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+class RegexBranchPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final RunAutomaton pattern;
+
+ RegexBranchPredicate(Provider<ReviewDb> dbProvider, String re) {
+ super(ChangeQueryBuilder.FIELD_BRANCH, re);
+
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ this.dbProvider = dbProvider;
+ this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+ return change.getDest().get().startsWith(Branch.R_HEADS)
+ && pattern.run(change.getDest().getShortName());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
new file mode 100644
index 0000000000..c35b66e2cc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.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.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;
+
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+class RegexProjectPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final RunAutomaton pattern;
+
+ RegexProjectPredicate(Provider<ReviewDb> dbProvider, String re) {
+ super(ChangeQueryBuilder.FIELD_PROJECT, re);
+
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ this.dbProvider = dbProvider;
+ this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
+ }
+
+ @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 pattern.run(p.get());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
new file mode 100644
index 0000000000..e9e9958f39
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
@@ -0,0 +1,58 @@
+// 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;
+
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+class RegexRefPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final RunAutomaton pattern;
+
+ RegexRefPredicate(Provider<ReviewDb> dbProvider, String re) {
+ super(ChangeQueryBuilder.FIELD_REF, re);
+
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ this.dbProvider = dbProvider;
+ this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null) {
+ return false;
+ }
+ return pattern.run(change.getDest().get());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
new file mode 100644
index 0000000000..e16088cf3a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -0,0 +1,58 @@
+// 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;
+
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+class RegexTopicPredicate extends OperatorPredicate<ChangeData> {
+ private final Provider<ReviewDb> dbProvider;
+ private final RunAutomaton pattern;
+
+ RegexTopicPredicate(Provider<ReviewDb> dbProvider, String re) {
+ super(ChangeQueryBuilder.FIELD_TOPIC, re);
+
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ this.dbProvider = dbProvider;
+ this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
+ }
+
+ @Override
+ public boolean match(final ChangeData object) throws OrmException {
+ Change change = object.change(dbProvider);
+ if (change == null || change.getTopic() == null) {
+ return false;
+ }
+ return pattern.run(change.getTopic());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 302e22b813..9a266abb6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -150,12 +150,23 @@ public class SchemaCreator {
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(batchUsers)));
+ final AccountGroup owners =
+ new AccountGroup(new AccountGroup.NameKey("Project Owners"),
+ new AccountGroup.Id(c.nextAccountGroupId()));
+ owners.setDescription("Any owner of the project");
+ owners.setOwnerGroupId(admin.getId());
+ owners.setType(AccountGroup.Type.SYSTEM);
+ c.accountGroups().insert(Collections.singleton(owners));
+ c.accountGroupNames().insert(
+ Collections.singleton(new AccountGroupName(owners)));
+
final SystemConfig s = SystemConfig.create();
s.registerEmailPrivateKey = SignedToken.generateRandomKey();
s.adminGroupId = admin.getId();
s.anonymousGroupId = anonymous.getId();
s.registeredGroupId = registered.getId();
s.batchUsersGroupId = batchUsers.getId();
+ s.ownerGroupId = owners.getId();
s.wildProjectName = DEFAULT_WILD_NAME;
try {
s.sitePath = site_path.getCanonicalPath();
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 2f8d4ca86b..b7328b5394 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_40.class;
+ private static final Class<? extends SchemaVersion> C = Schema_47.class;
public static class Module extends AbstractModule {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java
new file mode 100644
index 0000000000..508db43665
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.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_41 extends SchemaVersion {
+ @Inject
+ Schema_41(Provider<Schema_40> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.java
new file mode 100644
index 0000000000..83bca7b837
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.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_42 extends SchemaVersion {
+ @Inject
+ Schema_42(Provider<Schema_41> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.java
new file mode 100644
index 0000000000..0edb7e5ca3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.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_43 extends SchemaVersion {
+ @Inject
+ Schema_43(Provider<Schema_42> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.java
new file mode 100644
index 0000000000..4ab1986a72
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.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_44 extends SchemaVersion {
+ @Inject
+ Schema_44(Provider<Schema_43> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.java
new file mode 100644
index 0000000000..e37e87de01
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.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_45 extends SchemaVersion {
+ @Inject
+ Schema_45(Provider<Schema_44> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java
new file mode 100644
index 0000000000..8730b4e943
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java
@@ -0,0 +1,62 @@
+// 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.AccountGroup;
+import com.google.gerrit.reviewdb.AccountGroupName;
+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.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collections;
+
+public class Schema_46 extends SchemaVersion {
+
+ @Inject
+ Schema_46(final Provider<Schema_45> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException,
+ OrmException {
+ AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
+
+ // update system_config
+ final Connection connection = ((JdbcSchema) db).getConnection();
+ final Statement stmt = connection.createStatement();
+ stmt.execute("UPDATE system_config SET OWNER_GROUP_ID = " + groupId.get());
+ final ResultSet resultSet =
+ stmt.executeQuery("SELECT ADMIN_GROUP_ID FROM system_config");
+ resultSet.next();
+ final int adminGroupId = resultSet.getInt(1);
+
+ // create 'Project Owners' group
+ AccountGroup.NameKey nameKey = new AccountGroup.NameKey("Project Owners");
+ AccountGroup group = new AccountGroup(nameKey, groupId);
+ group.setType(AccountGroup.Type.SYSTEM);
+ group.setOwnerGroupId(new AccountGroup.Id(adminGroupId));
+ group.setDescription("Any owner of the project");
+ AccountGroupName gn = new AccountGroupName(group);
+ db.accountGroupNames().insert(Collections.singleton(gn));
+ db.accountGroups().insert(Collections.singleton(group));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.java
new file mode 100644
index 0000000000..124cc0232c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.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_47 extends SchemaVersion {
+ @Inject
+ Schema_47(Provider<Schema_46> prior) {
+ super(prior);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java
index dd29f0478e..6700393251 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java
@@ -33,6 +33,7 @@ public abstract class CategoryFunction {
all.put(MaxWithBlock.NAME, new MaxWithBlock());
all.put(MaxNoBlock.NAME, new MaxNoBlock());
all.put(NoOpFunction.NAME, new NoOpFunction());
+ all.put(NoBlock.NAME, new NoBlock());
}
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/NoBlock.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/NoBlock.java
new file mode 100644
index 0000000000..a08981703a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/NoBlock.java
@@ -0,0 +1,34 @@
+// 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.workflow;
+
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.server.CurrentUser;
+
+/** A function that does nothing. */
+public class NoBlock extends CategoryFunction {
+ public static String NAME = "NoBlock";
+
+ @Override
+ public void run(final ApprovalType at, final FunctionState state) {
+ state.valid(at, true);
+ }
+
+ @Override
+ public boolean isValid(final CurrentUser user, final ApprovalType at,
+ final FunctionState state) {
+ return true;
+ }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm
new file mode 100644
index 0000000000..20529b222a
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Abandoned.vm template will determine the contents of the email related
+## to a change being abandoned. It is a ChangeEmail: see ChangeSubject.vm and
+## ChangeFooter.vm.
+##
+$fromName has abandoned this change.
+
+Change subject: $change.subject
+......................................................................
+
+
+#if ($coverLetter)
+$coverLetter
+
+#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
new file mode 100644
index 0000000000..5b74453450
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The ChangeFooter.vm template will determine the contents of the footer
+## text that will be appended to ALL emails related to changes.
+##
+--
+#if ($email.changeUrl)
+To view, visit $email.changeUrl
+#set ($notblank = 1)
+#end
+#if ($email.settingsUrl)
+To unsubscribe, visit $email.settingsUrl
+#set ($notblank = 1)
+#end
+#if ($notblank == 1)
+
+#end
+Gerrit-MessageType: $messageClass
+Gerrit-Change-Id: $changeId
+Gerrit-PatchSet: $patchSet.patchSetId
+Gerrit-Project: $projectName
+Gerrit-Branch: $branch.shortName
+Gerrit-Owner: $email.getNameEmailFor($change.owner)
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
new file mode 100644
index 0000000000..24cc23cf0c
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
@@ -0,0 +1,37 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The ChangeSubject.vm template will determine the contents of the email
+## subject line for ALL emails related to changes.
+##
+#macro(elipses $length $str)
+#if($str.length() > $length)${str.substring(0,$length)}...#else$str#end
+#end
+Change in $projectName.replaceAll('/.*/', '...')[$branch.shortName]: #elipses(60, $change.subject)
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
new file mode 100644
index 0000000000..4ad355b86a
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Comment.vm template will determine the contents of the email related to
+## a user submitting comments on changes. It is a ChangeEmail: see
+## ChangeSubject.vm and ChangeFooter.vm.
+##
+#if ($email.coverLetter || $email.inlineComments)
+$fromName has posted comments on this change.
+
+Change subject: $change.subject
+......................................................................
+
+
+#if ($email.coverLetter)
+$email.coverLetter
+
+#end
+#if($email.inlineComments)$email.inlineComments#end
+#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm
new file mode 100644
index 0000000000..dfe3d92f3a
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The MergeFail.vm template will determine the contents of the email related
+## to a failure upon attempting to merge a change to the head. It is a
+## ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
+##
+$fromName has submitted this change and it FAILED to merge.
+
+Change subject: $change.subject
+......................................................................
+
+
+#if ($email.coverLetter)
+$email.coverLetter
+
+#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm
new file mode 100644
index 0000000000..296a37a215
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Merged.vm template will determine the contents of the email related to
+## a change successfully merged to the head. It is a ChangeEmail: see
+## ChangeSubject.vm and ChangeFooter.vm.
+##
+#macro(elipses $length $str)
+#if($str.length() > $length)${str.substring(0,$length)}...#else$str#end
+#end
+$fromName has submitted this change and it was merged.
+
+Change subject: $change.subject
+......................................................................
+
+
+$email.changeDetail$email.approvals
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
new file mode 100644
index 0000000000..a9a5fed091
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The NewChange.vm template will determine the contents of the email related
+## to a user submitting a new change for review. It is a ChangeEmail: see
+## ChangeSubject.vm and ChangeFooter.vm.
+##
+#if($email.reviewerNames)
+Hello $StringUtils.join($email.reviewerNames, ' ,'),
+
+I'd like you to do a code review.#if($email.changeUrl) Please visit
+
+ $email.changeUrl
+
+to review the following change.
+#end
+#else
+$fromName has uploaded a new change for review.
+#end
+
+Change subject: $change.subject
+......................................................................
+
+$email.changeDetail
+ git pull ssh://$email.sshHost/$projectName $patchSet.refName
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm
new file mode 100644
index 0000000000..c1de87e657
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm
@@ -0,0 +1,49 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The RegisterNewEmail.vm template will determine the contents of the email
+## related to registering new email accounts.
+##
+Welcome to Gerrit Code Review at ${email.gerritHost}.
+
+To add a verified email address to your user account, please
+click on the following link:
+
+$email.gerritUrl#VE,$email.emailRegistrationToken
+
+If you have received this mail in error, you do not need to take any
+action to cancel the account. The account will not be activated, and
+you will not receive any further emails.
+
+If clicking the link above does not work, copy and paste the URL in a
+new browser window instead.
+
+This is a send-only email address. Replies to this message will not
+be read or answered.
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm
new file mode 100644
index 0000000000..356f02ee4d
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm
@@ -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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The ReplacePatchSet.vm template will determine the contents of the email
+## related to a user submitting a new patchset for a change. It is a
+## ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
+##
+#if($email.reviewerNames)
+Hello $StringUtils.join($email.reviewerNames, ' ,'),
+
+I'd like you to reexamine a change.#if($email.changeUrl) Please visit
+
+ $email.changeUrl
+
+to look at the new patch set (#$patchSet.patchSetId).
+#end
+#else
+$fromName has uploaded a new patch set (#$patchSet.patchSetId).
+#end
+
+Change subject: $change.subject
+......................................................................
+
+$email.changeDetail
+ git pull ssh://$email.sshHost/$projectName $patchSet.refName
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
new file mode 100644
index 0000000000..386a6d1a6b
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -0,0 +1,76 @@
+// 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.config;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.*;
+
+import junit.framework.TestCase;
+
+import java.util.concurrent.TimeUnit;
+
+public class ConfigUtilTest extends TestCase {
+ public void testTimeUnit() {
+ assertEquals(ms(2, MILLISECONDS), parse("2ms"));
+ assertEquals(ms(200, MILLISECONDS), parse("200 milliseconds"));
+
+ assertEquals(ms(2, SECONDS), parse("2s"));
+ assertEquals(ms(231, SECONDS), parse("231sec"));
+ assertEquals(ms(1, SECONDS), parse("1second"));
+ assertEquals(ms(300, SECONDS), parse("300 seconds"));
+
+ assertEquals(ms(2, MINUTES), parse("2m"));
+ assertEquals(ms(2, MINUTES), parse("2min"));
+ assertEquals(ms(1, MINUTES), parse("1 minute"));
+ assertEquals(ms(10, MINUTES), parse("10 minutes"));
+
+ assertEquals(ms(5, HOURS), parse("5h"));
+ assertEquals(ms(5, HOURS), parse("5hr"));
+ assertEquals(ms(1, HOURS), parse("1hour"));
+ assertEquals(ms(48, HOURS), parse("48hours"));
+
+ assertEquals(ms(5, HOURS), parse("5 h"));
+ assertEquals(ms(5, HOURS), parse("5 hr"));
+ assertEquals(ms(1, HOURS), parse("1 hour"));
+ assertEquals(ms(48, HOURS), parse("48 hours"));
+ assertEquals(ms(48, HOURS), parse("48 \t \r hours"));
+
+ assertEquals(ms(4, DAYS), parse("4d"));
+ assertEquals(ms(1, DAYS), parse("1day"));
+ assertEquals(ms(14, DAYS), parse("14days"));
+
+ assertEquals(ms(7, DAYS), parse("1w"));
+ assertEquals(ms(7, DAYS), parse("1week"));
+ assertEquals(ms(14, DAYS), parse("2w"));
+ assertEquals(ms(14, DAYS), parse("2weeks"));
+
+ assertEquals(ms(30, DAYS), parse("1mon"));
+ assertEquals(ms(30, DAYS), parse("1month"));
+ assertEquals(ms(60, DAYS), parse("2mon"));
+ assertEquals(ms(60, DAYS), parse("2months"));
+
+ assertEquals(ms(365, DAYS), parse("1y"));
+ assertEquals(ms(365, DAYS), parse("1year"));
+ assertEquals(ms(365 * 2, DAYS), parse("2years"));
+ }
+
+ private static long ms(int cnt, TimeUnit unit) {
+ return MILLISECONDS.convert(cnt, unit);
+ }
+
+ private static long parse(String string) {
+ return ConfigUtil.getTimeUnit(string, 1, MILLISECONDS);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 8c26705226..81b1762efd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.project;
import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
+import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
@@ -50,7 +51,7 @@ import java.util.Set;
public class RefControlTest extends TestCase {
public void testOwnerProject() {
- local.add(grant(OWN, admin, "refs/*", 1));
+ grant(local, OWN, admin, "refs/*", 1);
ProjectControl uBlah = user(devs);
ProjectControl uAdmin = user(devs, admin);
@@ -60,8 +61,8 @@ public class RefControlTest extends TestCase {
}
public void testBranchDelegation1() {
- local.add(grant(OWN, admin, "refs/*", 1));
- local.add(grant(OWN, devs, "refs/heads/x/*", 1));
+ grant(local, OWN, admin, "refs/*", 1);
+ grant(local, OWN, devs, "refs/heads/x/*", 1);
ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner());
@@ -76,9 +77,9 @@ public class RefControlTest extends TestCase {
}
public void testBranchDelegation2() {
- local.add(grant(OWN, admin, "refs/*", 1));
- local.add(grant(OWN, devs, "refs/heads/x/*", 1));
- local.add(grant(OWN, fixers, "-refs/heads/x/y/*", 1));
+ grant(local, OWN, admin, "refs/*", 1);
+ grant(local, OWN, devs, "refs/heads/x/*", 1);
+ grant(local, OWN, fixers, "-refs/heads/x/y/*", 1);
ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner());
@@ -86,9 +87,9 @@ public class RefControlTest extends TestCase {
assertOwner("refs/heads/x/*", uDev);
assertOwner("refs/heads/x/y", uDev);
+ assertOwner("refs/heads/x/y/*", uDev);
assertNotOwner("refs/*", uDev);
assertNotOwner("refs/heads/master", uDev);
- assertNotOwner("refs/heads/x/y/*", uDev);
ProjectControl uFix = user(fixers);
assertFalse("not owner", uFix.isOwner());
@@ -103,11 +104,11 @@ public class RefControlTest extends TestCase {
}
public void testInheritRead_SingleBranchDeniesUpload() {
- inherited.add(grant(READ, registered, "refs/*", 1, 2));
- local.add(grant(READ, registered, "-refs/heads/foobar", 1, 1));
+ grant(parent, READ, registered, "refs/*", 1, 2);
+ grant(local, READ, registered, "-refs/heads/foobar", 1);
ProjectControl u = user();
- assertTrue("can upload", u.canUploadToAtLeastOneRef());
+ assertTrue("can upload", u.canPushToAtLeastOneRef());
assertTrue("can upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
@@ -117,11 +118,11 @@ public class RefControlTest extends TestCase {
}
public void testInheritRead_SingleBranchDoesNotOverrideInherited() {
- inherited.add(grant(READ, registered, "refs/*", 1, 2));
- local.add(grant(READ, registered, "refs/heads/foobar", 1, 1));
+ grant(parent, READ, registered, "refs/*", 1, 2);
+ grant(local, READ, registered, "refs/heads/foobar", 1);
ProjectControl u = user();
- assertTrue("can upload", u.canUploadToAtLeastOneRef());
+ assertTrue("can upload", u.canPushToAtLeastOneRef());
assertTrue("can upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
@@ -130,12 +131,54 @@ public class RefControlTest extends TestCase {
u.controlForRef("refs/heads/foobar").canUpload());
}
+ public void testInheritRead_OverrideWithDeny() {
+ grant(parent, READ, registered, "refs/*", 1);
+ grant(local, READ, registered, "refs/*", 0);
+
+ ProjectControl u = user();
+ assertFalse("can't read", u.isVisible());
+ }
+
+ public void testInheritRead_AppendWithDenyOfRef() {
+ grant(parent, READ, registered, "refs/*", 1);
+ grant(local, READ, registered, "refs/heads/*", 0);
+
+ ProjectControl u = user();
+ assertTrue("can read", u.isVisible());
+ assertTrue("can read", u.controlForRef("refs/master").isVisible());
+ assertTrue("can read", u.controlForRef("refs/tags/foobar").isVisible());
+ assertTrue("no master", u.controlForRef("refs/heads/master").isVisible());
+ }
+
+ public void testInheritRead_OverridesAndDeniesOfRef() {
+ grant(parent, READ, registered, "refs/*", 1);
+ grant(local, READ, registered, "refs/*", 0);
+ grant(local, READ, registered, "refs/heads/*", -1, 1);
+
+ ProjectControl u = user();
+ assertTrue("can read", u.isVisible());
+ assertFalse("can't read", u.controlForRef("refs/foobar").isVisible());
+ assertFalse("can't read", u.controlForRef("refs/tags/foobar").isVisible());
+ assertTrue("can read", u.controlForRef("refs/heads/foobar").isVisible());
+ }
+
+ public void testInheritSubmit_OverridesAndDeniesOfRef() {
+ grant(parent, SUBMIT, registered, "refs/*", 1);
+ grant(local, SUBMIT, registered, "refs/*", 0);
+ grant(local, SUBMIT, registered, "refs/heads/*", -1, 1);
+
+ ProjectControl u = user();
+ assertFalse("can't submit", u.controlForRef("refs/foobar").canSubmit());
+ assertFalse("can't submit", u.controlForRef("refs/tags/foobar").canSubmit());
+ assertTrue("can submit", u.controlForRef("refs/heads/foobar").canSubmit());
+ }
+
public void testCannotUploadToAnyRef() {
- inherited.add(grant(READ, registered, "refs/*", 1, 1));
- local.add(grant(READ, devs, "refs/heads/*",1,2));
+ grant(parent, READ, registered, "refs/*", 1);
+ grant(local, READ, devs, "refs/heads/*", 1, 2);
ProjectControl u = user();
- assertFalse("cannot upload", u.canUploadToAtLeastOneRef());
+ assertFalse("cannot upload", u.canPushToAtLeastOneRef());
assertFalse("cannot upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
}
@@ -143,22 +186,26 @@ public class RefControlTest extends TestCase {
// -----------------------------------------------------------------------
- private final Project.NameKey projectNameKey = new Project.NameKey("test");
+ private final Project.NameKey local = new Project.NameKey("test");
+ private final Project.NameKey parent = new Project.NameKey("parent");
private final AccountGroup.Id admin = new AccountGroup.Id(1);
private final AccountGroup.Id anonymous = new AccountGroup.Id(2);
private final AccountGroup.Id registered = new AccountGroup.Id(3);
+ private final AccountGroup.Id owners = new AccountGroup.Id(4);
- private final AccountGroup.Id devs = new AccountGroup.Id(4);
- private final AccountGroup.Id fixers = new AccountGroup.Id(5);
+ private final AccountGroup.Id devs = new AccountGroup.Id(5);
+ private final AccountGroup.Id fixers = new AccountGroup.Id(6);
+ private final SystemConfig systemConfig;
private final AuthConfig authConfig;
private final AnonymousUser anonymousUser;
public RefControlTest() {
- final SystemConfig systemConfig = SystemConfig.create();
+ systemConfig = SystemConfig.create();
systemConfig.adminGroupId = admin;
systemConfig.anonymousGroupId = anonymous;
systemConfig.registeredGroupId = registered;
+ systemConfig.ownerGroupId = owners;
systemConfig.batchUsersGroupId = anonymous;
try {
byte[] bin = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
@@ -183,14 +230,14 @@ public class RefControlTest extends TestCase {
anonymousUser = injector.getInstance(AnonymousUser.class);
}
- private List<RefRight> local;
- private List<RefRight> inherited;
+ private List<RefRight> localRights;
+ private List<RefRight> inheritedRights;
@Override
protected void setUp() throws Exception {
super.setUp();
- local = new ArrayList<RefRight>();
- inherited = new ArrayList<RefRight>();
+ localRights = new ArrayList<RefRight>();
+ inheritedRights = new ArrayList<RefRight>();
}
private static void assertOwner(String ref, ProjectControl u) {
@@ -201,32 +248,48 @@ public class RefControlTest extends TestCase {
assertFalse("NOT OWN " + ref, u.controlForRef(ref).isOwner());
}
- private RefRight grant(ApprovalCategory.Id categoryId, AccountGroup.Id group,
- String ref, int maxValue) {
- return grant(categoryId, group, ref, maxValue, maxValue);
+ private void grant(Project.NameKey project, ApprovalCategory.Id categoryId,
+ AccountGroup.Id group, String ref, int maxValue) {
+ grant(project, categoryId, group, ref, maxValue, maxValue);
}
- private RefRight grant(ApprovalCategory.Id categoryId, AccountGroup.Id group,
+ private void grant(Project.NameKey project, ApprovalCategory.Id categoryId, AccountGroup.Id group,
String ref, int minValue, int maxValue) {
RefRight right =
- new RefRight(new RefRight.Key(projectNameKey, new RefPattern(ref),
+ new RefRight(new RefRight.Key(project, new RefPattern(ref),
categoryId, group));
right.setMinValue((short) minValue);
right.setMaxValue((short) maxValue);
- return right;
+
+ if (project == parent) {
+ inheritedRights.add(right);
+ } else if (project == local) {
+ localRights.add(right);
+ } else {
+ fail("Unknown project key: " + project);
+ }
}
private ProjectControl user(AccountGroup.Id... memberOf) {
- return new ProjectControl(new MockUser(memberOf), newProjectState());
+ RefControl.Factory refControlFactory = new RefControl.Factory() {
+ @Override
+ public RefControl create(final ProjectControl projectControl, final String ref) {
+ return new RefControl(systemConfig, projectControl, ref);
+ }
+ };
+ return new ProjectControl(Collections.<AccountGroup.Id> emptySet(),
+ Collections.<AccountGroup.Id> emptySet(), refControlFactory,
+ new MockUser(memberOf), newProjectState());
}
private ProjectState newProjectState() {
ProjectCache projectCache = null;
Project.NameKey wildProject = null;
+ ProjectControl.AssistedFactory projectControlFactory = null;
ProjectState ps =
- new ProjectState(anonymousUser, projectCache, wildProject, new Project(
- projectNameKey), local);
- ps.setInheritedRights(inherited);
+ new ProjectState(anonymousUser, projectCache, wildProject,
+ projectControlFactory, new Project(parent), localRights);
+ ps.setInheritedRights(inheritedRights);
return ps;
}
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 1644d22ed2..e05ce2e65f 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
@@ -163,6 +163,7 @@ public class SchemaCreatorTest extends TestCase {
assertEquals("-- All Projects --", all.getName());
assertFalse(all.isUseContributorAgreements());
assertFalse(all.isUseSignedOffBy());
+ assertFalse(all.isRequireChangeID());
} finally {
c.close();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
index 4738802e73..0d1b6f9b86 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
@@ -19,11 +19,11 @@ import com.google.gerrit.server.util.HostPlatform;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.lib.Commit;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@@ -144,7 +144,7 @@ public class CommitMsgHookTest extends HookTestCase {
"Change-Id: I7fc3876fee63c766a2063df97fbe04a2dddd8d7c\n",//
call("a\n"));
- final DirCacheBuilder builder = DirCache.lock(repository).builder();
+ final DirCacheBuilder builder = repository.lockDirCache().builder();
builder.add(file("A"));
assertTrue(builder.commit());
@@ -386,35 +386,41 @@ public class CommitMsgHookTest extends HookTestCase {
}
private DirCacheEntry file(final String name) throws IOException {
- final DirCacheEntry e = new DirCacheEntry(name);
- e.setFileMode(FileMode.REGULAR_FILE);
- e.setObjectId(writer().writeBlob(Constants.encode(name)));
- return e;
+ final ObjectInserter oi = repository.newObjectInserter();
+ try {
+ final DirCacheEntry e = new DirCacheEntry(name);
+ e.setFileMode(FileMode.REGULAR_FILE);
+ e.setObjectId(oi.insert(Constants.OBJ_BLOB, Constants.encode(name)));
+ oi.flush();
+ return e;
+ } finally {
+ oi.release();
+ }
}
private void setHEAD() throws Exception {
- final ObjectWriter ow = writer();
- final Commit commit = new Commit(repository);
- commit.setTreeId(DirCache.newInCore().writeTree(ow));
- commit.setAuthor(author);
- commit.setCommitter(committer);
- commit.setMessage("test\n");
- final ObjectId commitId = ow.writeCommit(commit);
-
- final RefUpdate ref = repository.updateRef(Constants.HEAD);
- ref.setNewObjectId(commitId);
- switch (ref.forceUpdate()) {
- case NEW:
- case FAST_FORWARD:
- case FORCED:
- case NO_CHANGE:
- break;
- default:
- fail(Constants.HEAD + " did not change: " + ref.getResult());
+ final ObjectInserter oi = repository.newObjectInserter();
+ try {
+ final CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {}));
+ commit.setAuthor(author);
+ commit.setCommitter(committer);
+ commit.setMessage("test\n");
+ ObjectId commitId = oi.insert(commit);
+
+ final RefUpdate ref = repository.updateRef(Constants.HEAD);
+ ref.setNewObjectId(commitId);
+ switch (ref.forceUpdate()) {
+ case NEW:
+ case FAST_FORWARD:
+ case FORCED:
+ case NO_CHANGE:
+ break;
+ default:
+ fail(Constants.HEAD + " did not change: " + ref.getResult());
+ }
+ } finally {
+ oi.release();
}
}
-
- private ObjectWriter writer() {
- return new ObjectWriter(repository);
- }
}
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 2bb4713c7e..32bcf57c54 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-sshd</artifactId>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 6c96b702f1..977d20914f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -135,6 +135,11 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
}
}
+ if (!createUser(sd, key).getAccount().isActive()) {
+ sd.authenticationError(username, "inactive-account");
+ return false;
+ }
+
return success(username, session, sd, createUser(sd, key));
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
index 95c3416df1..431b4209bd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
@@ -14,15 +14,28 @@
package com.google.gerrit.sshd;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gerrit.sshd.SshScope.Context;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
import org.apache.sshd.common.Factory;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.SystemReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
/**
* Dummy shell which prints a message and terminates.
@@ -32,41 +45,135 @@ import java.io.OutputStream;
* cannot continue further.
*/
class NoShell implements Factory<Command> {
+ private final Provider<SendMessage> shell;
+
+ @Inject
+ NoShell(Provider<SendMessage> shell) {
+ this.shell = shell;
+ }
+
public Command create() {
- return new Command() {
- private InputStream in;
- private OutputStream out;
- private OutputStream err;
- private ExitCallback exit;
-
- public void setInputStream(final InputStream in) {
- this.in = in;
- }
+ return shell.get();
+ }
- public void setOutputStream(final OutputStream out) {
- this.out = out;
- }
+ static class SendMessage implements Command, SessionAware {
+ private final Provider<MessageFactory> messageFactory;
+
+ private InputStream in;
+ private OutputStream out;
+ private OutputStream err;
+ private ExitCallback exit;
+ private Context context;
+
+ @Inject
+ SendMessage(Provider<MessageFactory> messageFactory) {
+ this.messageFactory = messageFactory;
+ }
+
+ public void setInputStream(final InputStream in) {
+ this.in = in;
+ }
+
+ public void setOutputStream(final OutputStream out) {
+ this.out = out;
+ }
+
+ public void setErrorStream(final OutputStream err) {
+ this.err = err;
+ }
- public void setErrorStream(final OutputStream err) {
- this.err = err;
+ public void setExitCallback(final ExitCallback callback) {
+ this.exit = callback;
+ }
+
+ public void setSession(final ServerSession session) {
+ this.context = new Context(session.getAttribute(SshSession.KEY), "");
+ }
+
+ public void start(final Environment env) throws IOException {
+ Context old = SshScope.set(context);
+ String message;
+ try {
+ message = messageFactory.get().getMessage();
+ } finally {
+ SshScope.set(old);
}
+ err.write(Constants.encodeASCII(message.toString()));
+ err.flush();
+
+ in.close();
+ out.close();
+ err.close();
+ exit.onExit(127);
+ }
+
+ public void destroy() {
+ }
+ }
+
+ static class MessageFactory {
+ private final IdentifiedUser user;
+ private final SshInfo sshInfo;
+ private final Provider<String> urlProvider;
- public void setExitCallback(final ExitCallback callback) {
- this.exit = callback;
+ @Inject
+ MessageFactory(IdentifiedUser user, SshInfo sshInfo,
+ @CanonicalWebUrl Provider<String> urlProvider) {
+ this.user = user;
+ this.sshInfo = sshInfo;
+ this.urlProvider = urlProvider;
+ }
+
+ String getMessage() {
+ StringBuilder msg = new StringBuilder();
+
+ msg.append("\r\n");
+ msg.append(" **** Welcome to Gerrit Code Review ****\r\n");
+ msg.append("\r\n");
+
+ Account account = user.getAccount();
+ String name = account.getFullName();
+ if (name == null || name.isEmpty()) {
+ name = user.getUserName();
}
+ msg.append(" Hi ");
+ msg.append(name);
+ msg.append(", you have successfully connected over SSH.");
+ msg.append("\r\n");
+ msg.append("\r\n");
- public void start(final Environment env) throws IOException {
- err.write(Constants.encodeASCII("gerrit: no shell available\r\n"));
- err.flush();
+ msg.append(" Unfortunately, interactive shells are disabled.\r\n");
+ msg.append(" To clone a hosted Git repository, use:\r\n");
+ msg.append("\r\n");
- in.close();
- out.close();
- err.close();
- exit.onExit(127);
+ if (!sshInfo.getHostKeys().isEmpty()) {
+ String host = sshInfo.getHostKeys().get(0).getHost();
+ if (host.startsWith("*:")) {
+ host = getGerritHost() + host.substring(1);
+ }
+
+ msg.append(" git clone ssh://");
+ msg.append(user.getUserName());
+ msg.append("@");
+ msg.append(host);
+ msg.append("/");
+ msg.append("REPOSITORY_NAME.git");
+ msg.append("\r\n");
}
- public void destroy() {
+ msg.append("\r\n");
+ return msg.toString();
+ }
+
+ private String getGerritHost() {
+ String url = urlProvider.get();
+ if (url != null) {
+ try {
+ return new URL(url).getHost();
+ } catch (MalformedURLException e) {
+ }
}
- };
+ return SystemReader.getInstance().getHostname();
+ }
}
}
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 2636ff2597..dee55a5922 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
@@ -14,7 +14,12 @@
package com.google.gerrit.sshd;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.gerrit.common.Version;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.IdGenerator;
@@ -58,8 +63,11 @@ import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.FileSystemFactory;
+import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.ForwardingFilter;
import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.channel.ChannelDirectTcpip;
@@ -116,7 +124,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private volatile IoAcceptor acceptor;
@Inject
- SshDaemon(final CommandFactory commandFactory,
+ SshDaemon(final CommandFactory commandFactory, final NoShell noShell,
final PublickeyAuthenticator userAuth,
final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
@GerritServerConfig final Config cfg, final SshLog sshLog) {
@@ -126,6 +134,25 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true);
keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
+ getProperties().put(SERVER_IDENTIFICATION,
+ "GerritCodeReview_" + Version.getVersion() //
+ + " (" + super.getVersion() + ")");
+
+ getProperties().put(MAX_AUTH_REQUESTS,
+ String.valueOf(cfg.getInt("sshd", "maxAuthTries", 6)));
+
+ getProperties().put(
+ AUTH_TIMEOUT,
+ String.valueOf(MILLISECONDS.convert(ConfigUtil.getTimeUnit(cfg, "sshd",
+ null, "loginGraceTime", 120, SECONDS), SECONDS)));
+
+ final int maxConnectionsPerUser =
+ cfg.getInt("sshd", "maxConnectionsPerUser", 64);
+ if (0 < maxConnectionsPerUser) {
+ getProperties().put(MAX_CONCURRENT_SESSIONS,
+ String.valueOf(maxConnectionsPerUser));
+ }
+
if (SecurityUtils.isBouncyCastleRegistered()) {
initProviderBouncyCastle();
} else {
@@ -136,12 +163,13 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
initSignatures();
initChannels();
initForwardingFilter();
+ initFileSystemFactory();
initSubsystems();
initCompression();
initUserAuth(userAuth);
setKeyPairProvider(hostKeyProvider);
setCommandFactory(commandFactory);
- setShellFactory(new NoShell());
+ setShellFactory(noShell);
setSessionFactory(new SessionFactory() {
@Override
protected ServerSession createSession(final IoSession io)
@@ -485,4 +513,17 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
});
}
+
+ private void initFileSystemFactory() {
+ setFileSystemFactory(new FileSystemFactory() {
+ @Override
+ public FileSystemView createFileSystemView(String userName) {
+ return new FileSystemView() {
+ @Override
+ public SshFile getFile(String file) {
+ return null;
+ }};
+ }
+ });
+ }
}
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 40d271b975..c9e9b72ee7 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
@@ -26,6 +26,7 @@ import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritRequestModule;
+import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.ssh.SshInfo;
@@ -77,7 +78,6 @@ public class SshModule extends FactoryModule {
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);
- bind(TransferConfig.class);
install(new DefaultCommandModule());
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java
index a78ced5a1f..c27ad0db32 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java
@@ -21,7 +21,7 @@ import net.sf.ehcache.Statistics;
import net.sf.ehcache.config.CacheConfiguration;
import org.apache.sshd.server.Environment;
-import org.eclipse.jgit.lib.WindowCacheStatAccessor;
+import org.eclipse.jgit.storage.file.WindowCacheStatAccessor;
import java.io.PrintWriter;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java
index 6d027d71f2..5269e4342f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java
@@ -14,12 +14,14 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.common.CollectionsUtil;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Project.SubmitType;
+import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.ProjectCreatorGroups;
import com.google.gerrit.server.config.ProjectOwnerGroups;
@@ -31,14 +33,21 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RefUpdate.Result;
import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -46,6 +55,8 @@ import java.util.Set;
/** Create a new project. **/
final class CreateProject extends BaseCommand {
+ private static final Logger log = LoggerFactory.getLogger(CreateProject.class);
+
@Option(name = "--name", required = true, aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created")
private String projectName;
@@ -55,6 +66,9 @@ final class CreateProject extends BaseCommand {
@Option(name = "--parent", aliases = {"-p"}, metaVar = "NAME", usage = "parent project")
private ProjectControl newParent;
+ @Option(name = "--permissions-only", usage = "create project for use only as parent")
+ private boolean permissionsOnly;
+
@Option(name = "--description", aliases = {"-d"}, metaVar = "DESC", usage = "description of project")
private String projectDescription = "";
@@ -68,10 +82,19 @@ final class CreateProject extends BaseCommand {
@Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
private boolean signedOffBy;
+ @Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
+ private boolean contentMerge;
+
+ @Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
+ private boolean requireChangeID;
+
@Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", usage = "initial branch name\n"
+ "(default: master)")
private String branch = Constants.MASTER;
+ @Option(name = "--empty-commit", usage = "to create initial empty commit")
+ private boolean createEmptyCommit;
+
@Inject
private ReviewDb db;
@@ -92,6 +115,10 @@ final class CreateProject extends BaseCommand {
@Inject
private ReplicationQueue rq;
+ @Inject
+ @GerritPersonIdent
+ private PersonIdent serverIdent;
+
@Override
public void start(final Environment env) {
startThread(new CommandRunnable() {
@@ -104,18 +131,30 @@ final class CreateProject extends BaseCommand {
try {
validateParameters();
- Repository repo = repoManager.createRepository(projectName);
- repo.create(true);
+ if (!permissionsOnly) {
+ final Repository repo = repoManager.createRepository(projectName);
+ try {
+ repo.create(true);
- RefUpdate u = repo.updateRef(Constants.HEAD);
- u.disableRefLog();
- u.link(branch);
+ RefUpdate u = repo.updateRef(Constants.HEAD);
+ u.disableRefLog();
+ u.link(branch);
- repoManager.setProjectDescription(projectName, projectDescription);
+ repoManager
+ .setProjectDescription(projectName, projectDescription);
- createProject();
+ final Project.NameKey project = new Project.NameKey(projectName);
+ rq.replicateNewProject(project, branch);
+
+ if (createEmptyCommit) {
+ createEmptyCommit(repo, project, branch);
+ }
+ } finally {
+ repo.close();
+ }
+ }
- rq.replicateNewProject(new Project.NameKey(projectName), branch);
+ createProject();
} catch (Exception e) {
p.print("Error when trying to create project: " + e.getMessage()
+ "\n");
@@ -126,24 +165,36 @@ final class CreateProject extends BaseCommand {
});
}
- /**
- * Checks if any of the elements in the first collection can be found in the
- * second collection.
- *
- * @param findAnyOfThese which elements to look for.
- * @param inThisCollection where to look for them.
- * @param <E> type of the elements in question.
- * @return {@code true} if any of the elements in {@code findAnyOfThese} can
- * be found in {@code inThisCollection}, {@code false} otherwise.
- */
- private static <E> boolean isAnyIncludedIn(Collection<E> findAnyOfThese,
- Collection<E> inThisCollection) {
- for (E findThisItem : findAnyOfThese) {
- if (inThisCollection.contains(findThisItem)) {
- return true;
+ private void createEmptyCommit(final Repository repo,
+ final Project.NameKey project, final String ref) throws IOException {
+ ObjectInserter oi = repo.newObjectInserter();
+ try {
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {}));
+ cb.setCommitter(serverIdent);
+ cb.setAuthor(cb.getCommitter());
+ cb.setMessage("Initial empty repository");
+
+ ObjectId id = oi.insert(cb);
+ oi.flush();
+
+ RefUpdate ru = repo.updateRef(Constants.HEAD);
+ ru.setNewObjectId(id);
+ final Result result = ru.update();
+ switch (result) {
+ case NEW:
+ rq.scheduleUpdate(project, ref);
+ break;
+ default: {
+ throw new IOException(result.name());
+ }
}
+ } catch (IOException e) {
+ log.error("Cannot create empty commit for " + projectName, e);
+ throw e;
+ } finally {
+ oi.release();
}
- return false;
}
private void createProject() throws OrmException {
@@ -166,6 +217,8 @@ final class CreateProject extends BaseCommand {
newProject.setSubmitType(submitType);
newProject.setUseContributorAgreements(contributorAgreements);
newProject.setUseSignedOffBy(signedOffBy);
+ newProject.setUseContentMerge(contentMerge);
+ newProject.setRequireChangeID(requireChangeID);
if (newParent != null) {
newProject.setParent(newParent.getProject().getNameKey());
}
@@ -179,7 +232,7 @@ final class CreateProject extends BaseCommand {
projectName.substring(0, projectName.length() - ".git".length());
}
- if (!isAnyIncludedIn(currentUser.getEffectiveGroups(), projectCreatorGroups)) {
+ if (!CollectionsUtil.isAnyIncludedIn(currentUser.getEffectiveGroups(), projectCreatorGroups)) {
throw new Failure(1, "fatal: Not permitted to create " + projectName);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index d2bf26cb4e..fecdc59e6d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -43,6 +43,14 @@ class Query extends BaseCommand {
processor.setIncludePatchSets(on);
}
+ @Option(name = "--all-approvals", usage = "Include information about all patch sets and approvals")
+ void setApprovals(boolean on) {
+ if (on) {
+ processor.setIncludePatchSets(on);
+ }
+ processor.setIncludeApprovals(on);
+ }
+
@Argument(index = 0, required = true, multiValued = true, metaVar = "QUERY", usage = "Query to execute")
private List<String> query;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index eb5d1da68b..1996ce466a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -17,16 +17,23 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.sshd.AbstractGitCommand;
-import com.google.gerrit.sshd.TransferConfig;
import com.google.inject.Inject;
+import org.eclipse.jgit.errors.UnpackException;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.RefFilter;
import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
/** Receives change upload over SSH using the Git receive-pack protocol. */
@@ -58,6 +65,10 @@ final class Receive extends AbstractGitCommand {
@Override
protected void runImpl() throws IOException, Failure {
+ if (!projectControl.canRunReceivePack()) {
+ throw new Failure(1, "fatal: receive-pack not permitted on this server");
+ }
+
final ReceiveCommits receive = factory.create(projectControl, repo);
ReceiveCommits.Capable r = receive.canUpload();
@@ -78,6 +89,49 @@ final class Receive extends AbstractGitCommand {
rp.receive(in, out, err);
} catch (InterruptedIOException err) {
throw new Failure(128, "fatal: client IO read/write timeout", err);
+
+ } catch (UnpackException badStream) {
+ // This may have been triggered by branch level access controls.
+ // Log what the heck is going on, as detailed as we can.
+ //
+ StringBuilder msg = new StringBuilder();
+ msg.append("Unpack error on project \""
+ + projectControl.getProject().getName() + "\":\n");
+
+ msg.append(" RefFilter: " + rp.getRefFilter());
+ if (rp.getRefFilter() == RefFilter.DEFAULT) {
+ msg.append("DEFAULT");
+ } else if (rp.getRefFilter() instanceof VisibleRefFilter) {
+ msg.append("VisibleRefFilter");
+ } else {
+ msg.append(rp.getRefFilter().getClass());
+ }
+ msg.append("\n");
+
+ if (rp.getRefFilter() instanceof VisibleRefFilter) {
+ Map<String, Ref> adv = rp.getAdvertisedRefs();
+ msg.append(" Visible references (" + adv.size() + "):\n");
+ for (Ref ref : adv.values()) {
+ msg.append(" - " + ref.getObjectId().abbreviate(8).name() + " "
+ + ref.getName() + "\n");
+ }
+
+ List<Ref> hidden = new ArrayList<Ref>();
+ for (Ref ref : rp.getRepository().getAllRefs().values()) {
+ if (!adv.containsKey(ref.getName())) {
+ hidden.add(ref);
+ }
+ }
+
+ msg.append(" Hidden references (" + hidden.size() + "):\n");
+ for (Ref ref : hidden) {
+ msg.append(" - " + ref.getObjectId().abbreviate(8).name() + " "
+ + ref.getName() + "\n");
+ }
+ }
+
+ IOException detail = new IOException(msg.toString(), badStream);
+ throw new Failure(128, "fatal: Unpack error, check server log", detail);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
index eca797d54b..bcc9d19d32 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -15,9 +15,9 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.sshd.AbstractGitCommand;
-import com.google.gerrit.sshd.TransferConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -36,10 +36,15 @@ final class Upload extends AbstractGitCommand {
@Override
protected void runImpl() throws IOException, Failure {
+ if (!projectControl.canRunUploadPack()) {
+ throw new Failure(1, "fatal: upload-pack not permitted on this server");
+ }
+
final UploadPack up = new UploadPack(repo);
if (!projectControl.allRefsAreVisible()) {
up.setRefFilter(new VisibleRefFilter(repo, projectControl, db.get()));
}
+ up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
try {
up.upload(in, out, err);
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index 20f28e10f0..6db5241a94 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index 220ae47978..39d3ce09d9 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 4bfa17e266..87cfaa5f54 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
</parent>
<artifactId>gerrit-war</artifactId>
diff --git a/gerrit-war/src/main/resources/log4j.properties b/gerrit-war/src/main/resources/log4j.properties
index f7a5cdcf1b..5993790122 100644
--- a/gerrit-war/src/main/resources/log4j.properties
+++ b/gerrit-war/src/main/resources/log4j.properties
@@ -57,3 +57,7 @@ log4j.logger.net.sf.ehcache=WARN
log4j.logger.com.mchange.v2.c3p0=WARN
log4j.logger.com.mchange.v2.resourcepool=WARN
log4j.logger.com.mchange.v2.sql=WARN
+
+# Silence non-critical messages from Velocity
+#
+log4j.logger.velocity=WARN
diff --git a/pom.xml b/pom.xml
index 633cecbbcb..0f59058161 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.4-SNAPSHOT</version>
+ <version>2.1-SNAPSHOT</version>
<name>Gerrit Code Review - Parent</name>
<url>http://code.google.com/p/gerrit/</url>
@@ -46,7 +46,7 @@ limitations under the License.
</issueManagement>
<properties>
- <jgitVersion>0.8.4.89-ge2f5716</jgitVersion>
+ <jgitVersion>0.9.3.133-gaa09599</jgitVersion>
<gwtormVersion>1.1.4</gwtormVersion>
<gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.2</gwtexpuiVersion>
@@ -478,7 +478,13 @@ limitations under the License.
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
- <version>0.4.0-r897374</version>
+ <version>0.5.1-r1031886</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity</artifactId>
+ <version>1.6.4</version>
</dependency>
<dependency>