From f4d20684559f9a7c09a5ac29ffd35f457a73ce5f Mon Sep 17 00:00:00 2001 From: Sumeet Wilkhu Date: Tue, 5 Mar 2019 19:27:39 -0500 Subject: [PATCH] Adding changes for git repository branch as an option. --- .gitignore | 15 + LICENSE.txt | 21 + README.md | 11 + pom.xml | 292 +++++++++ .../JenkinsFilesHelper.java | 31 + .../SCMManagerFactory.java | 43 ++ .../SCMManipulator.java | 301 ++++++++++ .../ScmSyncConfigurationBusiness.java | 410 +++++++++++++ .../ScmSyncConfigurationDataProvider.java | 119 ++++ .../ScmSyncConfigurationPlugin.java | 557 ++++++++++++++++++ .../ScmSyncConfigurationStatusManager.java | 77 +++ .../exceptions/LoggableException.java | 33 ++ .../ScmSyncConfigurationFilter.java | 73 +++ .../ScmSyncConfigurationItemListener.java | 97 +++ .../ScmSyncConfigurationPageDecorator.java | 25 + .../ScmSyncConfigurationSaveableListener.java | 32 + .../model/BotherTimeout.java | 75 +++ .../model/ChangeSet.java | 119 ++++ .../scm_sync_configuration/model/Commit.java | 92 +++ .../model/MessageWeight.java | 12 + .../scm_sync_configuration/model/Path.java | 108 ++++ .../model/ScmContext.java | 47 ++ .../model/WeightedMessage.java | 26 + .../scm_sync_configuration/scms/SCM.java | 168 ++++++ .../scms/SCMCredentialConfiguration.java | 41 ++ .../scms/ScmSyncGitSCM.java | 33 ++ .../scms/ScmSyncNoSCM.java | 28 + .../scms/ScmSyncSubversionSCM.java | 153 +++++ .../git/gitexe/FixedGitStatusConsumer.java | 205 +++++++ .../git/gitexe/ScmSyncGitAddCommand.java | 118 ++++ .../git/gitexe/ScmSyncGitCheckInCommand.java | 133 +++++ .../git/gitexe/ScmSyncGitExeScmProvider.java | 38 ++ .../git/gitexe/ScmSyncGitRemoveCommand.java | 72 +++ .../git/gitexe/ScmSyncGitStatusCommand.java | 39 ++ .../git/gitexe/ScmSyncGitUtils.java | 193 ++++++ .../strategies/AbstractScmSyncStrategy.java | 135 +++++ .../strategies/ScmSyncStrategy.java | 70 +++ .../BasicPluginsConfigScmSyncStrategy.java | 53 ++ .../impl/JenkinsConfigScmSyncStrategy.java | 60 ++ .../impl/JobConfigScmSyncStrategy.java | 48 ++ .../impl/ManualIncludesScmSyncStrategy.java | 34 ++ .../impl/UserConfigScmSyncStrategy.java | 62 ++ ...lassAndFileConfigurationEntityMatcher.java | 29 + .../model/ConfigurationEntityMatcher.java | 49 ++ ...JobOrFolderConfigurationEntityMatcher.java | 106 ++++ .../strategies/model/PageMatcher.java | 22 + .../model/PatternsEntityMatcher.java | 81 +++ .../transactions/AtomicTransaction.java | 38 ++ .../transactions/ScmTransaction.java | 69 +++ .../transactions/ThreadedTransaction.java | 17 + .../utils/Checksums.java | 30 + .../ScmSyncConfigurationXStreamConverter.java | 168 ++++++ .../xstream/migration/AbstractMigrator.java | 110 ++++ .../xstream/migration/DefaultSSCPOJO.java | 68 +++ .../ScmSyncConfigurationDataMigrator.java | 15 + .../migration/ScmSyncConfigurationPOJO.java | 26 + .../ScmSyncConfigurationXStreamReader.java | 13 + .../xstream/migration/v0/InitialMigrator.java | 52 ++ .../v0/V0ScmSyncConfigurationPOJO.java | 8 + .../xstream/migration/v1/V0ToV1Migrator.java | 29 + .../v1/V1ScmSyncConfigurationPOJO.java | 8 + .../resources/META-INF/plexus/components.xml | 48 ++ .../Messages.properties | 4 + .../ScmSyncConfigurationPlugin/config.jelly | 56 ++ .../config_fr.properties | 15 + .../help/manualSynchronizationIncludes.jelly | 25 + ...anualSynchronizationIncludes_fr.properties | 13 + .../scms/git/config.jelly | 12 + .../scms/git/config_fr.properties | 1 + .../scms/git/url-help.jelly | 32 + .../scms/git/url-help.properties | 24 + .../scms/git/url-help_fr.properties | 24 + .../scms/none/config.jelly | 3 + .../scms/svn/config.jelly | 8 + .../scms/svn/config_fr.properties | 1 + .../scms/svn/url-help.jelly | 35 ++ .../scms/svn/url-help.properties | 25 + .../scms/svn/url-help_fr.properties | 25 + .../footer.jelly | 80 +++ .../scm_sync_configuration/reload.jelly | 20 + src/main/resources/index.jelly | 3 + .../help/commitMessagePattern-help.html | 12 + .../help/commitMessagePattern-help_fr.html | 12 + src/main/webapp/help/displayStatus-help.html | 5 + .../webapp/help/displayStatus-help_fr.html | 5 + .../webapp/help/noUserCommitMessage-help.html | 5 + .../help/noUserCommitMessage-help_fr.html | 5 + .../webapp/help/reloadScmConfig-help.html | 41 ++ .../webapp/help/reloadScmConfig-help_fr.html | 41 ++ src/main/webapp/help/scm-help.html | 5 + src/main/webapp/help/scm-help_fr.html | 5 + .../scm-sync-configuration-page-handler.js | 169 ++++++ .../basic/ScmSyncConfigurationBasicTest.java | 62 ++ .../data/CurrentVersionCompatibilityTest.java | 62 ++ .../data/V0_0_2CompatibilityTest.java | 79 +++ .../data/V0_0_3CompatibilityTest.java | 62 ++ .../data/V0_0_4CompatibilityTest.java | 30 + .../repository/HudsonExtensionsGitTest.java | 11 + .../HudsonExtensionsSubversionTest.java | 96 +++ .../repository/HudsonExtensionsTest.java | 469 +++++++++++++++ .../repository/InitRepositoryGitTest.java | 13 + .../InitRepositorySubversionTest.java | 11 + .../repository/InitRepositoryTest.java | 94 +++ .../impl/JobConfigScmSyncStrategyTest.java | 63 ++ .../util/ScmSyncConfigurationBaseTest.java | 252 ++++++++ .../ScmSyncConfigurationPluginBaseTest.java | 41 ++ .../plugins/test/utils/DirectoryUtils.java | 92 +++ .../hudson/plugins/test/utils/PluginUtil.java | 19 + .../plugins/test/utils/scms/ScmUnderTest.java | 19 + .../test/utils/scms/ScmUnderTestGit.java | 35 ++ .../utils/scms/ScmUnderTestSubversion.java | 32 + .../config.xml | 31 + .../hudson.config.xml2 | 0 .../hudson.scm.SubversionSCM.xml | 0 .../jobs/config.xml | 21 + .../jobs/myFolder/config.xml | 21 + .../jobs/myFolder/jobs/myJob/config.xml | 21 + .../jobs/myJob/config.xml | 21 + .../jobs/myJob/config2.xml | 21 + .../nodeMonitors.xml | 0 .../scm-sync-configuration.xml | 5 + .../toto/config.xml | 21 + .../toto/hudson.config.xml | 0 .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../jobs/newFakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/.gitkeep | 0 .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/.gitkeep | 0 .../jobs/newFakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/newFakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/-newFakeJob/config.xml | 21 + .../jobs/fakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../jobs/new fake Job/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../hudsonRoot0.0.2BaseTemplate/config.xml | 31 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../scm-sync-configuration.xml | 2 + .../hudsonRoot0.0.3BaseTemplate/config.xml | 31 + .../scm-sync-configuration.xml | 5 + .../config.xml | 31 + .../scm-sync-configuration.xml | 2 + .../hudsonRootBaseTemplate/config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../scm-sync-configuration.xml | 5 + .../jobConfigStrategyTemplate/config.xml | 31 + .../hudson.tasks.Shell.xml | 5 + .../jobs/fakeJob/config.xml | 21 + .../jobs/fakeJob/modules/submodule/config.xml | 21 + .../scm-sync-configuration.xml | 5 + 187 files changed, 8686 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/SCMManagerFactory.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationSaveableListener.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/BotherTimeout.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/MessageWeight.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/ScmContext.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/SCMCredentialConfiguration.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncNoSCM.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/FixedGitStatusConsumer.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitAddCommand.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitCheckInCommand.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitExeScmProvider.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitRemoveCommand.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitStatusCommand.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitUtils.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/BasicPluginsConfigScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JenkinsConfigScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/ManualIncludesScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/UserConfigScmSyncStrategy.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ClassAndFileConfigurationEntityMatcher.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ConfigurationEntityMatcher.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/JobOrFolderConfigurationEntityMatcher.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PageMatcher.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PatternsEntityMatcher.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/transactions/AtomicTransaction.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/transactions/ThreadedTransaction.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/utils/Checksums.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/ScmSyncConfigurationXStreamConverter.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/AbstractMigrator.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/DefaultSSCPOJO.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationDataMigrator.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationPOJO.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationXStreamReader.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/InitialMigrator.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/V0ScmSyncConfigurationPOJO.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V0ToV1Migrator.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V1ScmSyncConfigurationPOJO.java create mode 100644 src/main/resources/META-INF/plexus/components.xml create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/Messages.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config_fr.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes_fr.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config_fr.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help_fr.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/none/config.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config_fr.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help_fr.properties create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator/footer.jelly create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/reload.jelly create mode 100644 src/main/resources/index.jelly create mode 100644 src/main/webapp/help/commitMessagePattern-help.html create mode 100644 src/main/webapp/help/commitMessagePattern-help_fr.html create mode 100644 src/main/webapp/help/displayStatus-help.html create mode 100644 src/main/webapp/help/displayStatus-help_fr.html create mode 100644 src/main/webapp/help/noUserCommitMessage-help.html create mode 100644 src/main/webapp/help/noUserCommitMessage-help_fr.html create mode 100644 src/main/webapp/help/reloadScmConfig-help.html create mode 100644 src/main/webapp/help/reloadScmConfig-help_fr.html create mode 100644 src/main/webapp/help/scm-help.html create mode 100644 src/main/webapp/help/scm-help_fr.html create mode 100644 src/main/webapp/scripts/scm-sync-configuration/scm-sync-configuration-page-handler.js create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/data/CurrentVersionCompatibilityTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_2CompatibilityTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_3CompatibilityTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_4CompatibilityTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryGitTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositorySubversionTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategyTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java create mode 100644 src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationPluginBaseTest.java create mode 100644 src/test/java/hudson/plugins/test/utils/DirectoryUtils.java create mode 100644 src/test/java/hudson/plugins/test/utils/PluginUtil.java create mode 100644 src/test/java/hudson/plugins/test/utils/scms/ScmUnderTest.java create mode 100644 src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestGit.java create mode 100644 src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestSubversion.java create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/config.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/hudson.config.xml2 create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/hudson.scm.SubversionSCM.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/config.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/config.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/jobs/myJob/config.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config2.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/nodeMonitors.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/scm-sync-configuration.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/config.xml create mode 100644 src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/hudson.config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/jobs/.gitkeep create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/.gitkeep create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/new fake Job/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/scm-sync-configuration.xml create mode 100644 src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/hudson.tasks.Shell.xml create mode 100644 src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/jobs/fakeJob/config.xml create mode 100644 src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/scm-sync-configuration.xml create mode 100644 src/test/resources/hudsonRoot0.0.2BaseTemplate/config.xml create mode 100644 src/test/resources/hudsonRoot0.0.2BaseTemplate/scm-sync-configuration.xml create mode 100644 src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/config.xml create mode 100644 src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/scm-sync-configuration.xml create mode 100644 src/test/resources/hudsonRoot0.0.3BaseTemplate/config.xml create mode 100644 src/test/resources/hudsonRoot0.0.3BaseTemplate/scm-sync-configuration.xml create mode 100644 src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/config.xml create mode 100644 src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/scm-sync-configuration.xml create mode 100644 src/test/resources/hudsonRootBaseTemplate/config.xml create mode 100644 src/test/resources/hudsonRootBaseTemplate/hudson.tasks.Shell.xml create mode 100644 src/test/resources/hudsonRootBaseTemplate/jobs/fakeJob/config.xml create mode 100644 src/test/resources/hudsonRootBaseTemplate/scm-sync-configuration.xml create mode 100644 src/test/resources/jobConfigStrategyTemplate/config.xml create mode 100644 src/test/resources/jobConfigStrategyTemplate/hudson.tasks.Shell.xml create mode 100644 src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/config.xml create mode 100644 src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/modules/submodule/config.xml create mode 100644 src/test/resources/jobConfigStrategyTemplate/scm-sync-configuration.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2eea90cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +target/ +work*/ + +# IntelliJ project files +*.iml +*.ipr +*.iws +.idea/ + +# eclipse project file +.factorypath +.settings +.classpath +.project +build/ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..5dcfddd9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010-, Frédéric Camblor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..6c1b8d80 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +Jenkins SCM Sync Configuration Plugin +===================== + +Read more: [https://wiki.jenkins-ci.org/display/JENKINS/SCM+Sync+configuration+plugin](https://wiki.jenkins-ci.org/display/JENKINS/SCM+Sync+configuration+plugin) + +# Build process +```bash +$ git clone https://bitbucket.equifax.com/scm/grm/scm-sync-configuration-plugin.git +$ mvn clean package +``` +This will generate scm-sync-configuration.hpi file under target/ directory. diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..daefa756 --- /dev/null +++ b/pom.xml @@ -0,0 +1,292 @@ + + 4.0.0 + + org.jenkins-ci.plugins + plugin + 1.609.1 + + + scm-sync-configuration + SCM Sync Configuration Plugin + 0.0.11-SNAPSHOT + hpi + http://wiki.jenkins-ci.org/display/JENKINS/SCM+Sync+configuration+plugin + SCM Sync Configuration Jenkins plugin is aimed at 2 main features : First, keep sync'ed your config.xml (and other ressources) jenkins files with a SCM repository (backup), Secondly, track changes (and author) made on every file with commit messages. + + + + MIT + http://www.opensource.org/licenses/mit-license.php + + + + + + fcamblor + Frederic Camblor + fcamblor+jenkinswiki@gmail.com + + + bpaquet + Bertrand Paquet + bertrand.paquet@gmail.com + + + rodrigc + Craig Rodrigues + rodrigc@FreeBSD.org + + + + + UTF-8 + 1.6.4 + + + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + + + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.gmaven + gmaven-plugin + [1.5,) + + generateTestStubs + testCompile + + + + + false + + + + + + org.codehaus.plexus + plexus-maven-plugin + [1.3.8,) + + merge-descriptors + + + + + false + + + + + + com.google.code.maven-replacer-plugin + maven-replacer-plugin + [1.3.2,) + + replace + + + + + false + + + + + + + + + maven-release-plugin + + 2.5.1 + + + true + true + + + + maven-surefire-plugin + + + -XX:MaxPermSize=128m + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + + org.codehaus.plexus + plexus-maven-plugin + 1.3.8 + + + merge + + + src/main/resources/META-INF/plexus/components.xml + + + generate-resources + + merge-descriptors + + + + + + + com.google.code.maven-replacer-plugin + maven-replacer-plugin + 1.3.2 + + + process-test-sources + + replace + + + + + + target/inject-tests/*.java + target/generated-test-sources/injected/*.java + + false + + + Map parameters = new HashMap(); + Map<String,String> parameters = new HashMap<String,String>(); + + + new org.jvnet.hudson.test.PluginAutomaticTestBuilder() + org.jvnet.hudson.test.PluginAutomaticTestBuilder + + + + + + + + + + + org.jenkins-ci.main + maven-plugin + 2.3 + + + + org.jvnet.hudson.plugins + subversion + + + + + org.jenkins-ci.main + jenkins-test-harness + ${project.parent.version} + + + + org.jvnet.hudson.plugins + subversion + + + + + + + + + org.sonatype.sisu + sisu-inject-plexus + 2.6.0 + + + + org.apache.maven.scm + maven-scm-manager-plexus + 1.9.5 + + + + org.codehaus.plexus + plexus-container-default + + + + + + org.jenkins-ci.plugins + subversion + 2.5.7 + + + + com.google.code.maven-scm-provider-svnjava + maven-scm-provider-svnjava + 2.1.1 + + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9.5 + + + + + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito + ${powermock.version} + test + + + + + scm:git:https://github.com/jenkinsci/scm-sync-configuration-plugin.git + scm:git:https://github.com/jenkinsci/scm-sync-configuration-plugin.git + https://github.com/jenkinsci/scm-sync-configuration-plugin + HEAD + + diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java new file mode 100644 index 00000000..da320a80 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java @@ -0,0 +1,31 @@ +package hudson.plugins.scm_sync_configuration; + +import java.io.File; + +import jenkins.model.Jenkins; + +public class JenkinsFilesHelper { + + public static String buildPathRelativeToHudsonRoot(File file) { + File jenkinsRoot = Jenkins.getInstance().getRootDir(); + String jenkinsRootPath = jenkinsRoot.getAbsolutePath(); + String fileAbsolutePath = file.getAbsolutePath(); + if (fileAbsolutePath.equals(jenkinsRootPath)) { + // Hmmm. Should never occur. + throw new IllegalArgumentException("Cannot build relative path to $JENKINS_HOME for $JENKINS_HOME itself; would be empty."); + } + if (!jenkinsRootPath.endsWith(File.separator)) { + jenkinsRootPath += File.separator; + } + if (!fileAbsolutePath.startsWith(jenkinsRootPath)) { + // Oops, the file is not relative to $JENKINS_HOME + return null; + } + String truncatedPath = fileAbsolutePath.substring(jenkinsRootPath.length()); + return truncatedPath.replace(File.separatorChar, '/'); + } + + public static File buildFileFromPathRelativeToHudsonRoot(String pathRelativeToJenkinsRoot){ + return new File(Jenkins.getInstance().getRootDir(), pathRelativeToJenkinsRoot); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManagerFactory.java b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManagerFactory.java new file mode 100644 index 00000000..1d892956 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManagerFactory.java @@ -0,0 +1,43 @@ +package hudson.plugins.scm_sync_configuration; + +import org.apache.maven.scm.manager.ScmManager; +import org.codehaus.plexus.DefaultPlexusContainer; +import org.codehaus.plexus.PlexusContainer; +import org.codehaus.plexus.PlexusContainerException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; + +public class SCMManagerFactory { + + private static final SCMManagerFactory INSTANCE = new SCMManagerFactory(); + + private PlexusContainer plexus = null; + + private SCMManagerFactory(){ + } + + public void start() throws PlexusContainerException { + if(plexus == null){ + this.plexus = new DefaultPlexusContainer(); + try { + // These will only be useful for Hudson v1.395 and under + // ... Since the use of sisu-plexus-inject will initialize + // everything in the constructor + PlexusContainer.class.getDeclaredMethod("initialize").invoke(this.plexus); + PlexusContainer.class.getDeclaredMethod("start").invoke(this.plexus); + } catch (Throwable e) { /* Don't do anything here ... initialize/start methods should be called prior to v1.395 ! */ } + } + } + + public ScmManager createScmManager() throws ComponentLookupException { + return (ScmManager)this.plexus.lookup(ScmManager.ROLE); + } + + public void stop() throws Exception { + this.plexus.dispose(); + this.plexus = null; + } + + public static SCMManagerFactory getInstance(){ + return INSTANCE; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java new file mode 100644 index 00000000..253df60a --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java @@ -0,0 +1,301 @@ +package hudson.plugins.scm_sync_configuration; + +import hudson.plugins.scm_sync_configuration.model.ScmContext; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import org.apache.maven.scm.*; +import org.apache.maven.scm.command.add.AddScmResult; +import org.apache.maven.scm.command.checkin.CheckInScmResult; +import org.apache.maven.scm.command.checkout.CheckOutScmResult; +import org.apache.maven.scm.command.remove.RemoveScmResult; +import org.apache.maven.scm.command.status.StatusScmResult; +import org.apache.maven.scm.command.update.UpdateScmResult; +import org.apache.maven.scm.manager.NoSuchScmProviderException; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.repository.ScmRepository; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Class providing atomic scm commands and wrapping calls to maven scm api + * with logging informations + * @author fcamblor + */ +public class SCMManipulator { + + private static final Logger LOGGER = Logger.getLogger(SCMManipulator.class.getName()); + + private final ScmManager scmManager; + private ScmRepository scmRepository = null; + private String scmSpecificFilename = null; + private String gitRepositoryBranch = null; + + public SCMManipulator(ScmManager _scmManager) { + this.scmManager = _scmManager; + } + + /** + * Will check if everything is settled up (useful before a scm manipulation) + * @param scmContext + * @param resetScmRepository + * @return + */ + public boolean scmConfigurationSettledUp(ScmContext scmContext, boolean resetScmRepository){ + String scmRepositoryUrl = scmContext.getScmRepositoryUrl(); + SCM scm = scmContext.getScm(); + gitRepositoryBranch = scmContext.getGitRepositoryBranch(); + if(scmRepositoryUrl == null || scm == null){ + return false; + } + + if(resetScmRepository){ + LOGGER.info("Creating scmRepository connection data .."); + this.scmRepository = scm.getConfiguredRepository(this.scmManager, scmRepositoryUrl); + try { + this.scmSpecificFilename = this.scmManager.getProviderByRepository(this.scmRepository).getScmSpecificFilename(); + } + catch(NoSuchScmProviderException e) { + LOGGER.throwing(ScmManager.class.getName(), "getScmSpecificFilename", e); + LOGGER.severe("[getScmSpecificFilename] Error while getScmSpecificFilename : "+e.getMessage()); + return false; + } + } + + return expectScmRepositoryInitiated(); + } + + private boolean expectScmRepositoryInitiated(){ + boolean scmRepositoryInitiated = this.scmRepository != null; + if(!scmRepositoryInitiated) { + LOGGER.warning("SCM Repository has not yet been initiated !"); + } + return scmRepositoryInitiated; + } + + public UpdateScmResult update(File root) throws ScmException { + return this.scmManager.update(scmRepository, new ScmFileSet(root)); + } + public boolean checkout(File checkoutDirectory){ + boolean checkoutOk = false; + + if(!expectScmRepositoryInitiated()){ + return checkoutOk; + } + + // Checkouting sources + LOGGER.fine("Checking out SCM files into ["+checkoutDirectory.getAbsolutePath()+"] ..."); + try { + ScmVersion version = new NewScmVersion(this.gitRepositoryBranch); + CheckOutScmResult result = scmManager.checkOut(this.scmRepository, new ScmFileSet(checkoutDirectory), version); + if(!result.isSuccess()){ + LOGGER.severe("[checkout] Error during checkout : "+result.getProviderMessage()+" || "+result.getCommandOutput()); + return checkoutOk; + } + checkoutOk = true; + } catch (ScmException e) { + LOGGER.throwing(ScmManager.class.getName(), "checkOut", e); + LOGGER.severe("[checkout] Error during checkout : "+e.getMessage()); + return checkoutOk; + } + + if(checkoutOk){ + LOGGER.fine("Checked out SCM files into ["+checkoutDirectory.getAbsolutePath()+"] !"); + } + + return checkoutOk; + } + + public List deleteHierarchy(File hierarchyToDelete){ + if(!expectScmRepositoryInitiated()){ + return null; + } + + File enclosingDirectory = hierarchyToDelete.getParentFile(); + + LOGGER.fine("Deleting SCM hierarchy ["+hierarchyToDelete.getAbsolutePath()+"] from SCM ..."); + + File commitFile = hierarchyToDelete; + while(! commitFile.isDirectory()) { + commitFile = commitFile.getParentFile(); + } + + try { + ScmFileSet deleteFileSet = new ScmFileSet(enclosingDirectory, hierarchyToDelete); + StatusScmResult checkForChanges = this.scmManager.status(scmRepository, deleteFileSet); + LOGGER.fine("Checking for changes on SCM hierarchy ["+hierarchyToDelete.getAbsolutePath()+"] from SCM ..."); + for (ScmFile changedFile : checkForChanges.getChangedFiles()) { + //check in this change as it affect our hierarchy + LOGGER.fine("[checkForChanges] Found changed file "+changedFile.toString()+", try to check-in..."); + CheckInScmResult checkedInChangedFile = scmManager.checkIn(scmRepository, new ScmFileSet(enclosingDirectory.getParentFile(), new File(changedFile.getPath())), "Check-In changes for "+changedFile.getPath()); + if(!checkedInChangedFile.isSuccess()){ + LOGGER.severe("[checkForChanges] Failed to check-in changed file ["+changedFile.getPath()+"]: "+checkedInChangedFile.getProviderMessage()); + } + } + RemoveScmResult removeResult = this.scmManager.remove(this.scmRepository, deleteFileSet, "Delete hierarchy "+hierarchyToDelete.getPath()); + if(!removeResult.isSuccess()){ + LOGGER.severe("[deleteHierarchy] Problem during remove : "+removeResult.getProviderMessage()); + return null; + } + + List filesToCommit = new ArrayList(); + filesToCommit.add(commitFile); + + filesToCommit = refineUpdatedFilesInScmResult(filesToCommit); + return filesToCommit; + } catch (ScmException e) { + LOGGER.throwing(ScmManager.class.getName(), "remove", e); + LOGGER.severe("[deleteHierarchy] Hierarchy deletion aborted : "+e.getMessage()); + return null; + } + } + + public List addFile(File scmRoot, String filePathRelativeToScmRoot){ + List synchronizedFiles = new ArrayList(); + + if(!expectScmRepositoryInitiated()){ + return synchronizedFiles; + } + + LOGGER.fine("Adding SCM file ["+filePathRelativeToScmRoot+"] ..."); + + try { + // Split every directory leading through modifiedFilePathRelativeToHudsonRoot + // and try add it in the scm + String[] pathChunks = filePathRelativeToScmRoot.split("\\\\|/"); + StringBuilder currentPath = new StringBuilder(); + for(int i=0; i", e); + LOGGER.warning("[addFile] Error while creating ScmFileset : "+e.getMessage()); + return synchronizedFiles; + } catch (ScmException e) { + LOGGER.throwing(ScmManager.class.getName(), "add", e); + LOGGER.warning("[addFile] Error while adding file : "+e.getMessage()); + return synchronizedFiles; + } + + + if(!synchronizedFiles.isEmpty()){ + LOGGER.fine("Added SCM files : "+Arrays.toString(synchronizedFiles.toArray(new File[0]))+" !"); + } + + return synchronizedFiles; + } + + private List refineUpdatedFilesInScmResult(List updatedFiles){ + List refinedUpdatedFiles = new ArrayList(); + + // Cannot use directly a List or List here, since result type will depend upon + // current scm api version + for (Object scmFile :updatedFiles) { + if(scmFile instanceof File){ + String checkoutScmDir = ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath(); + String scmPath = ((File) scmFile).getAbsolutePath(); + if(scmPath.startsWith(checkoutScmDir)){ + scmPath = scmPath.substring(checkoutScmDir.length() + 1); + } + refinedUpdatedFiles.add(new File(scmPath)); + } else if(scmFile instanceof ScmFile){ + refinedUpdatedFiles.add(new File(((ScmFile)scmFile).getPath())); + } else { + LOGGER.severe("Unhandled AddScmResult.addedFiles type : " + scmFile.getClass().getName()); + } + } + + return refinedUpdatedFiles; + } + + public boolean checkinFiles(File scmRoot, String commitMessage){ + boolean checkinOk = false; + + if(!expectScmRepositoryInitiated()){ + return checkinOk; + } + + LOGGER.fine("Checking in SCM files ..."); + + ScmFileSet fileSet = new ScmFileSet(scmRoot); + + // Let's commit everything ! + try { + ScmVersion version = new NewScmVersion(this.gitRepositoryBranch); + CheckInScmResult result = this.scmManager.checkIn(this.scmRepository, fileSet, version, commitMessage); + if(!result.isSuccess()){ + LOGGER.severe("[checkinFiles] Problem during SCM commit : "+result.getCommandOutput()); + return checkinOk; + } + checkinOk = true; + } catch (ScmException e) { + LOGGER.throwing(ScmManager.class.getName(), "checkIn", e); + LOGGER.severe("[checkinFiles] Error while checkin : "+e.getMessage()); + return checkinOk; + } + + + if(checkinOk){ + LOGGER.fine("Checked in SCM files !"); + } + + return checkinOk; + } + + public String getScmSpecificFilename() { + return scmSpecificFilename; + } + public class NewScmVersion implements ScmVersion { + String name = null; + + public NewScmVersion(String name) { + this.name = name; + } + + @Override + public String getType() { + return name; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java new file mode 100644 index 00000000..48085a8c --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -0,0 +1,410 @@ +package hudson.plugins.scm_sync_configuration; + +import com.google.common.io.Files; + +import hudson.model.User; +import hudson.plugins.scm_sync_configuration.exceptions.LoggableException; +import hudson.plugins.scm_sync_configuration.model.*; +import hudson.plugins.scm_sync_configuration.strategies.ScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.utils.Checksums; +import hudson.security.Permission; +import hudson.util.DaemonThreadFactory; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.manager.ScmManager; +import org.codehaus.plexus.PlexusContainerException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import jenkins.model.Jenkins; + + +public class ScmSyncConfigurationBusiness { + + private static final String WORKING_DIRECTORY = "scm-sync-configuration"; + private static final String CHECKOUT_SCM_DIRECTORY = "checkoutConfiguration"; + private static final Logger LOGGER = Logger.getLogger(ScmSyncConfigurationBusiness.class.getName()); + + private boolean checkoutSucceeded; + private SCMManipulator scmManipulator; + private File checkoutScmDirectory = null; + private ScmSyncConfigurationStatusManager scmSyncConfigurationStatusManager = null; + private List manualSynchronizationIncludes = new ArrayList(); + + /** + * Use of a size 1 thread pool frees us from worrying about accidental thread death and + * changeset commit concurrency + */ + /*package*/ final ExecutorService writer = Executors.newFixedThreadPool(1, new DaemonThreadFactory()); + + // TODO: Refactor this into the plugin object ??? + private final List commitsQueue = new ArrayList(); + + public ScmSyncConfigurationBusiness(){ + } + + public ScmSyncConfigurationStatusManager getScmSyncConfigurationStatusManager() { + if (scmSyncConfigurationStatusManager == null) { + scmSyncConfigurationStatusManager = new ScmSyncConfigurationStatusManager(); + } + return scmSyncConfigurationStatusManager; + } + + public void init(ScmContext scmContext) throws ComponentLookupException, PlexusContainerException { + ScmManager scmManager = SCMManagerFactory.getInstance().createScmManager(); + this.scmManipulator = new SCMManipulator(scmManager); + this.checkoutScmDirectory = new File(getCheckoutScmDirectoryAbsolutePath()); + this.checkoutSucceeded = false; + initializeRepository(scmContext, false); + } + + public void initializeRepository(ScmContext scmContext, boolean deleteCheckoutScmDir){ + // Let's check if everything is available to checkout sources + if(scmManipulator != null && scmManipulator.scmConfigurationSettledUp(scmContext, true)){ + LOGGER.info("Initializing SCM repository for scm-sync-configuration plugin ..."); + // If checkoutScmDirectory was not empty and deleteCheckoutScmDir is asked, reinitialize it ! + if(deleteCheckoutScmDir){ + cleanChekoutScmDirectory(); + } + + // Creating checkout scm directory + if(!checkoutScmDirectory.exists()){ + try { + FileUtils.forceMkdir(checkoutScmDirectory); + LOGGER.info("Directory ["+ checkoutScmDirectory.getAbsolutePath() +"] created !"); + } catch (IOException e) { + LOGGER.warning("Directory ["+ checkoutScmDirectory.getAbsolutePath() +"] cannot be created !"); + } + } + + this.checkoutSucceeded = this.scmManipulator.checkout(this.checkoutScmDirectory); + if(this.checkoutSucceeded){ + LOGGER.info("SCM repository initialization done."); + } + signal("Checkout " + this.checkoutScmDirectory, this.checkoutSucceeded); + } + } + + public void cleanChekoutScmDirectory(){ + if(checkoutScmDirectory != null && checkoutScmDirectory.exists()){ + LOGGER.info("Deleting old checkout SCM directory ..."); + try { + FileUtils.forceDelete(checkoutScmDirectory); + } catch (IOException e) { + LOGGER.throwing(FileUtils.class.getName(), "forceDelete", e); + LOGGER.severe("Error while deleting ["+checkoutScmDirectory.getAbsolutePath()+"] : "+e.getMessage()); + } + this.checkoutSucceeded = false; + } + } + + public List deleteHierarchy(ScmContext scmContext, Path hierarchyPath){ + if(scmManipulator == null || !scmManipulator.scmConfigurationSettledUp(scmContext, false)){ + return null; + } + + + File rootHierarchyTranslatedInScm = hierarchyPath.getScmFile(); + List filesToCommit = scmManipulator.deleteHierarchy(rootHierarchyTranslatedInScm); + + // Once done, we should delete path in scm if it is a directory + if(hierarchyPath.isDirectory()){ + try { + FileUtils.deleteDirectory(rootHierarchyTranslatedInScm); + } catch (IOException e) { + throw new LoggableException("Failed to recursively delete scm directory "+rootHierarchyTranslatedInScm.getAbsolutePath(), FileUtils.class, "deleteDirectory", e); + } + } + + + signal("Delete " + hierarchyPath, filesToCommit != null); + + return filesToCommit; + } + + public Future queueChangeSet(final ScmContext scmContext, ChangeSet changeset, User user, String userMessage) { + if(scmManipulator == null || !scmManipulator.scmConfigurationSettledUp(scmContext, false)){ + LOGGER.info("Queue of changeset "+changeset.toString()+" aborted (scm manipulator not settled !)"); + return null; + } + + Commit commit = new Commit(changeset, user, userMessage, scmContext); + LOGGER.finest("Queuing commit "+commit.toString()+" to SCM ..."); + synchronized(commitsQueue) { + commitsQueue.add(commit); + + return writer.submit(new Callable() { + @Override + public Void call() throws Exception { + processCommitsQueue(); + return null; + } + }); + } + } + + private void processCommitsQueue() { + File scmRoot = new File(getCheckoutScmDirectoryAbsolutePath()); + + // Copying shared commitQueue in order to allow conccurrent modification + List currentCommitQueue; + synchronized (commitsQueue) { + currentCommitQueue = new ArrayList(commitsQueue); + } + List checkedInCommits = new ArrayList(); + + try { + // Reading commit queue and commiting changeset + for(Commit commit: currentCommitQueue){ + String logMessage = "Processing commit : " + commit.toString(); + LOGGER.finest(logMessage); + // Preparing files to add / delete + // + // Two points: + // 1. getPathContents() already returns only those paths that are not also to be deleted. + // 2. For svn, we must not run svn add for files already in the repo. For git, we should run git add to stage the + // change. The second happens to work per chance because the git checkIn implementation will use git commit -a + // if a file set without files but only some directory is given, which we do. + List updatedFiles = new ArrayList(); + + for(Map.Entry pathContent : commit.getChangeset().getPathContents().entrySet()){ + Path pathRelativeToJenkinsRoot = pathContent.getKey(); + byte[] content = pathContent.getValue(); + + File fileTranslatedInScm = pathRelativeToJenkinsRoot.getScmFile(); + if(pathRelativeToJenkinsRoot.isDirectory()) { + if(!fileTranslatedInScm.exists()){ + // Retrieving non existing parent scm path *before* copying it from jenkins directory + String firstNonExistingParentScmPath = pathRelativeToJenkinsRoot.getFirstNonExistingParentScmPath(); + + try { + File buildFileFromPathRelativeToHudsonRoot = JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(pathRelativeToJenkinsRoot.getPath()); + FileUtils.copyDirectory(buildFileFromPathRelativeToHudsonRoot, fileTranslatedInScm, new FileFilter() { + @Override + public boolean accept(File pathname) { + if(pathname.getPath().endsWith(".xml") + || getManualSynchronizationIncludes().contains(pathname)){ + return true; + } + return false; + } + }); + } catch (IOException e) { + throw new LoggableException("Error while copying file hierarchy to SCM directory", FileUtils.class, "copyDirectory", e); + } + updatedFiles.addAll(scmManipulator.addFile(scmRoot, firstNonExistingParentScmPath)); + } + } else { + // We should remember if file in scm existed or not before any manipulation, + // especially writing content + boolean fileTranslatedInScmInitiallyExists = fileTranslatedInScm.exists(); + + boolean fileContentModified = writeScmContentOnlyIfItDiffers(pathRelativeToJenkinsRoot, content, fileTranslatedInScm); + if(fileTranslatedInScmInitiallyExists){ + if(fileContentModified){ + // No need to call scmManipulator.addFile() if fileTranslatedInScm already existed + updatedFiles.add(fileTranslatedInScm); + } + } else { + updatedFiles.addAll(scmManipulator.addFile(scmRoot, pathRelativeToJenkinsRoot.getPath())); + } + } + } + for(Path path : commit.getChangeset().getPathsToDelete()){ + List deletedFiles = deleteHierarchy(commit.getScmContext(), path); + if(deletedFiles != null) + updatedFiles.addAll(deletedFiles); + } + + if(updatedFiles.isEmpty()){ + LOGGER.finest("Empty changeset to commit (no changes found on files) => commit skipped !"); + checkedInCommits.add(commit); + } else { + // Commiting files... + boolean result = scmManipulator.checkinFiles(scmRoot, commit.getMessage()); + + if(result){ + LOGGER.finest("Commit "+commit.toString()+" pushed to SCM !"); + checkedInCommits.add(commit); + } else { + throw new LoggableException("Error while checking in file to scm repository", SCMManipulator.class, "checkinFiles"); + } + + signal(logMessage, true); + } + } + // As soon as a commit doesn't goes well, we should abort commit queue processing... + }catch(LoggableException e){ + LOGGER.throwing(e.getClazz().getName(), e.getMethodName(), e); + LOGGER.severe("Error while processing commit queue : "+e.getMessage()); + signal(e.getMessage(), false); + } finally { + // We should remove every checkedInCommits + synchronized (commitsQueue) { + commitsQueue.removeAll(checkedInCommits); + } + } + } + + public List getManualSynchronizationIncludes() { + return manualSynchronizationIncludes; + } + + public void setManualSynchronizationIncludes( + List manualSynchronizationIncludes) { + this.manualSynchronizationIncludes = manualSynchronizationIncludes; + } + + private boolean writeScmContentOnlyIfItDiffers(Path pathRelativeToJenkinsRoot, byte[] content, File fileTranslatedInScm) + throws LoggableException { + boolean scmContentUpdated = false; + boolean contentDiffer = false; + try { + contentDiffer = !Checksums.fileAndByteArrayContentAreEqual(fileTranslatedInScm, content); + } catch (IOException e) { + throw new LoggableException("Error while checking content checksum", Checksums.class, "fileAndByteArrayContentAreEqual", e); + } + + if(contentDiffer){ + createScmContent(pathRelativeToJenkinsRoot, content, fileTranslatedInScm); + scmContentUpdated = true; + } else { + // Don't do anything + } + return scmContentUpdated; + } + + private void createScmContent(Path pathRelativeToJenkinsRoot, byte[] content, File fileTranslatedInScm) + throws LoggableException { + Stack directoriesToCreate = new Stack(); + File directory = fileTranslatedInScm.getParentFile(); + + // Eventually, creating non existing enclosing directories + while(!directory.exists()){ + directoriesToCreate.push(directory); + directory = directory.getParentFile(); + } + while(!directoriesToCreate.empty()){ + directory = directoriesToCreate.pop(); + if(!directory.mkdir()){ + throw new LoggableException("Error while creating directory "+directory.getAbsolutePath(), File.class, "mkdir"); + } + } + + try { + // Copying content if pathRelativeToJenkinsRoot is a file, or creating the directory if it is a directory + if(pathRelativeToJenkinsRoot.isDirectory()){ + if(!fileTranslatedInScm.mkdir()){ + throw new LoggableException("Error while creating directory "+fileTranslatedInScm.getAbsolutePath(), File.class, "mkdir"); + } + } else { + Files.write(content, fileTranslatedInScm); + } + } catch (IOException e) { + throw new LoggableException("Error while creating file in SCM directory", Files.class, "write", e); + } + } + + public void synchronizeAllConfigs(ScmSyncStrategy[] availableStrategies){ + List filesToSync = new ArrayList(); + // Building synced files from strategies + for(ScmSyncStrategy strategy : availableStrategies){ + filesToSync.addAll(strategy.collect()); + } + + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + plugin.startThreadedTransaction(); + try { + for(File fileToSync : filesToSync){ + String hudsonConfigPathRelativeToHudsonRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(fileToSync); + + plugin.getTransaction().defineCommitMessage(new WeightedMessage("New included files", MessageWeight.IMPORTANT)); + plugin.getTransaction().registerPath(hudsonConfigPathRelativeToHudsonRoot); + } + } finally { + plugin.getTransaction().commit(); + } + } + + public boolean scmCheckoutDirectorySettledUp(ScmContext scmContext){ + return scmManipulator != null && this.scmManipulator.scmConfigurationSettledUp(scmContext, false) && this.checkoutSucceeded; + } + + public List reloadAllFilesFromScm() throws IOException, ScmException { + this.scmManipulator.update(new File(getCheckoutScmDirectoryAbsolutePath())); + return syncDirectories(new File(getCheckoutScmDirectoryAbsolutePath()), ""); + } + + private List syncDirectories(File from, String relative) throws IOException { + List l = new ArrayList(); + for(File f : from.listFiles()) { + String newRelative = relative + File.separator + f.getName(); + File jenkinsFile = new File(Jenkins.getInstance().getRootDir() + newRelative); + if (f.getName().equals(scmManipulator.getScmSpecificFilename())) { + // nothing to do + } else if (f.isDirectory()) { + if (!jenkinsFile.exists()) { + FileUtils.copyDirectory(f, jenkinsFile, new FileFilter() { + + @Override + public boolean accept(File f) { + return !f.getName().equals(scmManipulator.getScmSpecificFilename()); + } + + }); + l.add(jenkinsFile); + } + else { + l.addAll(syncDirectories(f, newRelative)); + } + } else { + if (!jenkinsFile.exists() || !FileUtils.contentEquals(f, jenkinsFile)) { + FileUtils.copyFile(f, jenkinsFile); + l.add(jenkinsFile); + } + } + } + return l; + } + + private void signal(String operation, boolean result) { + if (result) { + getScmSyncConfigurationStatusManager().signalSuccess(); + } + else { + getScmSyncConfigurationStatusManager().signalFailed(operation); + } + } + + public static String getCheckoutScmDirectoryAbsolutePath(){ + return new File(new File(Jenkins.getInstance().getRootDir(), WORKING_DIRECTORY), CHECKOUT_SCM_DIRECTORY).getAbsolutePath(); + } + + public static String getScmDirectoryName() { + return WORKING_DIRECTORY; + } + + public void purgeFailLogs() { + Jenkins.getInstance().checkPermission(purgeFailLogPermission()); + scmSyncConfigurationStatusManager.purgeFailLogs(); + } + + public boolean canCurrentUserPurgeFailLogs() { + return Jenkins.getInstance().hasPermission(purgeFailLogPermission()); + } + + private static Permission purgeFailLogPermission(){ + // Only administrators should be able to purge logs + return Jenkins.ADMINISTER; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java new file mode 100644 index 00000000..2e6c5267 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java @@ -0,0 +1,119 @@ +package hudson.plugins.scm_sync_configuration; + +import hudson.plugins.scm_sync_configuration.model.BotherTimeout; + +import org.kohsuke.stapler.Stapler; + +import javax.servlet.http.HttpServletRequest; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.Callable; + +public class ScmSyncConfigurationDataProvider { + + private static final ThreadLocal CURRENT_REQUEST = new ThreadLocal(); + private static final String COMMENT_SESSION_KEY = "__commitMessage"; + private static final String BOTHER_TIMEOUTS_SESSION_KEY = "__botherTimeouts"; + + public static void provideBotherTimeout(String type, int timeoutMinutesFromNow, String currentUrl){ + // FIXME: see if it wouldn't be possible to replace "currentURL" by "current file path" + // in order to be able to target same files with different urls (jobs via views for example) + + Map botherTimeouts = retrievePurgedBotherTimeouts(); + if(botherTimeouts == null){ + botherTimeouts = new HashMap(); + } + + // Removing existing BotherTimeouts matching current url + List< Entry > entriesToDelete = new ArrayList< Entry >(); + for(Entry entry : botherTimeouts.entrySet()){ + if(entry.getKey().matchesUrl(currentUrl)){ + entriesToDelete.add(entry); + } + } + botherTimeouts.keySet().removeAll(entriesToDelete); + + // Adding new BotherTimeout with updated timeout + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MINUTE, timeoutMinutesFromNow); + BotherTimeout bt = BotherTimeout.FACTORY.createBotherTimeout(type, timeoutMinutesFromNow, currentUrl); + botherTimeouts.put(bt, cal.getTime()); + + // Updating session + currentRequest().getSession().setAttribute(BOTHER_TIMEOUTS_SESSION_KEY, botherTimeouts); + } + + public static Date retrieveBotherTimeoutMatchingUrl(String currentURL){ + Map botherTimeouts = retrievePurgedBotherTimeouts(); + Date timeoutMatchingUrl = null; + if(botherTimeouts != null){ + for(Entry entry : botherTimeouts.entrySet()){ + if(entry.getKey().matchesUrl(currentURL)){ + timeoutMatchingUrl = entry.getValue(); + break; + } + } + } + return timeoutMatchingUrl; + } + + protected static Map retrievePurgedBotherTimeouts(){ + @SuppressWarnings("unchecked") + Map botherTimeouts = (Map)retrieveObject(BOTHER_TIMEOUTS_SESSION_KEY, false); + if(botherTimeouts != null){ + purgeOutdatedBotherTimeouts(botherTimeouts); + } + return botherTimeouts; + } + + protected static void purgeOutdatedBotherTimeouts(Map botherTimeouts){ + Date now = Calendar.getInstance().getTime(); + List< Entry > entriesToDelete = new ArrayList< Entry >(); + for(Entry entry : botherTimeouts.entrySet()){ + if(entry.getValue().before(now)){ + entriesToDelete.add(entry); + } + } + botherTimeouts.entrySet().removeAll(entriesToDelete); + } + + public static void provideComment(String comment){ + currentRequest().getSession().setAttribute(COMMENT_SESSION_KEY, comment); + } + + public static String retrieveComment(boolean cleanComment){ + return (String)retrieveObject(COMMENT_SESSION_KEY, cleanComment); + } + + private static Object retrieveObject(String key, boolean cleanObject){ + HttpServletRequest request = currentRequest(); + Object obj = null; + // Sometimes, request can be null : when hudson starts for instance ! + if(request != null){ + obj = request.getSession().getAttribute(key); + if(cleanObject){ + request.getSession().removeAttribute(key); + } + } + return obj; + } + + public static void provideRequestDuring(HttpServletRequest request, Callable callable) throws Exception { + CURRENT_REQUEST.set(request); + + try { + callable.call(); + } finally { + CURRENT_REQUEST.set(null); + } + } + + protected static HttpServletRequest currentRequest(){ + if(Stapler.getCurrentRequest() == null){ + return CURRENT_REQUEST.get(); + } else { + return Stapler.getCurrentRequest(); + } + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java new file mode 100644 index 00000000..2ba5f8f9 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -0,0 +1,557 @@ +package hudson.plugins.scm_sync_configuration; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import hudson.Plugin; +import hudson.model.Descriptor; +import hudson.model.Descriptor.FormException; +import hudson.model.Saveable; +import hudson.model.User; +import hudson.plugins.scm_sync_configuration.extensions.ScmSyncConfigurationFilter; +import hudson.plugins.scm_sync_configuration.model.ChangeSet; +import hudson.plugins.scm_sync_configuration.model.ScmContext; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncNoSCM; +import hudson.plugins.scm_sync_configuration.strategies.ScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.impl.*; +import hudson.plugins.scm_sync_configuration.transactions.AtomicTransaction; +import hudson.plugins.scm_sync_configuration.transactions.ScmTransaction; +import hudson.plugins.scm_sync_configuration.transactions.ThreadedTransaction; +import hudson.plugins.scm_sync_configuration.xstream.ScmSyncConfigurationXStreamConverter; +import hudson.plugins.scm_sync_configuration.xstream.migration.ScmSyncConfigurationPOJO; +import hudson.security.Permission; +import hudson.util.FormValidation; +import hudson.util.PluginServletFilter; +import net.sf.json.JSONObject; + +import org.acegisecurity.AccessDeniedException; +import org.apache.maven.scm.CommandParameters; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.provider.ScmProvider; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +import javax.annotation.Nullable; +import javax.servlet.ServletException; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import jenkins.model.Jenkins; + +public class ScmSyncConfigurationPlugin extends Plugin{ + + public static final transient ScmSyncStrategy[] AVAILABLE_STRATEGIES = new ScmSyncStrategy[]{ + new JenkinsConfigScmSyncStrategy(), + new BasicPluginsConfigScmSyncStrategy(), + new JobConfigScmSyncStrategy(), + new UserConfigScmSyncStrategy(), + new ManualIncludesScmSyncStrategy() + }; + + /** + * Strategies that cannot be updated by user + */ + public static final transient List DEFAULT_STRATEGIES = ImmutableList.copyOf( + Collections2.filter(Arrays.asList(AVAILABLE_STRATEGIES), new Predicate() { + @Override + public boolean apply(@Nullable ScmSyncStrategy scmSyncStrategy) { + return !( scmSyncStrategy instanceof ManualIncludesScmSyncStrategy ); + } + })); + + public void purgeFailLogs() { + business.purgeFailLogs(); + } + + public static interface AtomicTransactionFactory { + AtomicTransaction createAtomicTransaction(); + } + + private static final Logger LOGGER = Logger.getLogger(ScmSyncConfigurationPlugin.class.getName()); + + private transient ScmSyncConfigurationBusiness business; + + /** + * Flag allowing to process commit synchronously instead of asynchronously (default) + * Could be useful, particularly during tests execution + */ + + private transient boolean synchronousTransactions = false; + + /** + * SCM Transaction which is currently used. This transaction is thread scoped and will be, by default, + * an AtomicTransaction (each time a change is recorded, it will be immediately commited). + * Every time a transaction will be commited, it will be resetted to null + */ + private transient ThreadLocal transaction = new ThreadLocal(); + + private String scmRepositoryUrl; + private SCM scm; + private boolean noUserCommitMessage; + private boolean displayStatus = true; + // The [message] is a magic string that will be replaced with commit message + // when commit occurs + private String commitMessagePattern = "[message]"; + private String gitRepositoryBranch = "master"; + private List filesModifiedByLastReload; + private List manualSynchronizationIncludes; + + public ScmSyncConfigurationPlugin(){ + // By default, transactions should be asynchronous + this(false); + } + + public ScmSyncConfigurationPlugin(boolean synchronousTransactions){ + this.synchronousTransactions = synchronousTransactions; + setBusiness(new ScmSyncConfigurationBusiness()); + + try { + PluginServletFilter.addFilter(new ScmSyncConfigurationFilter()); + } catch (ServletException e) { + throw new RuntimeException(e); + } + } + + public List getManualSynchronizationIncludes(){ + return manualSynchronizationIncludes; + } + + @Override + public void start() throws Exception { + super.start(); + + Jenkins.XSTREAM.registerConverter(new ScmSyncConfigurationXStreamConverter()); + + this.load(); + + // If scm has not been read in scm-sync-configuration.xml, let's initialize it + // to the "no scm" SCM + if(this.scm == null){ + this.scm = SCM.valueOf(ScmSyncNoSCM.class); + this.scmRepositoryUrl = null; + } + + // SCMManagerFactory.start() must be called here instead of ScmSyncConfigurationItemListener.onLoaded() + // because, for some unknown reasons, we reach plexus bootstraping exceptions when + // calling Embedder.start() when everything is loaded (very strange...) + SCMManagerFactory.getInstance().start(); + initialInit(); + } + + public void loadData(ScmSyncConfigurationPOJO pojo){ + this.scmRepositoryUrl = pojo.getScmRepositoryUrl(); + this.scm = pojo.getScm(); + this.noUserCommitMessage = pojo.isNoUserCommitMessage(); + this.displayStatus = pojo.isDisplayStatus(); + this.commitMessagePattern = pojo.getCommitMessagePattern(); + this.manualSynchronizationIncludes = pojo.getManualSynchronizationIncludes(); + this.business.setManualSynchronizationIncludes(manualSynchronizationIncludes); + } + + protected void initialInit() throws Exception { + // We need to init() here in addition to ScmSyncConfigurationItemListener.onLoaded() to ensure that we do + // indeed create the SCM work directory when we are loaded. Otherwise, the plugin can be installed but + // then fails to operate until the next time Jenkins is restarted. Using postInitialize() for this might + // be too late if the plugin is copied to the plugin directory and then Jenkins is started. + this.business.init(createScmContext()); + } + + public void init() { + try { + this.business.init(createScmContext()); + } catch (Exception e) { + throw new RuntimeException("Error during ScmSyncConfiguration initialisation !", e); + } + } + + @Override + public void stop() throws Exception { + SCMManagerFactory.getInstance().stop(); + super.stop(); + } + + @Override + public void configure(StaplerRequest req, JSONObject formData) + throws IOException, ServletException, FormException { + super.configure(req, formData); + + boolean repoInitializationRequired = false; + boolean configsResynchronizationRequired = false; + boolean repoCleaningRequired = false; + + this.noUserCommitMessage = formData.getBoolean("noUserCommitMessage"); + this.displayStatus = formData.getBoolean("displayStatus"); + this.commitMessagePattern = req.getParameter("commitMessagePattern"); + LOGGER.info("Here: "+ req.getParameter("gitRepositoryBranch")); + this.gitRepositoryBranch = req.getParameter("gitRepositoryBranch"); + + String oldScmRepositoryUrl = this.scmRepositoryUrl; + String scmType = req.getParameter("scm"); + if(scmType != null){ + this.scm = SCM.valueOf(scmType); + String newScmRepositoryUrl = this.scm.createScmUrlFromRequest(req); + + this.scmRepositoryUrl = newScmRepositoryUrl; + + // If something changed, let's reinitialize repository in working directory ! + repoInitializationRequired = newScmRepositoryUrl != null && !newScmRepositoryUrl.equals(oldScmRepositoryUrl); + configsResynchronizationRequired = repoInitializationRequired; + repoCleaningRequired = newScmRepositoryUrl==null && oldScmRepositoryUrl!=null; + } + + if(req.getParameterValues("manualSynchronizationIncludes") != null){ + List submittedManualIncludes = new ArrayList(Arrays.asList(req.getParameterValues("manualSynchronizationIncludes"))); + List newManualIncludes = new ArrayList(submittedManualIncludes); + if(this.manualSynchronizationIncludes != null){ + newManualIncludes.removeAll(this.manualSynchronizationIncludes); + } + this.manualSynchronizationIncludes = submittedManualIncludes; + + configsResynchronizationRequired = !newManualIncludes.isEmpty(); + } else { + this.manualSynchronizationIncludes = new ArrayList(); + } + + this.business.setManualSynchronizationIncludes(manualSynchronizationIncludes); + + // Repo initialization should be made _before_ plugin save, in order to let scm-sync-configuration.xml + // file synchronizable + if(repoInitializationRequired){ + this.business.initializeRepository(createScmContext(), true); + } + if(configsResynchronizationRequired){ + this.business.synchronizeAllConfigs(AVAILABLE_STRATEGIES); + } + if(repoCleaningRequired){ + this.business.cleanChekoutScmDirectory(); + } + + // Persisting plugin data + // Note that save() is made _after_ the synchronizeAllConfigs() because, otherwise, scm-sync-configuration.xml + // file would be commited _before_ every other jenkins configuration file, which doesn't seem "natural" + this.save(); + } + + public Iterable collectAllFilesForScm() { + return Iterables.concat(Iterables.transform(Lists.newArrayList(AVAILABLE_STRATEGIES), new Function>() { + @Override + public Iterable apply(ScmSyncStrategy strategy) { + return strategy.collect(); + }})); + } + + public Iterable collectAllFilesForScm(final File fromSubDirectory) { + return Iterables.concat(Iterables.transform(Lists.newArrayList(AVAILABLE_STRATEGIES), new Function>() { + @Override + public Iterable apply(ScmSyncStrategy strategy) { + return strategy.collect(fromSubDirectory); + }})); + } + + // public method for UI-less reload in e. g. Groovy scripts + public void reloadAllFilesFromScm() throws ServletException, IOException { + try { + filesModifiedByLastReload = business.reloadAllFilesFromScm(); + } + catch(ScmException e) { + throw new ServletException("Unable to reload SCM " + scm.getTitle() + ":" + getScmUrl(), e); + } + } + + public void doReloadAllFilesFromScm(StaplerRequest req, StaplerResponse res) throws ServletException, IOException { + try { + filesModifiedByLastReload = business.reloadAllFilesFromScm(); + req.getView(this, "/hudson/plugins/scm_sync_configuration/reload.jelly").forward(req, res); + } + catch(ScmException e) { + throw new ServletException("Unable to reload SCM " + scm.getTitle() + ":" + getScmUrl(), e); + } + } + + public void doSubmitComment(StaplerRequest req, StaplerResponse res) throws ServletException, IOException { + // TODO: complexify this in order to pass a strategy identifier in the session key + ScmSyncConfigurationDataProvider.provideComment(req.getParameter("comment")); + if(Boolean.valueOf(req.getParameter("dontBotherMe")).booleanValue()){ + ScmSyncConfigurationDataProvider.provideBotherTimeout(req.getParameter("botherType"), + Integer.valueOf(req.getParameter("botherTime")), req.getParameter("currentURL")); + } + } + + // TODO: do retrieve help file with an action ! + public void doHelpForRepositoryUrl(StaplerRequest req, StaplerResponse res) throws ServletException, IOException{ + req.getView(this, SCM.valueOf(req.getParameter("scm")).getRepositoryUrlHelpPath()).forward(req, res); + } + + // Help url for manualSynchronizationIncludes field is a jelly script and not a html file + // because we need default includes list to be displayed in it ! + public void doManualIncludesHelp(StaplerRequest req, StaplerResponse res) throws ServletException, IOException { + req.getView(this, "/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes.jelly").forward(req, res); + } + + public void doSynchronizeFile(@QueryParameter String path){ + getTransaction().registerPath(path); + } + + /** + * This method is invoked via jelly to display a list of all the default includes. + * + * @return a list of explanatory strings about the patterns matched by a specific strategy's matcher. + */ + public List getDefaultIncludes(){ + List includes = new ArrayList(); + for(ScmSyncStrategy strategy : DEFAULT_STRATEGIES){ + includes.addAll(strategy.getSyncIncludes()); + } + return includes; + } + + private User getCurrentUser(){ + User user = null; + try { + user = Jenkins.getInstance().getMe(); + }catch(AccessDeniedException e){} + return user; + } + + public static ScmSyncConfigurationPlugin getInstance(){ + return Jenkins.getInstance().getPlugin(ScmSyncConfigurationPlugin.class); + } + + public ScmSyncStrategy getStrategyForSaveable(Saveable s, File f){ + for(ScmSyncStrategy strat : AVAILABLE_STRATEGIES){ + if(strat.isSaveableApplicable(s, f)){ + return strat; + } + } + // Strategy not found ! + return null; + } + + /** + * Tries to find at least one strategy that would have applied to a deleted item. + * + * @param s the saveable that was deleted. It still exists in Jenkins' model, but has already been eradicated from disk. + * @param pathRelativeToRoot where the item had lived on disk + * @param wasDirectory whether it was a directory + * @return a strategy that thinks it might have applied + */ + public ScmSyncStrategy getStrategyForDeletedSaveable(Saveable s, String pathRelativeToRoot, boolean wasDirectory) { + for (ScmSyncStrategy strategy : AVAILABLE_STRATEGIES) { + if (strategy.mightHaveBeenApplicableToDeletedSaveable(s, pathRelativeToRoot, wasDirectory)) { + return strategy; + } + } + return null; + } + + public ScmContext createScmContext(){ + return new ScmContext(this.scm, this.scmRepositoryUrl, this.commitMessagePattern, this.gitRepositoryBranch); + } + + public boolean shouldDecorationOccursOnURL(String url){ + // Removing comment from session here... + ScmSyncConfigurationDataProvider.retrieveComment(true); + + // Displaying commit message popup is based on following tests : + // Zero : never ask for a commit message + // First : no botherTimeout should match with current url + // Second : a strategy should exist, matching current url + // Third : SCM Sync should be settled up + return !noUserCommitMessage && ScmSyncConfigurationDataProvider.retrieveBotherTimeoutMatchingUrl(url) == null + && getStrategyForURL(url) != null && this.business.scmCheckoutDirectorySettledUp(createScmContext()); + } + + public ScmSyncStrategy getStrategyForURL(String url){ + for(ScmSyncStrategy strat : AVAILABLE_STRATEGIES){ + if(strat.isCurrentUrlApplicable(url)){ + return strat; + } + } + // Strategy not found ! + return null; + } + + public boolean isNoUserCommitMessage() { + return noUserCommitMessage; + } + + public SCM[] getScms(){ + return SCM.values(); + } + + public void setBusiness(ScmSyncConfigurationBusiness business) { + this.business = business; + } + + public ScmSyncConfigurationStatusManager getScmSyncConfigurationStatusManager() { + return business.getScmSyncConfigurationStatusManager(); + } + + public String getScmRepositoryUrl() { + return scmRepositoryUrl; + } + + public boolean isScmSelected(SCM _scm){ + return this.scm == _scm; + } + + public SCM getSCM(){ + return this.scm; + } + + public String getGitRepositoryBranch() { + return this.gitRepositoryBranch; + } + + public String getScmUrl(){ + if(this.scm != null){ + return this.scm.extractScmUrlFrom(this.scmRepositoryUrl); + } else { + return null; + } + } + + public List getFilesModifiedByLastReload() { + return filesModifiedByLastReload; + } + + public boolean isDisplayStatus() { + return displayStatus; + } + + public String getCommitMessagePattern() { + return commitMessagePattern; + } + + public Descriptor getDescriptorForSCM(String scmName){ + return SCM.valueOf(scmName).getSCMDescriptor(); + } + + public void startThreadedTransaction(){ + this.setTransaction(new ThreadedTransaction(synchronousTransactions)); + } + + public Future commitChangeset(ChangeSet changeset){ + try { + if(!changeset.isEmpty()){ + return this.business.queueChangeSet(createScmContext(), changeset, getCurrentUser(), ScmSyncConfigurationDataProvider.retrieveComment(false)); + } else { + return null; + } + } finally { + // Reinitializing transaction once commited + this.setTransaction(null); + } + } + + public ScmTransaction getTransaction() { + if(transaction.get() == null){ + setTransaction(new AtomicTransaction(synchronousTransactions)); + } + return transaction.get(); + } + + protected void setTransaction(ScmTransaction transactionToRegister){ + if(transaction.get() != null && transactionToRegister != null){ + LOGGER.warning("Existing threaded transaction will be overriden !"); + } + transaction.set(transactionToRegister); + } + + public boolean currentUserCannotPurgeFailLogs() { + return !business.canCurrentUserPurgeFailLogs(); + } + + private static final Pattern STARTS_WITH_DRIVE_LETTER = Pattern.compile("^[a-zA-Z]:"); + + /** + * UI form validation for the git repository URL. Must be non-empty, a valid URL, and git must be able to access the repository through it. + * + * @param value from the UI form + * @return the validation status, with possible error or warning messages. + */ + public FormValidation doCheckGitUrl(@QueryParameter String value) { + if (Strings.isNullOrEmpty(value)) { + return FormValidation.error(Messages.ScmSyncConfigurationsPlugin_gitRepoUrlEmpty()); + } + String trimmed = value.trim(); + // Plain file paths are valid URIs, except maybe on windows if starting with a drive letter + if (!isValidUrl (trimmed)) { + // We have two more possibilities: + // - a plain file path starting with a drive letter and a colon on windows(?). Just delegate to the repository access below. + // - a ssh-like short form like [user@]host.domain.tld:repository + if (!STARTS_WITH_DRIVE_LETTER.matcher(trimmed).find()) { + // Possible ssh short form? + if (trimmed.indexOf("://") < 0 && trimmed.indexOf(':') > 0) { + if (!isValidUrl("ssh://" + trimmed.replaceFirst(":", "/"))) { + return FormValidation.error(Messages.ScmSyncConfigurationsPlugin_gitRepoUrlInvalid()); + } + } else { + return FormValidation.error(Messages.ScmSyncConfigurationsPlugin_gitRepoUrlInvalid()); + } + } + } + // Try to access the repository... + if (Jenkins.getInstance().hasPermission(Permission.CONFIGURE)) { + try { + ScmProvider scmProvider = SCMManagerFactory.getInstance().createScmManager().getProviderByUrl("scm:git:" + trimmed); + // Stupid interface. Why do I have to pass a delimiter if the URL must already be without "scm:git:" prefix?? + ScmProviderRepository remoteRepo = scmProvider.makeProviderScmRepository(trimmed, ':'); + // File set and parameters are ignored by the maven SCM gitexe implementation (for now...) + scmProvider.remoteInfo(remoteRepo, new ScmFileSet(Jenkins.getInstance().getRootDir()), new CommandParameters()); + // We actually don't care about the result. If this cannot access the repo, it'll raise an exception. + } catch (ComponentLookupException e) { + LOGGER.warning("Cannot validate repository URL: no ScmManager: " + e.getMessage()); + // And otherwise ignore + } catch (ScmException e) { + LOGGER.warning("Repository at " + trimmed + " is inaccessible"); + return FormValidation.error(Messages.ScmSyncConfigurationsPlugin_gitRepoUrlInaccessible(trimmed)); + } + } + if (trimmed.length() != value.length()) { + return FormValidation.warning(Messages.ScmSyncConfigurationsPlugin_gitRepoUrlWhitespaceWarning()); + } + return FormValidation.ok(); + } + + /** + * Determines whether the given string is a valid URL. + * + * @param input to check + * @return {@code true} if the input string is a vlid URL, {@code false} otherwise. + */ + private boolean isValidUrl(String input) { + try { + // There might be no "stream handler" in URL for ssh or git. We always replace the protocol by http for this check. + String httpUrl = input.replaceFirst("^[a-zA-Z]+://", "http://"); + new URI(httpUrl).toURL(); + return true; + } catch (MalformedURLException e) { + return false; + } catch (URISyntaxException e) { + return false; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java new file mode 100644 index 00000000..056e35de --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java @@ -0,0 +1,77 @@ +package hudson.plugins.scm_sync_configuration; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.logging.Logger; + +import jenkins.model.Jenkins; + +import org.codehaus.plexus.util.FileUtils; + +public class ScmSyncConfigurationStatusManager { + + private static final Logger LOGGER = Logger.getLogger(ScmSyncConfigurationStatusManager.class.getName()); + + public static final String LOG_SUCCESS_FILENAME = "scm-sync-configuration.success.log"; + + public static final String LOG_FAIL_FILENAME = "scm-sync-configuration.fail.log"; + + private final File fail; + private final File success; + + public ScmSyncConfigurationStatusManager() { + fail = new File(Jenkins.getInstance().getRootDir().getAbsolutePath(), LOG_FAIL_FILENAME); + success = new File(Jenkins.getInstance().getRootDir().getAbsolutePath(), LOG_SUCCESS_FILENAME); + } + + public String getLastFail() { + return readFile(fail); + } + + public String getLastSuccess() { + return readFile(success); + } + + public void signalSuccess() { + writeFile(success, new Date().toString()); + } + + public void signalFailed(String description) { + appendFile(fail, new Date().toString() + " : " + description + "
"); + } + + private static String readFile(File f) { + try { + if(f.exists()) { + return FileUtils.fileRead(f); + } + } + catch(IOException e) { + LOGGER.severe("Unable to read file " + f.getAbsolutePath() + " : " + e.getMessage()); + } + return null; + } + + private static void writeFile(File f, String data) { + try { + FileUtils.fileWrite(f.getAbsolutePath(), data); + } + catch(IOException e) { + LOGGER.severe("Unable to write file " + f.getAbsolutePath() + " : " + e.getMessage()); + } + } + + private static void appendFile(File f, String data) { + try { + FileUtils.fileAppend(f.getAbsolutePath(), data); + } + catch(IOException e) { + LOGGER.severe("Unable to write file " + f.getAbsolutePath() + " : " + e.getMessage()); + } + } + + public void purgeFailLogs() { + fail.delete(); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java b/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java new file mode 100644 index 00000000..f0df1247 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java @@ -0,0 +1,33 @@ +package hudson.plugins.scm_sync_configuration.exceptions; + +/** + * @author fcamblor + * Exception which will be easily loggable, by providing both class and method called, causing the exception + */ +public class LoggableException extends RuntimeException { + + private static final long serialVersionUID = 442135528912013310L; + + private final Class clazz; + private final String methodName; + + public LoggableException(String message, Class clazz, String methodName, Throwable cause) { + super(message, cause); + this.clazz = clazz; + this.methodName = methodName; + } + + public LoggableException(String message, Class clazz, String methodName) { + super(message); + this.clazz = clazz; + this.methodName = methodName; + } + + public Class getClazz() { + return clazz; + } + + public String getMethodName() { + return methodName; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java new file mode 100644 index 00000000..4a900706 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java @@ -0,0 +1,73 @@ +package hudson.plugins.scm_sync_configuration.extensions; + +import hudson.Extension; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationDataProvider; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.logging.Level; + +/** + * @author fcamblor + * Very important class in the plugin : it is the entry point allowing to decide what files should be + * synchronized or not during current thread execution + */ +@Extension +public class ScmSyncConfigurationFilter implements Filter { + + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(ScmSyncConfigurationFilter.class.getName()); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { + // In the beginning of every http request, we should create a new threaded transaction + final ScmSyncConfigurationPlugin plugin; + try { + plugin = ScmSyncConfigurationPlugin.getInstance(); + } catch(Throwable t){ + LOG.log(Level.SEVERE, "Error when retrieving ScmSyncConfig plugin instance => No filtering enabled on current request", t); + return; + } + + if(plugin != null){ + plugin.startThreadedTransaction(); + + try { + // Providing current ServletRequest in ScmSyncConfigurationDataProvider's thread local + // in order to be able to access it from everywhere inside this call + ScmSyncConfigurationDataProvider.provideRequestDuring((HttpServletRequest)request, new Callable() { + @Override + public Void call() throws Exception { + try { + // Handling "normally" http request + chain.doFilter(request, response); + }finally{ + // In the end of http request, we should commit current transaction + plugin.getTransaction().commit(); + } + + return null; + } + }); + } catch(RuntimeException e){ + throw e; + } catch(ServletException e){ + throw e; + } catch(IOException e){ + throw e; + } catch(Exception e){ + throw new ServletException(e); + } + } + } + + @Override + public void destroy() { + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java new file mode 100644 index 00000000..3a0cce4a --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java @@ -0,0 +1,97 @@ +package hudson.plugins.scm_sync_configuration.extensions; + +import hudson.Extension; +import hudson.model.Item; +import hudson.model.TopLevelItem; +import hudson.model.listeners.ItemListener; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.ScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.transactions.ScmTransaction; + +import java.io.File; + +import jenkins.model.DirectlyModifiableTopLevelItemGroup; +import jenkins.model.Jenkins; + +@Extension +public class ScmSyncConfigurationItemListener extends ItemListener { + + @Override + public void onLoaded() { + super.onLoaded(); + + // After every plugin is loaded, let's init ScmSyncConfigurationPlugin + // Init is needed after plugin loads since it relies on scm implementations plugins loaded + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + if (plugin != null) { + plugin.init(); + } + } + + @Override + public void onDeleted(Item item) { + super.onDeleted(item); + + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + if(plugin != null){ + String path = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(item.getRootDir()); + ScmSyncStrategy strategy = plugin.getStrategyForDeletedSaveable(item, path, true); + if (strategy != null) { + WeightedMessage message = strategy.getCommitMessageFactory().getMessageWhenItemDeleted(item); + ScmTransaction transaction = plugin.getTransaction(); + transaction.defineCommitMessage(message); + transaction.registerPathForDeletion(path); + } + } + } + + @Override + public void onLocationChanged(Item item, String oldFullName, String newFullName) { + super.onLocationChanged(item, oldFullName, newFullName); + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + if (plugin == null) { + return; + } + // Figure out where the item previously might have been. + File oldDir = null; + Jenkins jenkins = Jenkins.getInstance(); + int i = oldFullName.lastIndexOf('/'); + String oldSimpleName = i > 0 ? oldFullName.substring(i+1) : oldFullName; + Object oldParent = i > 0 ? jenkins.getItemByFullName(oldFullName.substring(0, i)) : jenkins; + Object newParent = item.getParent(); + if (newParent == null) { + // Shouldn't happen. + newParent = jenkins; + } + if (oldParent == newParent && oldParent != null) { + // Simple rename within the same directory + oldDir = new File (item.getRootDir().getParentFile(), oldSimpleName); + } else if (oldParent instanceof DirectlyModifiableTopLevelItemGroup && item instanceof TopLevelItem) { + oldDir = ((DirectlyModifiableTopLevelItemGroup) oldParent).getRootDirFor((TopLevelItem) item); + oldDir = new File (oldDir.getParentFile(), oldSimpleName); + } + ScmSyncStrategy oldStrategy = null; + if (oldDir != null) { + oldStrategy = plugin.getStrategyForDeletedSaveable(item, JenkinsFilesHelper.buildPathRelativeToHudsonRoot(oldDir), true); + } + File newDir = item.getRootDir(); + ScmSyncStrategy newStrategy = plugin.getStrategyForSaveable(item, newDir); + ScmTransaction transaction = plugin.getTransaction(); + if (newStrategy == null) { + if (oldStrategy != null) { + // Delete old + WeightedMessage message = oldStrategy.getCommitMessageFactory().getMessageWhenItemRenamed(item, oldFullName, newFullName); + transaction.defineCommitMessage(message); + transaction.registerPathForDeletion(JenkinsFilesHelper.buildPathRelativeToHudsonRoot(oldDir)); + } + } else { + String newPathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(newDir); + // Something moved to a place where we do cover it. + WeightedMessage message = newStrategy.getCommitMessageFactory().getMessageWhenItemRenamed(item, oldFullName, newFullName); + transaction.defineCommitMessage(message); + transaction.registerRenamedPath(oldStrategy != null ? JenkinsFilesHelper.buildPathRelativeToHudsonRoot(oldDir) : null, newPathRelativeToRoot); + } + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java new file mode 100644 index 00000000..d193dfdf --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java @@ -0,0 +1,25 @@ +package hudson.plugins.scm_sync_configuration.extensions; + +import hudson.Extension; +import hudson.model.PageDecorator; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; + +import org.kohsuke.stapler.bind.JavaScriptMethod; + +@Extension +public class ScmSyncConfigurationPageDecorator extends PageDecorator{ + + @SuppressWarnings("deprecation") // Super constructor is deprecated. Unsure if default constructor would work, though. + public ScmSyncConfigurationPageDecorator(){ + super(ScmSyncConfigurationPageDecorator.class); + } + + public ScmSyncConfigurationPlugin getScmSyncConfigPlugin(){ + return ScmSyncConfigurationPlugin.getInstance(); + } + + @JavaScriptMethod + public void purgeScmSyncConfigLogs() { + getScmSyncConfigPlugin().purgeFailLogs(); + } +} \ No newline at end of file diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationSaveableListener.java b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationSaveableListener.java new file mode 100644 index 00000000..cd3709af --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationSaveableListener.java @@ -0,0 +1,32 @@ +package hudson.plugins.scm_sync_configuration.extensions; + +import hudson.Extension; +import hudson.XmlFile; +import hudson.model.Saveable; +import hudson.model.listeners.SaveableListener; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.ScmSyncStrategy; + +@Extension +public class ScmSyncConfigurationSaveableListener extends SaveableListener{ + + @Override + public void onChange(Saveable o, XmlFile file) { + + super.onChange(o, file); + + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + if(plugin != null){ + ScmSyncStrategy strategy = plugin.getStrategyForSaveable(o, file.getFile()); + + if(strategy != null){ + WeightedMessage message = strategy.getCommitMessageFactory().getMessageWhenSaveableUpdated(o, file); + plugin.getTransaction().defineCommitMessage(message); + String path = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file.getFile()); + plugin.getTransaction().registerPath(path); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/BotherTimeout.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/BotherTimeout.java new file mode 100644 index 00000000..0c3b0670 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/BotherTimeout.java @@ -0,0 +1,75 @@ +package hudson.plugins.scm_sync_configuration.model; + +import java.util.Calendar; +import java.util.Date; + +import org.apache.commons.lang.builder.HashCodeBuilder; + +public abstract class BotherTimeout { + + protected Date timeout; + + protected BotherTimeout(int _timeoutMinutesFromNow){ + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MINUTE, _timeoutMinutesFromNow); + this.timeout = cal.getTime(); + } + + public boolean isOutdated(){ + return this.timeout.after(new Date()); + } + + public abstract boolean matchesUrl(String currentUrl); + + public static class FACTORY { + public static BotherTimeout createBotherTimeout(String type, int timeoutMinutesFromNow, String currentUrl){ + if("thisConfig".equals(type)){ + return new CurrentConfig(timeoutMinutesFromNow, currentUrl); + } else if("anyConfigs".equals(type)){ + return new EveryConfigs(timeoutMinutesFromNow); + } else { + throw new IllegalArgumentException("Invalid bother timeout type : "+String.valueOf(type)); + } + } + } + + public static class EveryConfigs extends BotherTimeout { + protected EveryConfigs(int _timeoutMinutesFromNow){ + super(_timeoutMinutesFromNow); + } + public boolean matchesUrl(String currentUrl) { + return true; + } + @Override + public boolean equals(Object that) { + if ( this == that ) return true; + return that instanceof EveryConfigs; + } + @Override + public int hashCode() { + return new HashCodeBuilder(17, 23).toHashCode(); + } + } + + public static class CurrentConfig extends BotherTimeout { + private String url; + public CurrentConfig(int _timeoutMinutesFromNow, String _url){ + super(_timeoutMinutesFromNow); + this.url = _url; + } + public boolean matchesUrl(String currentUrl){ + if(currentUrl==null) return false; + return this.url.equals(currentUrl); + } + @Override + public boolean equals(Object that) { + if ( this == that ) return true; + if ( !(that instanceof CurrentConfig) ) return false; + return url.equals(((CurrentConfig)that).url); + } + @Override + public int hashCode() { + return new HashCodeBuilder(13, 17).append(url).toHashCode(); + } + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java new file mode 100644 index 00000000..2538a6fd --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java @@ -0,0 +1,119 @@ +package hudson.plugins.scm_sync_configuration.model; + +import com.google.common.io.Files; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.exceptions.LoggableException; +import hudson.plugins.scm_sync_configuration.utils.Checksums; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author fcamblor + * POJO representing a Changeset built during a scm transaction + */ +public class ChangeSet { + + // Changeset commit message + WeightedMessage message = null; + // [Path, content in bytes] which are queued for addition/modification + Map pathContents; + // Paths which are queued for deletion + List pathsToDelete; + + public ChangeSet(){ + pathContents = new HashMap(); + pathsToDelete = new ArrayList(); + } + + public void registerPath(String path) { + boolean contentAlreadyRegistered = false; + File hudsonFile = JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(path); + Path pathToRegister = new Path(hudsonFile); + + if(pathToRegister.isDirectory()){ + pathContents.put(pathToRegister, new byte[0]); + } else { + // Verifying if path content is already in pathContent and, if this is the case, + // look at checksums + if(pathContents.containsKey(pathToRegister)){ + try { + contentAlreadyRegistered = Checksums.fileAndByteArrayContentAreEqual(pathToRegister.getHudsonFile(), pathContents.get(pathToRegister)); + } catch (IOException e) { + throw new LoggableException("Changeset path <"+path+"> registration failed", Checksums.class, "fileAndByteArrayContentAreEqual", e); + } + } + + if(!contentAlreadyRegistered){ + try { + pathContents.put(pathToRegister, Files.toByteArray(pathToRegister.getHudsonFile())); + } catch (IOException e) { + throw new LoggableException("Changeset path <"+path+"> registration failed", Files.class, "toByteArray", e); + } + } + } + } + + public void registerPathForDeletion(String path){ + // We should determine if path is a directory by watching scm path (and not hudson path) because in most of time, + // when we are here, directory is already deleted in hudson hierarchy... + if(new Path(path).getScmFile().exists()) { + boolean isDirectory = new Path(path).getScmFile().isDirectory(); + pathsToDelete.add(new Path(path, isDirectory)); + } + } + + public boolean isEmpty(){ + return pathContents.isEmpty() && pathsToDelete.isEmpty(); + } + + public Map getPathContents(){ + Map filteredPathContents = new HashMap(pathContents); + + // Avoiding ConcurrentModificationException... + List filteredPaths = new ArrayList(); + + for(Path pathToAdd : filteredPathContents.keySet()){ + for(Path pathToDelete : pathsToDelete){ + // Removing paths being both in pathsToDelete and pathContents + if(pathToDelete.equals(pathToAdd)){ + filteredPaths.add(pathToAdd); + } + } + } + + for(Path path : filteredPaths){ + filteredPathContents.remove(path); + } + + return filteredPathContents; + } + + public List getPathsToDelete(){ + return Collections.unmodifiableList(pathsToDelete); + } + + public void defineMessage(WeightedMessage weightedMessage) { + // Defining message only once ! + if(this.message == null || weightedMessage.getWeight().compareTo(message.getWeight()) > 0){ + this.message = weightedMessage; + } + } + + public String getMessage(){ + return this.message.getMessage(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for(Path path : getPathContents().keySet()){ + sb.append(String.format(" A %s%n", path.toString())); + } + for(Path path : getPathsToDelete()){ + sb.append(String.format(" D %s%n", path.toString())); + } + return sb.toString(); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java new file mode 100644 index 00000000..8682f5cc --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java @@ -0,0 +1,92 @@ +package hudson.plugins.scm_sync_configuration.model; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.WordUtils; + +import com.google.common.base.Strings; + +import hudson.model.User; + +/** + * @author fcamblor + * Commit is an aggregation of a changeset with a commit message + * Note that commit message won't _always_ be the same as changeset.message since some additionnal contextual + * informations will be provided. + */ +public class Commit { + String message; + ChangeSet changeset; + ScmContext scmContext; + User author; + + public Commit(ChangeSet changeset, User author, String userMessage, ScmContext scmContext) { + this.message = createCommitMessage(scmContext, changeset.getMessage(), author, userMessage); + this.changeset = changeset; + this.scmContext = scmContext; + this.author = author; + } + + public String getMessage() { + return message; + } + + public ChangeSet getChangeset() { + return changeset; + } + + public ScmContext getScmContext(){ + return scmContext; + } + + private static String createCommitMessage(ScmContext context, String messagePrefix, User user, String userComment){ + StringBuilder commitMessage = new StringBuilder(); + if (user != null) { + commitMessage.append(user.getId()).append(": "); + } + commitMessage.append(messagePrefix).append('\n'); + if (user != null) { + commitMessage.append('\n').append("Change performed by ").append(user.getDisplayName()).append('\n'); + } + if (userComment != null && !"".equals(userComment.trim())){ + commitMessage.append('\n').append(userComment.trim()); + } + String message = commitMessage.toString(); + + if (!Strings.isNullOrEmpty(context.getCommitMessagePattern())) { + message = context.getCommitMessagePattern().replaceAll("\\[message\\]", message.replaceAll("\\$", "\\\\\\$")); + } + return wrapText(message, 72); + } + + private static String wrapText(String str, int lineLength) { + if (str == null) { + return null; + } + int i = 0; + int max = str.length(); + StringBuilder text = new StringBuilder(); + while (i < max) { + int next = str.indexOf('\n', i); + if (next < 0) { + next = max; + } + String line = StringUtils.stripEnd(str.substring(i, next), null); + if (line.length() > lineLength) { + line = WordUtils.wrap(line, lineLength, "\n", false); + } + text.append(line).append('\n'); + i = next+1; + } + return text.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("Commit %s : %n", super.toString())); + sb.append(String.format(" Author : %s%n", String.valueOf(author))); + sb.append(String.format(" Comment : %s%n", message)); + sb.append(String.format(" Changeset : %n%s%n", changeset.toString())); + return sb.toString(); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/MessageWeight.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/MessageWeight.java new file mode 100644 index 00000000..e512f4dd --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/MessageWeight.java @@ -0,0 +1,12 @@ +package hudson.plugins.scm_sync_configuration.model; + +/** + * @author fcamblor + * Message weight should be used to prioritize messages into a Scm Transaction + */ +public enum MessageWeight { + MINIMAL, + NORMAL, + IMPORTANT, + MORE_IMPORTANT; +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java new file mode 100644 index 00000000..462f933c --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java @@ -0,0 +1,108 @@ +package hudson.plugins.scm_sync_configuration.model; + +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationBusiness; + +import java.io.File; + +/** + * @author fcamblor + * Paths allows to know if a given path is a directory or not, without using a File object since, + * generally, Path will be relative to jenkins root + */ +public class Path { + + private final String path; + private final boolean isDirectory; + + public Path(String path){ + this(JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(path)); + } + + public Path(File hudsonFile){ + this(JenkinsFilesHelper.buildPathRelativeToHudsonRoot(hudsonFile), hudsonFile.isDirectory()); + } + + public Path(String path, boolean isDirectory) { + this.path = path.replace(File.separatorChar, '/'); // Make sure we use the system-independent separator. + this.isDirectory = isDirectory; + } + + public String getPath() { + return path; + } + + public File getHudsonFile(){ + return JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(this.path); + } + + public File getScmFile(){ + // TODO: Externalize ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath() + // in another class ? + return new File(ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath(), getPath()); + } + + public String getFirstNonExistingParentScmPath(){ + File scmFile = getScmFile(); + File latestNonExistingScmFile = null; + File currentFile = scmFile; + while(!currentFile.exists()){ + latestNonExistingScmFile = currentFile; + currentFile = currentFile.getParentFile(); + } + + return latestNonExistingScmFile.getAbsolutePath().substring(ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath().length()+1); + } + + public boolean isDirectory() { + return isDirectory; + } + + public boolean contains(Path p){ + if (this.isDirectory()) { + String path = this.getPath(); + if (!path.endsWith("/")) { + path += '/'; + } + String otherPath = p.getPath(); + if (p.isDirectory() && !otherPath.endsWith("/")) { + otherPath += '/'; + } + return otherPath.startsWith(path); + } + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Path)) { + return false; + } + + Path path1 = (Path) o; + + if (isDirectory != path1.isDirectory) { + return false; + } + if (path != null ? !path.equals(path1.path) : path1.path != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = path != null ? path.hashCode() : 0; + result = 31 * result + (isDirectory ? 1 : 0); + return result; + } + + @Override + public String toString() { + return getPath()+(isDirectory()?"/":""); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/ScmContext.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/ScmContext.java new file mode 100644 index 00000000..1047e4b5 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/ScmContext.java @@ -0,0 +1,47 @@ +package hudson.plugins.scm_sync_configuration.model; + +import org.apache.commons.lang.builder.ToStringBuilder; + +import hudson.plugins.scm_sync_configuration.scms.SCM; + +public class ScmContext { + + private String scmRepositoryUrl; + private String gitRepositoryBranch; + private SCM scm; + private String commitMessagePattern; + + public ScmContext(SCM _scm, String _scmRepositoryUrl){ + this(_scm, _scmRepositoryUrl, "[message]", "master"); + } + + public ScmContext(SCM _scm, String _scmRepositoryUrl, String _commitMessagePattern, String gitRepositoryBranch){ + this.scm = _scm; + this.scmRepositoryUrl = _scmRepositoryUrl; + this.commitMessagePattern = _commitMessagePattern; + this.gitRepositoryBranch = gitRepositoryBranch; + } + + public String getGitRepositoryBranch() { + return gitRepositoryBranch; + } + + public String getScmRepositoryUrl() { + return scmRepositoryUrl; + } + + public SCM getScm() { + return scm; + } + + public String getCommitMessagePattern(){ + return commitMessagePattern; + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("scm", scm).append("scmRepositoryUrl", scmRepositoryUrl) + .append("commitMessagePattern", commitMessagePattern) + .append("gitRepositoryBranch", gitRepositoryBranch).toString(); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java new file mode 100644 index 00000000..b470cbdc --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java @@ -0,0 +1,26 @@ +package hudson.plugins.scm_sync_configuration.model; + +/** + * @author fcamblor + * WeightMessage is used to define a message with a weight + * Weight will be used to prioritize commit messages into a ScmTransaction, in order to + * have only the more important commit message kept during the transaction + */ +public class WeightedMessage { + + private final String message; + private final MessageWeight weight; + + public WeightedMessage(String message, MessageWeight weight) { + this.message = message; + this.weight = weight; + } + + public String getMessage() { + return message; + } + + public MessageWeight getWeight() { + return weight; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java new file mode 100644 index 00000000..1c927270 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java @@ -0,0 +1,168 @@ +package hudson.plugins.scm_sync_configuration.scms; + +import hudson.model.Descriptor; + +import java.util.List; +import java.util.logging.Logger; + +import jenkins.model.Jenkins; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.maven.scm.manager.NoSuchScmProviderException; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; +import org.apache.maven.scm.repository.ScmRepository; +import org.apache.maven.scm.repository.ScmRepositoryException; +import org.codehaus.plexus.util.StringUtils; +import org.kohsuke.stapler.StaplerRequest; + +import com.google.common.collect.ImmutableList; + +public abstract class SCM { + + protected static final Logger LOGGER = Logger.getLogger(SCM.class.getName()); + + protected static final List SCM_IMPLEMENTATIONS = ImmutableList.of( + new ScmSyncNoSCM(), + new ScmSyncSubversionSCM(), + new ScmSyncGitSCM() + ); + + transient protected String title; + transient protected String scmClassName; + transient protected String configPage; + transient protected String repositoryUrlHelpPath; + + protected SCM(String _title, String _configPage, String _scmClassName, String _repositoryUrlHelpPath){ + this.title = _title; + this.configPage = _configPage; + this.scmClassName = _scmClassName; + this.repositoryUrlHelpPath = _repositoryUrlHelpPath; + } + + public String getTitle(){ + return this.title; + } + + public String getConfigPage(){ + return this.configPage; + } + + public String getSCMClassName() { + return this.scmClassName; + } + + @SuppressWarnings("unchecked") + public Descriptor getSCMDescriptor(){ + return Jenkins.getInstance().getDescriptorByName(getSCMClassName()); + } + + public String getRepositoryUrlHelpPath() { + return this.repositoryUrlHelpPath; + } + + public ScmRepository getConfiguredRepository(ScmManager scmManager, String scmRepositoryURL) { + SCMCredentialConfiguration credentials = extractScmCredentials( extractScmUrlFrom(scmRepositoryURL) ); + + LOGGER.info("Creating SCM repository object for url : "+scmRepositoryURL); + ScmRepository repository = null; + try { + repository = scmManager.makeScmRepository( scmRepositoryURL ); + } catch (ScmRepositoryException e) { + LOGGER.throwing(ScmManager.class.getName(), "makeScmRepository", e); + LOGGER.severe("Error creating ScmRepository : "+e.getMessage()); + } catch (NoSuchScmProviderException e) { + LOGGER.throwing(ScmManager.class.getName(), "makeScmRepository", e); + LOGGER.severe("Error creating ScmRepository : "+e.getMessage()); + } + if(repository == null){ + return null; + } + + ScmProviderRepository scmRepo = repository.getProviderRepository(); + + // TODO: uncomment this ??? (MRELEASE-76) + //scmRepo.setPersistCheckout( false ); + + // TODO: instead of creating a SCMCredentialConfiguration, create a ScmProviderRepository + // XXX: host & port are unused afterwards? + // if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) + // { + // LOGGER.info("Populating host data into SCM repository object ..."); + // ScmProviderRepositoryWithHost repositoryWithHost = + // (ScmProviderRepositoryWithHost) repository.getProviderRepository(); + // String host = repositoryWithHost.getHost(); + // + // int port = repositoryWithHost.getPort(); + // + // if ( port > 0 ) + // { + // host += ":" + port; + // } + // } + + if(credentials != null){ + LOGGER.info("Populating credentials data into SCM repository object ..."); + if ( !StringUtils.isEmpty( credentials.getUsername() ) ) + { + scmRepo.setUser( credentials.getUsername() ); + } + if ( !StringUtils.isEmpty( credentials.getPassword() ) ) + { + scmRepo.setPassword( credentials.getPassword() ); + } + + if ( scmRepo instanceof ScmProviderRepositoryWithHost ) + { + ScmProviderRepositoryWithHost repositoryWithHost = (ScmProviderRepositoryWithHost) scmRepo; + if ( !StringUtils.isEmpty( credentials.getPrivateKey() ) ) + { + repositoryWithHost.setPrivateKey( credentials.getPrivateKey() ); + } + + if ( !StringUtils.isEmpty( credentials.getPassphrase() ) ) + { + repositoryWithHost.setPassphrase( credentials.getPassphrase() ); + } + } + } + + return repository; + } + + public abstract String createScmUrlFromRequest(StaplerRequest req); + public abstract String extractScmUrlFrom(String scmUrl); + public abstract SCMCredentialConfiguration extractScmCredentials(String scmRepositoryURL); + + public static SCM valueOf(Class clazz){ + return valueOf(getId(clazz)); + } + + public static SCM valueOf(String scmId){ + for(SCM scm : SCM_IMPLEMENTATIONS){ + if(scmId.equals(scm.getId())){ + return scm; + } + } + return null; + } + + public static SCM[] values(){ + return SCM_IMPLEMENTATIONS.toArray(new SCM[0]); + } + + @Override + public String toString(){ + return new ToStringBuilder(this).append("class", getClass().getName()).append("title", title).append("scmClassName", scmClassName) + .append("configPage", configPage).append("repositoryUrlHelpPath", repositoryUrlHelpPath).toString(); + } + + private static String getId(Class clazz){ + return clazz.getName(); + } + + public String getId(){ + return getId(getClass()); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCMCredentialConfiguration.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCMCredentialConfiguration.java new file mode 100644 index 00000000..6e8d818f --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCMCredentialConfiguration.java @@ -0,0 +1,41 @@ +package hudson.plugins.scm_sync_configuration.scms; + +public class SCMCredentialConfiguration { + private String username; + private String password; + private String privateKey; + private String passphrase; + + public SCMCredentialConfiguration(String _username, String _password, String _passPhrase, char[] _privateKey){ + this.username = _username; + this.password = _password; + this.passphrase = _passPhrase; + if(_privateKey!=null){ + this.privateKey = String.valueOf(_privateKey); + } + } + + public SCMCredentialConfiguration(String _username, String _password){ + this(_username, _password, null, null); + } + + public SCMCredentialConfiguration(String _username){ + this(_username, null, null, null); + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getPrivateKey() { + return privateKey; + } + + public String getPassphrase() { + return passphrase; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java new file mode 100644 index 00000000..ff2354ed --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java @@ -0,0 +1,33 @@ +package hudson.plugins.scm_sync_configuration.scms; + +import org.kohsuke.stapler.StaplerRequest; + +public class ScmSyncGitSCM extends SCM { + + private static final String SCM_URL_PREFIX="scm:git:"; + + ScmSyncGitSCM(){ + super("Git", "git/config.jelly", "hudson.plugins.git.GitSCM", "/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.jelly"); + } + + @Override + public String createScmUrlFromRequest(StaplerRequest req) { + String repoURL = req.getParameter("gitRepositoryUrl"); + if (repoURL == null) { + return null; + } else { + return SCM_URL_PREFIX + repoURL.trim(); + } + } + + @Override + public String extractScmUrlFrom(String scmUrl) { + return scmUrl.substring(SCM_URL_PREFIX.length()); + } + + @Override + public SCMCredentialConfiguration extractScmCredentials(String scmUrl) { + return null; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncNoSCM.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncNoSCM.java new file mode 100644 index 00000000..88caa195 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncNoSCM.java @@ -0,0 +1,28 @@ +package hudson.plugins.scm_sync_configuration.scms; + +import org.kohsuke.stapler.StaplerRequest; + + +public class ScmSyncNoSCM extends SCM { + + ScmSyncNoSCM(){ + super("None", "none/config.jelly", null, "/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/none/url-help.jelly"); + } + + @Override + public String createScmUrlFromRequest(StaplerRequest req) { + return null; + } + + @Override + public String extractScmUrlFrom(String scmUrl) { + return null; + } + + @Override + public SCMCredentialConfiguration extractScmCredentials( + String scmRepositoryURL) { + return null; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java new file mode 100644 index 00000000..7eae1a61 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java @@ -0,0 +1,153 @@ +package hudson.plugins.scm_sync_configuration.scms; + +import hudson.scm.SubversionSCM; +import hudson.scm.SubversionSCM.DescriptorImpl.Credential; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.logging.Level; + +import org.kohsuke.stapler.StaplerRequest; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.auth.SVNAuthentication; +import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; +import org.tmatesoft.svn.core.auth.SVNSSHAuthentication; +import org.tmatesoft.svn.core.auth.SVNSSLAuthentication; +import org.tmatesoft.svn.core.auth.SVNUserNameAuthentication; +import org.tmatesoft.svn.core.internal.wc.DefaultSVNAuthenticationManager; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.io.SVNRepositoryFactory; +import org.tmatesoft.svn.core.wc.SVNWCUtil; + +public class ScmSyncSubversionSCM extends SCM { + + private static final String SCM_URL_PREFIX="scm:svn:"; + + ScmSyncSubversionSCM(){ + super("Subversion", "svn/config.jelly", "hudson.scm.SubversionSCM", "/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.jelly"); + } + + @Override + public String createScmUrlFromRequest(StaplerRequest req) { + String repoURL = req.getParameter("repositoryUrl"); + if (repoURL == null) { + return null; + } else { + return SCM_URL_PREFIX + repoURL.trim(); + } + } + + @Override + public String extractScmUrlFrom(String scmUrl) { + return scmUrl.substring(SCM_URL_PREFIX.length()); + } + + @Override + public SCMCredentialConfiguration extractScmCredentials(String scmUrl) { + LOGGER.info("Extracting SVN Credentials for url : "+scmUrl); + String realm = retrieveRealmFor(scmUrl); + if(realm != null){ + LOGGER.fine("Extracted realm from "+scmUrl+" is ["+realm+"]"); + SubversionSCM.DescriptorImpl subversionDescriptor = (SubversionSCM.DescriptorImpl)getSCMDescriptor(); + try { + Field credentialField = SubversionSCM.DescriptorImpl.class.getDeclaredField("credentials"); + credentialField.setAccessible(true); + @SuppressWarnings("unchecked") + Map credentials = (Map)credentialField.get(subversionDescriptor); + Credential cred = credentials.get(realm); + if(cred == null){ + LOGGER.severe("No credentials are stored in Hudson for realm ["+realm+"] !"); + return null; + } + String kind = ISVNAuthenticationManager.PASSWORD; + if(scmUrl.startsWith("svn+ssh")){ + kind = ISVNAuthenticationManager.SSH; + } + return createSCMCredentialConfiguration(cred.createSVNAuthentication(kind)); + } catch (SecurityException e) { + LOGGER.log(Level.SEVERE, "'credentials' field not readable on SubversionSCM.DescriptorImpl !"); + } catch (NoSuchFieldException e) { + LOGGER.log(Level.SEVERE, "'credentials' field not readable on SubversionSCM.DescriptorImpl !"); + } catch (IllegalArgumentException e) { + LOGGER.log(Level.SEVERE, "'credentials' field not accessible on "+String.valueOf(subversionDescriptor)+" !"); + } catch (IllegalAccessException e) { + LOGGER.log(Level.SEVERE, "'credentials' field not accessible on "+String.valueOf(subversionDescriptor)+" !"); + } catch (SVNException e) { + LOGGER.log(Level.WARNING, "Error creating SVN authentication from realm ["+realm+"] !", e); + } + } else { + LOGGER.warning("No credential (realm) found for url ["+scmUrl+"] : it seems that you should enter your credentials in the UI at " + +"this url"); + } + return null; + } + + private String retrieveRealmFor(String scmURL) { + final String[] realms = new String[]{ null }; + + SVNRepository repository; + try { + repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(scmURL)); + repository.setTunnelProvider(SVNWCUtil.createDefaultOptions(true)); + }catch(SVNException e){ + LOGGER.throwing(SVNRepositoryFactory.class.getName(), "create", e); + LOGGER.severe("Error while creating SVNRepository : "+e.getMessage()); + return null; + } + try { + repository.setAuthenticationManager(new DefaultSVNAuthenticationManager(SVNWCUtil.getDefaultConfigurationDirectory(), true, "", "", null, "") { + @Override + public SVNAuthentication getFirstAuthentication(String kind, String realm, SVNURL url) throws SVNException { + realms[0] = realm; + return super.getFirstAuthentication(kind, realm, url); + } + @Override + public SVNAuthentication getNextAuthentication(String kind, String realm, SVNURL url) throws SVNException { + realms[0] = realm; + return super.getNextAuthentication(kind, realm, url); + } + + @Override + public void acknowledgeAuthentication(boolean accepted, String kind, String realm, SVNErrorMessage errorMessage, SVNAuthentication authentication) throws SVNException { + realms[0] = realm; + super.acknowledgeAuthentication(accepted, kind, realm, errorMessage, authentication); + } + }); + repository.testConnection(); + + } catch (SVNException e) { + // If a problem happens, don't do anything, it implies realm doesn't exist in current cache + } + + return realms[0]; + } + + /** + * Ugly method to convert a SVN authentication into a SCMCredentialConfiguration + */ + public SCMCredentialConfiguration createSCMCredentialConfiguration(SVNAuthentication auth){ + SCMCredentialConfiguration credentials = null; + if(auth instanceof SVNPasswordAuthentication){ + SVNPasswordAuthentication passAuth = (SVNPasswordAuthentication)auth; + credentials = new SCMCredentialConfiguration(passAuth.getUserName(), passAuth.getPassword()); + } else if(auth instanceof SVNSSHAuthentication){ + SVNSSHAuthentication sshAuth = (SVNSSHAuthentication)auth; + credentials = new SCMCredentialConfiguration(sshAuth.getUserName(), sshAuth.getPassword(), sshAuth.getPassphrase(), sshAuth.getPrivateKey()); + } else if(auth instanceof SVNSSLAuthentication){ + SVNSSLAuthentication sslAuth = (SVNSSLAuthentication)auth; + credentials = new SCMCredentialConfiguration(sslAuth.getUserName(), sslAuth.getPassword()); + } else if(auth instanceof SVNUserNameAuthentication){ + SVNUserNameAuthentication unameAuth = (SVNUserNameAuthentication)auth; + credentials = new SCMCredentialConfiguration(unameAuth.getUserName()); + } + + if(credentials != null){ + LOGGER.info("Created SCM Credentials for user "+credentials.getUsername()+"..."); + } + + return credentials; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/FixedGitStatusConsumer.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/FixedGitStatusConsumer.java new file mode 100644 index 00000000..54fd8dee --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/FixedGitStatusConsumer.java @@ -0,0 +1,205 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; +import org.apache.maven.scm.ScmFile; +import org.apache.maven.scm.ScmFileStatus; +import org.apache.maven.scm.log.ScmLogger; +import org.codehaus.plexus.util.cli.StreamConsumer; + +import com.google.common.base.Strings; + +/** + * Copied from org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer (maven-scm 1.9.1) + * and fixed to account for https://issues.apache.org/jira/browse/SCM-772 . + */ +public class FixedGitStatusConsumer +implements StreamConsumer +{ + + /** + * The pattern used to match added file lines + */ + private static final Pattern ADDED_PATTERN = Pattern.compile( "^A[ M]* (.*)$" ); + + /** + * The pattern used to match modified file lines + */ + private static final Pattern MODIFIED_PATTERN = Pattern.compile( "^ *M[ M]* (.*)$" ); + + /** + * The pattern used to match deleted file lines + */ + private static final Pattern DELETED_PATTERN = Pattern.compile( "^ *D * (.*)$" ); + + /** + * The pattern used to match renamed file lines + */ + private static final Pattern RENAMED_PATTERN = Pattern.compile( "^R (.*) -> (.*)$" ); + + private final ScmLogger logger; + + private final File workingDirectory; + + /** + * Entries are relative to working directory, not to the repositoryroot + */ + private final List changedFiles = new ArrayList(); + + private String relativeRepositoryPath; + + private final File repositoryRoot; + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + public FixedGitStatusConsumer (ScmLogger logger, File workingDirectory, File repositoryRoot) { + this.logger = logger; + this.workingDirectory = workingDirectory; + if (repositoryRoot != null) { + String absoluteRepositoryRoot = repositoryRoot.getAbsolutePath(); // Make sure all separators are File.separator + // The revparse runs with fileset.getBasedir(). That of course must be under the repo root. + String basePath = workingDirectory.getAbsolutePath(); + if (!absoluteRepositoryRoot.endsWith(File.separator)) { + absoluteRepositoryRoot += File.separator; + } + if (basePath.startsWith(absoluteRepositoryRoot)) { + String pathInsideRepo = basePath.substring(absoluteRepositoryRoot.length()); + if (!Strings.isNullOrEmpty(pathInsideRepo)) { + relativeRepositoryPath = pathInsideRepo; + } + } + } + this.repositoryRoot = repositoryRoot; + // Either the workingDirectory == repositoryRoot: we have no relativeRepositoryPath set + // Or the working directory was a subdirectory (in the workspace!) of repositoryRoot, then + // relativeRepositoryPath contains now the relative path to the working directory. + // + // It would appear that git status --porcelain always returns paths relative to the repository + // root. + } + + // ---------------------------------------------------------------------- + // StreamConsumer Implementation + // ---------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void consumeLine( String line ) + { + if ( logger.isDebugEnabled() ) + { + logger.debug( line ); + } + if ( StringUtils.isEmpty( line ) ) + { + return; + } + + ScmFileStatus status = null; + + List files = new ArrayList(); + + Matcher matcher; + if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() ) + { + status = ScmFileStatus.ADDED; + files.add(ScmSyncGitUtils.dequote(matcher.group(1))); + } + else if ( ( matcher = MODIFIED_PATTERN.matcher( line ) ).find() ) + { + status = ScmFileStatus.MODIFIED; + files.add(ScmSyncGitUtils.dequote(matcher.group(1))); + } + else if ( ( matcher = DELETED_PATTERN.matcher( line ) ) .find() ) + { + status = ScmFileStatus.DELETED; + files.add(ScmSyncGitUtils.dequote(matcher.group(1))); + } + else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() ) + { + status = ScmFileStatus.RENAMED; + files.add(ScmSyncGitUtils.dequote(matcher.group(1))); + files.add(ScmSyncGitUtils.dequote(matcher.group(2))); + } + else + { + logger.warn( "Ignoring unrecognized line: " + line ); + return; + } + + // If the file isn't a file; don't add it. + if (files.isEmpty() || status == null) { + return; + } + File checkDir = repositoryRoot; + if (workingDirectory != null && relativeRepositoryPath != null) { + // Make all paths relative to this directory. + List relativeNames = new ArrayList(); + for (String repoRelativeName : files) { + relativeNames.add(ScmSyncGitUtils.relativizePath(relativeRepositoryPath, new File(repoRelativeName).getPath())); + } + files = relativeNames; + checkDir = workingDirectory; + } + // Now check them all against the checkDir. This check has been taken over from the base implementation + // in maven-scm's GitStatusConsumer, but I'm not really sure this makes sense. Who said the workspace + // had to be equal to the git index (staging area) here? + if (status == ScmFileStatus.RENAMED) { + String oldFilePath = files.get( 0 ); + String newFilePath = files.get( 1 ); + if (new File(checkDir, oldFilePath).isFile()) { + logger.debug("file '" + oldFilePath + "' still exists after rename"); + return; + } + if (!new File(checkDir, newFilePath).isFile()) { + logger.debug("file '" + newFilePath + "' does not exist after rename"); + return; + } + } else if (status == ScmFileStatus.DELETED) { + if (new File(checkDir, files.get(0)).isFile()) { + return; + } + } else { + if (!new File(checkDir, files.get(0)).isFile()) { + return; + } + } + + for (String file : files) { + changedFiles.add(new ScmFile(file.replace(File.separatorChar, '/'), status)); + } + } + + public List getChangedFiles() + { + return changedFiles; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitAddCommand.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitAddCommand.java new file mode 100644 index 00000000..897f4b68 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitAddCommand.java @@ -0,0 +1,118 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFile; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.command.add.AddScmResult; +import org.apache.maven.scm.command.status.StatusScmResult; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; +import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand; +import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; +import org.codehaus.plexus.util.Os; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; + +public class ScmSyncGitAddCommand extends GitAddCommand { + + @Override + protected AddScmResult executeAddCommand(ScmProviderRepository repo, ScmFileSet fileSet, String message, boolean binary) + throws ScmException { + GitScmProviderRepository repository = (GitScmProviderRepository) repo; + + if (fileSet.getFileList().isEmpty()) { + throw new ScmException("You must provide at least one file/directory to add"); + } + + AddScmResult result = executeAddFileSet(fileSet); + + if (result != null) { + return result; + } + + ScmSyncGitStatusCommand statusCommand = new ScmSyncGitStatusCommand(); + statusCommand.setLogger(getLogger()); + StatusScmResult status = statusCommand.executeStatusCommand(repository, fileSet); + getLogger().warn("add - status - " + status.isSuccess()); + for (ScmFile s : status.getChangedFiles()) { + getLogger().warn("added " + s.getPath()); + } + List changedFiles = new ArrayList(); + + if (fileSet.getFileList().isEmpty()) { + changedFiles = status.getChangedFiles(); + } else { + for (ScmFile scmfile : status.getChangedFiles()) { + // if a specific fileSet is given, we have to check if the file is really tracked + for (File f : fileSet.getFileList()) { + if (FilenameUtils.separatorsToUnix(f.getPath()).equals(scmfile.getPath())) { + changedFiles.add(scmfile); + } + } + } + } + Commandline cl = createCommandLine(fileSet.getBasedir(), fileSet.getFileList()); + return new AddScmResult(cl.toString(), changedFiles); + } + + public static Commandline createCommandLine(File workingDirectory, List files) throws ScmException { + Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "add"); + + // use this separator to make clear that the following parameters are files and not revision info. + cl.createArg().setValue("--"); + + ScmSyncGitUtils.addTarget(cl, files); + + return cl; + } + + protected AddScmResult executeAddFileSet(ScmFileSet fileSet) throws ScmException { + File workingDirectory = fileSet.getBasedir(); + List files = fileSet.getFileList(); + + // command line can be too long for windows so add files individually (see SCM-697) + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + for (File file : files) { + AddScmResult result = executeAddFiles(workingDirectory, Collections.singletonList(file)); + if (result != null) { + return result; + } + } + } else { + AddScmResult result = executeAddFiles(workingDirectory, files); + if (result != null) { + return result; + } + } + + return null; + } + + private AddScmResult executeAddFiles(File workingDirectory, List files) throws ScmException { + Commandline cl = createCommandLine(workingDirectory, files); + + CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); + CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer(); + + int exitCode = -1; + try { + exitCode = GitCommandLineUtils.execute(cl, stdout, stderr, getLogger()); + } catch (Throwable t) { + getLogger().error("Failed:", t); + } + if (exitCode != 0) { + String msg = stderr.getOutput(); + getLogger().info("Add failed:" + msg); + return new AddScmResult(cl.toString(), "The git-add command failed.", msg, false); + } + + return null; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitCheckInCommand.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitCheckInCommand.java new file mode 100644 index 00000000..2bf64e36 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitCheckInCommand.java @@ -0,0 +1,133 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFile; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmFileStatus; +import org.apache.maven.scm.ScmVersion; +import org.apache.maven.scm.command.add.AddScmResult; +import org.apache.maven.scm.command.checkin.CheckInScmResult; +import org.apache.maven.scm.command.status.StatusScmResult; +import org.apache.maven.scm.log.ScmLogger; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; +import org.apache.maven.scm.provider.git.gitexe.command.branch.GitBranchCommand; +import org.apache.maven.scm.provider.git.gitexe.command.checkin.GitCheckInCommand; +import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; + +/** + * @author fcamblor Crappy hack because for the moment, in maven-scmprovider-gitext 1.8.1, when we checkIn file, checkin + * is originally made by passing pushUrl instead of a local reference (such as "origin") Problem is passing + * pushUrl doesn't update current local reference once pushed => after push, origin/ will not be + * updated to latest commit => on next push, there will be an error saying some pull is needed. This workaround + * could be betterly handled when something like "checkinAndFetch" could be implemented generically in + * maven-scm-api (see http://maven.40175.n5.nabble.com/SCM-GitExe-no-fetch-after-push-td5745064.html) + * + * @author Tom Rewritten executeCheckInCommand to account for SCM-772 and SCM-695. Make use of also + * fixed GitAddCommand instead of re-inventing the wheel once more again. + */ +public class ScmSyncGitCheckInCommand extends GitCheckInCommand { + // Retrieved implementation from GitCheckInCommande v1.8.1, only overriding call to createPushCommandLine() + // by a *custom* implementation + @Override + protected CheckInScmResult executeCheckInCommand(ScmProviderRepository repo, ScmFileSet fileSet, String message, + ScmVersion version) + throws ScmException { + GitScmProviderRepository repository = (GitScmProviderRepository) repo; + + CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); + CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer(); + + File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null); + try { + FileUtils.fileWrite(messageFile.getAbsolutePath(), message); + } catch (IOException ex) { + return new CheckInScmResult(null, "Error while making a temporary file for the commit message: " + + ex.getMessage(), null, false); + } + + try { + // if specific fileSet is given, we have to git-add them first + // otherwise we will use 'git-commit -a' later + if (!fileSet.getFileList().isEmpty()) { + ScmSyncGitAddCommand addCommand = new ScmSyncGitAddCommand(); + addCommand.setLogger(getLogger()); + AddScmResult addResult = addCommand.executeAddFileSet(fileSet); + if (addResult != null && !addResult.isSuccess()) { + return new CheckInScmResult(new ArrayList(), addResult); + } + } + // Must run status here. There might have been earlier additions!! + ScmSyncGitStatusCommand statusCommand = new ScmSyncGitStatusCommand(); + statusCommand.setLogger(getLogger()); + StatusScmResult status = statusCommand.executeStatusCommand(repository, + new ScmFileSet(fileSet.getBasedir())); + List changedFiles = null; + if (status.isSuccess()) { + changedFiles = status.getChangedFiles(); + if (changedFiles.isEmpty()) { + return new CheckInScmResult(null, changedFiles); + } + } else { + return new CheckInScmResult(new ArrayList(), status); + } + Commandline clCommit = createCommitCommandLine(repository, fileSet, messageFile); + int exitCode = GitCommandLineUtils.execute(clCommit, stdout, stderr, getLogger()); + if (exitCode != 0) { + String msg = stderr.getOutput(); + return new CheckInScmResult(clCommit.toString(), "The git-commit command failed.", msg, false); + } + if (repo.isPushChanges()) { + Commandline cl = createSpecificPushCommandLine(getLogger(), repository, fileSet, version); + exitCode = GitCommandLineUtils.execute(cl, stdout, stderr, getLogger()); + if (exitCode != 0) { + String msg = stderr.getOutput(); + return new CheckInScmResult(cl.toString(), "The git-push command failed.", msg, false); + } + } + + List checkedInFiles = new ArrayList(changedFiles.size()); + + // rewrite all detected files to now have status 'checked_in' + for (ScmFile changedFile : changedFiles) { + checkedInFiles.add(new ScmFile(changedFile.getPath(), ScmFileStatus.CHECKED_IN)); + } + + return new CheckInScmResult(clCommit.toString(), checkedInFiles); + } finally { + try { + FileUtils.forceDelete(messageFile); + } catch (IOException ex) { + // ignore + } + } + } + + public static Commandline createSpecificPushCommandLine(ScmLogger logger, GitScmProviderRepository repository, ScmFileSet fileSet, ScmVersion version) + throws ScmException { + Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "push"); + + String branch = GitBranchCommand.getCurrentBranch(logger, repository, fileSet); + + if (branch == null || branch.length() == 0) { + throw new ScmException("Could not detect the current branch. Don't know where I should push to!"); + } + + // Overloaded branch name here : if repository.getUrl() is kept, during checkin(), current *local* branch + // reference is not updated, whereas by using origin, it will be done ! + cl.createArg().setValue("origin"); + + cl.createArg().setValue(branch + ":" + branch); + + return cl; + } + +} \ No newline at end of file diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitExeScmProvider.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitExeScmProvider.java new file mode 100644 index 00000000..83011fd6 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitExeScmProvider.java @@ -0,0 +1,38 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +import org.apache.maven.scm.provider.git.command.GitCommand; +import org.apache.maven.scm.provider.git.gitexe.GitExeScmProvider; + +/** + * Try to fix those very broken maven scm git commands. We should really move to using the git-client plugin. + */ +public class ScmSyncGitExeScmProvider extends GitExeScmProvider { + + @Override + protected GitCommand getCheckInCommand() { + // Push to origin (fcamblor) + // Handle quoted output from git, and fix relative path computations SCM-695, SCM-772 + return new ScmSyncGitCheckInCommand(); + } + + @Override + protected GitCommand getRemoveCommand() { + // Include -- in git rm + return new ScmSyncGitRemoveCommand(); + } + + @Override + protected GitCommand getStatusCommand() { + // Handle quoted output from git, and fix relative path computations SCM-695, SCM-772 + return new ScmSyncGitStatusCommand(); + } + + @Override + protected GitCommand getAddCommand() { + // Handle quoted output from git, and fix relative path computations SCM-695, SCM-772 + return new ScmSyncGitAddCommand(); + } + + // TODO: we also use checkout and update. Those call git ls-files, which parses the result wrongly... + // (doesn't account for the partial escaping done there for \t, \n, and \\ .) +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitRemoveCommand.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitRemoveCommand.java new file mode 100644 index 00000000..15355af9 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitRemoveCommand.java @@ -0,0 +1,72 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +import java.io.File; +import java.util.List; + +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmResult; +import org.apache.maven.scm.command.remove.RemoveScmResult; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; +import org.apache.maven.scm.provider.git.gitexe.command.remove.GitRemoveCommand; +import org.apache.maven.scm.provider.git.gitexe.command.remove.GitRemoveConsumer; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; + +/** + * Yet another crappy hack to fix maven's gitexe implementation. It doesn't pass "--" to git rm, leading to failures if + * a file starting with a dash is to be removed. Because of the poor design of that library using static methods galore, + * we cannot just override the wrong method... + */ +public class ScmSyncGitRemoveCommand extends GitRemoveCommand { + // Implementation copied from v1.9.1; single change in createCommandLine below. + + @Override + protected ScmResult executeRemoveCommand(ScmProviderRepository repo, ScmFileSet fileSet, String message) + throws ScmException { + if (fileSet.getFileList().isEmpty()) { + throw new ScmException("You must provide at least one file/directory to remove"); + } + + Commandline cl = createCommandLine(fileSet.getBasedir(), fileSet.getFileList()); + GitRemoveConsumer consumer = new GitRemoveConsumer(getLogger()); + + CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); + + int exitCode; + + exitCode = GitCommandLineUtils.execute(cl, consumer, stderr, getLogger()); + if (exitCode != 0) { + return new RemoveScmResult(cl.toString(), "The git command failed.", stderr.getOutput(), false); + } + + return new RemoveScmResult(cl.toString(), consumer.getRemovedFiles()); + } + + public static Commandline createCommandLine(File workingDirectory, List files) throws ScmException { + Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "rm"); + + for (File file : files) { + if (file.isAbsolute()) { + if (file.isDirectory()) { + cl.createArg().setValue("-r"); + break; + } + } else { + File absFile = new File(workingDirectory, file.getPath()); + if (absFile.isDirectory()) { + cl.createArg().setValue("-r"); + break; + } + } + } + + cl.createArg().setValue("--"); // This is missing upstream. + + ScmSyncGitUtils.addTarget(cl, files); + + return cl; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitStatusCommand.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitStatusCommand.java new file mode 100644 index 00000000..3b4d2656 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitStatusCommand.java @@ -0,0 +1,39 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +import java.io.File; + +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.command.status.StatusScmResult; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; +import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusCommand; +import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; + +public class ScmSyncGitStatusCommand extends GitStatusCommand { + + @Override + protected StatusScmResult executeStatusCommand(ScmProviderRepository repo, ScmFileSet fileSet) throws ScmException { + Commandline cl = createCommandLine( (GitScmProviderRepository) repo, fileSet ); + File repoRootDirectory = ScmSyncGitUtils.getRepositoryRootDirectory(fileSet.getBasedir(), getLogger()); + FixedGitStatusConsumer consumer = new FixedGitStatusConsumer(getLogger(), fileSet.getBasedir(), repoRootDirectory); + CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); + int exitCode = GitCommandLineUtils.execute( cl, consumer, stderr, getLogger() ); + if (exitCode != 0) { + if (getLogger().isInfoEnabled()) { + getLogger().info( "nothing added to commit but untracked files present (use \"git add\" to track)" ); + } + } + return new StatusScmResult( cl.toString(), consumer.getChangedFiles() ); + } + + public static Commandline createCommandLine( GitScmProviderRepository repository, ScmFileSet fileSet ) + { + Commandline cl = GitCommandLineUtils.getBaseGitCommandLine( fileSet.getBasedir(), "status" ); + cl.addArguments( new String[] { "--porcelain", "." } ); + return cl; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitUtils.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitUtils.java new file mode 100644 index 00000000..a1ece5d7 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitUtils.java @@ -0,0 +1,193 @@ +package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.log.ScmLogger; +import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; + +import com.google.common.base.Ascii; +import com.google.common.base.Charsets; +import com.google.common.base.Strings; + +public final class ScmSyncGitUtils { + + private ScmSyncGitUtils() { + // No instantiation + } + + public static File getRepositoryRootDirectory(File someDirectoryInTheRepo, ScmLogger logger) throws ScmException { + // Factored out from GitStatusCommand. + Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(someDirectoryInTheRepo, "rev-parse"); + cl.createArg().setValue("--show-toplevel"); + + CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer(); + CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); + + int exitCode = GitCommandLineUtils.execute(cl, stdout, stderr, logger); + + if (exitCode != 0) { + // git-status returns non-zero if nothing to do + if (logger.isInfoEnabled()) { + logger.info( "Could not resolve toplevel from " + someDirectoryInTheRepo); + } + } else { + String absoluteRepositoryRoot = stdout.getOutput().trim(); // This comes back unquoted! + if (!Strings.isNullOrEmpty(absoluteRepositoryRoot)) { + return new File(absoluteRepositoryRoot); + } + } + return null; + } + + // Fix for https://issues.apache.org/jira/browse/SCM-695 + public static void addTarget(Commandline cl, List files) throws ScmException { + // Fix for https://issues.apache.org/jira/browse/SCM-695 . Function copied from + // GitCommandLineUtils. + if (files == null || files.isEmpty()) { + return; + } + final File workingDirectory = cl.getWorkingDirectory(); + try + { + String canonicalWorkingDirectory = workingDirectory.getCanonicalPath(); + if (!canonicalWorkingDirectory.endsWith(File.separator)) { + canonicalWorkingDirectory += File.separator; // Fixes SCM-695 + } + for (File file : files) { + String relativeFile = file.getPath(); + final String canonicalFile = file.getCanonicalPath(); + + if (canonicalFile.startsWith(canonicalWorkingDirectory)) { + relativeFile = canonicalFile.substring(canonicalWorkingDirectory.length()); + if (relativeFile.startsWith(File.separator)) { + relativeFile = relativeFile.substring(File.separator.length()); + } + } + // no setFile() since this screws up the working directory! + cl.createArg().setValue( relativeFile ); + } + } + catch ( IOException ex ) + { + throw new ScmException( "Could not get canonical paths for workingDirectory = " + + workingDirectory + " or files=" + files, ex ); + } + } + + public static String relativizePath (String parent, String child) { + // Fix for SCM-772. Compare FixedGitStatusConsumer. + if ( parent != null && child != null) { + if (parent.equals(child)) { + return ""; + } + if (!parent.endsWith(File.separator)) { + parent += File.separator; + } + if (child.startsWith(parent)) { + child = child.substring(parent.length()); + if (child.startsWith(File.separator)) { + child = child.substring(File.separator.length()); + } + } + } + return child; + } + + public static String dequote(String inputFromGit) { + if (inputFromGit.charAt(0) != '"') { + return inputFromGit; + } + // Per http://git-scm.com/docs/git-status : If a filename contains whitespace or other nonprintable characters, + // that field will be quoted in the manner of a C string literal: surrounded by ASCII double quote (34) characters, + // and with interior special characters backslash-escaped. + // + // Here, we have to undo this. We work on byte sequences and assume UTF-8. (Git may also give us back non-ASCII + // characters back as UTF-8 byte sequences that appear as octal or hex escapes.) + byte[] input = inputFromGit.substring(1, inputFromGit.length() - 1).getBytes(Charsets.UTF_8); + byte[] output = new byte[input.length]; // It can only get smaller + int j = 0; + for (int i = 0; i < input.length; i++, j++) { + if (input[i] == '\\') { + byte ch = input[++i]; + switch (ch) { + case '\\' : + case '"' : + output[j] = ch; + break; + case 'a' : + output[j] = Ascii.BEL; + break; + case 'b' : + output[j] = '\b'; + break; + case 'f' : + output[j] = '\f'; + break; + case 'n' : + output[j] = '\n'; + break; + case 'r' : + output[j] = '\r'; + break; + case 't' : + output[j] = '\t'; + break; + case 'v' : + output[j] = Ascii.VT; + break; + case 'x' : + // Hex escape; must be followed by two hex digits. We assume git gives us only valid sequences. + if (i + 2 < input.length) { + byte value = toHex(input[++i]); + output[j] = (byte) (value * 16 + toHex(input[++i])); + } else { + // Invalid. + output[j++] = '\\'; + output[j] = ch; + } + break; + case '0' : + case '1' : + case '2' : + case '3' : + // Octal escape; must be followed by two more octal digits. We assume git gives us only valid sequences. + if (i + 2 < input.length) { + byte value = (byte) (ch - '0'); + value = (byte) (value * 8 + (byte) (input[++i] - '0')); + output[j] = (byte) (value * 8 + (byte) (input[++i] - '0')); + } else { + // Invalid. + output[j++] = '\\'; + output[j] = ch; + } + break; + default : + // Unknown/invalid escape. + output[j++] = '\\'; + output[j] = ch; + break; + } + } else { + output[j] = input[i]; + } + } + return new String(output, 0, j, Charsets.UTF_8); + } + + private static byte toHex(byte in) { + if (in >= '0' && in <= '9') { + return (byte) (in - '0'); + } else if (in >= 'A' && in <= 'F') { + return (byte) (in - 'A'); + } else if (in >= 'a' && in <= 'f') { + return (byte) (in - 'a'); + } + return in; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java new file mode 100644 index 00000000..4723403f --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java @@ -0,0 +1,135 @@ +package hudson.plugins.scm_sync_configuration.strategies; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.model.MessageWeight; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; + +import javax.annotation.Nullable; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.selectors.FileSelector; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jenkins.model.Jenkins; + +public abstract class AbstractScmSyncStrategy implements ScmSyncStrategy { + + private static final Function PATH_TO_FILE_IN_HUDSON = new Function() { + @Override + public File apply(@Nullable String path) { + return new File(Jenkins.getInstance().getRootDir(), path); + } + }; + + protected static class DefaultCommitMessageFactory implements CommitMessageFactory { + @Override + public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { + return new WeightedMessage("Modification on configuration(s)", MessageWeight.MINIMAL); + } + @Override + public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { + return new WeightedMessage("Item renamed", MessageWeight.MINIMAL); + } + @Override + public WeightedMessage getMessageWhenItemDeleted(Item item) { + return new WeightedMessage("File hierarchy deleted", MessageWeight.MINIMAL); + } + } + + private final ConfigurationEntityMatcher configEntityMatcher; + private final List pageMatchers; + private CommitMessageFactory commitMessageFactory; + + protected AbstractScmSyncStrategy(ConfigurationEntityMatcher _configEntityMatcher, List _pageMatchers){ + this.configEntityMatcher = _configEntityMatcher; + this.pageMatchers = _pageMatchers; + } + + protected ConfigurationEntityMatcher createConfigEntityMatcher(){ + return configEntityMatcher; + } + + @Override + public boolean isSaveableApplicable(Saveable saveable, File file) { + return createConfigEntityMatcher().matches(saveable, file); + } + + public PageMatcher getPageMatcherMatching(String url){ + String rootUrl = Jenkins.getInstance().getRootUrlFromRequest(); + String cleanedUrl = null; + if(url.startsWith(rootUrl)){ + cleanedUrl = url.substring(rootUrl.length()); + } else { + cleanedUrl = url; + } + for(PageMatcher pm : pageMatchers){ + if(pm.getUrlRegex().matcher(cleanedUrl).matches()){ + return pm; + } + } + return null; + } + + @Override + public List collect() { + return collect(null); + } + + @Override + public List collect(File directory) { + File jenkinsRoot = Jenkins.getInstance().getRootDir(); + if (jenkinsRoot.equals(directory)) { + directory = null; + } + FileSelector selector = null; + if (directory != null) { + String pathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(directory); + if (pathRelativeToRoot == null) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " is not under " + jenkinsRoot.getAbsolutePath()); + } + final String restrictedPath = pathRelativeToRoot.endsWith("/") ? pathRelativeToRoot : pathRelativeToRoot + '/'; + selector = new FileSelector() { + @Override + public boolean isSelected(File basedir, String pathRelativeToBasedir, File file) throws BuildException { + // Only include directories leading to our directory (parent directories and the directory itself) and then whatever is below. + if (file.isDirectory()) { + pathRelativeToBasedir = pathRelativeToBasedir.endsWith("/") ? pathRelativeToBasedir : pathRelativeToBasedir + '/'; + } + return pathRelativeToBasedir.startsWith(restrictedPath) || restrictedPath.startsWith(pathRelativeToBasedir); + } + }; + } + String[] matchingFilePaths = createConfigEntityMatcher().matchingFilesFrom(jenkinsRoot, selector); + return new ArrayList(Collections2.transform(Arrays.asList(matchingFilePaths), PATH_TO_FILE_IN_HUDSON)); + } + + @Override + public boolean isCurrentUrlApplicable(String url) { + return getPageMatcherMatching(url)!=null; + } + + @Override + public List getSyncIncludes(){ + return createConfigEntityMatcher().getIncludes(); + } + + @Override + public CommitMessageFactory getCommitMessageFactory(){ + if (commitMessageFactory == null) { + commitMessageFactory = new DefaultCommitMessageFactory(); + } + return commitMessageFactory; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java new file mode 100644 index 00000000..ade4c617 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java @@ -0,0 +1,70 @@ +package hudson.plugins.scm_sync_configuration.strategies; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; + +import java.io.File; +import java.util.List; + +public interface ScmSyncStrategy { + + public static interface CommitMessageFactory { + public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file); + public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath); + public WeightedMessage getMessageWhenItemDeleted(Item item); + } + + /** + * Is the given Saveable eligible for the current strategy ? + * @param saveable A saveable which is saved + * @param file Corresponding file to the given Saveable object + * @return true if current Saveable instance matches with current ScmSyncStrategy target, + * false otherwise + */ + boolean isSaveableApplicable(Saveable saveable, File file); + + /** + * Determines whether the strategy might have applied to a deleted item. + * + * @param saveable that was deleted; still exists in Jenkins' model but has already been eradicated from disk + * @param pathRelativeToRoot where the item resided + * @param wasDirectory whether it was a directory + * @return + */ + boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory); + + /** + * Is the given url eligible for the current strategy ? + * @param url Current url, where hudson root url has been truncated + * @return true if current url matches with current ScmSyncStrategy target, false otherwise + */ + boolean isCurrentUrlApplicable(String url); + + /** + * Collects all files, from Jenkins' root directory, that match this strategy. + * + * @return the list of files matched. + */ + List collect(); + + /** + * Collects all files in the given directory, which must be under Jenkins' root directory, that match this strategy. + * + * @param directory to search in + * @return the list of files + * @throws IllegalArgumentException if the given directory is not under Jenkins' root directory + */ + List collect(File directory); + + /** + * @return List of sync'ed file includes brought by current strategy. Used only for informational purposes in the UI. + */ + List getSyncIncludes(); + + /** + * @return A Factory intended to generate commit message depending on contexts + */ + CommitMessageFactory getCommitMessageFactory(); +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/BasicPluginsConfigScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/BasicPluginsConfigScmSyncStrategy.java new file mode 100644 index 00000000..269e4b35 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/BasicPluginsConfigScmSyncStrategy.java @@ -0,0 +1,53 @@ +package hudson.plugins.scm_sync_configuration.strategies.impl; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.model.MessageWeight; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.AbstractScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PatternsEntityMatcher; + +import java.util.Collections; + +public class BasicPluginsConfigScmSyncStrategy extends AbstractScmSyncStrategy { + + private static final String[] PATTERNS = new String[]{ + "hudson*.xml", + "jenkins*.xml", + "scm-sync-configuration.xml" + }; + + private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new PatternsEntityMatcher(PATTERNS); + + public BasicPluginsConfigScmSyncStrategy(){ + super(CONFIG_ENTITY_MATCHER, Collections.emptyList()); + } + + @Override + public CommitMessageFactory getCommitMessageFactory(){ + return new CommitMessageFactory(){ + @Override + public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { + return new WeightedMessage("Plugin configuration files updated", MessageWeight.MINIMAL); + } + @Override + public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { + // It should never happen... but who cares how will behave *every* plugin in the jenkins land ? + return new WeightedMessage("Plugin configuration files renamed", MessageWeight.MINIMAL); + } + @Override + public WeightedMessage getMessageWhenItemDeleted(Item item) { + // It should never happen... but who cares how will behave *every* plugin in the jenkins land ? + return new WeightedMessage("Plugin configuration files deleted", MessageWeight.MINIMAL); + } + }; + } + + @Override + public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { + return !wasDirectory && pathRelativeToRoot != null && CONFIG_ENTITY_MATCHER.matches(saveable, pathRelativeToRoot, false); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JenkinsConfigScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JenkinsConfigScmSyncStrategy.java new file mode 100644 index 00000000..fc14f1e1 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JenkinsConfigScmSyncStrategy.java @@ -0,0 +1,60 @@ +package hudson.plugins.scm_sync_configuration.strategies.impl; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.model.MessageWeight; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.AbstractScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PatternsEntityMatcher; + +import java.util.List; + +import com.google.common.collect.ImmutableList; + +public class JenkinsConfigScmSyncStrategy extends AbstractScmSyncStrategy { + + private static final List PAGE_MATCHERS = ImmutableList.of( + // Global configuration page + new PageMatcher("^configure$", "form[name='config']"), + // View configuration pages + new PageMatcher("^(.+/)?view/[^/]+/configure$", "form[name='viewConfig']"), + new PageMatcher("^newView$", "form[name='createView'],form[name='createItem']") + ); + + private static final String[] PATTERNS = new String[]{ + "config.xml" + }; + + private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new PatternsEntityMatcher(PATTERNS); + + public JenkinsConfigScmSyncStrategy(){ + super(CONFIG_ENTITY_MATCHER, PAGE_MATCHERS); + } + + @Override + public CommitMessageFactory getCommitMessageFactory(){ + return new CommitMessageFactory(){ + @Override + public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { + return new WeightedMessage("Jenkins configuration files updated", MessageWeight.NORMAL); + } + @Override + public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { + throw new IllegalStateException("Jenkins configuration files should never be renamed !"); + } + @Override + public WeightedMessage getMessageWhenItemDeleted(Item item) { + throw new IllegalStateException("Jenkins configuration files should never be deleted !"); + } + }; + } + + @Override + public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { + // Uh-oh... Jenkins config should never be deleted. + return false; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategy.java new file mode 100644 index 00000000..330e5d39 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategy.java @@ -0,0 +1,48 @@ +package hudson.plugins.scm_sync_configuration.strategies.impl; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.model.MessageWeight; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.AbstractScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.JobOrFolderConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; + +import java.util.Collections; + +public class JobConfigScmSyncStrategy extends AbstractScmSyncStrategy { + + private static final ConfigurationEntityMatcher CONFIG_MATCHER = new JobOrFolderConfigurationEntityMatcher(); + + public JobConfigScmSyncStrategy(){ + super(CONFIG_MATCHER, Collections.singletonList(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']"))); + } + + @Override + public CommitMessageFactory getCommitMessageFactory(){ + return new CommitMessageFactory(){ + @Override + public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { + return new WeightedMessage("Job ["+((Item)s).getName()+"] configuration updated", + MessageWeight.IMPORTANT); + } + @Override + public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { + return new WeightedMessage("Job ["+item.getName()+"] hierarchy renamed from ["+oldPath+"] to ["+newPath+"]", + MessageWeight.MORE_IMPORTANT); + } + @Override + public WeightedMessage getMessageWhenItemDeleted(Item item) { + return new WeightedMessage("Job ["+item.getName()+"] hierarchy deleted", + MessageWeight.MORE_IMPORTANT); + } + }; + } + + @Override + public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeFromRoot, boolean wasDirectory) { + return CONFIG_MATCHER.matches(saveable, pathRelativeFromRoot, wasDirectory); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/ManualIncludesScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/ManualIncludesScmSyncStrategy.java new file mode 100644 index 00000000..1c027d25 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/ManualIncludesScmSyncStrategy.java @@ -0,0 +1,34 @@ +package hudson.plugins.scm_sync_configuration.strategies.impl; + +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.strategies.AbstractScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PatternsEntityMatcher; + +import java.util.Collections; +import java.util.List; + +public class ManualIncludesScmSyncStrategy extends AbstractScmSyncStrategy { + + public ManualIncludesScmSyncStrategy(){ + super(null, Collections.emptyList()); + } + + @Override + protected ConfigurationEntityMatcher createConfigEntityMatcher(){ + String[] includes = new String[0]; + List manualSynchronizationIncludes = ScmSyncConfigurationPlugin.getInstance().getManualSynchronizationIncludes(); + if(manualSynchronizationIncludes != null){ + includes = manualSynchronizationIncludes.toArray(new String[0]); + } + return new PatternsEntityMatcher(includes); + } + + @Override + public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { + // Best we can do here. We'll double check later on in the transaction. + return true; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/UserConfigScmSyncStrategy.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/UserConfigScmSyncStrategy.java new file mode 100644 index 00000000..d860ba8d --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/impl/UserConfigScmSyncStrategy.java @@ -0,0 +1,62 @@ +package hudson.plugins.scm_sync_configuration.strategies.impl; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Saveable; +import hudson.model.User; +import hudson.plugins.scm_sync_configuration.model.MessageWeight; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; +import hudson.plugins.scm_sync_configuration.strategies.AbstractScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.model.ClassAndFileConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; +import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; + +import java.util.List; + +import com.google.common.collect.ImmutableList; + +public class UserConfigScmSyncStrategy extends AbstractScmSyncStrategy { + + // Don't miss to take into account view urls since we can configure a job through a view ! + private static final List PAGE_MATCHERS = ImmutableList.of( + new PageMatcher("^securityRealm/addUser$", "#main-panel form"), + new PageMatcher("^securityRealm/user/[^/]+/configure$", "form[name='config']") + ); + + // Only saving config.xml file located in user directory + private static final String [] PATTERNS = new String[] { + "users/*/config.xml" + }; + + private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new ClassAndFileConfigurationEntityMatcher(User.class, PATTERNS); + + public UserConfigScmSyncStrategy(){ + super(CONFIG_ENTITY_MATCHER, PAGE_MATCHERS); + } + + @Override + public CommitMessageFactory getCommitMessageFactory(){ + return new CommitMessageFactory(){ + @Override + public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { + return new WeightedMessage("User ["+((User)s).getDisplayName()+"] configuration updated", + MessageWeight.IMPORTANT); + } + @Override + public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { + return new WeightedMessage("User ["+item.getName()+"] configuration renamed from ["+oldPath+"] to ["+newPath+"]", + MessageWeight.MORE_IMPORTANT); + } + @Override + public WeightedMessage getMessageWhenItemDeleted(Item item) { + return new WeightedMessage("User ["+item.getName()+"] hierarchy deleted", + MessageWeight.MORE_IMPORTANT); + } + }; + } + + @Override + public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { + return CONFIG_ENTITY_MATCHER.matches(saveable, pathRelativeToRoot, wasDirectory); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ClassAndFileConfigurationEntityMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ClassAndFileConfigurationEntityMatcher.java new file mode 100644 index 00000000..a501a552 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ClassAndFileConfigurationEntityMatcher.java @@ -0,0 +1,29 @@ +package hudson.plugins.scm_sync_configuration.strategies.model; + +import hudson.model.Saveable; + +import java.io.File; + +public class ClassAndFileConfigurationEntityMatcher extends PatternsEntityMatcher { + + private final Class saveableClazz; + + public ClassAndFileConfigurationEntityMatcher(Class clazz, String[] patterns){ + super(patterns); + this.saveableClazz = clazz; + } + + @Override + public boolean matches(Saveable saveable, File file) { + if (saveableClazz.isAssignableFrom(saveable.getClass())){ + if (file == null) { + return true; + } else { + return super.matches(saveable, file); + } + } + + return false; + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ConfigurationEntityMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ConfigurationEntityMatcher.java new file mode 100644 index 00000000..acce5387 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/ConfigurationEntityMatcher.java @@ -0,0 +1,49 @@ +package hudson.plugins.scm_sync_configuration.strategies.model; + +import hudson.model.Saveable; + +import java.io.File; +import java.util.List; + +import org.apache.tools.ant.types.selectors.FileSelector; + +/** + * A matcher that matches specific files under $JENKINS_HOME. + */ +public interface ConfigurationEntityMatcher { + + /** + * Determines whether the matcher matches a given combination of saveable and file. + * + * @param saveable the file belongs to + * @param file that is to be matched + * @return {@code true} on match, {@code false} otherwise + */ + public boolean matches(Saveable saveable, File file); + + /** + * Determines whether the matcher would have matched a deleted file, of which we know only its path and possibly whether it was directory. + * + * @param saveable the file belonged to + * @param pathRelativeToRoot of the file or directory (which Jenkins has already deleted) + * @param isDirectory {@code true} if it's known that the path referred to a directory, {@code false} otherwise + * @return {@code true} on match, {@code false} otherwise + */ + public boolean matches(Saveable saveable, String pathRelativeToRoot, boolean isDirectory); + + /** + * Collects all files under the given rootDirectory that match, restricted by the given {@code link FileSelector}. + * + * @param rootDirectory to traverse + * @param selector restricting the traversal + * @return an array of all path names relative to the rootDirectory of all files that match. + */ + public String[] matchingFilesFrom(File rootDirectory, FileSelector selector); + + /** + * All patterns this matcher matches; used only for informational purposes in the UI. + * + * @return A list of explanatory messages about the pattern the matcher matches. + */ + List getIncludes(); +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/JobOrFolderConfigurationEntityMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/JobOrFolderConfigurationEntityMatcher.java new file mode 100644 index 00000000..2da878c5 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/JobOrFolderConfigurationEntityMatcher.java @@ -0,0 +1,106 @@ +package hudson.plugins.scm_sync_configuration.strategies.model; + +import hudson.model.Saveable; +import hudson.model.AbstractItem; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.selectors.FileSelector; + +/** + * A {@link ConfigurationEntityMatcher} for job and Cloudbees Folders plugin folder configuration files. + * Matches config.xml located in jobs/project/config.xml (normal Jenkins), but also in nested directories + * below that as long as the path has the pattern (jobs/someName/)+config.xml (Cloudbees directory structure). + */ +public class JobOrFolderConfigurationEntityMatcher extends PatternsEntityMatcher { + + private static final String JOBS_DIR_NAME = "jobs"; + private static final String CONFIG_FILE_NAME = "config.xml"; + private static final String DIRECTORY_REGEXP = "(?:" + JOBS_DIR_NAME + "/[^/]+/)+"; + + private static final Pattern CONFIGS_TO_MATCH = Pattern.compile(DIRECTORY_REGEXP + CONFIG_FILE_NAME); + private static final Pattern DIRECTORIES_TO_MATCH = Pattern.compile(DIRECTORY_REGEXP); + + private static final String[] ANT_PATTERN = new String[] { JOBS_DIR_NAME + "/**/" + CONFIG_FILE_NAME }; + + public JobOrFolderConfigurationEntityMatcher() { + super(ANT_PATTERN); + // This pattern is only used for matchingFileFrom() below, and is augmented by a FileSelector enforcing the (jobs/someName/)+ pattern + } + + @Override + public boolean matches(Saveable saveable, File file) { + // The file may be null, indicating a deletion! + if (saveable instanceof AbstractItem) { + // Both jobs and folders are AbstractItems, which are Saveables. + if (file == null) { + // Deleted. + file = ((AbstractItem) saveable).getConfigFile().getFile(); + } else if (file.isDirectory()) { + file = new File(file, CONFIG_FILE_NAME); + } + return matches(saveable, JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file), false); + } + return false; + } + + @Override + public boolean matches(Saveable saveable, String pathRelativeToRoot, boolean isDirectory) { + if ((saveable instanceof AbstractItem) && pathRelativeToRoot != null) { + if (isDirectory) { + if (!pathRelativeToRoot.endsWith("/")) { + pathRelativeToRoot += '/'; + } + pathRelativeToRoot += CONFIG_FILE_NAME; + } + return CONFIGS_TO_MATCH.matcher(pathRelativeToRoot).matches(); + } + return false; + } + + @Override + public List getIncludes() { + // This is used only for display in the UI. + return Collections.singletonList(ANT_PATTERN[0] + " (** restricted to real project and folder directories)"); + } + + @Override + public String[] matchingFilesFrom(File rootDirectory, FileSelector selector) { + // Create a selector that enforces the (jobs/someName)+ pattern + final FileSelector originalSelector = selector; + FileSelector combinedSelector = new FileSelector() { + @Override + public boolean isSelected(File basedir, String pathRelativeToBaseDir, File file) throws BuildException { + if (originalSelector != null && !originalSelector.isSelected(basedir, pathRelativeToBaseDir, file)) { + return false; + } + if (CONFIGS_TO_MATCH.matcher(pathRelativeToBaseDir).matches()) { + return true; + } + + if (JOBS_DIR_NAME.equals(pathRelativeToBaseDir)) { + return true; + } + if (!pathRelativeToBaseDir.endsWith("/")) { + pathRelativeToBaseDir += '/'; + } + if (pathRelativeToBaseDir.endsWith('/' + JOBS_DIR_NAME + '/')) { + pathRelativeToBaseDir = StringUtils.removeEnd(pathRelativeToBaseDir, JOBS_DIR_NAME + '/'); + return DIRECTORIES_TO_MATCH.matcher(pathRelativeToBaseDir).matches(); + } else { + // Compare https://github.com/jenkinsci/cloudbees-folder-plugin/blob/70a4d47314a36b54d522cae0a78b3c76d153e627/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java#L200 + // The Cloudbees Folders plugin prunes the hierarchy on directories not containing a config.xml. + return DIRECTORIES_TO_MATCH.matcher(pathRelativeToBaseDir).matches() && file.isDirectory() && new File(file, CONFIG_FILE_NAME).exists(); + } + } + }; + return super.matchingFilesFrom(rootDirectory, combinedSelector); + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PageMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PageMatcher.java new file mode 100644 index 00000000..1e62c08b --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PageMatcher.java @@ -0,0 +1,22 @@ +package hudson.plugins.scm_sync_configuration.strategies.model; + +import java.util.regex.Pattern; + +public class PageMatcher { + + private Pattern urlRegex; + private String targetFormSelector; + + public PageMatcher(String _urlRegexStr, String _targetFormSelector){ + this.urlRegex = Pattern.compile(_urlRegexStr); + this.targetFormSelector = _targetFormSelector; + } + + public Pattern getUrlRegex() { + return urlRegex; + } + + public String getTargetFormSelector() { + return targetFormSelector; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PatternsEntityMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PatternsEntityMatcher.java new file mode 100644 index 00000000..cbf74aa5 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PatternsEntityMatcher.java @@ -0,0 +1,81 @@ +package hudson.plugins.scm_sync_configuration.strategies.model; + +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationBusiness; + +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.selectors.FileSelector; +import org.springframework.util.AntPathMatcher; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +public class PatternsEntityMatcher implements ConfigurationEntityMatcher { + + private final String[] includesPatterns; + + private static String SCM_WORKING_DIRECTORY = ScmSyncConfigurationBusiness.getScmDirectoryName(); + private static String WAR_DIRECTORY = "war"; + + public PatternsEntityMatcher(String[] includesPatterns){ + this.includesPatterns = includesPatterns; + } + + @Override + public boolean matches(Saveable saveable, File file) { + if (file == null) { + return false; + } + return matches(saveable, JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file), file.isDirectory()); + } + + @Override + public boolean matches(Saveable saveable, String pathRelativeToRoot, boolean isDirectory) { + if (pathRelativeToRoot != null) { + // Guard our own SCM workspace and the war directory. User-defined includes might inadvertently include those if they start with * or **! + if (pathRelativeToRoot.equals(SCM_WORKING_DIRECTORY) || pathRelativeToRoot.startsWith(SCM_WORKING_DIRECTORY + '/')) { + return false; + } else if (pathRelativeToRoot.equals(WAR_DIRECTORY) || pathRelativeToRoot.startsWith(WAR_DIRECTORY + '/')) { + return false; + } + AntPathMatcher matcher = new AntPathMatcher(); + String directoryName = null; + for (String pattern : includesPatterns) { + if (matcher.match(pattern, pathRelativeToRoot)) { + return true; + } else if (isDirectory) { + // pathRelativeFromRoot is be a directory, and the pattern end in a file name. In this case, we must claim a match. + int i = pattern.lastIndexOf('/'); + if (directoryName == null) { + directoryName = pathRelativeToRoot.endsWith("/") ? pathRelativeToRoot.substring(0, pathRelativeToRoot.length() - 1) : pathRelativeToRoot; + } + if (i > 0 && matcher.match(pattern.substring(0, i), directoryName)) { + return true; + } + } + } + } + return false; + } + + @Override + public List getIncludes(){ + return Arrays.asList(includesPatterns); + } + + @Override + public String[] matchingFilesFrom(File rootDirectory, FileSelector selector) { + DirectoryScanner scanner = new DirectoryScanner(); + scanner.setExcludes(new String[] { SCM_WORKING_DIRECTORY, SCM_WORKING_DIRECTORY + '/', WAR_DIRECTORY, WAR_DIRECTORY + '/'}); // Guard special directories + scanner.setIncludes(includesPatterns); + scanner.setBasedir(rootDirectory); + if (selector != null) { + scanner.setSelectors(new FileSelector[] { selector}); + } + scanner.scan(); + return scanner.getIncludedFiles(); + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/transactions/AtomicTransaction.java b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/AtomicTransaction.java new file mode 100644 index 00000000..4ada30b4 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/AtomicTransaction.java @@ -0,0 +1,38 @@ +package hudson.plugins.scm_sync_configuration.transactions; + +/** + * This ScmTransaction implementation should be aimed at commiting changes immediately + * @author fcamblor + */ +public class AtomicTransaction extends ScmTransaction { + + public AtomicTransaction(boolean synchronousCommit){ + super(synchronousCommit); + } + + public AtomicTransaction(){ + super(); + } + + @Override + public void registerPath(String path){ + super.registerPath(path); + // We should commit transaction after every change + commit(); + } + + @Override + public void registerPathForDeletion(String path) { + super.registerPathForDeletion(path); + // We should commit transaction after every change + commit(); + } + + @Override + public void registerRenamedPath(String oldPath, String newPath){ + super.registerRenamedPath(oldPath, newPath); + // We should commit transaction after every change + commit(); + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java new file mode 100644 index 00000000..82bda3b7 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java @@ -0,0 +1,69 @@ +package hudson.plugins.scm_sync_configuration.transactions; + +import java.io.File; +import java.util.concurrent.Future; + +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.model.ChangeSet; +import hudson.plugins.scm_sync_configuration.model.WeightedMessage; + +/** + * @author fcamblor + */ +public abstract class ScmTransaction { + private final ChangeSet changeset; + // Flag allowing to say if transaction will be asynchronous (default) or synchronous + // Synchronous commit are useful during tests execution + private final boolean synchronousCommit; + + protected ScmTransaction(){ + this(false); + } + + protected ScmTransaction(boolean synchronousCommit){ + this.changeset = new ChangeSet(); + this.synchronousCommit = synchronousCommit; + } + + public void defineCommitMessage(WeightedMessage weightedMessage){ + this.changeset.defineMessage(weightedMessage); + } + + public void commit(){ + Future future = ScmSyncConfigurationPlugin.getInstance().commitChangeset(changeset); + if (synchronousCommit && future != null) { + // Synchronous transactions should wait for the future to be fully processed + try { + future.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + public void registerPath(String path){ + this.changeset.registerPath(path); + } + + public void registerPathForDeletion(String path) { + this.changeset.registerPathForDeletion(path); + } + + public void registerRenamedPath(String oldPath, String newPath){ + File newFile = JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(newPath); + if (newFile.isDirectory()) { + for (File f : ScmSyncConfigurationPlugin.getInstance().collectAllFilesForScm(newFile)) { + String pathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(f); + if (pathRelativeToRoot != null) { + this.changeset.registerPath(pathRelativeToRoot); + } + } + } else { + this.changeset.registerPath(newPath); + } + if (oldPath != null) { + this.changeset.registerPathForDeletion(oldPath); + } + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ThreadedTransaction.java b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ThreadedTransaction.java new file mode 100644 index 00000000..be4b2158 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ThreadedTransaction.java @@ -0,0 +1,17 @@ +package hudson.plugins.scm_sync_configuration.transactions; + +/** + * This ScmTransaction implementation should be aimed at memorizing every changes made during a thread + * transaction, then commiting changes in the end of the thread + * @author fcamblor + */ +public class ThreadedTransaction extends ScmTransaction { + + public ThreadedTransaction(boolean synchronousCommit){ + super(synchronousCommit); + } + + public ThreadedTransaction(){ + super(); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/utils/Checksums.java b/src/main/java/hudson/plugins/scm_sync_configuration/utils/Checksums.java new file mode 100644 index 00000000..c66ae455 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/utils/Checksums.java @@ -0,0 +1,30 @@ +package hudson.plugins.scm_sync_configuration.utils; + +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; + +import java.io.File; +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +/** + * @author fcamblor + * Utility class allowing to provide easy access to jenkins files checksums + */ +public class Checksums { + public static boolean fileAndByteArrayContentAreEqual(File file, byte[] content) throws IOException { + if(!file.exists()){ + return content == null || content.length == 0; + } + + Checksum checksum = createChecksum(); + long fileChecksum = Files.getChecksum(file, checksum); + long contentChecksum = ByteStreams.getChecksum(ByteStreams.newInputStreamSupplier(content), checksum); + return fileChecksum == contentChecksum; + } + + private static Checksum createChecksum(){ + return new CRC32(); + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/ScmSyncConfigurationXStreamConverter.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/ScmSyncConfigurationXStreamConverter.java new file mode 100644 index 00000000..0f8c8926 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/ScmSyncConfigurationXStreamConverter.java @@ -0,0 +1,168 @@ +package hudson.plugins.scm_sync_configuration.xstream; + +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.xstream.migration.AbstractMigrator; +import hudson.plugins.scm_sync_configuration.xstream.migration.ScmSyncConfigurationDataMigrator; +import hudson.plugins.scm_sync_configuration.xstream.migration.ScmSyncConfigurationPOJO; +import hudson.plugins.scm_sync_configuration.xstream.migration.v0.InitialMigrator; +import hudson.plugins.scm_sync_configuration.xstream.migration.v1.V0ToV1Migrator; + +import java.util.logging.Logger; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * XStream converter for ScmSyncConfigurationPlugin XStream data + * Allows to provide API to migrate from one version to another of persisted scm sync configuration data + * When creating a new migrator you must : + * - Create a new package hudson.plugins.scm_sync_configuration.xstream.migration.v[X] + * - Inside this package, copy/paste every classes located in hudson.plugins.scm_sync_configuration.xstream.migration.v[X-1] + * - Rename every *V[X-1]* POJOs to *V[X]* POJO + * - Eventually, change attributes in V[X]ScmSyncConfigurationPOJO (for example, if additionnal attribute has appeared) + * - Provide implementation for V[X]Migrator.migrate() algorithm + * - If parsing algorithm has changed, update V[X]Migrator.readScmSyncConfigurationPOJO with the new algorithm (if, for example, new root + * elements has appeared in XStream file) + * - Update ScmSyncConfigurationXStreamConverter.MIGRATORS with new provided class + * @author fcamblor + */ +public class ScmSyncConfigurationXStreamConverter implements Converter { + + private static final Logger LOGGER = Logger.getLogger(ScmSyncConfigurationXStreamConverter.class.getName()); + + protected static final String VERSION_ATTRIBUTE = "version"; + + /** + * Migrators for old versions of GlobalBuildStatsPlugin data representations + */ + @SuppressWarnings("rawtypes") // Generic arrays not possible + private static final ScmSyncConfigurationDataMigrator[] MIGRATORS = new ScmSyncConfigurationDataMigrator[]{ + new InitialMigrator(), + new V0ToV1Migrator() + }; + + /** + * Converter is only applicable on GlobalBuildStatsPlugin data + */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class type) { // Inherited signature + return ScmSyncConfigurationPlugin.class.isAssignableFrom(type); + } + + public void marshal(Object source, HierarchicalStreamWriter writer, + MarshallingContext context) { + + ScmSyncConfigurationPlugin plugin = (ScmSyncConfigurationPlugin)source; + + // Since "v1", providing version number in scm sync configuration heading tag + writer.addAttribute(VERSION_ATTRIBUTE, String.valueOf(getCurrentScmSyncConfigurationVersionNumber())); + + if(plugin.getSCM() != null){ + writer.startNode(AbstractMigrator.SCM_TAG); + writer.addAttribute(AbstractMigrator.SCM_CLASS_ATTRIBUTE, plugin.getSCM().getId()); + writer.endNode(); + } + if(plugin.getGitRepositoryBranch() != null) { + writer.startNode(AbstractMigrator.SCM_REPOSITORY_BRANCH); + writer.setValue(plugin.getGitRepositoryBranch()); + writer.endNode(); + } + + if(plugin.getScmRepositoryUrl() != null){ + writer.startNode(AbstractMigrator.SCM_REPOSITORY_URL_TAG); + writer.setValue(plugin.getScmRepositoryUrl()); + writer.endNode(); + } + + writer.startNode(AbstractMigrator.SCM_NO_USER_COMMIT_MESSAGE); + writer.setValue(Boolean.toString(plugin.isNoUserCommitMessage())); + writer.endNode(); + + writer.startNode(AbstractMigrator.SCM_DISPLAY_STATUS); + writer.setValue(Boolean.toString(plugin.isDisplayStatus())); + writer.endNode(); + + if(plugin.getCommitMessagePattern() != null){ + writer.startNode(AbstractMigrator.SCM_COMMIT_MESSAGE_PATTERN); + writer.setValue(plugin.getCommitMessagePattern()); + writer.endNode(); + } + + if(plugin.getManualSynchronizationIncludes() != null){ + writer.startNode(AbstractMigrator.SCM_MANUAL_INCLUDES); + for(String include : plugin.getManualSynchronizationIncludes()){ + writer.startNode("include"); + writer.setValue(include); + writer.endNode(); + } + writer.endNode(); + } + } + + /** + * @return current version number of scm sync configuration plugin + * data representation in XStream + */ + private static int getCurrentScmSyncConfigurationVersionNumber(){ + return MIGRATORS.length-1; + } + + /** + * Will transform scm sync configuration XStream data representation into + * current ScmSyncConfigurationPlugin instance + */ + public Object unmarshal(HierarchicalStreamReader reader, + UnmarshallingContext context) { + + ScmSyncConfigurationPlugin plugin; + if(context.currentObject() == null || !(context.currentObject() instanceof ScmSyncConfigurationPlugin)){ + // This should never happen to get here + plugin = new ScmSyncConfigurationPlugin(); + } else { + // Retrieving already instantiated ScmSyncConfiguration plugin into current context .. + plugin = (ScmSyncConfigurationPlugin)context.currentObject(); + } + + // Retrieving data representation version number + String version = reader.getAttribute(VERSION_ATTRIBUTE); + // Before version 1 (version 0), there wasn't any version in the scm sync configuration + // configuration file + int versionNumber = 0; + if(version != null){ + versionNumber = Integer.parseInt(version); + } + + if(versionNumber != getCurrentScmSyncConfigurationVersionNumber()){ + // There will be a data migration .. + LOGGER.info("Your version of persisted ScmSyncConfigurationPlugin data is not up-to-date (v"+versionNumber+" < v"+getCurrentScmSyncConfigurationVersionNumber()+") : data will be migrated !"); + } + + // Calling version's reader to read data representation + ScmSyncConfigurationPOJO pojo = MIGRATORS[versionNumber].readScmSyncConfigurationPOJO(reader, context); + + // Migrating old data into up-to-date data + // Added "+1" because we take into consideration InitialMigrator + for(int i=versionNumber+1; i implements ScmSyncConfigurationDataMigrator { + + public static final String SCM_REPOSITORY_URL_TAG = "scmRepositoryUrl"; + public static final String SCM_TAG = "scm"; + public static final String SCM_CLASS_ATTRIBUTE = "class"; + public static final String SCM_NO_USER_COMMIT_MESSAGE = "noUserCommitMessage"; + public static final String SCM_DISPLAY_STATUS = "displayStatus"; + public static final String SCM_COMMIT_MESSAGE_PATTERN = "commitMessagePattern"; + public static final String SCM_MANUAL_INCLUDES = "manualSynchronizationIncludes"; + public static final String SCM_REPOSITORY_BRANCH = "gitRepositoryBranch"; + + private static final Logger LOGGER = Logger.getLogger(AbstractMigrator.class.getName()); + + public TTO migrate(TFROM pojo){ + TTO migratedPojo = createMigratedPojo(); + + migratedPojo.setScmRepositoryUrl( migrateScmRepositoryUrl(pojo.getScmRepositoryUrl()) ); + migratedPojo.setScm( migrateScm(pojo.getScm()) ); + + return migratedPojo; + } + + public TTO readScmSyncConfigurationPOJO( + HierarchicalStreamReader reader, UnmarshallingContext context) { + + TTO pojo = createMigratedPojo(); + + String scmRepositoryUrl = null; + String gitRepositoryBranch = null; + String scmClassAttribute = null; + String scmContent = null; + boolean noUserCommitMessage = false; + boolean displayStatus = true; + String commitMessagePattern = "[message]"; + List manualIncludes = null; + + while(reader.hasMoreChildren()) { + reader.moveDown(); + if (SCM_REPOSITORY_URL_TAG.equals(reader.getNodeName())) { + scmRepositoryUrl = reader.getValue(); + } else if (SCM_REPOSITORY_BRANCH.equals(reader.getNodeName())) { + gitRepositoryBranch = reader.getValue(); + } else if(SCM_NO_USER_COMMIT_MESSAGE.equals(reader.getNodeName())){ + noUserCommitMessage = Boolean.parseBoolean(reader.getValue()); + } else if(SCM_DISPLAY_STATUS.equals(reader.getNodeName())){ + displayStatus = Boolean.parseBoolean(reader.getValue()); + } else if(SCM_TAG.equals(reader.getNodeName())){ + scmClassAttribute = reader.getAttribute(SCM_CLASS_ATTRIBUTE); + scmContent = reader.getValue(); + } else if(SCM_COMMIT_MESSAGE_PATTERN.equals(reader.getNodeName())){ + commitMessagePattern = reader.getValue(); + } else if(SCM_MANUAL_INCLUDES.equals(reader.getNodeName())){ + manualIncludes = new ArrayList(); + while(reader.hasMoreChildren()){ + reader.moveDown(); + manualIncludes.add(reader.getValue()); + reader.moveUp(); + } + } else { + IllegalArgumentException iae = new IllegalArgumentException("Unknown tag : "+reader.getNodeName()); + LOGGER.throwing(this.getClass().getName(), "readScmSyncConfigurationPOJO", iae); + LOGGER.severe("Unknown tag : "+reader.getNodeName()); + throw iae; + } + reader.moveUp(); + } + + pojo.setScm(createSCMFrom(scmClassAttribute, scmContent)); + pojo.setScmRepositoryUrl(scmRepositoryUrl); + pojo.setGitRepositoryBranch(gitRepositoryBranch); + pojo.setNoUserCommitMessage(noUserCommitMessage); + pojo.setDisplayStatus(displayStatus); + pojo.setCommitMessagePattern(commitMessagePattern); + pojo.setManualSynchronizationIncludes(manualIncludes); + + return pojo; + } + + // Overridable + protected String migrateScmRepositoryUrl(String scmRepositoryUrl){ + if(scmRepositoryUrl == null){ + return null; + } else { + return new String(scmRepositoryUrl); + } + } + + // Overridable + protected SCM migrateScm(SCM scm){ + if(scm == null){ + return null; + } else { + return SCM.valueOf(scm.getClass().getName()); + } + } + + protected abstract TTO createMigratedPojo(); + protected abstract SCM createSCMFrom(String clazz, String content); +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/DefaultSSCPOJO.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/DefaultSSCPOJO.java new file mode 100644 index 00000000..51f02b7b --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/DefaultSSCPOJO.java @@ -0,0 +1,68 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration; + +import hudson.plugins.scm_sync_configuration.scms.SCM; + +import java.util.List; + +public class DefaultSSCPOJO implements ScmSyncConfigurationPOJO { + + private String scmRepositoryUrl; + private String gitRepositoryBranch; + private SCM scm; + private boolean noUserCommitMessage; + private boolean displayStatus; + private String commitMessagePattern; + private List manualSynchronizationIncludes; + + public String getScmRepositoryUrl() { + return scmRepositoryUrl; + } + public void setScmRepositoryUrl(String scmRepositoryUrl) { + this.scmRepositoryUrl = scmRepositoryUrl; + } + + @Override + public String getGitRepositoryBranch() { + return gitRepositoryBranch; + } + + @Override + public void setGitRepositoryBranch(String gitRepositoryBranch) { + this.gitRepositoryBranch = gitRepositoryBranch; + } + + public SCM getScm() { + return scm; + } + public void setScm(SCM scm) { + this.scm = scm; + } + public boolean isNoUserCommitMessage() { + return noUserCommitMessage; + } + public void setNoUserCommitMessage(boolean noUserCommitMessage) { + this.noUserCommitMessage = noUserCommitMessage; + } + public boolean isDisplayStatus() { + return displayStatus; + } + public void setDisplayStatus(boolean displayStatus) { + this.displayStatus = displayStatus; + } + + public String getCommitMessagePattern() { + return commitMessagePattern; + } + + public void setCommitMessagePattern(String commitMessagePattern) { + this.commitMessagePattern = commitMessagePattern; + } + + public void setManualSynchronizationIncludes(List _manualSynchronizationIncludes){ + this.manualSynchronizationIncludes = _manualSynchronizationIncludes; + } + + public List getManualSynchronizationIncludes(){ + return this.manualSynchronizationIncludes; + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationDataMigrator.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationDataMigrator.java new file mode 100644 index 00000000..a1d479df --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationDataMigrator.java @@ -0,0 +1,15 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * Migrator from old GlobalBuildStats POJO to later GlobalBuildStats POJO + * @author fcamblor + * @param + * @param + */ +public interface ScmSyncConfigurationDataMigrator { + public TTO migrate(TFROM pojo); + public TTO readScmSyncConfigurationPOJO(HierarchicalStreamReader reader, UnmarshallingContext context); +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationPOJO.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationPOJO.java new file mode 100644 index 00000000..c1f09dd2 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationPOJO.java @@ -0,0 +1,26 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration; + +import hudson.plugins.scm_sync_configuration.scms.SCM; + +import java.util.List; + +/** + * Generic interface for ScmSyncConfiguration POJOs + * @author fcamblor + */ +public interface ScmSyncConfigurationPOJO { + public String getScmRepositoryUrl(); + public void setScmRepositoryUrl(String scmRepositoryUrl); + public String getGitRepositoryBranch(); + public void setGitRepositoryBranch(String gitRepositoryBranch); + public SCM getScm(); + public void setScm(SCM scm); + public boolean isNoUserCommitMessage(); + public void setNoUserCommitMessage(boolean noUserCommitMessage); + public boolean isDisplayStatus(); + public void setDisplayStatus(boolean displayStatus); + public String getCommitMessagePattern(); + public void setCommitMessagePattern(String commitMessagePattern); + public List getManualSynchronizationIncludes(); + public void setManualSynchronizationIncludes(List manualSynchronizationIncludes); +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationXStreamReader.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationXStreamReader.java new file mode 100644 index 00000000..095dbf25 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/ScmSyncConfigurationXStreamReader.java @@ -0,0 +1,13 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * Behavior for ScmSyncConfiguration readers + * @author fcamblor + * @param + */ +public interface ScmSyncConfigurationXStreamReader { + T readScmSyncConfigurationPOJO(HierarchicalStreamReader reader, UnmarshallingContext context); +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/InitialMigrator.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/InitialMigrator.java new file mode 100644 index 00000000..8221b5f0 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/InitialMigrator.java @@ -0,0 +1,52 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration.v0; + +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncNoSCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncSubversionSCM; +import hudson.plugins.scm_sync_configuration.xstream.migration.AbstractMigrator; + +/** + * Initial representation of scm-sync-configuration.xml file + * @author fcamblor + */ +public class InitialMigrator extends AbstractMigrator { + + @Override + protected V0ScmSyncConfigurationPOJO createMigratedPojo() { + return new V0ScmSyncConfigurationPOJO(); + } + + @Override + public V0ScmSyncConfigurationPOJO migrate(V0ScmSyncConfigurationPOJO pojo) { + throw new IllegalAccessError("migrate() method should never be called on InitialMigrator !"); + } + + @Override + protected SCM createSCMFrom(String classname, String content) { + // No scm tag => no scm entered + if(content == null){ + return SCM.valueOf(ScmSyncNoSCM.class); + } + + // Starting from here, scm tag was provided... + + // v0.0.2 of the plugin was representing SCM as an enum type + // so "class" attribute was not present here + if(classname == null){ + // And the only SCM implementation in v0.0.2 was the subversion one + return SCM.valueOf(ScmSyncSubversionSCM.class); + // In v0.0.3 there wasn't any "version" attribute and the + // SCM was not represented as an enum type anymore .. so the "class" attribute + // will be present and will be useful to determine the SCM implementation to chose + } else { + // For backward compatibility + if("hudson.plugins.scm_sync_configuration.scms.impl.ScmSyncSubversionSCM".equals(classname)){ + classname = ScmSyncSubversionSCM.class.getName(); + } else if("hudson.plugins.scm_sync_configuration.scms.impl.ScmSyncNoSCM".equals(classname)){ + classname = ScmSyncNoSCM.class.getName(); + } + return SCM.valueOf(classname); + } + } + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/V0ScmSyncConfigurationPOJO.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/V0ScmSyncConfigurationPOJO.java new file mode 100644 index 00000000..8cc62ea3 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v0/V0ScmSyncConfigurationPOJO.java @@ -0,0 +1,8 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration.v0; + +import hudson.plugins.scm_sync_configuration.xstream.migration.DefaultSSCPOJO; + + +public class V0ScmSyncConfigurationPOJO extends DefaultSSCPOJO { + +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V0ToV1Migrator.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V0ToV1Migrator.java new file mode 100644 index 00000000..b3beae4b --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V0ToV1Migrator.java @@ -0,0 +1,29 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration.v1; + +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncNoSCM; +import hudson.plugins.scm_sync_configuration.xstream.migration.AbstractMigrator; +import hudson.plugins.scm_sync_configuration.xstream.migration.v0.V0ScmSyncConfigurationPOJO; + +/** + * V1 Evolutions : + * - Apparition of tag "version" valued to "1" in scm-sync-configuration root tag + * - SCM implementation package moved from hudson.plugins.scm_sync_configuration.scms.impl to hudson.plugins.scm_sync_configuration.scms + * @author fcamblor + */ +public class V0ToV1Migrator extends AbstractMigrator { + + @Override + protected V1ScmSyncConfigurationPOJO createMigratedPojo() { + return new V1ScmSyncConfigurationPOJO(); + } + + @Override + protected SCM createSCMFrom(String classname, String content) { + if(content == null){ + return SCM.valueOf(ScmSyncNoSCM.class); + } else { + return SCM.valueOf(classname); + } + } +} diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V1ScmSyncConfigurationPOJO.java b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V1ScmSyncConfigurationPOJO.java new file mode 100644 index 00000000..033b7861 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/migration/v1/V1ScmSyncConfigurationPOJO.java @@ -0,0 +1,8 @@ +package hudson.plugins.scm_sync_configuration.xstream.migration.v1; + +import hudson.plugins.scm_sync_configuration.xstream.migration.DefaultSSCPOJO; + + +public class V1ScmSyncConfigurationPOJO extends DefaultSSCPOJO { + +} diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml new file mode 100644 index 00000000..64a7f68c --- /dev/null +++ b/src/main/resources/META-INF/plexus/components.xml @@ -0,0 +1,48 @@ + + + + + + + org.apache.maven.scm.manager.ScmManager + org.apache.maven.scm.manager.plexus.DefaultScmManager + + + org.apache.maven.scm.provider.ScmProvider + scmProviders + + + + + + org.apache.maven.scm.provider.ScmProvider + svn + org.apache.maven.scm.provider.svn.svnjava.SvnJavaScmProvider + + + + + org.apache.maven.scm.provider.ScmProvider + git + hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe.ScmSyncGitExeScmProvider + + + + diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/Messages.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/Messages.properties new file mode 100644 index 00000000..8ee9fb27 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/Messages.properties @@ -0,0 +1,4 @@ +ScmSyncConfigurationsPlugin.gitRepoUrlEmpty=Repository URL is required\! +ScmSyncConfigurationsPlugin.gitRepoUrlInvalid=Please enter a valid repository URL. +ScmSyncConfigurationsPlugin.gitRepoUrlInaccessible=Repository {0} is inaccessible. If you're sure it exists, verify that the OS user running Jenkins can actually access it. Check ssh keys, and verify that the OS user has user.name and user.email set. +ScmSyncConfigurationsPlugin.gitRepoUrlWhitespaceWarning=Leading and trailing whitespace will be ignored. diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config.jelly new file mode 100644 index 00000000..043a21a3 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config.jelly @@ -0,0 +1,56 @@ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + ${%Reload} + +
+ +
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config_fr.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config_fr.properties new file mode 100644 index 00000000..0433f211 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/config_fr.properties @@ -0,0 +1,15 @@ +SCM\ Sync\ configuration=SCM Sync Configuration +SCM=SCM +Never\ bother\ me\ with\ commit\ messages=Ne jamais me d\u00E9ranger avec des messages de commit +Display\ SCM\ Sync\ Status=Afficher le statut du SCM Sync +Commit\ message\ pattern=Mod\u00E8le de message de commit +Manual\ synchronization\ includes=Chemins synchronis\u00E9s manuellement +Add\ new\ include=Ajouter un nouveau chemin +Include=Chemin +Delete\ include=Supprimer le chemin +Reload\ config\ from\ SCM=Recharger la configuration depuis le SCM +WARNING\ \:\ the\ Jenkins\ config\ will\ be\ reloaded\ from\ SCM=Attention : la configuration Jenkins va \u00EAtre recharg\u00E9e depuis le SCM +Only\ file\ modifications\ are\ handled=Seules les modifications de fichier sont g\u00E9r\u00E9es +File\ added\ or\ removed\ will\ not\ be\ handled=Les fichiers ajout\u00E9s ou supprim\u00E9s ne seront pas g\u00E9r\u00E9s +Continue=Continuer +Reload=Recharger diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes.jelly new file mode 100644 index 00000000..71ef9cee --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes.jelly @@ -0,0 +1,25 @@ + +
+ ${%List of ant-like includes allowing to specify additional files to synchronize with the repository}.
+ ${%Includes should be case sensitive paths starting from your JENKINS_HOME directory, without / in the beginning; Use of wildcards} (${%* and **}) ${%is allowed}.
+ ${%You can have a look at} ${%community shared includes in Jenkins wiki}, ${%feel free to share your owns on this page} !
+ ${%Before creating new includes, you should be aware of some things} : +
    +
  • + ${%Avoid includes for big and updated-often files} : ${%otherwise, your jenkins instance will spend its CPU time to commit your file} +
  • +
  • + ${%In order to be noticed at the right time, your files must be represented in Jenkins by a} Saveable, + ${%most of them are, but who knows}... +
  • +
  • + ${%Following includes are brought "out of the box" by scm-sync-configuration default includes} : +
      + +
    • ${include}
    • +
      +
    +
  • +
+
+
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes_fr.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes_fr.properties new file mode 100644 index 00000000..ec375ad7 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes_fr.properties @@ -0,0 +1,13 @@ +List\ of\ ant-like\ includes\ allowing\ to\ specify\ additional\ files\ to\ synchronize\ with\ the\ repository=Liste de chemin \u00E0 la Ant, permettant de sp\u00E9cifier des fichiers suppl\u00E9mentaires qui seront synchronis\u00E9s avec le repository +Includes\ should\ be\ case\ sensitive\ paths\ starting\ from\ your\ JENKINS_HOME\ directory,\ without\ /\ in\ the\ beginning;\ Use\ of\ wildcards=Les chemins doivent \u00EAtre sensibles \u00E0 la casse, bas\u00E9s depuis votre r\u00E9pertoire JENKINS_HOME, et ne d\u00E9marrant pas par un /; L''utilisation des wildcards +*\ and\ **=* et ** +is\ allowed=est autoris\u00E9 +You\ can\ have\ a\ look\ at=Vous pouvez jeter un oeil aux +community\ shared\ includes\ in\ Jenkins\ wiki=chemin partag\u00E9s par la communaut\u00E9 dans le wiki Jenkins +feel\ free\ to\ share\ your\ owns\ on\ this\ page=n''h\u00E9sitez pas \u00E0 partager les v\u00F4tres sur cette page +Before\ creating\ new\ includes,\ you\ should\ be\ aware\ of\ some\ things=Avant de cr\u00E9er de nouveaux chemins, vous devriez \u00EAtre conscient de certaines contraintes +Avoid\ includes\ for\ big\ and\ updated-often\ files=Evitez les chemins pour les gros fichiers ainsi que ceux mis \u00E0 jour souvent +otherwise,\ your\ jenkins\ instance\ will\ spend\ its\ CPU\ time\ to\ commit\ your\ file=autrement, votre instance Jenkins va passer son temps CPU \u00E0 commiter votre fichier +In\ order\ to\ be\ noticed\ at\ the\ right\ time,\ your\ files\ must\ be\ represented\ in\ Jenkins\ by\ a=Afin d''\u00EAtre notifi\u00E9 au bon moment, vos fichiers doivent correspondre, dans Jenkins, \u00E0 un +most\ of\ them\ are,\ but\ who\ knows=la plupart le sont, mais qui sait +Following\ includes\ are\ brought\ "out\ of\ the\ box"\ by\ scm-sync-configuration\ default\ includes=Les chemins suivants sont fournis par d\u00E9faut par le plugin scm-sync-configuration diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config.jelly new file mode 100644 index 00000000..8b8aae9f --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config.jelly @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config_fr.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config_fr.properties new file mode 100644 index 00000000..7c14cc25 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/config_fr.properties @@ -0,0 +1 @@ +Repository\ URL=URL du repository diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.jelly new file mode 100644 index 00000000..e5c23d37 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.jelly @@ -0,0 +1,32 @@ + + + + +
+ ${%description.1}
+ ${%description.2} +
+
+
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.properties new file mode 100644 index 00000000..888432f3 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +description.1=Specify the git repository URL to synchronize your configuration files with, such as "git@github.com\:mycompany/jenkins-config.git" +description.2=Note that, for the moment, you MUST reference your Git repository root diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help_fr.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help_fr.properties new file mode 100644 index 00000000..36fd76bf --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/git/url-help_fr.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Eric Lefevre-Ardant +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +description.1=Sp\u00E9cifiez l''url du repository Git vers lequel synchroniser vos fichiers de configuration, tel que "git@github.com\:mycompany/jenkins-config.git" +description.2=A noter qu''il est pour le moment obligatoire de r\u00E9f\u00E9rencer la racine du repository diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/none/config.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/none/config.jelly new file mode 100644 index 00000000..92945348 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/none/config.jelly @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config.jelly new file mode 100644 index 00000000..4fdfb427 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config.jelly @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config_fr.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config_fr.properties new file mode 100644 index 00000000..314af8a5 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/config_fr.properties @@ -0,0 +1 @@ +Repository\ URL=URL du repository diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.jelly new file mode 100644 index 00000000..28acfe2b --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.jelly @@ -0,0 +1,35 @@ + + + + +
+ ${%description.1} +
+ ${%description.2(rootURL)} +
+ ${%description.3} +
+
+
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.properties new file mode 100644 index 00000000..69924a3e --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +description.1=Specify the subversion repository URL to synchronize your configuration files with, such as "http\://yourcompany.com/repos/hudson-config/" +description.2=When you enter a URL, Jenkins automatically checks if it can access the repository If access requires authentication, it will ask you the necessary credentials. If you already have a working credential but would like to change it for other reasons, click this link and specify different credentials. +description.3=Each time you enter a new repository URL, Jenkins will first synchronize all your configuration files with the repository. This process can take a while, depending on the amount of configuration files to synchronize. diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help_fr.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help_fr.properties new file mode 100644 index 00000000..aa9f0669 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help_fr.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Eric Lefevre-Ardant +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +description.1=Sp\u00E9cifiez l''URL du repository Subversion avec lequel synchronier vos fichiers de configuration, par exemple "http\://yourcompany.com/repos/hudson-config/" +description.2=Quand vous entrez une URL, Jenkins v\u00E9rifie automatiquement s''il peut s''y connecter. Si l''acc\u00E8s n\u00E9cessite une authentification, il vous demandera les informations n\u00E9cessaires. Si vous disposez d\u00E9j\u00E0 d''informations d''identification qui marchent mais que vous voulez en changer, cliquez sur ce lien et renseignez des valeurs diff\u00E9rentes. +description.3=A chaque fois que vous entrerez une nouvelle URL de repository, Jenkins commencera par synchroniser tous vos fichier de configuration avec le repository. Ce processus peut prendre plusieurs minutes, en fonction du nombre de fichier de configuration \u00E0 synchroniser. diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator/footer.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator/footer.jelly new file mode 100644 index 00000000..a27be1d2 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator/footer.jelly @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/scm_sync_configuration/reload.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/reload.jelly new file mode 100644 index 00000000..49d20bf4 --- /dev/null +++ b/src/main/resources/hudson/plugins/scm_sync_configuration/reload.jelly @@ -0,0 +1,20 @@ + + + + +

+ ${%Config reloaded from} ${it.SCM.title}, url : ${it.scmUrl} +

+

Modified files : ${it.filesModifiedByLastReload.size()}

+

+

    + +
  • ${file}
  • +
    +
+

+

Please reload Jenkins config from disk by clicking here

+ +
+
+
\ No newline at end of file diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly new file mode 100644 index 00000000..4708b457 --- /dev/null +++ b/src/main/resources/index.jelly @@ -0,0 +1,3 @@ +
+ This plugin allows you to synchronize your hudson configuration files with an SCM, allowing you to specify a commit message every time a config file is modified. +
\ No newline at end of file diff --git a/src/main/webapp/help/commitMessagePattern-help.html b/src/main/webapp/help/commitMessagePattern-help.html new file mode 100644 index 00000000..bf04ad80 --- /dev/null +++ b/src/main/webapp/help/commitMessagePattern-help.html @@ -0,0 +1,12 @@ +
+ Pattern of the message which will be commited.
+ For example, if you need to add a prefix/suffix to every of your commit messages, + you can do it by changing this message pattern.
+ You can use following "special" strings in the pattern : +
    +
  • `[message]` : Will be replaced by the generated commit message
  • +
+

+ For instance, defining a commit message pattern of "ISSUE-1234: [message]" will make following commit message :
+ ISSUE-1234: Modification on file with following comment : Synchronization init +
\ No newline at end of file diff --git a/src/main/webapp/help/commitMessagePattern-help_fr.html b/src/main/webapp/help/commitMessagePattern-help_fr.html new file mode 100644 index 00000000..a838397b --- /dev/null +++ b/src/main/webapp/help/commitMessagePattern-help_fr.html @@ -0,0 +1,12 @@ +
+ Patron du message qui sera commité.
+ Par exemple, si vous avez besoin d'ajouter un préfixe/suffixe à l'ensemble de vos messages de commit, + vous pouvez le faire en changeant ce patron de message.
+ Vous pouvez utiliser les chaînes "spéciales" suivantes dans le patron : +
    +
  • `[message]` : Sera remplacé par le message généré pour le commit
  • +
+

+ Par exemple, en définissant un patron de commit à "ISSUE-1234: [message]", cela génèrera le message de commit suivant :
+ ISSUE-1234: Modification on file with following comment : Synchronization init +
\ No newline at end of file diff --git a/src/main/webapp/help/displayStatus-help.html b/src/main/webapp/help/displayStatus-help.html new file mode 100644 index 00000000..9d9fee48 --- /dev/null +++ b/src/main/webapp/help/displayStatus-help.html @@ -0,0 +1,5 @@ +
+ Displays a SCM Sync Configuration status health on the bottom of your Jenkins pages.
+ It is generally useful to activate this in order to be aware of a synchronization problem + (due, for instance, to repository credentials change) +
\ No newline at end of file diff --git a/src/main/webapp/help/displayStatus-help_fr.html b/src/main/webapp/help/displayStatus-help_fr.html new file mode 100644 index 00000000..32c9addd --- /dev/null +++ b/src/main/webapp/help/displayStatus-help_fr.html @@ -0,0 +1,5 @@ +
+ Affiche un état de la dernière synchronisation avec le SCM en bas de chaque page de Jenkins.
+ Il est généralement utile d'activer cela afin d'être alerté d'un problème de synchronisation + (dû, par exemple, à un changement de credentials) +
\ No newline at end of file diff --git a/src/main/webapp/help/noUserCommitMessage-help.html b/src/main/webapp/help/noUserCommitMessage-help.html new file mode 100644 index 00000000..478c128f --- /dev/null +++ b/src/main/webapp/help/noUserCommitMessage-help.html @@ -0,0 +1,5 @@ +
+ If unchecked, everytime you change a synchronized configuration file via Jenkins UI, a + prompt will be displayed allowing to specify a commit message for your modifications.
+ By checking this checkbox, you will never be prompted for such a commit message. +
\ No newline at end of file diff --git a/src/main/webapp/help/noUserCommitMessage-help_fr.html b/src/main/webapp/help/noUserCommitMessage-help_fr.html new file mode 100644 index 00000000..519afd92 --- /dev/null +++ b/src/main/webapp/help/noUserCommitMessage-help_fr.html @@ -0,0 +1,5 @@ +
+ Lorsque décoché, à chaque fois que vous modifierez un fichier de configuration synchronisé + via l'UI de Jenkins, vous pourrez saisir un message de commit représentant vos modifications.
+ En cochant cette case à cocher, vous n'aurez plus besoin de saisir ce message de commit. +
\ No newline at end of file diff --git a/src/main/webapp/help/reloadScmConfig-help.html b/src/main/webapp/help/reloadScmConfig-help.html new file mode 100644 index 00000000..39e70fa7 --- /dev/null +++ b/src/main/webapp/help/reloadScmConfig-help.html @@ -0,0 +1,41 @@ +
+ Beware : Some of your Jenkins config files can be overriden by this reload.
+ That is to say, if you want to get your repository configuration files back, you can do it by clicking this link.

+ Following strategies will be applied : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Local (jenkins) fileRepository fileAction
XXXXXX (same as Jenkins one)Nothing
XXXYYY (different as Jenkins one)YYY will override XXX
XXXDoesn't exist + Nothing (Jenkins file won't be removed)
+ Note that it should never happen since as soon as you activate scm sync config, every of your config file should be commited to the repository +
Doesn't existXXX + XXX will be imported from repository
+
+
\ No newline at end of file diff --git a/src/main/webapp/help/reloadScmConfig-help_fr.html b/src/main/webapp/help/reloadScmConfig-help_fr.html new file mode 100644 index 00000000..321700ab --- /dev/null +++ b/src/main/webapp/help/reloadScmConfig-help_fr.html @@ -0,0 +1,41 @@ +
+ Attention : Certaines de vos configurations Jenkins peuvent être écrasées par ce rechargement.
+ En cliquant sur ce lien, vous rapatrierez la configuration de votre repository dans Jenkins.

+ Les stratégies suivantes seront appliquées : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fichier local (jenkins)Fichier du repositoryAction
XXXXXX (même contenu que celui de Jenkins)Rien
XXXYYY (contenu différent de celui de Jenkins)YYY écrasera XXX
XXXN'existe pas + Rien (le fichier de Jenkins ne sera pas supprimé)
+ Notez que ce cas ne devrait jamais arriver puisque dès que vous activez le plugin scm sync config, tous vos fichiers de configuration sont commités dans le repository +
N'existe pasXXX + XXX sera importé depuis le repository
+
+
\ No newline at end of file diff --git a/src/main/webapp/help/scm-help.html b/src/main/webapp/help/scm-help.html new file mode 100644 index 00000000..e439fd89 --- /dev/null +++ b/src/main/webapp/help/scm-help.html @@ -0,0 +1,5 @@ +
+ Specify the repository where you want to sync jenkins config files to.
+ If you select "None", SCM Sync configuration plugin won't be activated.
+ Note that repository path must already exist in your repository (plugin won't create it for you). +
\ No newline at end of file diff --git a/src/main/webapp/help/scm-help_fr.html b/src/main/webapp/help/scm-help_fr.html new file mode 100644 index 00000000..8cad01f1 --- /dev/null +++ b/src/main/webapp/help/scm-help_fr.html @@ -0,0 +1,5 @@ +
+ Spécifier le repository où vous souhaitez synchroniser les fichiers de configuration de Jenkins.
+ Si vous sélectionnez "None", le plugin SCM Sync configuration ne sera pas activé.
+ Notez que le chemin du repository doit exister dans votre repository (le plugin ne le créera pas pour vous). +
\ No newline at end of file diff --git a/src/main/webapp/scripts/scm-sync-configuration/scm-sync-configuration-page-handler.js b/src/main/webapp/scripts/scm-sync-configuration/scm-sync-configuration-page-handler.js new file mode 100644 index 00000000..4213b655 --- /dev/null +++ b/src/main/webapp/scripts/scm-sync-configuration/scm-sync-configuration-page-handler.js @@ -0,0 +1,169 @@ +function retrieveTargetFormOnPage(){ + return $$(YAHOO.scmSyncConfiguration.targetFormSelector)[0]; +} +function decorateOnsubmitForm(){ + try { + var form = retrieveTargetFormOnPage(); + if(form != null){ + form.observe("submit", function(evt){ + var dontStopEvent = YAHOO.scmSyncConfiguration.commentPopupValidated || enterCommitComment(); + if(!dontStopEvent){ + Event.stop(evt); + } + }); + } else { + unexpectedError("Cannot retrieve target form on current page !"); + } + }catch(ex){ unexpectedError("Exception: "+logProps(ex)); } + form = null; // memory leak prevention +} + +function unexpectedError(message){ + alertMsg = 'Something went wrong with the scm-sync-configuration plugin !\n'; + alertMsg += 'Please report a JIRA with as much informations as possible (browser, os, current url, error message etc.).\n'; + alertMsg += 'Error message : '+message; + alert(alertMsg); +} + +/** + * Log l'ensemble des propriétés de l'objet javascript fourni + */ +function logProps(obj){ + str = ''; + if(obj == null){ + return "unknown object (null) !"; + } else { + for(prop in obj) + { + str += '['+prop+'='+obj[prop]+'],'; + } + return "Object : "+obj+" :: "+str; + } +} + +function ajaxCall(callType, param, successCallback){ + ajaxCall(callType, param, successCallback, false); +} + +function ajaxCall(callType, param, successCallback, skipLoading){ + + if(!skipLoading){ + YAHOO.namespace("scm.sync.configuration.wait"); + YAHOO.scm.sync.configuration.wait.modalPopup = + new YAHOO.widget.Panel("wait", + { width:"240px", + fixedcenter:true, + close:false, + draggable:false, + zindex:4, + modal:true + } + ); + + YAHOO.scm.sync.configuration.wait.modalPopup.setHeader("--- Waiting ---"); + YAHOO.scm.sync.configuration.wait.modalPopup.setBody("--- Waiting ---"); + YAHOO.scm.sync.configuration.wait.modalPopup.render(document.body); + } + + var ajaxCallParams = { + onSuccess: function(ret) { + successCallback.call(null, ret); + if(!skipLoading){ + YAHOO.scm.sync.configuration.wait.modalPopup.hide(); + } + },/* For unknown reasons, an exception is thrown after the onSuccess process .. :( + onException: function(transport, ex) { + alert('exception : '+ex); + if(!skipLoading){ + YAHOO.scm.sync.configuration.wait.modalPopup.hide(); + } + throw ex; + },*/ + onFailure: function(transport) { + alert('failure : '+Object.toJSON(transport)); + if(!skipLoading){ + YAHOO.scm.sync.configuration.wait.modalPopup.hide(); + } + } + }; + + if(callType == 'form'){ + $(param).request(ajaxCallParams); + } else { + new Ajax.Request(param, ajaxCallParams); + } +} + +function enterCommitComment(form){ + YAHOO.namespace("scm.sync.configuration"); + YAHOO.scm.sync.configuration._buttons = []; + YAHOO.scm.sync.configuration.handleSubmit = function() { + ajaxCall('form', 'commentForm', function(ret){ + YAHOO.scmSyncConfiguration.commentPopupValidated = true; + YAHOO.scm.sync.configuration.modalPopup.hide(); + retrieveTargetFormOnPage().submit(); + }, true); + } + YAHOO.scm.sync.configuration.handleCancel = function() { + YAHOO.scm.sync.configuration.modalPopup.hide(); + } + + YAHOO.scm.sync.configuration.modalPopup = + new YAHOO.widget.Panel("buildStatConfigForm", + { width:"720px", + fixedcenter:true, + close:false, + draggable:false, + zindex:4, + modal:true + } + ); + + var currentContext = createTemplateContext(); + var popupContentTemplate = new Template(getTemplateContent('popupContentTemplate')); + content = popupContentTemplate.evaluate(currentContext); + + YAHOO.scm.sync.configuration.modalPopup.setHeader("Commit comment"); + YAHOO.scm.sync.configuration.modalPopup.setBody(content); + YAHOO.scm.sync.configuration.modalPopup.setFooter(''); + YAHOO.scm.sync.configuration.modalPopup.showEvent.subscribe(function() { + if (this._buttons.length == 0) { + this._buttons[0] = new YAHOO.widget.Button({ + type: 'button', + label: "Submit comment", + container: 'panelFooter' + }); + this._buttons[0].on('click', YAHOO.scm.sync.configuration.handleSubmit); + this._buttons[1] = new YAHOO.widget.Button({ + type: 'button', + label: "Cancel", + container: 'panelFooter' + }); + this._buttons[1].on('click', YAHOO.scm.sync.configuration.handleCancel); + + // Ugly part here ... I dunno why, but directly calling focus() doesn't work + // since rendered buttons will steal the textarea's focus + // The only workaround found is to delay the textarea focus ! + setTimeout(function(){ $$("#commentForm #comment")[0].focus(); }, 300); + } + }, YAHOO.scm.sync.configuration, true); + YAHOO.scm.sync.configuration.modalPopup.render(document.body); + + return false; +} + +// For some unknown reasons, on firefox, some #{XXX} template variables are replaced by #%7BXXX%7D :( +function getTemplateContent(templateId){ + var content = $(templateId).innerHTML; + content = content.replace(new RegExp("%7B", "g"), "{"); + content = content.replace(new RegExp("%7D", "g"), "}"); + return content; +} + +function createTemplateContext(){ + // Creating context for creation + var currentContext = { + rootURL: rootURL + }; + return currentContext; +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java new file mode 100644 index 00000000..725876d0 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java @@ -0,0 +1,62 @@ +package hudson.plugins.scm_sync_configuration.basic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import hudson.model.Hudson; +import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationBaseTest; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + +import java.io.File; + +import jenkins.model.Jenkins; + +import org.junit.Test; + +public class ScmSyncConfigurationBasicTest extends ScmSyncConfigurationBaseTest { + + public ScmSyncConfigurationBasicTest() { + super(new ScmUnderTestSubversion()); + } + + @Test + public void shouldRetrieveMockedHudsonInstanceCorrectly() throws Throwable { + Jenkins jenkins = Jenkins.getInstance(); + assertNotNull("Jenkins instance must not be null", jenkins); + assertFalse("Expected a mocked Jenkins instance", jenkins.getClass().equals(Jenkins.class) || jenkins.getClass().equals(Hudson.class)); + } + + @Test + public void shouldVerifyIfHudsonRootDirectoryExists() throws Throwable { + Jenkins jenkins = Jenkins.getInstance(); + File jenkinsRootDir = jenkins.getRootDir(); + assertNotNull("Jenkins instance must not be null", jenkinsRootDir); + assertTrue("$JENKINS_HOME must be an existing directory", jenkinsRootDir.isDirectory()); + } + + @Test + public void testPathesOutsideJenkisRoot () throws Exception { + Jenkins jenkins = Jenkins.getInstance(); + File rootDirectory = jenkins.getRootDir().getAbsoluteFile(); + File parentDirectory = rootDirectory.getParentFile(); + assertNull("File outside $JENKINS_HOME should return null", JenkinsFilesHelper.buildPathRelativeToHudsonRoot(parentDirectory)); + assertNull("File outside $JENKINS_HOME should return null", JenkinsFilesHelper.buildPathRelativeToHudsonRoot(new File(parentDirectory, "foo.txt"))); + } + + @Test + public void testPathesInsideJenkisRoot () throws Exception { + Jenkins jenkins = Jenkins.getInstance(); + File rootDirectory = jenkins.getRootDir().getAbsoluteFile(); + File pathUnderTest = new File(rootDirectory, "config.xml"); + String result = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(pathUnderTest); + assertNotNull("File inside $JENKINS_HOME must not return null path", result); + assertEquals("Path " + pathUnderTest + " should resolve properly", result, "config.xml"); + pathUnderTest = new File(new File (rootDirectory, "someDir"), "foo.txt"); + result = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(pathUnderTest); + assertNotNull("File inside $JENKINS_HOME must not return null path", result); + assertEquals("Path " + pathUnderTest + " should resolve properly", result, "someDir/foo.txt"); + } +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/data/CurrentVersionCompatibilityTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/data/CurrentVersionCompatibilityTest.java new file mode 100644 index 00000000..c10904af --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/data/CurrentVersionCompatibilityTest.java @@ -0,0 +1,62 @@ +package hudson.plugins.scm_sync_configuration.data; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.notNull; +import static org.powermock.api.mockito.PowerMockito.doNothing; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import hudson.XmlFile; +import hudson.model.Saveable; +import hudson.model.listeners.SaveableListener; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncSubversionSCM; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.PluginUtil; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + +import org.junit.Test; +import org.powermock.core.classloader.annotations.PrepareForTest; + +@PrepareForTest(SaveableListener.class) +public class CurrentVersionCompatibilityTest extends ScmSyncConfigurationPluginBaseTest { + + public CurrentVersionCompatibilityTest() { + super(new ScmUnderTestSubversion()); + } + + protected String getHudsonRootBaseTemplate() { + // Use default template directory... + return super.getHudsonRootBaseTemplate(); + } + + @Test + public void shouldCurrentVersionPluginConfigurationFileLoadCorrectly() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + assertThat(plugin.getSCM(), is(notNullValue())); + assertThat(plugin.getSCM().getId(), is(equalTo(ScmSyncSubversionSCM.class.getName()))); + } + + @Test + public void shouldCurrentVersionPluginConfigurationMigrationBeIdemPotent() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + + // Plugin has been loaded : let's record scm & repository url + String expectedRepositoryUrl = plugin.getScmRepositoryUrl(); + SCM expectedScm = plugin.getSCM(); + + // Persisting data + mockStatic(SaveableListener.class); + doNothing().when(SaveableListener.class); SaveableListener.fireOnChange((Saveable)notNull(), (XmlFile)notNull()); + plugin.save(); + + // Then reloading it... + PluginUtil.loadPlugin(plugin); + + // Verifying repositoryUrl & SCM + assertThat(plugin.getSCM().getId(), is(equalTo(expectedScm.getId()))); + assertThat(plugin.getScmRepositoryUrl(), is(equalTo(expectedRepositoryUrl))); + } +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_2CompatibilityTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_2CompatibilityTest.java new file mode 100644 index 00000000..3f17f3f1 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_2CompatibilityTest.java @@ -0,0 +1,79 @@ +package hudson.plugins.scm_sync_configuration.data; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.notNull; +import static org.powermock.api.mockito.PowerMockito.doNothing; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import hudson.XmlFile; +import hudson.model.Saveable; +import hudson.model.listeners.SaveableListener; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncNoSCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncSubversionSCM; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.PluginUtil; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + +import org.junit.Test; +import org.powermock.core.classloader.annotations.PrepareForTest; + +@PrepareForTest(SaveableListener.class) +public class V0_0_2CompatibilityTest extends ScmSyncConfigurationPluginBaseTest { + + public V0_0_2CompatibilityTest() { + super(new ScmUnderTestSubversion()); + } + + protected String getHudsonRootBaseTemplate() { + if("should0_0_2_pluginConfigurationFileShouldLoadCorrectly".equals(testName.getMethodName())){ + return "hudsonRoot0.0.2BaseTemplate/"; + } else if("should0_0_2_pluginConfigurationMigrationBeIdemPotent".equals(testName.getMethodName())){ + return "hudsonRoot0.0.2BaseTemplate/"; + } else if("should0_0_2_pluginEmptyConfigurationFileShouldLoadCorrectly".equals(testName.getMethodName())){ + return "hudsonRoot0.0.2WithEmptyConfTemplate/"; + } else { + throw new IllegalArgumentException("Unsupported test name : "+testName); + } + } + + @Test + // JENKINS-8453 related + public void should0_0_2_pluginConfigurationFileShouldLoadCorrectly() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + assertThat(plugin.getSCM(), is(notNullValue())); + assertThat(plugin.getSCM().getId(), is(equalTo(ScmSyncSubversionSCM.class.getName()))); + assertThat(plugin.getScmRepositoryUrl(), is(equalTo("scm:svn:https://myrepo/synchronizedDirectory/"))); + } + + @Test + public void should0_0_2_pluginConfigurationMigrationBeIdemPotent() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + + // Plugin has been loaded : let's record scm & repository url + String expectedRepositoryUrl = plugin.getScmRepositoryUrl(); + SCM expectedScm = plugin.getSCM(); + + // Persisting data + mockStatic(SaveableListener.class); + doNothing().when(SaveableListener.class); SaveableListener.fireOnChange((Saveable)notNull(), (XmlFile)notNull()); + plugin.save(); + + // Then reloading it... + PluginUtil.loadPlugin(plugin); + + // Verifying repositoryUrl & SCM + assertThat(plugin.getSCM().getId(), is(equalTo(expectedScm.getId()))); + assertThat(plugin.getScmRepositoryUrl(), is(equalTo(expectedRepositoryUrl))); + } + + @Test + public void should0_0_2_pluginEmptyConfigurationFileShouldLoadCorrectly() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + assertThat(plugin.getSCM(), is(notNullValue())); + assertThat(plugin.getSCM().getId(), is(equalTo(ScmSyncNoSCM.class.getName()))); + } +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_3CompatibilityTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_3CompatibilityTest.java new file mode 100644 index 00000000..bb8b2c20 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_3CompatibilityTest.java @@ -0,0 +1,62 @@ +package hudson.plugins.scm_sync_configuration.data; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.notNull; +import static org.powermock.api.mockito.PowerMockito.doNothing; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import hudson.XmlFile; +import hudson.model.Saveable; +import hudson.model.listeners.SaveableListener; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncSubversionSCM; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.PluginUtil; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + +import org.junit.Test; +import org.powermock.core.classloader.annotations.PrepareForTest; + +@PrepareForTest(SaveableListener.class) +public class V0_0_3CompatibilityTest extends ScmSyncConfigurationPluginBaseTest { + + public V0_0_3CompatibilityTest() { + super(new ScmUnderTestSubversion()); + } + + protected String getHudsonRootBaseTemplate() { + return "hudsonRoot0.0.3BaseTemplate/"; + } + + @Test + // JENKINS-8453 related + public void should0_0_3_pluginConfigurationFileShouldLoadCorrectly() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + assertThat(plugin.getSCM(), is(notNullValue())); + assertThat(plugin.getSCM().getId(), is(equalTo(ScmSyncSubversionSCM.class.getName()))); + } + + @Test + public void should0_0_3_pluginConfigurationMigrationBeIdemPotent() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + + // Plugin has been loaded : let's record scm & repository url + String expectedRepositoryUrl = plugin.getScmRepositoryUrl(); + SCM expectedScm = plugin.getSCM(); + + // Persisting data + mockStatic(SaveableListener.class); + doNothing().when(SaveableListener.class); SaveableListener.fireOnChange((Saveable)notNull(), (XmlFile)notNull()); + plugin.save(); + + // Then reloading it... + PluginUtil.loadPlugin(plugin); + + // Verifying repositoryUrl & SCM + assertThat(plugin.getSCM().getId(), is(equalTo(expectedScm.getId()))); + assertThat(plugin.getScmRepositoryUrl(), is(equalTo(expectedRepositoryUrl))); + } +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_4CompatibilityTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_4CompatibilityTest.java new file mode 100644 index 00000000..8c5af912 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/data/V0_0_4CompatibilityTest.java @@ -0,0 +1,30 @@ +package hudson.plugins.scm_sync_configuration.data; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncNoSCM; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + +import org.junit.Test; + +public class V0_0_4CompatibilityTest extends ScmSyncConfigurationPluginBaseTest { + + public V0_0_4CompatibilityTest() { + super(new ScmUnderTestSubversion()); + } + + protected String getHudsonRootBaseTemplate() { + return "hudsonRoot0.0.4WithEmptyConfTemplate/"; + } + + @Test + public void should0_0_4_pluginEmptyConfigurationFileShouldLoadCorrectly() throws Throwable { + ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + assertThat(plugin.getSCM(), is(notNullValue())); + assertThat(plugin.getSCM().getId(), is(equalTo(ScmSyncNoSCM.class.getName()))); + } +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java new file mode 100644 index 00000000..89474b78 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java @@ -0,0 +1,11 @@ +package hudson.plugins.scm_sync_configuration.repository; + +import hudson.plugins.test.utils.scms.ScmUnderTestGit; + +public class HudsonExtensionsGitTest extends HudsonExtensionsTest { + + public HudsonExtensionsGitTest() { + super(new ScmUnderTestGit()); + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java new file mode 100644 index 00000000..401c0c80 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java @@ -0,0 +1,96 @@ +package hudson.plugins.scm_sync_configuration.repository; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.File; + +import org.codehaus.plexus.util.FileUtils; +import org.junit.Test; +import org.mockito.Mockito; + +import hudson.model.Item; +import hudson.model.Job; +import hudson.plugins.scm_sync_configuration.SCMManipulator; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + + +public class HudsonExtensionsSubversionTest extends HudsonExtensionsTest { + + public HudsonExtensionsSubversionTest() { + super(new ScmUnderTestSubversion()); + } + + @Test + public void shouldJobDeleteDoesntPerformAnyScmUpdate() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Let's checkout current scm view ... and commit something in it ... + SCMManipulator scmManipulator = createMockedScmManipulator(); + File checkoutDirectoryForVerifications = createTmpDirectory(this.getClass().getSimpleName()+"_"+testName.getMethodName()+"__tmpHierarchyForCommit"); + scmManipulator.checkout(checkoutDirectoryForVerifications); + final File hello1 = new File(checkoutDirectoryForVerifications.getAbsolutePath()+"/jobs/hello.txt"); + final File hello2 = new File(checkoutDirectoryForVerifications.getAbsolutePath()+"/hello2.txt"); + FileUtils.fileAppend(hello1.getAbsolutePath(), "hello world !"); + FileUtils.fileAppend(hello2.getAbsolutePath(), "hello world 2 !"); + scmManipulator.addFile(checkoutDirectoryForVerifications, "jobs/hello.txt"); + scmManipulator.addFile(checkoutDirectoryForVerifications, "hello2.txt"); + assertTrue("External check-in should succeed", scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit")); + + // Deleting fakeJob + Item mockedItem = Mockito.mock(Job.class); + File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/" ); + when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); + + sscItemListener.onDeleted(mockedItem); + + // Assert no hello file is present in current hudson root + assertThat(new File(this.getCurrentScmSyncConfigurationCheckoutDirectory()+"/jobs/hello.txt").exists(), is(false)); + assertThat(new File(this.getCurrentScmSyncConfigurationCheckoutDirectory()+"/hello2.txt").exists(), is(false)); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldJobRenameDoesntPerformAnyScmUpdate() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Let's checkout current scm view ... and commit something in it ... + SCMManipulator scmManipulator = createMockedScmManipulator(); + File checkoutDirectoryForVerifications = createTmpDirectory(this.getClass().getSimpleName()+"_"+testName.getMethodName()+"__tmpHierarchyForCommit"); + scmManipulator.checkout(checkoutDirectoryForVerifications); + final File hello1 = new File(checkoutDirectoryForVerifications.getAbsolutePath()+"/jobs/hello.txt"); + final File hello2 = new File(checkoutDirectoryForVerifications.getAbsolutePath()+"/hello2.txt"); + FileUtils.fileAppend(hello1.getAbsolutePath(), "hello world !"); + FileUtils.fileAppend(hello2.getAbsolutePath(), "hello world 2 !"); + scmManipulator.addFile(checkoutDirectoryForVerifications, "jobs/hello.txt"); + scmManipulator.addFile(checkoutDirectoryForVerifications, "hello2.txt"); + scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit"); + + // Renaming fakeJob to newFakeJob + Item mockedItem = Mockito.mock(Job.class); + File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/newFakeJob/" ); + when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); + + sscItemListener.onLocationChanged(mockedItem, "fakeJob", "newFakeJob"); + + // Assert no hello file is present in current hudson root + assertThat(new File(this.getCurrentScmSyncConfigurationCheckoutDirectory()+"/jobs/hello.txt").exists(), is(false)); + assertThat(new File(this.getCurrentScmSyncConfigurationCheckoutDirectory()+"/hello2.txt").exists(), is(false)); + + assertStatusManagerIsOk(); + } + + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java new file mode 100644 index 00000000..c884c0f9 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -0,0 +1,469 @@ +package hudson.plugins.scm_sync_configuration.repository; + +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.AbstractItem; +import hudson.model.Job; +import hudson.model.Saveable; +import hudson.plugins.scm_sync_configuration.SCMManipulator; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.extensions.ScmSyncConfigurationItemListener; +import hudson.plugins.scm_sync_configuration.extensions.ScmSyncConfigurationSaveableListener; +import hudson.plugins.scm_sync_configuration.strategies.ScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.impl.BasicPluginsConfigScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.impl.JenkinsConfigScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.strategies.impl.JobConfigScmSyncStrategy; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.scms.ScmUnderTest; + +import org.codehaus.plexus.util.FileUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; +import java.util.List; + +import jenkins.model.Jenkins; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +public abstract class HudsonExtensionsTest extends ScmSyncConfigurationPluginBaseTest { + + protected ScmSyncConfigurationItemListener sscItemListener; + protected ScmSyncConfigurationSaveableListener sscConfigurationSaveableListener; + + protected HudsonExtensionsTest(ScmUnderTest scmUnderTest) { + super(scmUnderTest); + } + + @Before + public void initObjectsUnderTests() throws Throwable{ + this.sscItemListener = new ScmSyncConfigurationItemListener(); + this.sscConfigurationSaveableListener = new ScmSyncConfigurationSaveableListener(); + } + + @Test + public void shouldJobRenameBeCorrectlyImpactedOnSCM() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Renaming fakeJob to newFakeJob + Item mockedItem = Mockito.mock(Job.class); + File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/newFakeJob/" ); + when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); + when(mockedItem.getName()).thenReturn("newFakeJob"); + when(mockedItem.getParent()).thenReturn(null); + // We should duplicate files in fakeJob to newFakeJob + File oldJobDirectory = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/"); + FileUtils.copyDirectory(oldJobDirectory, mockedItemRootDir); + + sscItemListener.onLocationChanged(mockedItem, "fakeJob", "newFakeJob"); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldJobAddBeCorrectlyImpactedOnSCM() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File jobDirectory = new File(getCurrentHudsonRootDirectory() + "/jobs/newFakeJob/" ); + File configFile = new File(jobDirectory.getAbsolutePath() + File.separator + "config.xml"); + jobDirectory.mkdir(); + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml").getFile(), configFile); + + // Creating fake new job + Item mockedItem = Mockito.mock(Job.class); + when(mockedItem.getRootDir()).thenReturn(jobDirectory); + + sscItemListener.onCreated(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldJobModificationBeCorrectlyImpactedOnSCM() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File jobDirectory = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/" ); + File configFile = new File(jobDirectory.getAbsolutePath() + File.separator + "config.xml"); + + // Creating fake new job + Item mockedItem = Mockito.mock(Job.class); + when(mockedItem.getRootDir()).thenReturn(jobDirectory); + + sscItemListener.onCreated(mockedItem); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml").getFile(), configFile); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldConfigModificationBeCorrectlyImpactedOnSCM() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File configFile = new File(getCurrentHudsonRootDirectory() + "/hudson.tasks.Shell.xml" ); + + // Creating fake new plugin config + Item mockedItem = Mockito.mock(Item.class); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml").getFile(), configFile); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldJobDeleteBeCorrectlyImpactedOnSCM() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Deleting fakeJob + Item mockedItem = Mockito.mock(Job.class); + File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/" ); + when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); + + sscItemListener.onDeleted(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM" + getSuffixForTestFiles() + "/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM() throws Throwable { + String newFakeJob = "expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/newFakeJob"; + FileUtils.copyDirectoryStructure(new ClassPathResource(newFakeJob).getFile(), new File(getCurrentHudsonRootDirectory() + File.separator + "jobs" + File.separator + "newFakeJob")); + + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Deleting fakeJob + Item mockedItem = Mockito.mock(Job.class); + File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/" ); + when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); + + sscItemListener.onDeleted(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldReloadAllFilesUpdateScmAndReloadAllFiles() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Let's checkout current scm view ... and commit something in it ... + SCMManipulator scmManipulator = createMockedScmManipulator(); + File checkoutDirectoryForVerifications = createTmpDirectory(this.getClass().getSimpleName()+"_"+testName.getMethodName()+"__tmpHierarchyForCommit"); + scmManipulator.checkout(checkoutDirectoryForVerifications); + + verifyCurrentScmContentMatchesCurrentHudsonDir(true); + + final File configFile = new File(checkoutDirectoryForVerifications.getAbsolutePath() + "/config.xml"); + FileUtils.fileAppend(configFile.getAbsolutePath(), "toto"); + scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit on config file"); + + final File configJobFile = new File(checkoutDirectoryForVerifications.getAbsolutePath() + "/jobs/fakeJob/config.xml"); + FileUtils.fileAppend(configJobFile.getAbsolutePath(), "titi"); + scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit on jonb file"); + + verifyCurrentScmContentMatchesCurrentHudsonDir(false); + + // Reload config + List syncedFiles = sscBusiness.reloadAllFilesFromScm(); + + verifyCurrentScmContentMatchesCurrentHudsonDir(true); + + assertThat(syncedFiles.size(), is(2)); + assertThat(syncedFiles.contains(new File(getCurrentHudsonRootDirectory().getAbsolutePath() + "/config.xml")), is(true)); + assertThat(syncedFiles.contains(new File(getCurrentHudsonRootDirectory().getAbsolutePath() + "/jobs/fakeJob/config.xml")), is(true)); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldReloadAllFilesUpdateScmAndReloadAllFilesWithFileAdd() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + // Let's checkout current scm view ... and commit something in it ... + SCMManipulator scmManipulator = createMockedScmManipulator(); + File checkoutDirectoryForVerifications = createTmpDirectory(this.getClass().getSimpleName()+"_"+testName.getMethodName()+"__tmpHierarchyForCommit"); + scmManipulator.checkout(checkoutDirectoryForVerifications); + + // Verifying there isn't any difference between hudson and scm repo once every file are synchronized + verifyCurrentScmContentMatchesCurrentHudsonDir(true); + + final File addedFile = new File(checkoutDirectoryForVerifications.getAbsolutePath() + "/myConfigFile.xml"); + FileUtils.fileWrite(addedFile.getAbsolutePath(), "toto"); + scmManipulator.addFile(checkoutDirectoryForVerifications, "myConfigFile.xml"); + scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit for add file"); + + final String jobDir = checkoutDirectoryForVerifications.getAbsolutePath() + "/jobs/myJob"; + FileUtils.mkdir(jobDir); + final File addedJobFile = new File(jobDir + "/config.xml"); + FileUtils.fileWrite(addedJobFile.getAbsolutePath(), "titi"); + scmManipulator.addFile(checkoutDirectoryForVerifications, "jobs/myJob"); + scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit for add job file"); + + verifyCurrentScmContentMatchesCurrentHudsonDir(false); + + // Reload config + List syncedFiles = sscBusiness.reloadAllFilesFromScm(); + + verifyCurrentScmContentMatchesCurrentHudsonDir(true); + + assertThat(syncedFiles.size(), is(2)); + assertThat(syncedFiles.contains(new File(getCurrentHudsonRootDirectory().getAbsolutePath() + "/myConfigFile.xml")), is(true)); + assertThat(syncedFiles.contains(new File(getCurrentHudsonRootDirectory().getAbsolutePath() + "/jobs/myJob")), is(true)); + + assertStatusManagerIsOk(); + } + + @Test + public void testJobNameStartingWithDash() throws Exception { + createSCMMock(); + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File jobDirectory = new File(getCurrentHudsonRootDirectory(), "jobs/-newFakeJob/" ); + File configFile = new File(jobDirectory, "config.xml"); + jobDirectory.mkdir(); + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml").getFile(), configFile); + + // Creating fake new job + Item mockedItem = Mockito.mock(Job.class); + when(mockedItem.getName()).thenReturn("-newFakeJob"); + when(mockedItem.getRootDir()).thenReturn(jobDirectory); + + sscItemListener.onCreated(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/"); + + assertStatusManagerIsOk(); + + // Now delete it again + assertTrue("Config file deletion", configFile.delete()); + assertTrue("Job dir deletion", jobDirectory.delete()); + + sscItemListener.onDeleted(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("hudsonRootBaseTemplate/"); + + assertStatusManagerIsOk(); + } + + @Test + public void testJobNameWithBlanks() throws Exception { + createSCMMock(); + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File jobDirectory = new File(getCurrentHudsonRootDirectory(), "jobs/new fake Job/" ); + File configFile = new File(jobDirectory, "config.xml"); + jobDirectory.mkdir(); + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/new fake Job/config.xml").getFile(), configFile); + + // Creating fake new job + Item mockedItem = Mockito.mock(Job.class); + when(mockedItem.getName()).thenReturn("new fake Job"); + when(mockedItem.getRootDir()).thenReturn(jobDirectory); + + sscItemListener.onCreated(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/"); + + assertStatusManagerIsOk(); + + // Now delete it again + assertTrue("Config file deletion", configFile.delete()); + assertTrue("Job dir deletion", jobDirectory.delete()); + + sscItemListener.onDeleted(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("hudsonRootBaseTemplate/"); + + assertStatusManagerIsOk(); + } + + @Test + public void testJobRenameWithBlanksAndDash() throws Exception { + createSCMMock(); + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File jobDirectory = new File(getCurrentHudsonRootDirectory(), "jobs/-newFakeJob/" ); + File configFile = new File(jobDirectory, "config.xml"); + jobDirectory.mkdir(); + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml").getFile(), configFile); + + // Creating fake new job + Item mockedItem = Mockito.mock(Job.class); + when(mockedItem.getName()).thenReturn("-newFakeJob"); + when(mockedItem.getRootDir()).thenReturn(jobDirectory); + + sscItemListener.onCreated(mockedItem); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/"); + + assertStatusManagerIsOk(); + + // Now fake a rename + assertTrue("Config file deletion", configFile.delete()); + assertTrue("Job dir deletion", jobDirectory.delete()); + jobDirectory = new File(getCurrentHudsonRootDirectory(), "jobs/new fake Job/" ); + configFile = new File(jobDirectory, "config.xml"); + jobDirectory.mkdir(); + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/new fake Job/config.xml").getFile(), configFile); + + Item mockedRenamedItem = Mockito.mock(Job.class); + when(mockedRenamedItem.getName()).thenReturn("new fake Job"); + when(mockedRenamedItem.getRootDir()).thenReturn(jobDirectory); + + sscItemListener.onLocationChanged(mockedRenamedItem, "-newFakeJob", "new fake Job"); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/"); + + assertStatusManagerIsOk(); + + // And while we're at it: let's rename it back + assertTrue("Config file deletion", configFile.delete()); + assertTrue("Job dir deletion", jobDirectory.delete()); + jobDirectory = new File(getCurrentHudsonRootDirectory(), "jobs/-newFakeJob/" ); + configFile = new File(jobDirectory, "config.xml"); + jobDirectory.mkdir(); + FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml").getFile(), configFile); + + sscItemListener.onLocationChanged(mockedItem, "new fake Job", "-newFakeJob"); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldFileWhichHaveToBeInSCM() throws Throwable { + // IMPORTANT NOTE : + // For every tested files in this test, file path should exist in + // HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/ directory + + assertStrategy(JenkinsConfigScmSyncStrategy.class, Mockito.mock(Saveable.class), "config.xml"); + assertStrategy(BasicPluginsConfigScmSyncStrategy.class, Mockito.mock(Saveable.class), "hudson.scm.SubversionSCM.xml"); + assertStrategy(null, Mockito.mock(Saveable.class), "hudson.config.xml2"); + assertStrategy(null, Mockito.mock(Saveable.class), "nodeMonitors.xml"); + assertStrategy(null, Mockito.mock(Saveable.class), "toto" + File.separator + "hudson.config.xml"); + + assertStrategy(null, Mockito.mock(Job.class), "toto" + File.separator + "config.xml"); + assertStrategy(null, Mockito.mock(Job.class), "jobs" + File.separator + "config.xml"); + assertStrategy(null, Mockito.mock(Saveable.class), "jobs" + File.separator + "myJob" + File.separator + "config.xml"); + assertStrategy(JobConfigScmSyncStrategy.class, Mockito.mock(Job.class), "jobs" + File.separator + "myJob" + File.separator + "config.xml"); + assertStrategy(JobConfigScmSyncStrategy.class, Mockito.mock(Job.class), "jobs" + File.separator + "myFolder" + File.separator + "jobs" + File.separator + "myJob" + File.separator + "config.xml"); + assertStrategy(JobConfigScmSyncStrategy.class, Mockito.mock(AbstractItem.class), "jobs" + File.separator + "myFolder" + File.separator + "config.xml"); + assertStrategy(null, Mockito.mock(Job.class), "jobs" + File.separator + "myJob" + File.separator + "config2.xml"); + } + + private void assertStrategy(Class expectedStrategyClass, Saveable saveableInstance, String targetPath) { + ScmSyncStrategy strategy = ScmSyncConfigurationPlugin.getInstance().getStrategyForSaveable(saveableInstance, new File(getCurrentHudsonRootDirectory() + File.separator + targetPath)); + if (expectedStrategyClass == null) { + assertThat(strategy, nullValue()); + } + else { + assertThat(strategy, notNullValue()); + assertThat(strategy, instanceOf(expectedStrategyClass)); + } + } + + @Override + protected String getHudsonRootBaseTemplate(){ + if("shouldFileWhichHaveToBeInSCM".equals(testName.getMethodName())){ + return "HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/"; + } + + return "hudsonRootBaseTemplate/"; + } + + @Test + public void testPageMatchers() throws Exception { + assertStrategy(JobConfigScmSyncStrategy.class, Jenkins.getInstance().getRootUrl() + "job/jobName/configure"); + assertStrategy(JobConfigScmSyncStrategy.class, Jenkins.getInstance().getRootUrl() + "job/folderName/job/jobName/configure"); + assertStrategy(null, Jenkins.getInstance().getRootUrl() + "job/folderName/job/configure"); + assertStrategy(null, Jenkins.getInstance().getRootUrl() + "job/folderName/job/someThing/configure/foo"); + } + + private void assertStrategy(Class expectedStrategyClass, String url) { + ScmSyncStrategy strategy = ScmSyncConfigurationPlugin.getInstance().getStrategyForURL(url); + if (expectedStrategyClass == null) { + assertThat(strategy, nullValue()); + } + else { + assertThat(strategy, notNullValue()); + assertThat(strategy, instanceOf(expectedStrategyClass)); + } + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryGitTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryGitTest.java new file mode 100644 index 00000000..bd02e59b --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryGitTest.java @@ -0,0 +1,13 @@ +package hudson.plugins.scm_sync_configuration.repository; + +import hudson.plugins.test.utils.scms.ScmUnderTestGit; + + + +public class InitRepositoryGitTest extends InitRepositoryTest { + + public InitRepositoryGitTest() { + super(new ScmUnderTestGit()); + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositorySubversionTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositorySubversionTest.java new file mode 100644 index 00000000..39191115 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositorySubversionTest.java @@ -0,0 +1,11 @@ +package hudson.plugins.scm_sync_configuration.repository; + +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; + +public class InitRepositorySubversionTest extends InitRepositoryTest { + + public InitRepositorySubversionTest() { + super(new ScmUnderTestSubversion()); + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java new file mode 100644 index 00000000..434a0cd0 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java @@ -0,0 +1,94 @@ +package hudson.plugins.scm_sync_configuration.repository; + +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.model.ScmContext; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.scms.ScmUnderTest; +import org.codehaus.plexus.util.FileUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.powermock.core.classloader.annotations.PrepareForTest; + +import java.io.File; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +@PrepareForTest(SCM.class) +public abstract class InitRepositoryTest extends ScmSyncConfigurationPluginBaseTest { + + protected InitRepositoryTest(ScmUnderTest scmUnderTest) { + super(scmUnderTest); + } + + @Test + public void shouldNotInitializeAnyRepositoryWhenScmContextIsEmpty() throws Throwable { + ScmContext emptyContext = new ScmContext(null, null); + sscBusiness.init(emptyContext); + assertThat(sscBusiness.scmCheckoutDirectorySettledUp(emptyContext), is(false)); + + emptyContext = new ScmContext(null, getSCMRepositoryURL()); + sscBusiness.init(emptyContext); + assertThat(sscBusiness.scmCheckoutDirectorySettledUp(emptyContext), is(false)); + + createSCMMock(null); + assertThat(sscBusiness.scmCheckoutDirectorySettledUp(emptyContext), is(false)); + + assertStatusManagerIsNull(); + } + + @Test + @Ignore("Not yet implemented ! (it is difficult because svn list/log has not yet been implemented in svnjava impl") + public void shouldInitializeLocalRepositoryWhenScmContextIsCorrentAndEvenIfScmDirectoryDoesntExist() throws Throwable { + createSCMMock(); + assertThat(sscBusiness.scmCheckoutDirectorySettledUp(scmContext), is(true)); + } + + @Test + public void shouldResetCheckoutConfigurationDirectoryWhenAsked() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // After init, local checked out repository should exist + assertThat(getCurrentScmSyncConfigurationCheckoutDirectory().exists(), is(true)); + + // Populating checkoutConfiguration directory .. + File fileWhichShouldBeDeletedAfterReset = new File(getCurrentScmSyncConfigurationCheckoutDirectory().getAbsolutePath()+"/hello.txt"); + assertThat(fileWhichShouldBeDeletedAfterReset.createNewFile(), is(true)); + FileUtils.fileWrite(fileWhichShouldBeDeletedAfterReset.getAbsolutePath(), "Hello world !"); + + // Reseting the repository, without cleanup + sscBusiness.initializeRepository(scmContext, false); + assertThat(fileWhichShouldBeDeletedAfterReset.exists(), is(true)); + + // Reseting the repository with cleanup + sscBusiness.initializeRepository(scmContext, true); + assertThat(fileWhichShouldBeDeletedAfterReset.exists(), is(false)); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldSynchronizeHudsonFiles() throws Throwable { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); + + assertStatusManagerIsOk(); + } + + @Test + public void shouldInitializeLocalRepositoryWhenScmContextIsCorrect() + throws Throwable { + createSCMMock(); + assertThat(sscBusiness.scmCheckoutDirectorySettledUp(scmContext), is(true)); + + assertStatusManagerIsOk(); + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategyTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategyTest.java new file mode 100644 index 00000000..153223bc --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/strategies/impl/JobConfigScmSyncStrategyTest.java @@ -0,0 +1,63 @@ +package hudson.plugins.scm_sync_configuration.strategies.impl; + +import hudson.XmlFile; +import hudson.model.Job; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.extensions.ScmSyncConfigurationSaveableListener; +import hudson.plugins.scm_sync_configuration.util.ScmSyncConfigurationPluginBaseTest; +import hudson.plugins.test.utils.scms.ScmUnderTestSubversion; +import org.codehaus.plexus.PlexusContainerException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; + +import static org.mockito.Mockito.when; + +/** + * @author fcamblor + */ +public class JobConfigScmSyncStrategyTest extends ScmSyncConfigurationPluginBaseTest { + + private ScmSyncConfigurationSaveableListener sscConfigurationSaveableListener; + + public JobConfigScmSyncStrategyTest() { + super(new ScmUnderTestSubversion()); + } + + @Before + public void initObjectsUnderTests() throws Throwable{ + this.sscConfigurationSaveableListener = new ScmSyncConfigurationSaveableListener(); + } + + @Override + protected String getHudsonRootBaseTemplate(){ + return "jobConfigStrategyTemplate/"; + } + + // Reproducing JENKINS-17545 + @Test + public void shouldConfigInSubmodulesNotSynced() throws ComponentLookupException, PlexusContainerException, IOException { + // Initializing the repository... + createSCMMock(); + + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + + File subModuleConfigFile = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/modules/submodule/config.xml" ); + + // Creating fake new item + Job mockedItem = Mockito.mock(Job.class); + when(mockedItem.getRootDir()).thenReturn(subModuleConfigFile.getParentFile()); + + sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(subModuleConfigFile)); + + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/"); + + assertStatusManagerIsOk(); + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java new file mode 100644 index 00000000..2bef1afe --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java @@ -0,0 +1,252 @@ +package hudson.plugins.scm_sync_configuration.util; + +import hudson.Plugin; +import hudson.PluginWrapper; +import hudson.model.Hudson; +import hudson.model.User; +import hudson.plugins.scm_sync_configuration.SCMManagerFactory; +import hudson.plugins.scm_sync_configuration.SCMManipulator; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationBusiness; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.scm_sync_configuration.model.ScmContext; +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.SCMCredentialConfiguration; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncSubversionSCM; +import hudson.plugins.scm_sync_configuration.xstream.migration.DefaultSSCPOJO; +import hudson.plugins.scm_sync_configuration.xstream.migration.ScmSyncConfigurationPOJO; +import hudson.plugins.test.utils.DirectoryUtils; +import hudson.plugins.test.utils.scms.ScmUnderTest; + +import org.codehaus.plexus.PlexusContainerException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.codehaus.plexus.util.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.objenesis.ObjenesisStd; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.core.io.ClassPathResource; + +import com.google.common.collect.Lists; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; +import java.util.regex.Pattern; + +import jenkins.model.Jenkins; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({ "org.tmatesoft.svn.*" }) +@PrepareForTest({Hudson.class, Jenkins.class, SCM.class, ScmSyncSubversionSCM.class, PluginWrapper.class}) +public abstract class ScmSyncConfigurationBaseTest { + + @Rule protected TestName testName = new TestName(); + + private static final String TEST_URL = "https://jenkins.example.org/"; + + private File currentTestDirectory = null; + private File curentLocalRepository = null; + private File currentHudsonRootDirectory = null; + protected ScmSyncConfigurationBusiness sscBusiness = null; + protected ScmContext scmContext = null; + + private final ScmUnderTest scmUnderTest; + + protected ScmSyncConfigurationBaseTest(ScmUnderTest scmUnderTest) { + this.scmUnderTest = scmUnderTest; + this.scmContext = null; + } + + @SuppressWarnings("deprecation") // We need to mock Hudson.getInstance() + @Before + public void setup() throws Throwable { + // Instantiating ScmSyncConfigurationPlugin instance for unit tests by using + // synchronous transactions (instead of an asynchronous ones) + // => this way, every commit will be processed synchronously ! + ScmSyncConfigurationPlugin scmSyncConfigPluginInstance = new ScmSyncConfigurationPlugin(true) { + @Override + public void initialInit() throws Exception { + // No-op. We *must not* initialize here in tests because the tests provide their own setup. + } + }; + + // Mocking PluginWrapper attached to current ScmSyncConfigurationPlugin instance + PluginWrapper pluginWrapper = PowerMockito.mock(PluginWrapper.class); + when(pluginWrapper.getShortName()).thenReturn("scm-sync-configuration"); + // Setting field on current plugin instance + Field wrapperField = Plugin.class.getDeclaredField("wrapper"); + boolean wrapperFieldAccessibility = wrapperField.isAccessible(); + wrapperField.setAccessible(true); + wrapperField.set(scmSyncConfigPluginInstance, pluginWrapper); + wrapperField.setAccessible(wrapperFieldAccessibility); + + Field businessField = ScmSyncConfigurationPlugin.class.getDeclaredField("business"); + businessField.setAccessible(true); + sscBusiness = (ScmSyncConfigurationBusiness) businessField.get(scmSyncConfigPluginInstance); + + // Mocking Hudson root directory + currentTestDirectory = createTmpDirectory("SCMSyncConfigTestsRoot"); + currentHudsonRootDirectory = new File(currentTestDirectory.getAbsolutePath()+"/hudsonRootDir/"); + if(!(currentHudsonRootDirectory.mkdir())) { throw new IOException("Could not create hudson root directory: " + currentHudsonRootDirectory.getAbsolutePath()); } + FileUtils.copyDirectoryStructure(new ClassPathResource(getHudsonRootBaseTemplate()).getFile(), currentHudsonRootDirectory); + + //EnvVars env = Computer.currentComputer().getEnvironment(); + //env.put("HUDSON_HOME", tmpHudsonRoot.getPath() ); + + // Creating local repository... + curentLocalRepository = new File(currentTestDirectory.getAbsolutePath()+"/localRepo/"); + if(!(curentLocalRepository.mkdir())) { throw new IOException("Could not create local repo directory: " + curentLocalRepository.getAbsolutePath()); } + scmUnderTest.initRepo(curentLocalRepository); + + // Mocking user + User mockedUser = Mockito.mock(User.class); + when(mockedUser.getId()).thenReturn("fcamblor"); + + // Mocking Hudson singleton instance ... + // Warning : this line will only work on Objenesis supported VMs : + // http://code.google.com/p/objenesis/wiki/ListOfCurrentlySupportedVMs + Hudson hudsonMockedInstance = spy((Hudson) new ObjenesisStd().getInstantiatorOf(Hudson.class).newInstance()); + PowerMockito.doReturn(currentHudsonRootDirectory).when(hudsonMockedInstance).getRootDir(); + PowerMockito.doReturn(mockedUser).when(hudsonMockedInstance).getMe(); + PowerMockito.doReturn(scmSyncConfigPluginInstance).when(hudsonMockedInstance).getPlugin(ScmSyncConfigurationPlugin.class); + PowerMockito.doReturn(TEST_URL).when(hudsonMockedInstance).getRootUrl(); + PowerMockito.doReturn(TEST_URL).when(hudsonMockedInstance).getRootUrlFromRequest(); + + PowerMockito.mockStatic(Jenkins.class); + PowerMockito.doReturn(hudsonMockedInstance).when(Jenkins.class); Jenkins.getInstance(); + PowerMockito.mockStatic(Hudson.class); + PowerMockito.doReturn(hudsonMockedInstance).when(Hudson.class); Hudson.getInstance(); + //when(Hudson.getInstance()).thenReturn(hudsonMockedInstance); + } + + @After + public void teardown() throws Throwable { + // Deleting current test directory + FileUtils.deleteDirectory(currentTestDirectory); + } + + // Overridable + protected String getHudsonRootBaseTemplate(){ + return "hudsonRootBaseTemplate/"; + } + + protected static File createTmpDirectory(String directoryPrefix) throws IOException { + final File temp = File.createTempFile(directoryPrefix, Long.toString(System.nanoTime())); + if(!(temp.delete())) { throw new IOException("Could not delete temp file: " + temp.getAbsolutePath()); } + if(!(temp.mkdir())) { throw new IOException("Could not create temp directory: " + temp.getAbsolutePath()); } + return (temp); + } + + protected SCM createSCMMock(){ + return createSCMMock(getSCMRepositoryURL()); + } + + protected SCM createSCMMock(String url){ + SCM mockedSCM = spy(SCM.valueOf(getSCMClass().getName())); + + if(scmUnderTest.useCredentials()){ + SCMCredentialConfiguration mockedCredential = new SCMCredentialConfiguration("toto"); + PowerMockito.doReturn(mockedCredential).when(mockedSCM).extractScmCredentials((String)Mockito.notNull()); + } + + scmContext = new ScmContext(mockedSCM, url); + ScmSyncConfigurationPOJO config = new DefaultSSCPOJO(); + config.setScm(scmContext.getScm()); + config.setScmRepositoryUrl(scmContext.getScmRepositoryUrl()); + ScmSyncConfigurationPlugin.getInstance().loadData(config); + ScmSyncConfigurationPlugin.getInstance().init(); + + return mockedSCM; + } + + protected SCMManipulator createMockedScmManipulator() throws ComponentLookupException, PlexusContainerException{ + // Settling up scm context + SCMManipulator scmManipulator = new SCMManipulator(SCMManagerFactory.getInstance().createScmManager()); + boolean configSettledUp = scmManipulator.scmConfigurationSettledUp(scmContext, true); + assertThat(configSettledUp, is(true)); + + return scmManipulator; + } + + protected void verifyCurrentScmContentMatchesCurrentHudsonDir(boolean match) throws ComponentLookupException, PlexusContainerException, IOException{ + verifyCurrentScmContentMatchesHierarchy(getCurrentHudsonRootDirectory(), match); + } + + protected void verifyCurrentScmContentMatchesHierarchy(String hierarchyPath, boolean match) throws ComponentLookupException, PlexusContainerException, IOException{ + verifyCurrentScmContentMatchesHierarchy(new ClassPathResource(hierarchyPath).getFile(), match); + } + + protected void verifyCurrentScmContentMatchesHierarchy(File hierarchy, boolean match) throws ComponentLookupException, PlexusContainerException, IOException{ + SCMManipulator scmManipulator = createMockedScmManipulator(); + + // Checkouting scm in temp directory + File checkoutDirectoryForVerifications = createTmpDirectory(this.getClass().getSimpleName()+"_"+testName.getMethodName()+"__verifyCurrentScmContentMatchesHierarchy"); + scmManipulator.checkout(checkoutDirectoryForVerifications); + List diffs = DirectoryUtils.diffDirectories(checkoutDirectoryForVerifications, hierarchy, + getSpecialSCMDirectoryExcludePattern(), true); + + FileUtils.deleteDirectory(checkoutDirectoryForVerifications); + + if(match){ + assertTrue("Directories doesn't match : "+diffs, diffs.isEmpty()); + } else { + assertFalse("Directories should _not_ match !", diffs.isEmpty()); + } + } + + protected void verifyCurrentScmContentMatchesHierarchy(String hierarchyPath) throws ComponentLookupException, PlexusContainerException, IOException{ + verifyCurrentScmContentMatchesHierarchy(hierarchyPath, true); + } + + // Overridable in a near future (when dealing with multiple scms ...) + protected String getSCMRepositoryURL(){ + return scmUnderTest.createUrl(this.getCurentLocalRepository().getAbsolutePath()); + } + + protected static List getSpecialSCMDirectoryExcludePattern(){ + return Lists.newArrayList( + Pattern.compile("\\.svn"), + Pattern.compile("\\.git.*"), + Pattern.compile("scm-sync-configuration\\..*\\.log"), + Pattern.compile("scm-sync-configuration") + ); + } + + protected String getSuffixForTestFiles() { + return scmUnderTest.getSuffixForTestFiles(); + } + + // Overridable in a near future (when dealing with multiple scms ...) + protected Class getSCMClass(){ + return scmUnderTest.getClazz(); + } + + protected File getCurrentTestDirectory() { + return currentTestDirectory; + } + + protected File getCurentLocalRepository() { + return curentLocalRepository; + } + + public File getCurrentHudsonRootDirectory() { + return currentHudsonRootDirectory; + } + + public File getCurrentScmSyncConfigurationCheckoutDirectory(){ + return new File(currentHudsonRootDirectory.getAbsolutePath()+"/scm-sync-configuration/checkoutConfiguration/"); + } + +} diff --git a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationPluginBaseTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationPluginBaseTest.java new file mode 100644 index 00000000..e00a9447 --- /dev/null +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationPluginBaseTest.java @@ -0,0 +1,41 @@ +package hudson.plugins.scm_sync_configuration.util; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; +import hudson.plugins.test.utils.scms.ScmUnderTest; + +// Class will start current ScmSyncConfigurationPlugin instance +public abstract class ScmSyncConfigurationPluginBaseTest extends + ScmSyncConfigurationBaseTest { + + protected ScmSyncConfigurationPluginBaseTest(ScmUnderTest scmUnderTest) { + super(scmUnderTest); + } + + public void setup() throws Throwable { + super.setup(); + + // Let's start the plugin... + ScmSyncConfigurationPlugin.getInstance().start(); + } + + public void teardown() throws Throwable { + // Stopping current plugin + ScmSyncConfigurationPlugin.getInstance().stop(); + + super.teardown(); + } + + protected void assertStatusManagerIsOk() { + assertThat(sscBusiness.getScmSyncConfigurationStatusManager().getLastFail(), nullValue()); + assertThat(sscBusiness.getScmSyncConfigurationStatusManager().getLastSuccess(), notNullValue()); + } + + protected void assertStatusManagerIsNull() { + assertThat(sscBusiness.getScmSyncConfigurationStatusManager().getLastFail(), nullValue()); + assertThat(sscBusiness.getScmSyncConfigurationStatusManager().getLastSuccess(), nullValue()); + } + +} diff --git a/src/test/java/hudson/plugins/test/utils/DirectoryUtils.java b/src/test/java/hudson/plugins/test/utils/DirectoryUtils.java new file mode 100644 index 00000000..b0a5e3cb --- /dev/null +++ b/src/test/java/hudson/plugins/test/utils/DirectoryUtils.java @@ -0,0 +1,92 @@ +package hudson.plugins.test.utils; + +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +public class DirectoryUtils { + + private static final Logger LOGGER = Logger.getLogger(DirectoryUtils.class.getName()); + + private static class FileComparator implements Comparator{ + public int compare(File o1, File o2) { + return o1.getName().compareTo(o2.getName()); + } + } + private static final FileComparator FILE_COMPARATOR = new FileComparator(); + + public static List diffDirectories(File dir1, File dir2, final List excludePatterns, boolean recursive){ + List diffs = new ArrayList(); + + if(!dir1.isDirectory() || !dir2.isDirectory()){ + throw new IllegalArgumentException("dir1 & dir2 should be directories !"); + } + + Predicate patternMatcherOnFilenamePredicate = new Predicate() { + public boolean apply(File f) { + for(Pattern pattern : excludePatterns) { + if (pattern.matcher(f.getName()).matches()) { + return false; + } + } + return true; + } + }; + + List dir1Files = new ArrayList( Arrays.asList(dir1.listFiles()) ); + List dir2Files = new ArrayList( Arrays.asList(dir2.listFiles()) ); + + Collections.sort(dir1Files, FILE_COMPARATOR); + Collections.sort(dir2Files, FILE_COMPARATOR); + + Collection dir1FilesCollec = dir1Files; + Collection dir2FilesCollec = dir2Files; + if(excludePatterns != null){ + dir1FilesCollec = Collections2.filter(dir1Files, patternMatcherOnFilenamePredicate); + dir2FilesCollec = Collections2.filter(dir2Files, patternMatcherOnFilenamePredicate); + } + + if(dir1FilesCollec.size() != dir2FilesCollec.size()){ + diffs.add(String.format("Number of files in %s (%s) and %s (%s) differ !", dir1, dir1FilesCollec.size(), dir2, dir2FilesCollec.size())); + return diffs; + } + + + Iterator dir1FileIter=dir1FilesCollec.iterator(); + Iterator dir2FileIter=dir2FilesCollec.iterator(); + for(int i=0; i content with <"+f2.getAbsolutePath()+"> content"); + diffs.add("Error occured when comparing <"+f1.getAbsolutePath()+"> content with <"+f2.getAbsolutePath()+"> content"); + } + } + if(recursive && f1.isDirectory()){ + diffs.addAll(diffDirectories(f1, f2, excludePatterns, recursive)); + } + } + + return diffs; + } + +} diff --git a/src/test/java/hudson/plugins/test/utils/PluginUtil.java b/src/test/java/hudson/plugins/test/utils/PluginUtil.java new file mode 100644 index 00000000..d6716028 --- /dev/null +++ b/src/test/java/hudson/plugins/test/utils/PluginUtil.java @@ -0,0 +1,19 @@ +package hudson.plugins.test.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import hudson.Plugin; + +public class PluginUtil { + + public static void loadPlugin(Plugin plugin) + throws SecurityException, NoSuchMethodException, IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + Method loadMethod = Plugin.class.getDeclaredMethod("load"); + boolean loadMethodAccessibility = loadMethod.isAccessible(); + loadMethod.setAccessible(true); + loadMethod.invoke(plugin); + loadMethod.setAccessible(loadMethodAccessibility); + } +} diff --git a/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTest.java b/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTest.java new file mode 100644 index 00000000..4e88afee --- /dev/null +++ b/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTest.java @@ -0,0 +1,19 @@ +package hudson.plugins.test.utils.scms; + +import hudson.plugins.scm_sync_configuration.scms.SCM; + +import java.io.File; + +public interface ScmUnderTest { + + void initRepo(File path) throws Exception; + + String createUrl(String url); + + Class getClazz(); + + boolean useCredentials(); + + String getSuffixForTestFiles(); + +} diff --git a/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestGit.java b/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestGit.java new file mode 100644 index 00000000..99b91c6e --- /dev/null +++ b/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestGit.java @@ -0,0 +1,35 @@ +package hudson.plugins.test.utils.scms; + +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncGitSCM; + +import java.io.File; + +public class ScmUnderTestGit implements ScmUnderTest { + + public void initRepo(File path) throws Exception { + ProcessBuilder pb = new ProcessBuilder("git", "init", "--bare"); + pb.directory(path); + Process p = pb.start(); + if (p.waitFor() != 0) { + throw new Exception("Unable to init git repo in " + path.getAbsolutePath()); + } + } + + public String createUrl(String url) { + return "scm:git:" + url; + } + + public Class getClazz() { + return ScmSyncGitSCM.class; + } + + public boolean useCredentials() { + return false; + } + + public String getSuffixForTestFiles() { + return ".git"; + } + +} diff --git a/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestSubversion.java b/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestSubversion.java new file mode 100644 index 00000000..337dd9bd --- /dev/null +++ b/src/test/java/hudson/plugins/test/utils/scms/ScmUnderTestSubversion.java @@ -0,0 +1,32 @@ +package hudson.plugins.test.utils.scms; + +import hudson.plugins.scm_sync_configuration.scms.SCM; +import hudson.plugins.scm_sync_configuration.scms.ScmSyncSubversionSCM; + +import java.io.File; + +import org.tmatesoft.svn.core.io.SVNRepositoryFactory; + +public class ScmUnderTestSubversion implements ScmUnderTest { + + public void initRepo(File path) throws Exception { + SVNRepositoryFactory.createLocalRepository(path, true , false); + } + + public String createUrl(String url) { + return "scm:svn:file://" + url; + } + + public Class getClazz() { + return ScmSyncSubversionSCM.class; + } + + public boolean useCredentials() { + return true; + } + + public String getSuffixForTestFiles() { + return ".subversion"; + } + +} diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/hudson.config.xml2 b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/hudson.config.xml2 new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/hudson.scm.SubversionSCM.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/hudson.scm.SubversionSCM.xml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/jobs/myJob/config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/jobs/myJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myFolder/jobs/myJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config2.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config2.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/jobs/myJob/config2.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/nodeMonitors.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/nodeMonitors.xml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/scm-sync-configuration.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/hudson.config.xml b/src/test/resources/HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/toto/hudson.config.xml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldConfigModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml new file mode 100644 index 00000000..fe28eac3 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + true + false + + false + true + false + true + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.git/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/jobs/.gitkeep b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/jobs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteBeCorrectlyImpactedOnSCM.subversion/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/.gitkeep b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml new file mode 100644 index 00000000..4d80eade --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml @@ -0,0 +1,21 @@ + + + + MySecondJobs + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml new file mode 100644 index 00000000..c28432c2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + My desc + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobModificationBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml new file mode 100644 index 00000000..fe28eac3 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/-newFakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + true + false + + false + true + false + true + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameStartingWithDash/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/new fake Job/config.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/new fake Job/config.xml new file mode 100644 index 00000000..fe28eac3 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/jobs/new fake Job/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + true + false + + false + true + false + true + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/HudsonExtensionsTest.testAddJobNameWithBlanks/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/config.xml b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/config.xml b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/hudson.tasks.Shell.xml b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/jobs/fakeJob/config.xml b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/scm-sync-configuration.xml b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.2BaseTemplate/config.xml b/src/test/resources/hudsonRoot0.0.2BaseTemplate/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.2BaseTemplate/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.2BaseTemplate/scm-sync-configuration.xml b/src/test/resources/hudsonRoot0.0.2BaseTemplate/scm-sync-configuration.xml new file mode 100644 index 00000000..fe8f5c52 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.2BaseTemplate/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + SUBVERSION + diff --git a/src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/config.xml b/src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/scm-sync-configuration.xml b/src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/scm-sync-configuration.xml new file mode 100644 index 00000000..3df67d29 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.2WithEmptyConfTemplate/scm-sync-configuration.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.3BaseTemplate/config.xml b/src/test/resources/hudsonRoot0.0.3BaseTemplate/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.3BaseTemplate/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.3BaseTemplate/scm-sync-configuration.xml b/src/test/resources/hudsonRoot0.0.3BaseTemplate/scm-sync-configuration.xml new file mode 100644 index 00000000..fbb42631 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.3BaseTemplate/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/config.xml b/src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/scm-sync-configuration.xml b/src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/scm-sync-configuration.xml new file mode 100644 index 00000000..b6dc2541 --- /dev/null +++ b/src/test/resources/hudsonRoot0.0.4WithEmptyConfTemplate/scm-sync-configuration.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/test/resources/hudsonRootBaseTemplate/config.xml b/src/test/resources/hudsonRootBaseTemplate/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/hudsonRootBaseTemplate/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/hudsonRootBaseTemplate/hudson.tasks.Shell.xml b/src/test/resources/hudsonRootBaseTemplate/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/hudsonRootBaseTemplate/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/hudsonRootBaseTemplate/jobs/fakeJob/config.xml b/src/test/resources/hudsonRootBaseTemplate/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/hudsonRootBaseTemplate/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/hudsonRootBaseTemplate/scm-sync-configuration.xml b/src/test/resources/hudsonRootBaseTemplate/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/hudsonRootBaseTemplate/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file diff --git a/src/test/resources/jobConfigStrategyTemplate/config.xml b/src/test/resources/jobConfigStrategyTemplate/config.xml new file mode 100644 index 00000000..824eae41 --- /dev/null +++ b/src/test/resources/jobConfigStrategyTemplate/config.xml @@ -0,0 +1,31 @@ + + + 1.339 + 2 + NORMAL + true + + + true + + Welcome ! + + + + 5 + 0 + + + + All + false + false + + + All + 0 + + + + + \ No newline at end of file diff --git a/src/test/resources/jobConfigStrategyTemplate/hudson.tasks.Shell.xml b/src/test/resources/jobConfigStrategyTemplate/hudson.tasks.Shell.xml new file mode 100644 index 00000000..8b953697 --- /dev/null +++ b/src/test/resources/jobConfigStrategyTemplate/hudson.tasks.Shell.xml @@ -0,0 +1,5 @@ + + + + /bin/bash + \ No newline at end of file diff --git a/src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/config.xml b/src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/modules/submodule/config.xml b/src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/modules/submodule/config.xml new file mode 100644 index 00000000..f1972d75 --- /dev/null +++ b/src/test/resources/jobConfigStrategyTemplate/jobs/fakeJob/modules/submodule/config.xml @@ -0,0 +1,21 @@ + + + + + false + + + true + false + false + + false + true + false + false + false + false + + + + \ No newline at end of file diff --git a/src/test/resources/jobConfigStrategyTemplate/scm-sync-configuration.xml b/src/test/resources/jobConfigStrategyTemplate/scm-sync-configuration.xml new file mode 100644 index 00000000..a83018f2 --- /dev/null +++ b/src/test/resources/jobConfigStrategyTemplate/scm-sync-configuration.xml @@ -0,0 +1,5 @@ + + + scm:svn:https://myrepo/synchronizedDirectory/ + + \ No newline at end of file