From b49dd9ebc30ca290c6a3153da7b69c4a853f0bf2 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 29 Jun 2015 22:15:40 +0200 Subject: [PATCH 01/22] Get this to compile, build, and pass the tests. I did not manage to get this going with the 1.409 POM. And anyway newer Jenkins versions have some changes that this plugin should account for. POM changes: * Increase minimum Jenkins version to 1.565.3 (LTS from October 2014) and adapt POM as needed to get tests to run successfully in my Eclipse/m2e setup. Also clean out some obsolete stuff relating to easymock, which isn't used anymore. * Add a license (MIT). * Include some Eclipes m2e-specific "lifecycle mappings". Unfortunately that's needed for Eclipse users, otherwise nothing will work. * Fix some versions. * Resolve the sisu-vs.-plexus problems by using a resent sisu-inject-plexus and making sure that comes first and thus wins. Exclude the very old plexus container stuff pulled in via maven-scm-manager-plexus; sisu-inject-plexus provides a newer one. Didn't touch the SCMManagerFactory, though. Code changes: * Mock Jenkins, too, not just Hudson. Miscellaneous: * Add the Eclipse m2e annotation processor file to .gitignore --- .gitignore | 1 + pom.xml | 100 +++++++++++------- .../util/ScmSyncConfigurationBaseTest.java | 5 +- 3 files changed, 68 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 830feecb..2eea90cf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ work*/ .idea/ # eclipse project file +.factorypath .settings .classpath .project diff --git a/pom.xml b/pom.xml index 229aaf63..2035c79b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,17 +3,23 @@ org.jenkins-ci.plugins plugin - 1.409 - ../pom.xml + 1.565.3 scm-sync-configuration SCM Sync Configuration Plugin - 0.0.8.1-SNAPSHOT + 0.0.9-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 @@ -29,11 +35,9 @@ UTF-8 - 1.4.8 - repo.jenkins-ci.org @@ -51,10 +55,52 @@ - + + 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 + + + + + + + + maven-release-plugin - 2.5 + 2.5.1 true @@ -74,6 +120,7 @@ org.codehaus.plexus plexus-maven-plugin + 1.3.8 merge @@ -97,7 +144,7 @@ org.jenkins-ci.main maven-plugin - ${project.parent.version} + 2.3 @@ -122,27 +169,25 @@ + + org.sonatype.sisu + sisu-inject-plexus + 2.6.0 + + org.apache.maven.scm maven-scm-manager-plexus 1.9.1 - org.codehaus.plexus plexus-container-default - --> - - + org.jenkins-ci.plugins subversion @@ -175,26 +220,7 @@ powermock-api-mockito ${powermock.version} test - - - - org.mockito - mockito-core - 1.8.5 - test - - junit - junit - 4.8.1 - jar - test - - - com.google.guava - guava - 12.0.1 - 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 index 5de847fa..7811f5d1 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java @@ -39,6 +39,7 @@ 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; @@ -46,7 +47,7 @@ @RunWith(PowerMockRunner.class) @PowerMockIgnore({ "org.tmatesoft.svn.*" }) -@PrepareForTest({Hudson.class, SCM.class, ScmSyncSubversionSCM.class, PluginWrapper.class}) +@PrepareForTest({Hudson.class, Jenkins.class, SCM.class, ScmSyncSubversionSCM.class, PluginWrapper.class}) public abstract class ScmSyncConfigurationBaseTest { @Rule protected TestName testName = new TestName(); @@ -110,6 +111,8 @@ public void setup() throws Throwable { PowerMockito.doReturn(mockedUser).when(hudsonMockedInstance).getMe(); PowerMockito.doReturn(scmSyncConfigPluginInstance).when(hudsonMockedInstance).getPlugin(ScmSyncConfigurationPlugin.class); + 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); From 7ad9ae33a19bf76b641346a0cf67a33324dc846d Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 29 Jun 2015 22:15:40 +0200 Subject: [PATCH 02/22] Switch to Java 6. It's the minimum requirement for Jenkins since 1.520. --- pom.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2035c79b..9046252e 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,16 @@ - + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + org.codehaus.plexus plexus-maven-plugin 1.3.8 From 83a1bb83ae758f6da31e0570d101983ddeb1f9d1 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 25 Jun 2015 22:50:10 +0200 Subject: [PATCH 03/22] JENKINS-22666: Fix regexp Since PR #19 at GitHub (commit 141cc2b5), we don't get a commit message dialog anymore for changes to job configurations. The regexp looks broken with the initial slashes and only a single [^/]. With the Cloudbees Folders plugin jobs can be not only at jobs/Some_Job but also at jobs/Folder1/jobs/Folder2/jobs/Some_Job This commit simplifies the regexp and fixes it. --- .../strategies/impl/JobConfigScmSyncStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 6f18527d..51e72722 100644 --- 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 @@ -19,7 +19,7 @@ public class JobConfigScmSyncStrategy 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 = new ArrayList(){ { - add(new PageMatcher("^(.*view/[^/]+/)?(/job/[^/])*/job/[^/]+/configure$", "form[name='config']")); + add(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']")); } }; // Only saving config.xml file located in job directory // Some plugins (like maven release plugin) could add their own configuration files in the job directory that we don't want to synchronize From 652c6d207a46510d61c9f4e3309ec3403d544867 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 26 Jun 2015 01:04:12 +0200 Subject: [PATCH 04/22] JENKINS-24993: restrict looking for jobs to the jobs directory. Having an ant pattern starting with "**" was a _very_ bad idea. It makes the plugin search way too many places, including even its own SCM workspace. Which then leads to recursively including ever deeper SCM workspace hierarchies in the SCM workspace and in the repository. Also, such an ant pattern makes the plugin scan all jobs' workspaces, which can take a long time and may discover bogus matches. (They're by default at $JENKINS_HOME/workspace.) The Cloudbees Folders plugin has all jobs under $JENKINS_HOME/jobs, and possibly nested as in jobs/Folder1/jobs/SomeProject Both Jobs and Folders are Saveables; folders also have a config.xml. Source changes: * Introduce a new ConfigurationEntityMatcher that matches and traverses only exactly this jobs/XXX/jobs/YYY/jobs... hierarchy. * To avoid problems with user-defined inclusions starting with * or even **, exclude our own SCM working directory always in ant pattern matching. * Also exclude the war directory in $JENKINS_HOME. * Undo more changes from commit 141cc2b5. Since we test new for AbstractItem, the tests should not mock TopLevelItem. Better mock Job. * Add two little test cases for config file matching for a job nested in a folder. * Add minimal test cases for page URL matching for jobs. --- .../ScmSyncConfigurationBusiness.java | 8 +- .../impl/JobConfigScmSyncStrategy.java | 24 +----- ...JobOrFolderConfigurationEntityMatcher.java | 86 +++++++++++++++++++ .../model/PatternsEntityMatcher.java | 20 ++++- .../repository/HudsonExtensionsGitTest.java | 1 - .../repository/HudsonExtensionsTest.java | 38 ++++++-- .../util/ScmSyncConfigurationBaseTest.java | 6 ++ .../jobs/myFolder/config.xml | 21 +++++ .../jobs/myFolder/jobs/myJob/config.xml | 21 +++++ 9 files changed, 191 insertions(+), 34 deletions(-) create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/JobOrFolderConfigurationEntityMatcher.java 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 diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index 8e43be65..1a0200b6 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -28,7 +28,7 @@ public class ScmSyncConfigurationBusiness { - private static final String WORKING_DIRECTORY_PATH = "/scm-sync-configuration/"; + 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()); @@ -351,9 +351,13 @@ private void signal(String operation, boolean result) { } public static String getCheckoutScmDirectoryAbsolutePath(){ - return Hudson.getInstance().getRootDir().getAbsolutePath()+WORKING_DIRECTORY_PATH+CHECKOUT_SCM_DIRECTORY; + return new File(new File(Hudson.getInstance().getRootDir(), WORKING_DIRECTORY), CHECKOUT_SCM_DIRECTORY).getAbsolutePath(); } + public static String getScmDirectoryName() { + return WORKING_DIRECTORY; + } + public void purgeFailLogs() { Hudson.getInstance().checkPermission(purgeFailLogPermission()); scmSyncConfigurationStatusManager.purgeFailLogs(); 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 index 51e72722..4760319b 100644 --- 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 @@ -2,35 +2,19 @@ import hudson.XmlFile; import hudson.model.Item; -import hudson.model.Job; import hudson.model.Saveable; -import hudson.model.TopLevelItem; 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.JobOrFolderConfigurationEntityMatcher; import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; -import java.util.ArrayList; -import java.util.List; +import java.util.Collections; public class JobConfigScmSyncStrategy 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 = new ArrayList(){ { - add(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']")); - } }; - // Only saving config.xml file located in job directory - // Some plugins (like maven release plugin) could add their own configuration files in the job directory that we don't want to synchronize - // ... at least in the current strategy ! - private static final String [] PATTERNS = new String[] { - "**/jobs/*/config.xml" - }; - private static final ConfigurationEntityMatcher CONFIG_ENTITY_MANAGER = new ClassAndFileConfigurationEntityMatcher(TopLevelItem.class, PATTERNS); - - public JobConfigScmSyncStrategy(){ - super(CONFIG_ENTITY_MANAGER, PAGE_MATCHERS); + public JobConfigScmSyncStrategy(){ + super(new JobOrFolderConfigurationEntityMatcher(), Collections.singletonList(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']"))); } public CommitMessageFactory getCommitMessageFactory(){ 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..89eeab4a --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/JobOrFolderConfigurationEntityMatcher.java @@ -0,0 +1,86 @@ +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.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +/** + * 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 implements ConfigurationEntityMatcher { + + private static final String JOBS_DIR_NAME = "jobs"; + private static final String CONFIG_FILE_NAME = "config.xml"; + + private static final Pattern CONFIGS_TO_MATCH = Pattern.compile("(?:" + JOBS_DIR_NAME + "/[^/]+/)+" + CONFIG_FILE_NAME); + + private static final FileFilter DIRECTORIES = new FileFilter() { + public boolean accept(File file) { + return file.isDirectory(); + } + }; + + 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(); + } + return CONFIGS_TO_MATCH.matcher(JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file)).matches(); + } + return false; + } + + public String[] matchingFilesFrom(File rootDirectory) { + // PatternsEntityMatcher uses a org.apache.tools.ant.DirectoryScanner, which returns a (sorted) list of matching file paths + // relative to the given root directory. Let's adhere to that specification, but only for our very special job config pattern. + if (rootDirectory == null || !rootDirectory.isDirectory()) { + return new String[0]; + } + List collected = new ArrayList(); + // Compare https://github.com/jenkinsci/cloudbees-folder-plugin/blob/70a4d47314a36b54d522cae0a78b3c76d153e627/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java#L200 + scanForJobConfigs (new File (rootDirectory, JOBS_DIR_NAME), JOBS_DIR_NAME, collected); + String[] result = collected.toArray(new String[collected.size()]); + Arrays.sort(result); + return result; + } + + private static void scanForJobConfigs(File jobsDir, String prefix, Collection result) { + if (!jobsDir.isDirectory()) { + return; + } + File[] projects = jobsDir.listFiles(DIRECTORIES); + if (projects == null) { + return; + } + for (File project : projects) { + if (new File(project, CONFIG_FILE_NAME).isFile()) { + result.add(prefix + '/' + project.getName() + '/' + CONFIG_FILE_NAME); + scanForJobConfigs(new File(project, JOBS_DIR_NAME), prefix + '/' + project.getName() + '/' + JOBS_DIR_NAME, result); + } + // Again, 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. + } + } + + public List getIncludes() { + // XXX The call hierarchy indicates that although this is called, ScmSyncConfigurationPlugin.getDefaultIncludes() isn't, + // and thus this whole method and assembling strings is futile. Let's just return an empty list as other matchers + // return ant patters, but there is no ant pattern that corresponds to our regular expression. + return Collections.emptyList(); + } + +} 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 index 3bccded2..457575a6 100644 --- 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 @@ -1,8 +1,9 @@ package hudson.plugins.scm_sync_configuration.strategies.model; -import hudson.model.Hudson; 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.springframework.util.AntPathMatcher; @@ -14,6 +15,9 @@ public class PatternsEntityMatcher implements ConfigurationEntityMatcher { private String[] includesPatterns; + private static String SCM_WORKING_DIRECTORY = ScmSyncConfigurationBusiness.getScmDirectoryName(); + private static String WAR_DIRECTORY = "war"; + public PatternsEntityMatcher(String[] includesPatterns){ this.includesPatterns = includesPatterns; } @@ -22,10 +26,16 @@ public boolean matches(Saveable saveable, File file) { if (file == null) { return false; } - String filePathRelativeToHudsonRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file); + String pathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file); + // 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(); - for(String pattern : includesPatterns) { - if(matcher.match(pattern, filePathRelativeToHudsonRoot)){ + for (String pattern : includesPatterns) { + if (matcher.match(pattern, pathRelativeToRoot)) { return true; } } @@ -38,9 +48,11 @@ public List getIncludes(){ public String[] matchingFilesFrom(File rootDirectory) { 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); scanner.scan(); return scanner.getIncludedFiles(); } + } 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 index c672c152..b88a7003 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java @@ -1,7 +1,6 @@ package hudson.plugins.scm_sync_configuration.repository; import hudson.plugins.test.utils.scms.ScmUnderTestGit; -import org.junit.Ignore; public class HudsonExtensionsGitTest extends HudsonExtensionsTest { 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 index bb4ebde4..ac03d6ac 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -2,9 +2,9 @@ import hudson.XmlFile; import hudson.model.Item; +import hudson.model.AbstractItem; import hudson.model.Job; import hudson.model.Saveable; -import hudson.model.TopLevelItem; import hudson.plugins.scm_sync_configuration.SCMManipulator; import hudson.plugins.scm_sync_configuration.ScmSyncConfigurationPlugin; import hudson.plugins.scm_sync_configuration.extensions.ScmSyncConfigurationItemListener; @@ -15,6 +15,7 @@ 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; @@ -26,6 +27,7 @@ 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.spy; @@ -61,7 +63,7 @@ public void shouldJobRenameBeCorrectlyImpactedOnSCM() throws Throwable { sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); // Renaming fakeJob to newFakeJob - Item mockedItem = Mockito.mock(TopLevelItem.class); + Item mockedItem = Mockito.mock(Job.class); File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/newFakeJob/" ); when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); when(mockedItem.getName()).thenReturn("newFakeJob"); @@ -91,7 +93,7 @@ public void shouldJobAddBeCorrectlyImpactedOnSCM() throws Throwable { FileUtils.copyFile(new ClassPathResource("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobAddBeCorrectlyImpactedOnSCM/jobs/newFakeJob/config.xml").getFile(), configFile); // Creating fake new job - Item mockedItem = Mockito.mock(TopLevelItem.class); + Item mockedItem = Mockito.mock(Job.class); when(mockedItem.getRootDir()).thenReturn(jobDirectory); sscItemListener.onCreated(mockedItem); @@ -117,7 +119,7 @@ public void shouldJobModificationBeCorrectlyImpactedOnSCM() throws Throwable { File configFile = new File(jobDirectory.getAbsolutePath() + File.separator + "config.xml"); // Creating fake new job - Item mockedItem = Mockito.mock(TopLevelItem.class); + Item mockedItem = Mockito.mock(Job.class); when(mockedItem.getRootDir()).thenReturn(jobDirectory); sscItemListener.onCreated(mockedItem); @@ -206,7 +208,7 @@ public void shouldJobDeleteBeCorrectlyImpactedOnSCM() throws Throwable { sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); // Deleting fakeJob - Item mockedItem = Mockito.mock(TopLevelItem.class); + Item mockedItem = Mockito.mock(Job.class); File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/" ); when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); @@ -229,7 +231,7 @@ public void shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM() throws Throwabl sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); // Deleting fakeJob - Item mockedItem = Mockito.mock(TopLevelItem.class); + Item mockedItem = Mockito.mock(Job.class); File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/" ); when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); @@ -368,7 +370,9 @@ public void shouldFileWhichHaveToBeInSCM() throws Throwable { 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(TopLevelItem.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"); } @@ -391,4 +395,24 @@ protected String getHudsonRootBaseTemplate(){ 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/util/ScmSyncConfigurationBaseTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java index 7811f5d1..0d76e752 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java @@ -16,6 +16,7 @@ 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; @@ -51,6 +52,9 @@ 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; @@ -110,6 +114,8 @@ public void setup() throws Throwable { 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(); 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 From dec742843b95e8423ffc7f7182c43574483fac6f Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 27 Jun 2015 16:20:06 +0200 Subject: [PATCH 05/22] JENKINS-18401: Don't fail on files outside $JENKINS_HOME This plugin can only put things under $JENKINS_HOME into SCM. If workspaces are located elsewhere, it failed with an exception. Newly, we just ignore such files. In all likelihood, users won't want to sync workspace files anyway. The same goes for the builds directory. People who really might want to put stuff from there into SCM can try symlinking to their desired workspace/build roots from $JENKINS_HOME. Restoring from SCM may, however, then produce unexpected results. Should fix JENKINS-18401 and related issues such as JENKINS-13593 and JENKINS-19984. Includes two tests for buildPathRelativeToHudsonRoot(), and use org.junit.Assert.assertNotNull etc.instead of the hamcrest matchers. --- .../JenkinsFilesHelper.java | 2 +- ...JobOrFolderConfigurationEntityMatcher.java | 3 +- .../model/PatternsEntityMatcher.java | 24 +++++----- .../basic/ScmSyncConfigurationBasicTest.java | 45 ++++++++++++++----- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java index a12de734..f4ebce0e 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java @@ -9,7 +9,7 @@ public class JenkinsFilesHelper { public static String buildPathRelativeToHudsonRoot(File file){ File hudsonRoot = Hudson.getInstance().getRootDir(); if(!file.getAbsolutePath().startsWith(hudsonRoot.getAbsolutePath())){ - throw new IllegalArgumentException("Err ! File ["+file.getAbsolutePath()+"] seems not to reside in ["+hudsonRoot.getAbsolutePath()+"] !"); + return null; } String truncatedPath = file.getAbsolutePath().substring(hudsonRoot.getAbsolutePath().length()+1); // "+1" because we don't need ending file separator return truncatedPath.replaceAll("\\\\", "/"); 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 index 89eeab4a..561108c3 100644 --- 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 @@ -39,7 +39,8 @@ public boolean matches(Saveable saveable, File file) { // Deleted. file = ((AbstractItem) saveable).getConfigFile().getFile(); } - return CONFIGS_TO_MATCH.matcher(JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file)).matches(); + String jenkinsRelativePath = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file); + return jenkinsRelativePath != null && CONFIGS_TO_MATCH.matcher(jenkinsRelativePath).matches(); } return false; } 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 index 457575a6..babc3660 100644 --- 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 @@ -27,17 +27,19 @@ public boolean matches(Saveable saveable, File file) { return false; } String pathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file); - // 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(); - for (String pattern : includesPatterns) { - if (matcher.match(pattern, pathRelativeToRoot)) { - return true; - } + 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(); + for (String pattern : includesPatterns) { + if (matcher.match(pattern, pathRelativeToRoot)) { + return true; + } + } } return false; } 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 index 0e15fc1e..97682689 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java @@ -1,16 +1,19 @@ package hudson.plugins.scm_sync_configuration.basic; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertThat; +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 { @@ -22,16 +25,38 @@ public ScmSyncConfigurationBasicTest() { @Test public void shouldRetrieveMockedHudsonInstanceCorrectly() throws Throwable { Hudson hudsonInstance = Hudson.getInstance(); - assertThat(hudsonInstance, is(notNullValue())); - assertThat(hudsonInstance.toString().split("@")[0], is(not(equalTo("hudson.model.Hudson")))); + assertNotNull("Jenkins instance must not be null", hudsonInstance); + assertFalse("Expected a mocked Jenkins instance", "hudson.model.Hudson".equals(hudsonInstance.toString().split("@")[0])); } @Test public void shouldVerifyIfHudsonRootDirectoryExists() throws Throwable { - Hudson hudsonInstance = Hudson.getInstance(); File hudsonRootDir = hudsonInstance.getRootDir(); - assertThat(hudsonRootDir, is(not(equalTo(null)))); - assertThat(hudsonRootDir.exists(), is(true)); + assertNotNull("Jenkins instance must not be null", hudsonRootDir); + assertTrue("$JENKINS_HOME must be an existing directory", hudsonRootDir.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"); } } From 283a3d5fbb6b4d42b2d65f29f03efb63f808939d Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 29 Jun 2015 23:26:55 +0200 Subject: [PATCH 06/22] Cleanup: avoid warnings about missing serialization ID. Use Collections.emptyList() of ImmutableList.of(). --- .../impl/BasicPluginsConfigScmSyncStrategy.java | 9 ++------- .../impl/JenkinsConfigScmSyncStrategy.java | 13 +++++++------ .../impl/ManualIncludesScmSyncStrategy.java | 8 ++------ .../strategies/impl/UserConfigScmSyncStrategy.java | 12 ++++++------ 4 files changed, 17 insertions(+), 25 deletions(-) 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 index d0f73375..a755f4e1 100644 --- 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 @@ -10,15 +10,10 @@ import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; import hudson.plugins.scm_sync_configuration.strategies.model.PatternsEntityMatcher; -import java.util.ArrayList; -import java.util.List; +import java.util.Collections; public class BasicPluginsConfigScmSyncStrategy extends AbstractScmSyncStrategy { - private static final List PAGE_MATCHERS = new ArrayList(){ { - // No page matchers for this strategy ... for the moment - } }; - private static final String[] PATTERNS = new String[]{ "hudson*.xml", "scm-sync-configuration.xml" @@ -27,7 +22,7 @@ public class BasicPluginsConfigScmSyncStrategy extends AbstractScmSyncStrategy { private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new PatternsEntityMatcher(PATTERNS); public BasicPluginsConfigScmSyncStrategy(){ - super(CONFIG_ENTITY_MATCHER, PAGE_MATCHERS); + super(CONFIG_ENTITY_MATCHER, Collections.emptyList()); } public CommitMessageFactory getCommitMessageFactory(){ 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 index acbb659b..19a96584 100644 --- 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 @@ -10,18 +10,19 @@ import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; import hudson.plugins.scm_sync_configuration.strategies.model.PatternsEntityMatcher; -import java.util.ArrayList; import java.util.List; +import com.google.common.collect.ImmutableList; + public class JenkinsConfigScmSyncStrategy extends AbstractScmSyncStrategy { - private static final List PAGE_MATCHERS = new ArrayList(){ { + private static final List PAGE_MATCHERS = ImmutableList.of( // Global configuration page - add(new PageMatcher("^configure$", "form[name='config']")); + new PageMatcher("^configure$", "form[name='config']"), // View configuration pages - add(new PageMatcher("^(.+/)?view/[^/]+/configure$", "form[name='viewConfig']")); - add(new PageMatcher("^newView$", "form[name='createView'],form[name='createItem']")); - } }; + 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" 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 index bdd42734..c2f40d7d 100644 --- 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 @@ -6,17 +6,13 @@ import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; import hudson.plugins.scm_sync_configuration.strategies.model.PatternsEntityMatcher; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class ManualIncludesScmSyncStrategy extends AbstractScmSyncStrategy { - private static final List PAGE_MATCHERS = new ArrayList(){ { - // No page matcher for this particular implementation - } }; - public ManualIncludesScmSyncStrategy(){ - super(null, PAGE_MATCHERS); + super(null, Collections.emptyList()); } @Override 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 index 586deee9..0289eb83 100644 --- 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 @@ -2,7 +2,6 @@ import hudson.XmlFile; import hudson.model.Item; -import hudson.model.Job; import hudson.model.Saveable; import hudson.model.User; import hudson.plugins.scm_sync_configuration.model.MessageWeight; @@ -12,16 +11,17 @@ import hudson.plugins.scm_sync_configuration.strategies.model.ConfigurationEntityMatcher; import hudson.plugins.scm_sync_configuration.strategies.model.PageMatcher; -import java.util.ArrayList; 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 = new ArrayList(){ { - add(new PageMatcher("^securityRealm/addUser$", "#main-panel form")); - add(new PageMatcher("^securityRealm/user/[^/]+/configure$", "form[name='config']")); - } }; + 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" From 8660de3bfb6cf31098d0dc2255a5f2214847071b Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 29 Jun 2015 23:28:40 +0200 Subject: [PATCH 07/22] JENKINS-22540: include built-in jenkins-plugins. With the new Jenkins baseline, we not only have hudson.*.xml but also jenkins.*.xml. --- .../strategies/impl/BasicPluginsConfigScmSyncStrategy.java | 1 + 1 file changed, 1 insertion(+) 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 index a755f4e1..83b49e62 100644 --- 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 @@ -16,6 +16,7 @@ public class BasicPluginsConfigScmSyncStrategy extends AbstractScmSyncStrategy { private static final String[] PATTERNS = new String[]{ "hudson*.xml", + "jenkins*.xml", "scm-sync-configuration.xml" }; From 25db7ec5e7b9bba1f09b3aca15b943c63a5c13e6 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 29 Jun 2015 23:51:35 +0200 Subject: [PATCH 08/22] Fix dynamic load problem: avoid need to restart Jenkins. When the plugin is dynamically loaded into a running Jenkins, it would not properly initialize and thsu refuse to do anything until Jenkins was restarted. Let's initialize early, but don't do so in tests where we do manually call init() at appropriate times. --- .../ScmSyncConfigurationPlugin.java | 11 +++++++++++ .../util/ScmSyncConfigurationBaseTest.java | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index 95b9ed11..4189b268 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -22,6 +22,7 @@ import hudson.plugins.scm_sync_configuration.xstream.migration.ScmSyncConfigurationPOJO; import hudson.util.PluginServletFilter; import net.sf.json.JSONObject; + import org.acegisecurity.AccessDeniedException; import org.apache.maven.scm.ScmException; import org.kohsuke.stapler.QueryParameter; @@ -30,6 +31,7 @@ import javax.annotation.Nullable; import javax.servlet.ServletException; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -136,6 +138,7 @@ public void start() throws Exception { // 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){ @@ -147,6 +150,14 @@ public void loadData(ScmSyncConfigurationPOJO pojo){ this.manualSynchronizationIncludes = pojo.getManualSynchronizationIncludes(); } + 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()); 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 index 0d76e752..2077d8cf 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java @@ -73,7 +73,12 @@ 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); + 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); From 28d4aee27703e1b02ad2989f6d5288fe47644ea6 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 29 Jun 2015 14:01:39 +0200 Subject: [PATCH 09/22] JENKINS-15218, JENKINS 16378, JENKINS-24881: job renaming 1. JENKINS-15218: Maven SCM 1.9.1 _does_ use `git rm -r` to remove a directory. (At least on a machine with git 1.8.3) So I was not able to reproduce that particular problem. 2. JENKINS-16378: The plugin mistakenly tried to copy the full job directory after a job rename. That's Github issue #8[1]. This may fail catastrophically if the build directory is in the job's directory and contains broken symlinks, for instance for new jobs or a job that never failed (lastFailed will point to a non-existing subdirectory named "-1") Even when it doesn't fail, it may end up putting way too many things into SCM, especially if the workspaces should also be located in the job directories. 3. JENKINS-24881 is a duplicate of JENKINS-16378. 4. Jenkins now does have a more general "move", the plugin has to account for that and use onLocationChanged instead of onRenamed. (Also in tests!) [1] https://github.com/jenkinsci/scm-sync-configuration-plugin/issues/8 Changes: * Refactored rename handling to submit only the stuff that is selected by some strategy. * ConfigurationEntityMatcher: removed unused method getIncludes(), added a new method to match deleted paths. Added a FileSelector to matchingFilesFrom() to be able to restrict the traversal to specific subdirectories under $JENKINS_HOME. * ChangeSet: removed registerRenamedPath (handled by transaction now). * Transaction: change rename handling to include only matched stuff under the new directory * AbstractScmSyncStrategy: createInitializationSynchronizedFileset() changed to account for FileSelector * ScmSyncConfigurationPlugin: fix the need to restart Jenkins to get going, and provide new operations to collect files. Tests: * The tests were broken. Moved two tests that cannot possibly succeed on git but per chance can succeed on svn to the svn-specific tests. These tests "succeeded" in the test runs so far only because they never did anything--they faked a job as Item.class, which never triggered anything, neither before nor after commit 141cc2b5. These tests modify the repository from outside the plugin-managed SCM workspace. In git, this will always require a pull. In svn, these two test happen to work because the outside modification is on different paths. It should fail if there was a path conflict, so these two tests are dubious anyway. * Also removed a spurious mocking of the available strategies that was unused. --- .../ScmSyncConfigurationBusiness.java | 2 +- .../ScmSyncConfigurationPlugin.java | 38 +++++++ .../ScmSyncConfigurationFilter.java | 4 +- .../ScmSyncConfigurationItemListener.java | 90 +++++++++------ .../model/ChangeSet.java | 7 -- .../strategies/AbstractScmSyncStrategy.java | 51 +++++++-- .../strategies/ScmSyncStrategy.java | 25 ++++- .../BasicPluginsConfigScmSyncStrategy.java | 4 + .../impl/JenkinsConfigScmSyncStrategy.java | 5 + .../impl/JobConfigScmSyncStrategy.java | 9 +- .../impl/ManualIncludesScmSyncStrategy.java | 6 + .../impl/UserConfigScmSyncStrategy.java | 10 +- ...lassAndFileConfigurationEntityMatcher.java | 4 +- .../model/ConfigurationEntityMatcher.java | 5 +- ...JobOrFolderConfigurationEntityMatcher.java | 106 ++++++++++-------- .../model/PatternsEntityMatcher.java | 25 ++++- .../transactions/ScmTransaction.java | 18 ++- .../HudsonExtensionsSubversionTest.java | 84 ++++++++++++++ .../repository/HudsonExtensionsTest.java | 89 +-------------- 19 files changed, 387 insertions(+), 195 deletions(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index 1a0200b6..ceb1cad0 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -282,7 +282,7 @@ public void synchronizeAllConfigs(ScmSyncStrategy[] availableStrategies){ List filesToSync = new ArrayList(); // Building synced files from strategies for(ScmSyncStrategy strategy : availableStrategies){ - filesToSync.addAll(strategy.createInitializationSynchronizedFileset()); + filesToSync.addAll(strategy.collect()); } ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index 4189b268..ef7655fe 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -1,7 +1,11 @@ package hudson.plugins.scm_sync_configuration; +import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + import hudson.Plugin; import hudson.model.Descriptor; import hudson.model.Descriptor.FormException; @@ -25,6 +29,7 @@ import org.acegisecurity.AccessDeniedException; import org.apache.maven.scm.ScmException; +import org.apache.tools.ant.types.selectors.FileSelector; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -40,6 +45,8 @@ import java.util.concurrent.Future; import java.util.logging.Logger; +import jenkins.model.Jenkins; + public class ScmSyncConfigurationPlugin extends Plugin{ public static final transient ScmSyncStrategy[] AVAILABLE_STRATEGIES = new ScmSyncStrategy[]{ @@ -231,6 +238,20 @@ public void configure(StaplerRequest req, JSONObject formData) this.save(); } + public Iterable collectAllFilesForScm() { + return Iterables.concat(Iterables.transform(Lists.newArrayList(AVAILABLE_STRATEGIES), new Function>() { + 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>() { + public Iterable apply(ScmSyncStrategy strategy) { + return strategy.collect(fromSubDirectory); + }})); + } + public void doReloadAllFilesFromScm(StaplerRequest req, StaplerResponse res) throws ServletException, IOException { try { filesModifiedByLastReload = business.reloadAllFilesFromScm(); @@ -295,6 +316,23 @@ public ScmSyncStrategy getStrategyForSaveable(Saveable s, File f){ 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); } 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 index 484e6b5e..4051e186 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java @@ -39,8 +39,8 @@ public void doFilter(final ServletRequest request, final ServletResponse respons 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() { - public Object call() throws Exception { + ScmSyncConfigurationDataProvider.provideRequestDuring((HttpServletRequest)request, new Callable() { + public Void call() throws Exception { try { // Handling "normally" http request chain.doFilter(request, response); 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 index 6d86292e..642e8dd0 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java @@ -2,14 +2,19 @@ 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 { @@ -20,7 +25,7 @@ public void 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){ + if (plugin != null) { plugin.init(); } } @@ -31,45 +36,62 @@ public void onDeleted(Item item) { ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); if(plugin != null){ - ScmSyncStrategy strategy = plugin.getStrategyForSaveable(item, null); - - if(strategy != null){ + String path = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(item.getRootDir()); + ScmSyncStrategy strategy = plugin.getStrategyForDeletedSaveable(item, path, true); + if (strategy != null) { WeightedMessage message = strategy.getCommitMessageFactory().getMessageWhenItemDeleted(item); - plugin.getTransaction().defineCommitMessage(message); - String path = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(item.getRootDir()); - plugin.getTransaction().registerPathForDeletion(path); + ScmTransaction transaction = plugin.getTransaction(); + transaction.defineCommitMessage(message); + transaction.registerPathForDeletion(path); } } } @Override - public void onCreated(Item item) { - super.onCreated(item); - } - - @Override - public void onCopied(Item src, Item item) { - super.onCopied(src, item); - } - - @Override - public void onRenamed(Item item, String oldName, String newName) { - super.onRenamed(item, oldName, newName); + public void onLocationChanged(Item item, String oldFullName, String newFullName) { + super.onLocationChanged(item, oldFullName, newFullName); ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); - if(plugin != null){ - ScmSyncStrategy strategy = plugin.getStrategyForSaveable(item, null); - - if(strategy != null){ - File parentDir = item.getRootDir().getParentFile(); - File oldDir = new File( parentDir.getAbsolutePath()+File.separator+oldName ); - File newDir = new File( parentDir.getAbsolutePath()+File.separator+newName ); - - String oldPath = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(oldDir); - String newPath = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(newDir); - WeightedMessage message = strategy.getCommitMessageFactory().getMessageWhenItemRenamed(item, oldPath, newPath); - plugin.getTransaction().defineCommitMessage(message); - plugin.getTransaction().registerRenamedPath(oldPath, newPath); - } - } + 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/model/ChangeSet.java b/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java index 9b39f18c..2da24c08 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java @@ -8,14 +8,12 @@ import java.io.File; import java.io.IOException; import java.util.*; -import java.util.logging.Logger; /** * @author fcamblor * POJO representing a Changeset built during a scm transaction */ public class ChangeSet { - private static final Logger LOGGER = Logger.getLogger(ChangeSet.class.getName()); // Changeset commit message WeightedMessage message = null; @@ -57,11 +55,6 @@ public void registerPath(String path) { } } - public void registerRenamedPath(String oldPath, String newPath) { - registerPathForDeletion(oldPath); - registerPath(newPath); - } - 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... 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 index c218c015..70085b3f 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java @@ -2,26 +2,33 @@ import com.google.common.base.Function; import com.google.common.collect.Collections2; + import hudson.XmlFile; -import hudson.model.Hudson; 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() { public File apply(@Nullable String path) { - return new File(Hudson.getInstance().getRootDir()+File.separator+path); + return new File(Jenkins.getInstance().getRootDir(), path); } }; @@ -39,6 +46,7 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { private ConfigurationEntityMatcher configEntityMatcher; private List pageMatchers; + private CommitMessageFactory commitMessageFactory; protected AbstractScmSyncStrategy(ConfigurationEntityMatcher _configEntityMatcher, List _pageMatchers){ this.configEntityMatcher = _configEntityMatcher; @@ -54,7 +62,7 @@ public boolean isSaveableApplicable(Saveable saveable, File file) { } public PageMatcher getPageMatcherMatching(String url){ - String rootUrl = Hudson.getInstance().getRootUrlFromRequest(); + String rootUrl = Jenkins.getInstance().getRootUrlFromRequest(); String cleanedUrl = null; if(url.startsWith(rootUrl)){ cleanedUrl = url.substring(rootUrl.length()); @@ -69,12 +77,36 @@ public PageMatcher getPageMatcherMatching(String url){ return null; } - public List createInitializationSynchronizedFileset() { - File hudsonRoot = Hudson.getInstance().getRootDir(); - String[] matchingFilePaths = createConfigEntityMatcher().matchingFilesFrom(hudsonRoot); - return new ArrayList(Collections2.transform(Arrays.asList(matchingFilePaths), PATH_TO_FILE_IN_HUDSON)); + public List collect() { + return collect(null); } + 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() { + 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)); + } + public boolean isCurrentUrlApplicable(String url) { return getPageMatcherMatching(url)!=null; } @@ -84,6 +116,9 @@ public List getSyncIncludes(){ } public CommitMessageFactory getCommitMessageFactory(){ - return new DefaultCommitMessageFactory(); + 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 index 87e62623..66dd3a27 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java @@ -25,6 +25,16 @@ public static interface CommitMessageFactory { */ 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 @@ -33,10 +43,21 @@ public static interface CommitMessageFactory { boolean isCurrentUrlApplicable(String url); /** - * @return a Fileset of file to synchronize when initializing scm repository + * Collects all files, from Jenkins' root directory, that match this strategy. + * + * @return the list of files matched. */ - List createInitializationSynchronizedFileset(); + 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 */ 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 index 83b49e62..96bd3323 100644 --- 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 @@ -41,4 +41,8 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { } }; } + + 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 index 19a96584..a72193e5 100644 --- 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 @@ -50,4 +50,9 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { } }; } + + 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 index 4760319b..85c6dac9 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -13,8 +14,10 @@ public class JobConfigScmSyncStrategy extends AbstractScmSyncStrategy { + private static final ConfigurationEntityMatcher CONFIG_MATCHER = new JobOrFolderConfigurationEntityMatcher(); + public JobConfigScmSyncStrategy(){ - super(new JobOrFolderConfigurationEntityMatcher(), Collections.singletonList(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']"))); + super(CONFIG_MATCHER, Collections.singletonList(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']"))); } public CommitMessageFactory getCommitMessageFactory(){ @@ -39,4 +42,8 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { } }; } + + 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 index c2f40d7d..ec1d7ed3 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -24,4 +25,9 @@ protected ConfigurationEntityMatcher createConfigEntityMatcher(){ } return new PatternsEntityMatcher(includes); } + + 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 index 0289eb83..b7932fad 100644 --- 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 @@ -22,14 +22,16 @@ public class UserConfigScmSyncStrategy extends AbstractScmSyncStrategy { 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_MANAGER = new ClassAndFileConfigurationEntityMatcher(User.class, PATTERNS); + + private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new ClassAndFileConfigurationEntityMatcher(User.class, PATTERNS); public UserConfigScmSyncStrategy(){ - super(CONFIG_ENTITY_MANAGER, PAGE_MATCHERS); + super(CONFIG_ENTITY_MATCHER, PAGE_MATCHERS); } public CommitMessageFactory getCommitMessageFactory(){ @@ -54,4 +56,8 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { } }; } + + 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 index c1e1861b..7f7c8dbf 100644 --- 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 @@ -15,8 +15,8 @@ public ClassAndFileConfigurationEntityMatcher(Class clazz, S } public boolean matches(Saveable saveable, File file) { - if(saveableClazz.isAssignableFrom(saveable.getClass())){ - if(file == null){ + if (saveableClazz.isAssignableFrom(saveable.getClass())){ + if (file == null) { return true; } else { return super.matches(saveable, file); 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 index aa45f19b..ce989ea5 100644 --- 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 @@ -5,8 +5,11 @@ import java.io.File; import java.util.List; +import org.apache.tools.ant.types.selectors.FileSelector; + public interface ConfigurationEntityMatcher { public boolean matches(Saveable saveable, File file); - public String[] matchingFilesFrom(File rootDirectory); + public boolean matches(Saveable saveable, String pathRelativeToRoot, boolean isDirectory); + public String[] matchingFilesFrom(File rootDirectory, FileSelector selector); 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 index 561108c3..d7610d93 100644 --- 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 @@ -5,32 +5,35 @@ import hudson.plugins.scm_sync_configuration.JenkinsFilesHelper; import java.io.File; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; 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 implements ConfigurationEntityMatcher { +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("(?:" + JOBS_DIR_NAME + "/[^/]+/)+" + CONFIG_FILE_NAME); - - private static final FileFilter DIRECTORIES = new FileFilter() { - public boolean accept(File file) { - return file.isDirectory(); - } - }; + 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 + } + public boolean matches(Saveable saveable, File file) { // The file may be null, indicating a deletion! if (saveable instanceof AbstractItem) { @@ -38,50 +41,61 @@ public boolean matches(Saveable saveable, File file) { if (file == null) { // Deleted. file = ((AbstractItem) saveable).getConfigFile().getFile(); + } else if (file.isDirectory()) { + file = new File(file, CONFIG_FILE_NAME); } - String jenkinsRelativePath = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file); - return jenkinsRelativePath != null && CONFIGS_TO_MATCH.matcher(jenkinsRelativePath).matches(); + return matches(saveable, JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file), false); } return false; } - public String[] matchingFilesFrom(File rootDirectory) { - // PatternsEntityMatcher uses a org.apache.tools.ant.DirectoryScanner, which returns a (sorted) list of matching file paths - // relative to the given root directory. Let's adhere to that specification, but only for our very special job config pattern. - if (rootDirectory == null || !rootDirectory.isDirectory()) { - return new String[0]; - } - List collected = new ArrayList(); - // Compare https://github.com/jenkinsci/cloudbees-folder-plugin/blob/70a4d47314a36b54d522cae0a78b3c76d153e627/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java#L200 - scanForJobConfigs (new File (rootDirectory, JOBS_DIR_NAME), JOBS_DIR_NAME, collected); - String[] result = collected.toArray(new String[collected.size()]); - Arrays.sort(result); - return result; - } - - private static void scanForJobConfigs(File jobsDir, String prefix, Collection result) { - if (!jobsDir.isDirectory()) { - return; - } - File[] projects = jobsDir.listFiles(DIRECTORIES); - if (projects == null) { - return; - } - for (File project : projects) { - if (new File(project, CONFIG_FILE_NAME).isFile()) { - result.add(prefix + '/' + project.getName() + '/' + CONFIG_FILE_NAME); - scanForJobConfigs(new File(project, JOBS_DIR_NAME), prefix + '/' + project.getName() + '/' + JOBS_DIR_NAME, result); + 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; } - // Again, 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 CONFIGS_TO_MATCH.matcher(pathRelativeToRoot).matches(); } + return false; } public List getIncludes() { - // XXX The call hierarchy indicates that although this is called, ScmSyncConfigurationPlugin.getDefaultIncludes() isn't, - // and thus this whole method and assembling strings is futile. Let's just return an empty list as other matchers - // return ant patters, but there is no ant pattern that corresponds to our regular expression. - return Collections.emptyList(); + // This is used only for display in the UI. + return Collections.singletonList(ANT_PATTERN[0] + " (** restricted to real project and folder directories)"); + } + + public String[] matchingFilesFrom(File rootDirectory, FileSelector selector) { + // Create a selector that enforces the (jobs/someName)+ pattern + final FileSelector originalSelector = selector; + FileSelector combinedSelector = new FileSelector() { + 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/PatternsEntityMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PatternsEntityMatcher.java index babc3660..3cf42e51 100644 --- 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 @@ -5,6 +5,7 @@ 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; @@ -26,7 +27,10 @@ public boolean matches(Saveable saveable, File file) { if (file == null) { return false; } - String pathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file); + return matches(saveable, JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file), file.isDirectory()); + } + + 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 + '/')) { @@ -35,9 +39,19 @@ public boolean matches(Saveable saveable, File file) { 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; + } } } } @@ -47,14 +61,17 @@ public boolean matches(Saveable saveable, File file) { public List getIncludes(){ return Arrays.asList(includesPatterns); } - - public String[] matchingFilesFrom(File rootDirectory) { + + 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/ScmTransaction.java b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java index 5faa3832..cc76bbf6 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java @@ -1,5 +1,8 @@ package hudson.plugins.scm_sync_configuration.transactions; +import java.io.File; + +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; @@ -48,6 +51,19 @@ public void registerPathForDeletion(String path) { } public void registerRenamedPath(String oldPath, String newPath){ - this.changeset.registerRenamedPath(oldPath, 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/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java index bf081928..6110faa6 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java @@ -1,5 +1,20 @@ 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; @@ -9,4 +24,73 @@ 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 index ac03d6ac..f8d39eea 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -20,8 +20,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; import org.springframework.core.io.ClassPathResource; import java.io.File; @@ -30,14 +28,12 @@ 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; -@PrepareForTest(ScmSyncConfigurationPlugin.class) public abstract class HudsonExtensionsTest extends ScmSyncConfigurationPluginBaseTest { - private ScmSyncConfigurationItemListener sscItemListener; - private ScmSyncConfigurationSaveableListener sscConfigurationSaveableListener; + protected ScmSyncConfigurationItemListener sscItemListener; + protected ScmSyncConfigurationSaveableListener sscConfigurationSaveableListener; protected HudsonExtensionsTest(ScmUnderTest scmUnderTest) { super(scmUnderTest); @@ -47,11 +43,6 @@ protected HudsonExtensionsTest(ScmUnderTest scmUnderTest) { public void initObjectsUnderTests() throws Throwable{ this.sscItemListener = new ScmSyncConfigurationItemListener(); this.sscConfigurationSaveableListener = new ScmSyncConfigurationSaveableListener(); - - // Mocking ScmSyncConfigurationPlugin.getStrategyForSaveable() - ScmSyncConfigurationPlugin sscPlugin = spy(ScmSyncConfigurationPlugin.getInstance()); - sscPlugin.setBusiness(this.sscBusiness); - PowerMockito.doReturn(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES[0]).when(sscPlugin).getStrategyForSaveable(Mockito.any(Saveable.class), Mockito.any(File.class)); } @Test @@ -67,12 +58,12 @@ public void shouldJobRenameBeCorrectlyImpactedOnSCM() throws Throwable { 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.onRenamed(mockedItem, "fakeJob", "newFakeJob"); + sscItemListener.onLocationChanged(mockedItem, "fakeJob", "newFakeJob"); verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/HudsonExtensionsTest.shouldJobRenameBeCorrectlyImpactedOnSCM/"); @@ -147,11 +138,9 @@ public void shouldConfigModificationBeCorrectlyImpactedOnSCM() throws Throwable File configFile = new File(getCurrentHudsonRootDirectory() + "/hudson.tasks.Shell.xml" ); - // Creating fake new job + // Creating fake new plugin config Item mockedItem = Mockito.mock(Item.class); - when(mockedItem.getRootDir()).thenReturn(configFile); - sscItemListener.onCreated(mockedItem); sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(configFile)); verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/InitRepositoryTest.shouldSynchronizeHudsonFiles/"); @@ -165,40 +154,6 @@ public void shouldConfigModificationBeCorrectlyImpactedOnSCM() throws Throwable 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(Item.class); - File mockedItemRootDir = new File(getCurrentHudsonRootDirectory() + "/jobs/newFakeJob/" ); - when(mockedItem.getRootDir()).thenReturn(mockedItemRootDir); - - sscItemListener.onRenamed(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(); - } - @Test public void shouldJobDeleteBeCorrectlyImpactedOnSCM() throws Throwable { // Initializing the repository... @@ -242,40 +197,6 @@ public void shouldJobDeleteWithTwoJobsBeCorrectlyImpactedOnSCM() throws Throwabl assertStatusManagerIsOk(); } - @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"); - scmManipulator.checkinFiles(checkoutDirectoryForVerifications, "external commit"); - - // Deleting fakeJob - Item mockedItem = Mockito.mock(Item.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 shouldReloadAllFilesUpdateScmAndReloadAllFiles() throws Throwable { // Initializing the repository... From 36756ca6464dc4ba8a537c54c6c9e7646bb6abb9 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 30 Jun 2015 00:40:14 +0200 Subject: [PATCH 10/22] Cleanup: grammar, and make commit messages wrap at 72 characters. --- .../SCMManipulator.java | 4 +- .../ScmSyncConfigurationBusiness.java | 6 +- .../ScmSyncConfigurationPlugin.java | 1 - .../model/ChangeSet.java | 2 +- .../scm_sync_configuration/model/Commit.java | 57 ++++++++++++++----- .../model/MessageWeight.java | 13 ++--- .../model/WeightedMessage.java | 4 +- .../impl/JenkinsConfigScmSyncStrategy.java | 5 +- .../impl/JobConfigScmSyncStrategy.java | 6 -- .../impl/UserConfigScmSyncStrategy.java | 6 -- .../repository/InitRepositoryTest.java | 2 +- 11 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java index 279cbfcb..00b8206d 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java @@ -86,7 +86,7 @@ public boolean checkout(File checkoutDirectory){ } // Checkouting sources - LOGGER.fine("Checkouting SCM files into ["+checkoutDirectory.getAbsolutePath()+"] ..."); + LOGGER.fine("Checking out SCM files into ["+checkoutDirectory.getAbsolutePath()+"] ..."); try { CheckOutScmResult result = scmManager.checkOut(this.scmRepository, new ScmFileSet(checkoutDirectory)); if(!result.isSuccess()){ @@ -101,7 +101,7 @@ public boolean checkout(File checkoutDirectory){ } if(checkoutOk){ - LOGGER.fine("Checkouted SCM files into ["+checkoutDirectory.getAbsolutePath()+"] !"); + LOGGER.fine("Checked out SCM files into ["+checkoutDirectory.getAbsolutePath()+"] !"); } return checkoutOk; diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index ceb1cad0..cb1095a5 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -175,7 +175,7 @@ private void processCommitsQueue() { FileUtils.copyDirectory(JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(pathRelativeToJenkinsRoot.getPath()), fileTranslatedInScm); } catch (IOException e) { - throw new LoggableException("Error while copying file hierarchy to SCM checkouted directory", FileUtils.class, "copyDirectory", e); + throw new LoggableException("Error while copying file hierarchy to SCM directory", FileUtils.class, "copyDirectory", e); } updatedFiles.addAll(scmManipulator.addFile(scmRoot, firstNonExistingParentScmPath)); } @@ -274,7 +274,7 @@ private void createScmContent(Path pathRelativeToJenkinsRoot, byte[] content, Fi Files.write(content, fileTranslatedInScm); } } catch (IOException e) { - throw new LoggableException("Error while creating file in checkouted directory", Files.class, "write", e); + throw new LoggableException("Error while creating file in SCM directory", Files.class, "write", e); } } @@ -291,7 +291,7 @@ public void synchronizeAllConfigs(ScmSyncStrategy[] availableStrategies){ for(File fileToSync : filesToSync){ String hudsonConfigPathRelativeToHudsonRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(fileToSync); - plugin.getTransaction().defineCommitMessage(new WeightedMessage("Repository initialization", MessageWeight.IMPORTANT)); + plugin.getTransaction().defineCommitMessage(new WeightedMessage("New included files", MessageWeight.IMPORTANT)); plugin.getTransaction().registerPath(hudsonConfigPathRelativeToHudsonRoot); } } finally { diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index ef7655fe..705add04 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -228,7 +228,6 @@ public void configure(StaplerRequest req, JSONObject formData) this.business.synchronizeAllConfigs(AVAILABLE_STRATEGIES); } if(repoCleaningRequired){ - // Cleaning checkouted repository this.business.cleanChekoutScmDirectory(); } 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 index 2da24c08..d2927b29 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/ChangeSet.java @@ -94,7 +94,7 @@ public List getPathsToDelete(){ public void defineMessage(WeightedMessage weightedMessage) { // Defining message only once ! - if(this.message == null || weightedMessage.getWeight().weighterThan(message.getWeight())){ + if(this.message == null || weightedMessage.getWeight().compareTo(message.getWeight()) > 0){ this.message = weightedMessage; } } 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 index 885e1cd9..b159a0b9 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java @@ -1,5 +1,10 @@ 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; /** @@ -34,23 +39,45 @@ public ScmContext getScmContext(){ } private static String createCommitMessage(ScmContext context, String messagePrefix, User user, String userComment){ - StringBuilder commitMessage = new StringBuilder(); - commitMessage.append(messagePrefix); - if(user != null){ - commitMessage.append(" by ").append(user.getId()); - } - if(userComment != null && !"".equals(userComment)){ - commitMessage.append(" with following comment : ").append(userComment); - } - String message = commitMessage.toString(); - - if(context.getCommitMessagePattern() == null || "".equals(context.getCommitMessagePattern())){ - return message; - } else { - return context.getCommitMessagePattern().replaceAll("\\[message\\]", message.replaceAll("\\$", "\\\\\\$")); - } + StringBuilder commitMessage = new StringBuilder(); + if (user != null) { + commitMessage.append(user.getId()).append(": "); + } + commitMessage.append(messagePrefix); + commitMessage.append("\n\n"); + commitMessage.append("Change performed by ").append(user.getDisplayName()); + commitMessage.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(); 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 index fec0e617..e512f4dd 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/MessageWeight.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/MessageWeight.java @@ -5,13 +5,8 @@ * Message weight should be used to prioritize messages into a Scm Transaction */ public enum MessageWeight { - MINIMAL(0),NORMAL(1),IMPORTANT(2),MORE_IMPORTANT(3); - - private int weight; - private MessageWeight(int _weight){ - this.weight = _weight; - } - public boolean weighterThan(MessageWeight ms){ - return this.weight > ms.weight; - } + MINIMAL, + NORMAL, + IMPORTANT, + MORE_IMPORTANT; } 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 index 52dcfb9d..2db57c6b 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java @@ -7,8 +7,8 @@ * have only the more important commit message kept during the transaction */ public class WeightedMessage { - String message; - MessageWeight weight; + private final String message; + private final MessageWeight weight; public WeightedMessage(String message, MessageWeight weight) { this.message = message; this.weight = weight; 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 index a72193e5..2aaf73fe 100644 --- 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 @@ -37,10 +37,7 @@ public JenkinsConfigScmSyncStrategy(){ public CommitMessageFactory getCommitMessageFactory(){ return new CommitMessageFactory(){ public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { - return new WeightedMessage("Jenkins configuration files updated", - // Jenkins config update message should be considered as "important", especially - // more important than the plugin descriptors Saveable updates - MessageWeight.NORMAL); + return new WeightedMessage("Jenkins configuration files updated", MessageWeight.NORMAL); } public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { throw new IllegalStateException("Jenkins configuration files should never be renamed !"); 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 index 85c6dac9..6792c0ac 100644 --- 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 @@ -24,20 +24,14 @@ public CommitMessageFactory getCommitMessageFactory(){ return new CommitMessageFactory(){ public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { return new WeightedMessage("Job ["+((Item)s).getName()+"] configuration updated", - // Job config update message should be considered as "important", especially - // more important than the plugin descriptors Saveable updates MessageWeight.IMPORTANT); } public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { return new WeightedMessage("Job ["+item.getName()+"] hierarchy renamed from ["+oldPath+"] to ["+newPath+"]", - // Job config rename message should be considered as "important", especially - // more important than the plugin descriptors Saveable renames MessageWeight.MORE_IMPORTANT); } public WeightedMessage getMessageWhenItemDeleted(Item item) { return new WeightedMessage("Job ["+item.getName()+"] hierarchy deleted", - // Job config deletion message should be considered as "important", especially - // more important than the plugin descriptors Saveable deletions MessageWeight.MORE_IMPORTANT); } }; 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 index b7932fad..da3bf1ac 100644 --- 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 @@ -38,20 +38,14 @@ public CommitMessageFactory getCommitMessageFactory(){ return new CommitMessageFactory(){ public WeightedMessage getMessageWhenSaveableUpdated(Saveable s, XmlFile file) { return new WeightedMessage("User ["+((User)s).getDisplayName()+"] configuration updated", - // Job config update message should be considered as "important", especially - // more important than the plugin descriptors Saveable updates MessageWeight.IMPORTANT); } public WeightedMessage getMessageWhenItemRenamed(Item item, String oldPath, String newPath) { return new WeightedMessage("User ["+item.getName()+"] configuration renamed from ["+oldPath+"] to ["+newPath+"]", - // Job config rename message should be considered as "important", especially - // more important than the plugin descriptors Saveable renames MessageWeight.MORE_IMPORTANT); } public WeightedMessage getMessageWhenItemDeleted(Item item) { return new WeightedMessage("User ["+item.getName()+"] hierarchy deleted", - // Job config deletion message should be considered as "important", especially - // more important than the plugin descriptors Saveable deletions MessageWeight.MORE_IMPORTANT); } }; 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 index 75107891..4de3fd9b 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java @@ -50,7 +50,7 @@ public void shouldResetCheckoutConfigurationDirectoryWhenAsked() throws Throwabl // Initializing the repository... createSCMMock(); - // After init, local checkouted repository should exists + // After init, local checked out repository should exist assertThat(getCurrentScmSyncConfigurationCheckoutDirectory().exists(), is(true)); // Populating checkoutConfiguration directory .. From d7fcc815cfc3460ff45a97d6c6ee6430e484499b Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 30 Jun 2015 15:26:22 +0200 Subject: [PATCH 11/22] Cleanup: get a warning-free build & workspace. With all these warning markers it's a bit hard to see what's going on. --- pom.xml | 48 +++++++++++++++++- .../JenkinsFilesHelper.java | 16 +++--- .../SCMManipulator.java | 7 +-- .../ScmSyncConfigurationBusiness.java | 23 +++++---- .../ScmSyncConfigurationDataProvider.java | 3 ++ .../ScmSyncConfigurationPlugin.java | 17 +++---- .../ScmSyncConfigurationStatusManager.java | 8 +-- .../exceptions/LoggableException.java | 11 ++-- .../ScmSyncConfigurationPageDecorator.java | 2 + .../scm_sync_configuration/scms/SCM.java | 50 ++++++++++--------- .../scms/ScmSyncSubversionSCM.java | 1 + ...lassAndFileConfigurationEntityMatcher.java | 1 - .../ScmSyncConfigurationXStreamConverter.java | 17 ++++++- .../basic/ScmSyncConfigurationBasicTest.java | 14 +++--- .../impl/JobConfigScmSyncStrategyTest.java | 8 +-- .../util/ScmSyncConfigurationBaseTest.java | 16 +++--- 16 files changed, 153 insertions(+), 89 deletions(-) diff --git a/pom.xml b/pom.xml index 9046252e..a598c626 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,21 @@ + + + com.google.code.maven-replacer-plugin + maven-replacer-plugin + [1.3.2,) + + replace + + + + + false + + + @@ -120,7 +135,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.1 1.6 1.6 @@ -145,6 +160,37 @@ + + + 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 + + + + diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java index f4ebce0e..b811f439 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java @@ -1,22 +1,22 @@ package hudson.plugins.scm_sync_configuration; -import hudson.model.Hudson; - import java.io.File; +import jenkins.model.Jenkins; + public class JenkinsFilesHelper { public static String buildPathRelativeToHudsonRoot(File file){ - File hudsonRoot = Hudson.getInstance().getRootDir(); - if(!file.getAbsolutePath().startsWith(hudsonRoot.getAbsolutePath())){ + File jenkinsRoot = Jenkins.getInstance().getRootDir(); + if(!file.getAbsolutePath().startsWith(jenkinsRoot.getAbsolutePath())){ return null; } - String truncatedPath = file.getAbsolutePath().substring(hudsonRoot.getAbsolutePath().length()+1); // "+1" because we don't need ending file separator + String truncatedPath = file.getAbsolutePath().substring(jenkinsRoot.getAbsolutePath().length()+1); // "+1" because we don't need ending file separator return truncatedPath.replaceAll("\\\\", "/"); } - public static File buildFileFromPathRelativeToHudsonRoot(String pathRelativeToHudsonRoot){ - File hudsonRoot = Hudson.getInstance().getRootDir(); - return new File(hudsonRoot.getAbsolutePath()+File.separator+pathRelativeToHudsonRoot); + public static File buildFileFromPathRelativeToHudsonRoot(String pathRelativeToJenkinsRoot){ + File jenkinsRoot = Jenkins.getInstance().getRootDir(); + return new File(jenkinsRoot.getAbsolutePath(), pathRelativeToJenkinsRoot); } } diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java index 00b8206d..f0992e77 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java @@ -18,7 +18,6 @@ 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; @@ -201,14 +200,12 @@ public List addFile(File scmRoot, String filePathRelativeToScmRoot){ return synchronizedFiles; } - private List refineUpdatedFilesInScmResult(List updatedFiles){ + 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 - Iterator scmFileIter = updatedFiles.iterator(); - while(scmFileIter.hasNext()){ - Object scmFile = scmFileIter.next(); + for (Object scmFile :updatedFiles) { if(scmFile instanceof File){ String checkoutScmDir = ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath(); String scmPath = ((File) scmFile).getAbsolutePath(); diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index cb1095a5..3ed3c83a 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -1,7 +1,7 @@ package hudson.plugins.scm_sync_configuration; import com.google.common.io.Files; -import hudson.model.Hudson; + import hudson.model.User; import hudson.plugins.scm_sync_configuration.exceptions.LoggableException; import hudson.plugins.scm_sync_configuration.model.*; @@ -9,6 +9,7 @@ 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; @@ -25,6 +26,8 @@ import java.util.concurrent.Future; import java.util.logging.Logger; +import jenkins.model.Jenkins; + public class ScmSyncConfigurationBusiness { @@ -305,18 +308,17 @@ public boolean scmCheckoutDirectorySettledUp(ScmContext scmContext){ public List reloadAllFilesFromScm() throws IOException, ScmException { this.scmManipulator.update(new File(getCheckoutScmDirectoryAbsolutePath())); - return syncDirectories(new File(getCheckoutScmDirectoryAbsolutePath() + File.separator), ""); + 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(Hudson.getInstance().getRootDir() + newRelative); + File jenkinsFile = new File(Jenkins.getInstance().getRootDir() + newRelative); if (f.getName().equals(scmManipulator.getScmSpecificFilename())) { // nothing to do - } - else if (f.isDirectory()) { + } else if (f.isDirectory()) { if (!jenkinsFile.exists()) { FileUtils.copyDirectory(f, jenkinsFile, new FileFilter() { @@ -330,8 +332,7 @@ public boolean accept(File f) { else { l.addAll(syncDirectories(f, newRelative)); } - } - else { + } else { if (!jenkinsFile.exists() || !FileUtils.contentEquals(f, jenkinsFile)) { FileUtils.copyFile(f, jenkinsFile); l.add(jenkinsFile); @@ -351,7 +352,7 @@ private void signal(String operation, boolean result) { } public static String getCheckoutScmDirectoryAbsolutePath(){ - return new File(new File(Hudson.getInstance().getRootDir(), WORKING_DIRECTORY), CHECKOUT_SCM_DIRECTORY).getAbsolutePath(); + return new File(new File(Jenkins.getInstance().getRootDir(), WORKING_DIRECTORY), CHECKOUT_SCM_DIRECTORY).getAbsolutePath(); } public static String getScmDirectoryName() { @@ -359,16 +360,16 @@ public static String getScmDirectoryName() { } public void purgeFailLogs() { - Hudson.getInstance().checkPermission(purgeFailLogPermission()); + Jenkins.getInstance().checkPermission(purgeFailLogPermission()); scmSyncConfigurationStatusManager.purgeFailLogs(); } public boolean canCurrentUserPurgeFailLogs() { - return Hudson.getInstance().hasPermission(purgeFailLogPermission()); + return Jenkins.getInstance().hasPermission(purgeFailLogPermission()); } private static Permission purgeFailLogPermission(){ // Only administrators should be able to purge logs - return Hudson.ADMINISTER; + 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 index d45b5d0d..e3e3cfcc 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java @@ -1,9 +1,11 @@ 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; @@ -57,6 +59,7 @@ public static Date retrieveBotherTimeoutMatchingUrl(String currentURL){ } protected static Map retrievePurgedBotherTimeouts(){ + @SuppressWarnings("unchecked") Map botherTimeouts = (Map)retrieveObject(BOTHER_TIMEOUTS_SESSION_KEY, false); if(botherTimeouts != null){ purgeOutdatedBotherTimeouts(botherTimeouts); diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index 705add04..f60942d4 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -3,13 +3,13 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; 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.Hudson; import hudson.model.Saveable; import hudson.model.User; import hudson.plugins.scm_sync_configuration.extensions.ScmSyncConfigurationFilter; @@ -29,7 +29,6 @@ import org.acegisecurity.AccessDeniedException; import org.apache.maven.scm.ScmException; -import org.apache.tools.ant.types.selectors.FileSelector; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -60,13 +59,13 @@ public class ScmSyncConfigurationPlugin extends Plugin{ /** * Strategies that cannot be updated by user */ - public static final transient List DEFAULT_STRATEGIES = new ArrayList(){{ - addAll(Collections2.filter(Arrays.asList(AVAILABLE_STRATEGIES), new Predicate() { + public static final transient List DEFAULT_STRATEGIES = ImmutableList.copyOf( + Collections2.filter(Arrays.asList(AVAILABLE_STRATEGIES), new Predicate() { public boolean apply(@Nullable ScmSyncStrategy scmSyncStrategy) { return !( scmSyncStrategy instanceof ManualIncludesScmSyncStrategy ); } - })); - }}; + }) + ); public void purgeFailLogs() { business.purgeFailLogs(); @@ -130,7 +129,7 @@ public List getManualSynchronizationIncludes(){ public void start() throws Exception { super.start(); - Hudson.XSTREAM.registerConverter(new ScmSyncConfigurationXStreamConverter()); + Jenkins.XSTREAM.registerConverter(new ScmSyncConfigurationXStreamConverter()); this.load(); @@ -296,13 +295,13 @@ public List getDefaultIncludes(){ private User getCurrentUser(){ User user = null; try { - user = Hudson.getInstance().getMe(); + user = Jenkins.getInstance().getMe(); }catch(AccessDeniedException e){} return user; } public static ScmSyncConfigurationPlugin getInstance(){ - return Hudson.getInstance().getPlugin(ScmSyncConfigurationPlugin.class); + return Jenkins.getInstance().getPlugin(ScmSyncConfigurationPlugin.class); } public ScmSyncStrategy getStrategyForSaveable(Saveable s, File f){ diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java index ed26afe2..6fd10e48 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java @@ -1,12 +1,12 @@ package hudson.plugins.scm_sync_configuration; -import hudson.model.Hudson; - 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 { @@ -21,8 +21,8 @@ public class ScmSyncConfigurationStatusManager { private File success; public ScmSyncConfigurationStatusManager() { - fail = new File(Hudson.getInstance().getRootDir().getAbsolutePath()+File.separator+LOG_FAIL_FILENAME); - success = new File(Hudson.getInstance().getRootDir().getAbsolutePath()+File.separator+LOG_SUCCESS_FILENAME); + fail = new File(Jenkins.getInstance().getRootDir().getAbsolutePath(), LOG_FAIL_FILENAME); + success = new File(Jenkins.getInstance().getRootDir().getAbsolutePath(), LOG_SUCCESS_FILENAME); } public String getLastFail() { 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 index 67f97998..fcc2b99b 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java @@ -5,22 +5,25 @@ * Exception which will be easily loggable, by providing both class and method called, causing the exception */ public class LoggableException extends RuntimeException { - Class clazz; + + private static final long serialVersionUID = 442135528912013310L; + + Class clazz; String methodName; - public LoggableException(String message, Class clazz, String methodName, Throwable cause) { + 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) { + public LoggableException(String message, Class clazz, String methodName) { super(message); this.clazz = clazz; this.methodName = methodName; } - public Class getClazz() { + public Class getClazz() { return clazz; } 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 index ca56d155..256320d6 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java @@ -3,11 +3,13 @@ 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); } 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 index f473948d..2907a251 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java @@ -1,12 +1,12 @@ package hudson.plugins.scm_sync_configuration.scms; import hudson.model.Descriptor; -import hudson.model.Hudson; -import java.util.ArrayList; 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; @@ -17,15 +17,17 @@ 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 = new ArrayList(){ { - add(new ScmSyncNoSCM()); - add(new ScmSyncSubversionSCM()); - add(new ScmSyncGitSCM()); - } }; + protected static final List SCM_IMPLEMENTATIONS = ImmutableList.of( + new ScmSyncNoSCM(), + new ScmSyncSubversionSCM(), + new ScmSyncGitSCM() + ); transient protected String title; transient protected String scmClassName; @@ -51,8 +53,9 @@ public String getSCMClassName() { return this.scmClassName; } - public Descriptor getSCMDescriptor(){ - return Hudson.getInstance().getDescriptorByName(getSCMClassName()); + @SuppressWarnings("unchecked") + public Descriptor getSCMDescriptor(){ + return Jenkins.getInstance().getDescriptorByName(getSCMClassName()); } public String getRepositoryUrlHelpPath() { @@ -83,20 +86,21 @@ public ScmRepository getConfiguredRepository(ScmManager scmManager, String scmRe //scmRepo.setPersistCheckout( false ); // TODO: instead of creating a SCMCredentialConfiguration, create a ScmProviderRepository - 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; - } - } + // 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 ..."); 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 index 6048132f..fb423d3b 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java @@ -47,6 +47,7 @@ public SCMCredentialConfiguration extractScmCredentials(String scmUrl) { 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){ 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 index 7f7c8dbf..94ff7015 100644 --- 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 @@ -3,7 +3,6 @@ import hudson.model.Saveable; import java.io.File; -import java.util.regex.Pattern; public class ClassAndFileConfigurationEntityMatcher extends PatternsEntityMatcher { 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 index ef2aa16e..ac5b7a5f 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/xstream/ScmSyncConfigurationXStreamConverter.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/xstream/ScmSyncConfigurationXStreamConverter.java @@ -38,6 +38,7 @@ public class ScmSyncConfigurationXStreamConverter implements Converter { /** * 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() @@ -46,7 +47,7 @@ public class ScmSyncConfigurationXStreamConverter implements Converter { /** * Converter is only applicable on GlobalBuildStatsPlugin data */ - public boolean canConvert(Class type) { + public boolean canConvert(@SuppressWarnings("rawtypes") Class type) { // Inherited signature return ScmSyncConfigurationPlugin.class.isAssignableFrom(type); } @@ -139,7 +140,7 @@ public Object unmarshal(HierarchicalStreamReader reader, // Migrating old data into up-to-date data // Added "+1" because we take into consideration InitialMigrator for(int i=versionNumber+1; i mockedItem = Mockito.mock(Job.class); when(mockedItem.getRootDir()).thenReturn(subModuleConfigFile.getParentFile()); sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(subModuleConfigFile)); 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 index 2077d8cf..7d642191 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java @@ -33,10 +33,11 @@ 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.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -68,6 +69,7 @@ protected ScmSyncConfigurationBaseTest(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 @@ -214,12 +216,12 @@ protected String getSCMRepositoryURL(){ } protected static List getSpecialSCMDirectoryExcludePattern(){ - return new ArrayList(){{ - add(Pattern.compile("\\.svn")); - add(Pattern.compile("\\.git.*")); - add(Pattern.compile("scm-sync-configuration\\..*\\.log")); - add(Pattern.compile("scm-sync-configuration")); - }}; + return Lists.newArrayList( + Pattern.compile("\\.svn"), + Pattern.compile("\\.git.*"), + Pattern.compile("scm-sync-configuration\\..*\\.log"), + Pattern.compile("scm-sync-configuration") + ); } protected String getSuffixForTestFiles() { From e2ad1924352de14391b2f5adea522bf2ca0d3b72 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 30 Jun 2015 15:42:17 +0200 Subject: [PATCH 12/22] JENKINS-16348: trim & validate repository URL obtained from user. Otherwise a mistakenly typed blank before or after may break things and leave the user puzzled about the reason. Also give that git repository URL a form validator. Yes, if this plugin is ever refactored to use git-plugin/git-client and Jenkins' proper Credential handling, this will have to change or be removed again, but I don't see this plugin being anywhere near that goal. The validation checks input for syntactic validity and also whether git actually can access the repository. It's still possible that pushing something to that repository fails later on (for instance, if Jenkins' OS user has no push permission), but at least this verifies the basic setup and gives the user a hint that user.name and user.email should be set, and that Jenkins' OS user may need to have a ssh key for repository access. I've included the validation in the plugin class itself and not in some git specific class because the corresponding jelly form is also the responsibility of the plugin class. Also some minor grammar things in help texts. --- .../ScmSyncConfigurationPlugin.java | 85 +++++++++++++++++++ .../scms/ScmSyncGitSCM.java | 7 +- .../scms/ScmSyncSubversionSCM.java | 9 +- .../Messages.properties | 4 + .../help/manualSynchronizationIncludes.jelly | 2 +- ...anualSynchronizationIncludes_fr.properties | 27 +++--- .../scms/git/config.jelly | 4 +- .../scms/git/url-help.properties | 6 +- .../scms/git/url-help_fr.properties | 6 +- .../scms/svn/url-help.properties | 14 +-- .../scms/svn/url-help_fr.properties | 13 +-- 11 files changed, 123 insertions(+), 54 deletions(-) create mode 100644 src/main/resources/hudson/plugins/scm_sync_configuration/Messages.properties diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index f60942d4..ce9c62bd 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -2,6 +2,7 @@ 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; @@ -24,11 +25,18 @@ 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; @@ -38,11 +46,15 @@ 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; @@ -449,4 +461,77 @@ public boolean currentUserCannotPurgeFailLogs() { public Future getLatestCommitFuture() { return latestCommitFuture; } + + 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/scms/ScmSyncGitSCM.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java index 1352fe24..8200e0d6 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java @@ -12,11 +12,10 @@ public class ScmSyncGitSCM extends SCM { public String createScmUrlFromRequest(StaplerRequest req) { String repoURL = req.getParameter("gitRepositoryUrl"); - if(repoURL == null){ + if (repoURL == null) { return null; - } - else { - return SCM_URL_PREFIX+repoURL; + } else { + return SCM_URL_PREFIX + repoURL.trim(); } } 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 index fb423d3b..c0840f93 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java @@ -32,12 +32,17 @@ public class ScmSyncSubversionSCM extends SCM { public String createScmUrlFromRequest(StaplerRequest req) { String repoURL = req.getParameter("repositoryUrl"); - if(repoURL == null){ return null; } - else { return SCM_URL_PREFIX+repoURL; } + if (repoURL == null) { + return null; + } else { + return SCM_URL_PREFIX + repoURL.trim(); + } } + public String extractScmUrlFrom(String scmUrl) { return scmUrl.substring(SCM_URL_PREFIX.length()); } + public SCMCredentialConfiguration extractScmCredentials(String scmUrl) { LOGGER.info("Extracting SVN Credentials for url : "+scmUrl); String realm = retrieveRealmFor(scmUrl); 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/help/manualSynchronizationIncludes.jelly b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/help/manualSynchronizationIncludes.jelly index b50928fc..71ef9cee 100644 --- 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 @@ -1,6 +1,6 @@
- ${%List of ant-like includes allowing to specify additionnal sync-ed files with repository}.
+ ${%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} : 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 index 89039da6..ec375ad7 100644 --- 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 @@ -1,20 +1,13 @@ -List\ of\ ant-like\ includes\ allowing\ to\ specify\ additionnal\ sync-ed\ files\ with\ 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 +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 +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 +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 +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 index 2c74e408..c12808fd 100644 --- 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 @@ -1,7 +1,9 @@ - + \ 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 index 7b68062d..888432f3 100644 --- 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 @@ -20,7 +20,5 @@ # 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, your MUST reference your Git repository root +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 index 6d1b0fe4..36fd76bf 100644 --- 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 @@ -20,7 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -description.1=\ - Spécifier 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éférencer la racine du repository +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/svn/url-help.properties b/src/main/resources/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin/scms/svn/url-help.properties index a8dc240c..69924a3e 100644 --- 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 @@ -20,14 +20,6 @@ # 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 URL, Hudson automatically checks if Hudson can connect to it. If access requires \ - authentication, it will ask you the necessary credential. If you already have a working \ - credential but would like to change it for other reasons, \ - click this link \ - and specify different credential. -description.3=\ - Each time you'll enter a new repository URL, Hudson will first synchronize all your configuration files with the repository \ - This process can take several minutes, depending on the amount of configuration files to synchronize. +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 index 9ec3cc15..aa9f0669 100644 --- 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 @@ -20,13 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -description.1=\ - Spécifiez 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, Hudson vérifie automatiquement s''il peut s''y connecter. Si l''accès nécessite \ - une authentification, il vous demandera les informations nécessaires. Si vous disposez déjà d''informations d''identification \ - qui marchent mais que vous voulez en changer, cliquez sur ce lien \ - et renseignez des valeurs différentes. -description.3=\ - A chaque fois que vous entrerez une nouvelle URL de repository, Hudson 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 à synchroniser. +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. From 25f8c79ca5f0651d6f93e4a9574832005a48fdf2 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 1 Jul 2015 14:30:20 +0200 Subject: [PATCH 13/22] Cleanup: add a few comments. In fact; I had been trapped by Eclipse's call hierarchy: getDefaultIncludes() appeared unreferenced. In reality, it is called via jelly from the manaualSynchronizationIncludes help text. --- .../ScmSyncConfigurationPlugin.java | 5 +++ .../strategies/ScmSyncStrategy.java | 2 +- .../model/ConfigurationEntityMatcher.java | 34 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index ce9c62bd..0f654f5c 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -296,6 +296,11 @@ 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){ 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 index 66dd3a27..1c371e7f 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java @@ -59,7 +59,7 @@ public static interface CommitMessageFactory { List collect(File directory); /** - * @return List of sync'ed file includes brought by current strategy + * @return List of sync'ed file includes brought by current strategy. Used only for informational purposes in the UI. */ List getSyncIncludes(); 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 index ce989ea5..58a8defa 100644 --- 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 @@ -7,9 +7,43 @@ 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(); } From a54d147090ad25babb6d551c1c10e99dbdd9f708 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 4 Jul 2015 21:34:52 +0200 Subject: [PATCH 14/22] Test: job name starting with dash Add a test case for a job name starting with a dash. Is ignored; tests will fail if it is enabled. (Bug in maven-scm: GitRemoveCommand does not use -- to separate options from file names.) --- .../repository/HudsonExtensionsTest.java | 38 +++++++++++++++++++ .../config.xml | 31 +++++++++++++++ .../hudson.tasks.Shell.xml | 5 +++ .../jobs/-newFakeJob/config.xml | 21 ++++++++++ .../jobs/fakeJob/config.xml | 21 ++++++++++ .../scm-sync-configuration.xml | 5 +++ 6 files changed, 121 insertions(+) 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 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 index f8d39eea..d6fef048 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -18,6 +18,7 @@ import org.codehaus.plexus.util.FileUtils; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.springframework.core.io.ClassPathResource; @@ -275,6 +276,43 @@ public void shouldReloadAllFilesUpdateScmAndReloadAllFilesWithFileAdd() throws T assertStatusManagerIsOk(); } + + @Test + @Ignore // Fails with git + 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 shouldFileWhichHaveToBeInSCM() throws Throwable { 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 From 91897e8c1904f761d8283245b060220f64103eed Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 1 Jul 2015 17:09:40 +0200 Subject: [PATCH 15/22] JENKINS-16441: make it work for job names starting with '-' The problem here is that this maven SCM library omits -- for git rm. Factored out the nested class from a previous similar hack into its own file and added a new override for GitRemoveCommand. Enabled the test case for job name starting with a dash. --- .../git/gitexe/ScmSyncGitCheckInCommand.java | 192 ++++++++++++++++ .../git/gitexe/ScmSyncGitExeScmProvider.java | 215 +++--------------- .../git/gitexe/ScmSyncGitRemoveCommand.java | 84 +++++++ .../repository/HudsonExtensionsTest.java | 2 - 4 files changed, 307 insertions(+), 186 deletions(-) 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/ScmSyncGitRemoveCommand.java 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..d14d11b4 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitCheckInCommand.java @@ -0,0 +1,192 @@ +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.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.ScmFileStatus; +import org.apache.maven.scm.ScmVersion; +import org.apache.maven.scm.command.checkin.CheckInScmResult; +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.add.GitAddCommand; +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.gitexe.command.status.GitStatusCommand; +import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer; +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) + */ +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(); + + int exitCode; + + 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 ( !fileSet.getFileList().isEmpty() ) + { + // if specific fileSet is given, we have to git-add them first + // otherwise we will use 'git-commit -a' later + + Commandline clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), fileSet.getFileList()); + + exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr, getLogger()); + + if ( exitCode != 0 ) + { + return new CheckInScmResult( clAdd.toString(), "The git-add command failed.", stderr.getOutput(), + false ); + } + + } + + // git-commit doesn't show single files, but only summary :/ + // so we must run git-status and consume the output + // borrow a few things from the git-status command + Commandline clStatus = GitStatusCommand.createCommandLine(repository, fileSet); + + GitStatusConsumer statusConsumer = new GitStatusConsumer( getLogger(), fileSet.getBasedir() ); + exitCode = GitCommandLineUtils.execute( clStatus, statusConsumer, stderr, getLogger() ); + if ( exitCode != 0 ) + { + // git-status returns non-zero if nothing to do + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "nothing added to commit but untracked files present (use \"git add\" to " + + "track)" ); + } + } + + if ( statusConsumer.getChangedFiles().isEmpty() ) + { + return new CheckInScmResult( null, statusConsumer.getChangedFiles() ); + } + + Commandline clCommit = createCommitCommandLine( repository, fileSet, messageFile ); + + exitCode = GitCommandLineUtils.execute( clCommit, stdout, stderr, getLogger() ); + if ( exitCode != 0 ) + { + return new CheckInScmResult( clCommit.toString(), "The git-commit command failed.", stderr.getOutput(), + false ); + } + + if( repo.isPushChanges() ) + { + Commandline cl = createSpecificPushCommandLine( getLogger(), repository, fileSet, version ); + + exitCode = GitCommandLineUtils.execute( cl, stdout, stderr, getLogger() ); + if ( exitCode != 0 ) + { + return new CheckInScmResult( cl.toString(), "The git-push command failed.", stderr.getOutput(), false ); + } + } + + List checkedInFiles = new ArrayList( statusConsumer.getChangedFiles().size() ); + + // rewrite all detected files to now have status 'checked_in' + for ( ScmFile changedFile : statusConsumer.getChangedFiles() ) + { + ScmFile scmfile = new ScmFile( changedFile.getPath(), ScmFileStatus.CHECKED_IN ); + + if ( fileSet.getFileList().isEmpty() ) + { + checkedInFiles.add( scmfile ); + } + else + { + // 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() ) ) + { + checkedInFiles.add( scmfile ); + } + + } + } + } + + 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 index 49e01232..9414a7d2 100644 --- 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 @@ -1,198 +1,45 @@ package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe; -import org.apache.commons.io.FilenameUtils; -import org.apache.maven.scm.*; -import org.apache.maven.scm.command.checkin.CheckInScmResult; -import org.apache.maven.scm.log.ScmLogger; -import org.apache.maven.scm.provider.ScmProviderRepository; import org.apache.maven.scm.provider.git.command.GitCommand; import org.apache.maven.scm.provider.git.gitexe.GitExeScmProvider; -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.gitexe.command.branch.GitBranchCommand; -import org.apache.maven.scm.provider.git.gitexe.command.checkin.GitCheckInCommand; -import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusCommand; -import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer; -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; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import org.codehaus.plexus.util.Os; /** - * @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) + * Try to fix some very broken maven scm git commands. */ public class ScmSyncGitExeScmProvider extends GitExeScmProvider { - public static 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(); - - int exitCode; - - 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 ( !fileSet.getFileList().isEmpty() ) - { - // if specific fileSet is given, we have to git-add them first - // otherwise we will use 'git-commit -a' later - - Commandline clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), fileSet.getFileList()); - - exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr, getLogger()); - - if ( exitCode != 0 ) - { - return new CheckInScmResult( clAdd.toString(), "The git-add command failed.", stderr.getOutput(), - false ); - } - - } - - // git-commit doesn't show single files, but only summary :/ - // so we must run git-status and consume the output - // borrow a few things from the git-status command - Commandline clStatus = GitStatusCommand.createCommandLine(repository, fileSet); - - GitStatusConsumer statusConsumer = new GitStatusConsumer( getLogger(), fileSet.getBasedir() ); - exitCode = GitCommandLineUtils.execute( clStatus, statusConsumer, stderr, getLogger() ); - if ( exitCode != 0 ) - { - // git-status returns non-zero if nothing to do - if ( getLogger().isInfoEnabled() ) - { - getLogger().info( "nothing added to commit but untracked files present (use \"git add\" to " + - "track)" ); - } - } - - if ( statusConsumer.getChangedFiles().isEmpty() ) - { - return new CheckInScmResult( null, statusConsumer.getChangedFiles() ); - } - - Commandline clCommit = createCommitCommandLine( repository, fileSet, messageFile ); - - exitCode = GitCommandLineUtils.execute( clCommit, stdout, stderr, getLogger() ); - if ( exitCode != 0 ) - { - return new CheckInScmResult( clCommit.toString(), "The git-commit command failed.", stderr.getOutput(), - false ); - } - - if( repo.isPushChanges() ) - { - Commandline cl = createSpecificPushCommandLine( getLogger(), repository, fileSet, version ); - - exitCode = GitCommandLineUtils.execute( cl, stdout, stderr, getLogger() ); - if ( exitCode != 0 ) - { - return new CheckInScmResult( cl.toString(), "The git-push command failed.", stderr.getOutput(), false ); - } - } - - List checkedInFiles = new ArrayList( statusConsumer.getChangedFiles().size() ); - - // rewrite all detected files to now have status 'checked_in' - for ( ScmFile changedFile : statusConsumer.getChangedFiles() ) - { - ScmFile scmfile = new ScmFile( changedFile.getPath(), ScmFileStatus.CHECKED_IN ); - - if ( fileSet.getFileList().isEmpty() ) - { - checkedInFiles.add( scmfile ); - } - else - { - // 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() ) ) - { - checkedInFiles.add( scmfile ); - } - - } - } - } - - 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; - } - + + @Override + protected GitCommand getCheckInCommand() { + // Push to origin + return new ScmSyncGitCheckInCommand(); } @Override - protected GitCommand getCheckInCommand() { - return new ScmSyncGitCheckInCommand(); + protected GitCommand getRemoveCommand() { + // Include -- in git rm + return new ScmSyncGitRemoveCommand(); + } + + // More hacks. The command line library used by the gitexe in maven scm does not automatically quote + // arguments containing blanks or other funny characters. + // + // Now this is going to be fun. + // + // On Unices, we need to protect both $ and blanks: single quote, and escape and single quote inside + // On Windows, we need to protect % and blanks: double the %'s and then double quote. + protected static String quote(String s) { + String quoteChar = "'"; + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + if (s.indexOf('%') >= 0) { + s = s.replaceAll("%", "%%"); + } + quoteChar="\""; + } + if (s.indexOf(quoteChar) >= 0) { + return quoteChar + s.replaceAll(quoteChar, '\\' + quoteChar) + quoteChar; + } + return s; } } 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..aab87f3d --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitRemoveCommand.java @@ -0,0 +1,84 @@ +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. + + 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. + + GitCommandLineUtils.addTarget( cl, files ); + + return cl; + } + +} 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 index d6fef048..3edd79d4 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -18,7 +18,6 @@ import org.codehaus.plexus.util.FileUtils; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.springframework.core.io.ClassPathResource; @@ -278,7 +277,6 @@ public void shouldReloadAllFilesUpdateScmAndReloadAllFilesWithFileAdd() throws T } @Test - @Ignore // Fails with git public void testJobNameStartingWithDash() throws Exception { createSCMMock(); sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); From f32a1ed446fc4ea525137aa52848b30ff1582a7c Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 4 Jul 2015 21:38:25 +0200 Subject: [PATCH 16/22] Test: job name with blanks Add two more test cases for adding/deleting/renaming a job with blanks in the name. Ignored because of maven-scm bug SCM-772.[1] [1] https://issues.apache.org/jira/browse/SCM-772 --- .../repository/HudsonExtensionsTest.java | 97 +++++++++++++++++++ .../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 + 6 files changed, 180 insertions(+) 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 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 index 3edd79d4..4fc501d2 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -18,6 +18,7 @@ import org.codehaus.plexus.util.FileUtils; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.springframework.core.io.ClassPathResource; @@ -312,6 +313,102 @@ public void testJobNameStartingWithDash() throws Exception { assertStatusManagerIsOk(); } + @Test + @Ignore // Fails with git + 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 + @Ignore // Fails with git + 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 : 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 From cb2f319bc0af7c6344aa72dd824c82f7e8fa647b Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 4 Jul 2015 22:51:49 +0200 Subject: [PATCH 17/22] JENKINS-24686: fix behavior for job names with blanks The basic problem here is SCM-772.[1] maven-scm cannot parse the output from git status if it contains quoted/escaped file names. There's at least two pull requests, one attached to SCM-772 and one at [2] aimed at fixing this; both are erroneous and don't look like they'd go in anytime soon. (The first one would at least replace \r by \f, and the second one only strips quotes but doesn't de-escape.) So we fix this by providing our own commands in our own gitexe provider and making sure that our implementation of these commands can deal with quoted/escaped filenames. Enabled the two test cases for job names with blanks. Also, since we're rewriting part of maven-scm here anyway, I've also included a minimal and proper fix for SCM-695.[3] [1] https://issues.apache.org/jira/browse/SCM-772 [2] https://github.com/apache/maven-scm/pull/26 [3] https://issues.apache.org/jira/browse/SCM-695 --- .../git/gitexe/FixedGitStatusConsumer.java | 204 ++++++++++++++++++ .../git/gitexe/ScmSyncGitAddCommand.java | 116 ++++++++++ .../git/gitexe/ScmSyncGitCheckInCommand.java | 139 ++++-------- .../git/gitexe/ScmSyncGitExeScmProvider.java | 39 ++-- .../git/gitexe/ScmSyncGitRemoveCommand.java | 4 +- .../git/gitexe/ScmSyncGitStatusCommand.java | 42 ++++ .../git/gitexe/ScmSyncGitUtils.java | 193 +++++++++++++++++ .../repository/HudsonExtensionsTest.java | 3 - 8 files changed, 616 insertions(+), 124 deletions(-) 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/ScmSyncGitStatusCommand.java create mode 100644 src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitUtils.java 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..4f423986 --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/FixedGitStatusConsumer.java @@ -0,0 +1,204 @@ +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 ScmLogger logger; + + private File workingDirectory; + + /** + * Entries are relative to working directory, not to the repositoryroot + */ + private 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} + */ + 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.replaceAll(File.separator, "/"), 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..9e05d09e --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitAddCommand.java @@ -0,0 +1,116 @@ +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 { + + 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 index d14d11b4..6618ec34 100644 --- 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 @@ -5,21 +5,19 @@ import java.util.ArrayList; 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.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.add.GitAddCommand; 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.gitexe.command.status.GitStatusCommand; -import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer; import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.cli.CommandLineUtils; @@ -35,6 +33,10 @@ * 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() @@ -48,125 +50,72 @@ protected CheckInScmResult executeCheckInCommand( CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer(); - int exitCode; - File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null); - try - { + try { FileUtils.fileWrite( messageFile.getAbsolutePath(), message ); - } - catch ( IOException ex ) - { + } 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() ) { - // if specific fileSet is given, we have to git-add them first - // otherwise we will use 'git-commit -a' later - - Commandline clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), fileSet.getFileList()); - - exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr, getLogger()); - - if ( exitCode != 0 ) - { - return new CheckInScmResult( clAdd.toString(), "The git-add command failed.", stderr.getOutput(), - false ); + ScmSyncGitAddCommand addCommand = new ScmSyncGitAddCommand(); + addCommand.setLogger(getLogger()); + AddScmResult addResult = addCommand.executeAddFileSet(fileSet); + if (addResult != null && addResult.isSuccess()) { + return new CheckInScmResult(new ArrayList(), addResult); } - - } - - // git-commit doesn't show single files, but only summary :/ - // so we must run git-status and consume the output - // borrow a few things from the git-status command - Commandline clStatus = GitStatusCommand.createCommandLine(repository, fileSet); - - GitStatusConsumer statusConsumer = new GitStatusConsumer( getLogger(), fileSet.getBasedir() ); - exitCode = GitCommandLineUtils.execute( clStatus, statusConsumer, stderr, getLogger() ); - if ( exitCode != 0 ) - { - // git-status returns non-zero if nothing to do - if ( getLogger().isInfoEnabled() ) - { - getLogger().info( "nothing added to commit but untracked files present (use \"git add\" to " + - "track)" ); - } - } - - if ( statusConsumer.getChangedFiles().isEmpty() ) - { - return new CheckInScmResult( null, statusConsumer.getChangedFiles() ); } - - Commandline clCommit = createCommitCommandLine( repository, fileSet, messageFile ); - - exitCode = GitCommandLineUtils.execute( clCommit, stdout, stderr, getLogger() ); - if ( exitCode != 0 ) - { - return new CheckInScmResult( clCommit.toString(), "The git-commit command failed.", stderr.getOutput(), - false ); + // 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() ) - { + if (repo.isPushChanges()) { Commandline cl = createSpecificPushCommandLine( getLogger(), repository, fileSet, version ); - exitCode = GitCommandLineUtils.execute( cl, stdout, stderr, getLogger() ); - if ( exitCode != 0 ) - { - return new CheckInScmResult( cl.toString(), "The git-push command failed.", stderr.getOutput(), false ); + if ( exitCode != 0 ) { + String msg = stderr.getOutput(); + return new CheckInScmResult(cl.toString(), "The git-push command failed.", msg, false); } } - List checkedInFiles = new ArrayList( statusConsumer.getChangedFiles().size() ); + List checkedInFiles = new ArrayList( changedFiles.size() ); // rewrite all detected files to now have status 'checked_in' - for ( ScmFile changedFile : statusConsumer.getChangedFiles() ) - { - ScmFile scmfile = new ScmFile( changedFile.getPath(), ScmFileStatus.CHECKED_IN ); - - if ( fileSet.getFileList().isEmpty() ) - { - checkedInFiles.add( scmfile ); - } - else - { - // 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() ) ) - { - checkedInFiles.add( scmfile ); - } - - } - } + for (ScmFile changedFile : changedFiles) { + checkedInFiles.add( new ScmFile(changedFile.getPath(), ScmFileStatus.CHECKED_IN)); } - return new CheckInScmResult( clCommit.toString(), checkedInFiles ); - } - finally - { - try - { + return new CheckInScmResult(clCommit.toString(), checkedInFiles); + } finally { + try { FileUtils.forceDelete( messageFile ); - } - catch ( IOException ex ) - { + } catch ( IOException ex ) { // ignore } } - } - // ---------------------------------------------------------------------- - // - // ---------------------------------------------------------------------- - public static Commandline createSpecificPushCommandLine( ScmLogger logger, GitScmProviderRepository repository, ScmFileSet fileSet, ScmVersion version ) throws ScmException 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 index 9414a7d2..bf57f24e 100644 --- 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 @@ -2,16 +2,16 @@ import org.apache.maven.scm.provider.git.command.GitCommand; import org.apache.maven.scm.provider.git.gitexe.GitExeScmProvider; -import org.codehaus.plexus.util.Os; /** - * Try to fix some very broken maven scm git commands. + * 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 + // Push to origin (fcamblor) + // Handle quoted output from git, and fix relative path computations SCM-695, SCM-772 return new ScmSyncGitCheckInCommand(); } @@ -21,25 +21,18 @@ protected GitCommand getRemoveCommand() { return new ScmSyncGitRemoveCommand(); } - // More hacks. The command line library used by the gitexe in maven scm does not automatically quote - // arguments containing blanks or other funny characters. - // - // Now this is going to be fun. - // - // On Unices, we need to protect both $ and blanks: single quote, and escape and single quote inside - // On Windows, we need to protect % and blanks: double the %'s and then double quote. - protected static String quote(String s) { - String quoteChar = "'"; - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - if (s.indexOf('%') >= 0) { - s = s.replaceAll("%", "%%"); - } - quoteChar="\""; - } - if (s.indexOf(quoteChar) >= 0) { - return quoteChar + s.replaceAll(quoteChar, '\\' + quoteChar) + quoteChar; - } - return s; + @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 index aab87f3d..06eedc62 100644 --- 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 @@ -25,14 +25,12 @@ public class ScmSyncGitRemoveCommand extends GitRemoveCommand { 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(); @@ -76,7 +74,7 @@ public static Commandline createCommandLine( File workingDirectory, List f cl.createArg().setValue( "--" ); // This is missing upstream. - GitCommandLineUtils.addTarget( cl, files ); + 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..6f8c4c4a --- /dev/null +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/customproviders/git/gitexe/ScmSyncGitStatusCommand.java @@ -0,0 +1,42 @@ +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 { + + 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..abaade72 --- /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/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java index 4fc501d2..7555020e 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -18,7 +18,6 @@ import org.codehaus.plexus.util.FileUtils; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.springframework.core.io.ClassPathResource; @@ -314,7 +313,6 @@ public void testJobNameStartingWithDash() throws Exception { } @Test - @Ignore // Fails with git public void testJobNameWithBlanks() throws Exception { createSCMMock(); sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); @@ -351,7 +349,6 @@ public void testJobNameWithBlanks() throws Exception { } @Test - @Ignore // Fails with git public void testJobRenameWithBlanksAndDash() throws Exception { createSCMMock(); sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); From e41072e5d1cbb4460e2af7f4c9cf6464869bc56e Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 6 Jul 2015 13:52:53 +0200 Subject: [PATCH 18/22] Cleanup: attempt to fix race condition. The latestCommitFuture is global, but was not protected against concurrent modifications. Code changes: * Make sure that adding the commit to the queue and queueing the new job on the executor happen synchronized. * Eliminate the global latestCommitFuture altogether. It's good enough to wait for the current transactions' commit future. --- .../ScmSyncConfigurationBusiness.java | 29 ++++++++++++------- .../ScmSyncConfigurationPlugin.java | 9 +----- .../transactions/ScmTransaction.java | 10 +++---- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index 3ed3c83a..478bf3c4 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -47,7 +47,7 @@ public class ScmSyncConfigurationBusiness { /*package*/ final ExecutorService writer = Executors.newFixedThreadPool(1, new DaemonThreadFactory()); // TODO: Refactor this into the plugin object ??? - private List commitsQueue = Collections.synchronizedList(new ArrayList()); + private List commitsQueue = new ArrayList(); public ScmSyncConfigurationBusiness(){ } @@ -139,21 +139,26 @@ public Future queueChangeSet(final ScmContext scmContext, ChangeSet change Commit commit = new Commit(changeset, user, userMessage, scmContext); LOGGER.finest("Queuing commit "+commit.toString()+" to SCM ..."); - commitsQueue.add(commit); - - return writer.submit(new Callable() { - public Void call() throws Exception { - processCommitsQueue(); - return null; - } - }); + synchronized(commitsQueue) { + commitsQueue.add(commit); + + return writer.submit(new Callable() { + 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 = new ArrayList(commitsQueue); + List currentCommitQueue; + synchronized (commitsQueue) { + currentCommitQueue = new ArrayList(commitsQueue); + } List checkedInCommits = new ArrayList(); try { @@ -227,7 +232,9 @@ private void processCommitsQueue() { signal(e.getMessage(), false); } finally { // We should remove every checkedInCommits - commitsQueue.removeAll(checkedInCommits); + synchronized (commitsQueue) { + commitsQueue.removeAll(checkedInCommits); + } } } diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index 0f654f5c..70e1bed1 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -105,8 +105,6 @@ public static interface AtomicTransactionFactory { */ private transient ThreadLocal transaction = new ThreadLocal(); - private transient Future latestCommitFuture; - private String scmRepositoryUrl; private SCM scm; private boolean noUserCommitMessage; @@ -434,8 +432,7 @@ public void startThreadedTransaction(){ public Future commitChangeset(ChangeSet changeset){ try { if(!changeset.isEmpty()){ - latestCommitFuture = this.business.queueChangeSet(createScmContext(), changeset, getCurrentUser(), ScmSyncConfigurationDataProvider.retrieveComment(false)); - return latestCommitFuture; + return this.business.queueChangeSet(createScmContext(), changeset, getCurrentUser(), ScmSyncConfigurationDataProvider.retrieveComment(false)); } else { return null; } @@ -462,10 +459,6 @@ protected void setTransaction(ScmTransaction transactionToRegister){ public boolean currentUserCannotPurgeFailLogs() { return !business.canCurrentUserPurgeFailLogs(); } - - public Future getLatestCommitFuture() { - return latestCommitFuture; - } private static final Pattern STARTS_WITH_DRIVE_LETTER = Pattern.compile("^[a-zA-Z]:"); 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 index cc76bbf6..35e9d4bd 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java @@ -1,6 +1,7 @@ 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; @@ -30,12 +31,11 @@ public void defineCommitMessage(WeightedMessage weightedMessage){ } public void commit(){ - ScmSyncConfigurationPlugin.getInstance().commitChangeset(changeset); - if(synchronousCommit){ - // Synchronous transactions should wait for latest commit future to be fully processed - // before going further + Future future = ScmSyncConfigurationPlugin.getInstance().commitChangeset(changeset); + if (synchronousCommit && future != null) { + // Synchronous transactions should wait for the future to be fully processed try { - ScmSyncConfigurationPlugin.getInstance().getLatestCommitFuture().get(); + future.get(); } catch (Exception e) { throw new RuntimeException(e); } From 78a71d813c440fbf2458d822ace5ca643465b5a0 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 6 Jul 2015 15:52:59 +0200 Subject: [PATCH 19/22] Cleanup: make Path.contains() more precise. Same problem as in maven-scm's SCM-695: to check whether one path refers to a parent directory of another path, it behooves one well to handle file separators, otherwise a directory path "foo/bar" may be seen incorrectly as a parent to be stripped from the file path "foo/barbar/someFile.txt". --- .../JenkinsFilesHelper.java | 21 +++++++++++++------ .../ScmSyncConfigurationBusiness.java | 8 ++++++- .../scm_sync_configuration/model/Path.java | 17 ++++++++++++--- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java index b811f439..e342250c 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java @@ -6,17 +6,26 @@ public class JenkinsFilesHelper { - public static String buildPathRelativeToHudsonRoot(File file){ + public static String buildPathRelativeToHudsonRoot(File file) { File jenkinsRoot = Jenkins.getInstance().getRootDir(); - if(!file.getAbsolutePath().startsWith(jenkinsRoot.getAbsolutePath())){ + 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 = file.getAbsolutePath().substring(jenkinsRoot.getAbsolutePath().length()+1); // "+1" because we don't need ending file separator - return truncatedPath.replaceAll("\\\\", "/"); + String truncatedPath = fileAbsolutePath.substring(jenkinsRootPath.length()); + return truncatedPath.replace(File.separatorChar, '/'); } public static File buildFileFromPathRelativeToHudsonRoot(String pathRelativeToJenkinsRoot){ - File jenkinsRoot = Jenkins.getInstance().getRootDir(); - return new File(jenkinsRoot.getAbsolutePath(), pathRelativeToJenkinsRoot); + return new File(Jenkins.getInstance().getRootDir(), pathRelativeToJenkinsRoot); } } diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index 478bf3c4..f352ae08 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -166,9 +166,15 @@ private void processCommitsQueue() { 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(); 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 index b572714c..ab488172 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java @@ -24,7 +24,7 @@ public Path(File hudsonFile){ } public Path(String path, boolean isDirectory) { - this.path = path; + this.path = path.replace(File.separatorChar, '/'); // Make sure we use the system-independent separator. this.isDirectory = isDirectory; } @@ -39,7 +39,7 @@ public File getHudsonFile(){ public File getScmFile(){ // TODO: Externalize ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath() // in another class ? - return new File(ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath()+File.separator+getPath()); + return new File(ScmSyncConfigurationBusiness.getCheckoutScmDirectoryAbsolutePath(), getPath()); } public String getFirstNonExistingParentScmPath(){ @@ -59,7 +59,18 @@ public boolean isDirectory() { } public boolean contains(Path p){ - return this.isDirectory() && p.getPath().startsWith(this.getPath()); + 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 From 712c127e78731b067794147bc2a70896fef31ec9 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 7 Jul 2015 07:01:11 +0200 Subject: [PATCH 20/22] Cleanup: minor fixes. * ScmSyncGitCheckInCommand: typo in a code path that is never taken in this plugin (it always calls checkIn with only a directory, no files). * Commit: check for user != null. Is that null check needed at all? * FixedGitStatusConsumer: use simpler & more efficient File.replace() instead of File.replaceAll(). File.separator is guaranteed to be the single character File.separatorChar per its contract. --- .../plugins/scm_sync_configuration/model/Commit.java | 8 ++++---- .../git/gitexe/FixedGitStatusConsumer.java | 2 +- .../git/gitexe/ScmSyncGitCheckInCommand.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) 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 index b159a0b9..ff736860 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java @@ -43,10 +43,10 @@ private static String createCommitMessage(ScmContext context, String messagePref if (user != null) { commitMessage.append(user.getId()).append(": "); } - commitMessage.append(messagePrefix); - commitMessage.append("\n\n"); - commitMessage.append("Change performed by ").append(user.getDisplayName()); - commitMessage.append('\n'); + 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()); } 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 index 4f423986..f239506b 100644 --- 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 @@ -193,7 +193,7 @@ else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() ) } for (String file : files) { - changedFiles.add(new ScmFile(file.replaceAll(File.separator, "/"), status)); + changedFiles.add(new ScmFile(file.replace(File.separatorChar, '/'), status)); } } 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 index 6618ec34..af3d73c5 100644 --- 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 @@ -67,7 +67,7 @@ protected CheckInScmResult executeCheckInCommand( ScmSyncGitAddCommand addCommand = new ScmSyncGitAddCommand(); addCommand.setLogger(getLogger()); AddScmResult addResult = addCommand.executeAddFileSet(fileSet); - if (addResult != null && addResult.isSuccess()) { + if (addResult != null && !addResult.isSuccess()) { return new CheckInScmResult(new ArrayList(), addResult); } } From cb6ad5e19814819be543268090908bd49b9c0589 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 7 Jul 2015 07:45:30 +0200 Subject: [PATCH 21/22] Cleanup: whitespace & formatting. No real code changes. Try to get a consistent indentation on all files touched in PR #31. Looks like these files used 4 spaces for indentation originally, but my Eclipse was set to use tabs. Fixed by IDE settings to use spaces instead of tabs and reformatted all Java files I touched. This also added some missing @Override annotations, and made some single-line if-statements use blocks -- I *never* use the short form. --- .../JenkinsFilesHelper.java | 36 +- .../SCMManipulator.java | 304 +++---- .../ScmSyncConfigurationBusiness.java | 42 +- .../ScmSyncConfigurationDataProvider.java | 154 ++-- .../ScmSyncConfigurationPlugin.java | 572 ++++++------ .../ScmSyncConfigurationStatusManager.java | 118 +-- .../exceptions/LoggableException.java | 6 +- .../ScmSyncConfigurationFilter.java | 4 + .../ScmSyncConfigurationItemListener.java | 114 +-- .../ScmSyncConfigurationPageDecorator.java | 20 +- .../ScmSyncConfigurationSaveableListener.java | 16 +- .../scm_sync_configuration/model/Commit.java | 66 +- .../scm_sync_configuration/model/Path.java | 44 +- .../model/WeightedMessage.java | 4 + .../scm_sync_configuration/scms/SCM.java | 241 ++--- .../scms/ScmSyncGitSCM.java | 49 +- .../scms/ScmSyncSubversionSCM.java | 201 ++--- .../git/gitexe/FixedGitStatusConsumer.java | 61 +- .../git/gitexe/ScmSyncGitAddCommand.java | 184 ++-- .../git/gitexe/ScmSyncGitCheckInCommand.java | 108 ++- .../git/gitexe/ScmSyncGitExeScmProvider.java | 26 +- .../git/gitexe/ScmSyncGitRemoveCommand.java | 70 +- .../git/gitexe/ScmSyncGitStatusCommand.java | 5 +- .../git/gitexe/ScmSyncGitUtils.java | 236 ++--- .../strategies/AbstractScmSyncStrategy.java | 111 +-- .../strategies/ScmSyncStrategy.java | 82 +- .../BasicPluginsConfigScmSyncStrategy.java | 19 +- .../impl/JenkinsConfigScmSyncStrategy.java | 41 +- .../impl/JobConfigScmSyncStrategy.java | 19 +- .../impl/ManualIncludesScmSyncStrategy.java | 15 +- .../impl/UserConfigScmSyncStrategy.java | 33 +- ...lassAndFileConfigurationEntityMatcher.java | 39 +- .../model/ConfigurationEntityMatcher.java | 56 +- ...JobOrFolderConfigurationEntityMatcher.java | 151 ++-- .../model/PatternsEntityMatcher.java | 82 +- .../transactions/ScmTransaction.java | 28 +- .../basic/ScmSyncConfigurationBasicTest.java | 82 +- .../repository/HudsonExtensionsGitTest.java | 7 +- .../HudsonExtensionsSubversionTest.java | 142 +-- .../repository/HudsonExtensionsTest.java | 820 +++++++++--------- .../repository/InitRepositoryTest.java | 146 ++-- .../impl/JobConfigScmSyncStrategyTest.java | 21 +- .../util/ScmSyncConfigurationBaseTest.java | 356 ++++---- 43 files changed, 2494 insertions(+), 2437 deletions(-) diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java index e342250c..da320a80 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/JenkinsFilesHelper.java @@ -6,24 +6,24 @@ 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 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/SCMManipulator.java b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java index f0992e77..24de088f 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/SCMManipulator.java @@ -31,80 +31,82 @@ public class SCMManipulator { private static final Logger LOGGER = Logger.getLogger(SCMManipulator.class.getName()); - private ScmManager scmManager; - private ScmRepository scmRepository = null; - private String scmSpecificFilename = 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(); - 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 { - CheckOutScmResult result = scmManager.checkOut(this.scmRepository, new ScmFileSet(checkoutDirectory)); - 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; - } + private final ScmManager scmManager; + private ScmRepository scmRepository = null; + private String scmSpecificFilename = 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(); + 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 { + CheckOutScmResult result = scmManager.checkOut(this.scmRepository, new ScmFileSet(checkoutDirectory)); + 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()){ @@ -140,32 +142,32 @@ public List deleteHierarchy(File hierarchyToDelete){ } } - 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 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 addFile(File scmRoot, String filePathRelativeToScmRoot){ LOGGER.severe("Error while adding SCM files in directory : " + addResult.getCommandOutput()); } } - } else { + } else { // If addResult.isSuccess() is false, it isn't an error if it is related to path chunks (except for latest one) : // if pathChunk is already synchronized, addResult.isSuccess() will be false. Level logLevel = (i==pathChunks.length-1)?Level.SEVERE:Level.FINE; LOGGER.log(logLevel, "Error while adding SCM file : " + addResult.getCommandOutput()); } - } + } } catch (IOException e) { LOGGER.throwing(ScmFileSet.class.getName(), "init<>", 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; - } + } 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]))+" !"); + } - if(!synchronizedFiles.isEmpty()){ - LOGGER.fine("Added SCM files : "+Arrays.toString(synchronizedFiles.toArray(new File[0]))+" !"); - } - - return synchronizedFiles; - } + return synchronizedFiles; + } private List refineUpdatedFilesInScmResult(List updatedFiles){ List refinedUpdatedFiles = new ArrayList(); @@ -222,42 +224,42 @@ private List refineUpdatedFilesInScmResult(List updatedFiles){ 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 { - CheckInScmResult result = this.scmManager.checkIn(this.scmRepository, fileSet, 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 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 { + CheckInScmResult result = this.scmManager.checkIn(this.scmRepository, fileSet, 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; + } + } diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java index f352ae08..a2feba7b 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationBusiness.java @@ -47,7 +47,7 @@ public class ScmSyncConfigurationBusiness { /*package*/ final ExecutorService writer = Executors.newFixedThreadPool(1, new DaemonThreadFactory()); // TODO: Refactor this into the plugin object ??? - private List commitsQueue = new ArrayList(); + private final List commitsQueue = new ArrayList(); public ScmSyncConfigurationBusiness(){ } @@ -135,19 +135,20 @@ public Future queueChangeSet(final ScmContext scmContext, ChangeSet change 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() { - public Void call() throws Exception { - processCommitsQueue(); - return null; - } - }); + commitsQueue.add(commit); + + return writer.submit(new Callable() { + @Override + public Void call() throws Exception { + processCommitsQueue(); + return null; + } + }); } } @@ -157,7 +158,7 @@ private void processCommitsQueue() { // Copying shared commitQueue in order to allow conccurrent modification List currentCommitQueue; synchronized (commitsQueue) { - currentCommitQueue = new ArrayList(commitsQueue); + currentCommitQueue = new ArrayList(commitsQueue); } List checkedInCommits = new ArrayList(); @@ -174,7 +175,7 @@ private void processCommitsQueue() { // 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(); @@ -231,21 +232,21 @@ private void processCommitsQueue() { signal(logMessage, true); } } - // As soon as a commit doesn't goes well, we should abort commit queue processing... + // 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); - } + synchronized (commitsQueue) { + commitsQueue.removeAll(checkedInCommits); + } } } private boolean writeScmContentOnlyIfItDiffers(Path pathRelativeToJenkinsRoot, byte[] content, File fileTranslatedInScm) - throws LoggableException { + throws LoggableException { boolean scmContentUpdated = false; boolean contentDiffer = false; try { @@ -264,7 +265,7 @@ private boolean writeScmContentOnlyIfItDiffers(Path pathRelativeToJenkinsRoot, b } private void createScmContent(Path pathRelativeToJenkinsRoot, byte[] content, File fileTranslatedInScm) - throws LoggableException { + throws LoggableException { Stack directoriesToCreate = new Stack(); File directory = fileTranslatedInScm.getParentFile(); @@ -335,6 +336,7 @@ private List syncDirectories(File from, String relative) throws IOExceptio if (!jenkinsFile.exists()) { FileUtils.copyDirectory(f, jenkinsFile, new FileFilter() { + @Override public boolean accept(File f) { return !f.getName().equals(scmManipulator.getScmSpecificFilename()); } @@ -369,9 +371,9 @@ public static String getCheckoutScmDirectoryAbsolutePath(){ } public static String getScmDirectoryName() { - return WORKING_DIRECTORY; + return WORKING_DIRECTORY; } - + public void purgeFailLogs() { Jenkins.getInstance().checkPermission(purgeFailLogPermission()); scmSyncConfigurationStatusManager.purgeFailLogs(); diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java index e3e3cfcc..2e6c5267 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationDataProvider.java @@ -13,91 +13,91 @@ 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){ + 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 ! + // 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; - } + return obj; + } public static void provideRequestDuring(HttpServletRequest request, Callable callable) throws Exception { CURRENT_REQUEST.set(request); diff --git a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java index 70e1bed1..51ffd987 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationPlugin.java @@ -60,24 +60,24 @@ public class ScmSyncConfigurationPlugin extends Plugin{ - public static final transient ScmSyncStrategy[] AVAILABLE_STRATEGIES = new ScmSyncStrategy[]{ - new JenkinsConfigScmSyncStrategy(), - new BasicPluginsConfigScmSyncStrategy(), - new JobConfigScmSyncStrategy(), - new UserConfigScmSyncStrategy(), - new ManualIncludesScmSyncStrategy() - }; + 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() { - public boolean apply(@Nullable ScmSyncStrategy scmSyncStrategy) { - return !( scmSyncStrategy instanceof ManualIncludesScmSyncStrategy ); - } - }) - ); + Collections2.filter(Arrays.asList(AVAILABLE_STRATEGIES), new Predicate() { + @Override + public boolean apply(@Nullable ScmSyncStrategy scmSyncStrategy) { + return !( scmSyncStrategy instanceof ManualIncludesScmSyncStrategy ); + } + })); public void purgeFailLogs() { business.purgeFailLogs(); @@ -89,7 +89,7 @@ public static interface AtomicTransactionFactory { private static final Logger LOGGER = Logger.getLogger(ScmSyncConfigurationPlugin.class.getName()); - private transient ScmSyncConfigurationBusiness business; + private transient ScmSyncConfigurationBusiness business; /** * Flag allowing to process commit synchronously instead of asynchronously (default) @@ -105,10 +105,10 @@ public static interface AtomicTransactionFactory { */ private transient ThreadLocal transaction = new ThreadLocal(); - private String scmRepositoryUrl; - private SCM scm; - private boolean noUserCommitMessage; - private boolean displayStatus = true; + 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]"; @@ -120,78 +120,78 @@ public ScmSyncConfigurationPlugin(){ this(false); } - public ScmSyncConfigurationPlugin(boolean synchronousTransactions){ + public ScmSyncConfigurationPlugin(boolean synchronousTransactions){ this.synchronousTransactions = synchronousTransactions; - setBusiness(new ScmSyncConfigurationBusiness()); + 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(); + @Override + public void start() throws Exception { + super.start(); - Jenkins.XSTREAM.registerConverter(new ScmSyncConfigurationXStreamConverter()); + Jenkins.XSTREAM.registerConverter(new ScmSyncConfigurationXStreamConverter()); - this.load(); + 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; - } + // 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(); - } + // 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(); + 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(); - } - - 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); + } + + 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; @@ -202,12 +202,12 @@ public void configure(StaplerRequest req, JSONObject formData) this.commitMessagePattern = req.getParameter("commitMessagePattern"); String oldScmRepositoryUrl = this.scmRepositoryUrl; - String scmType = req.getParameter("scm"); - if(scmType != null){ - this.scm = SCM.valueOf(scmType); - String newScmRepositoryUrl = this.scm.createScmUrlFromRequest(req); + String scmType = req.getParameter("scm"); + if(scmType != null){ + this.scm = SCM.valueOf(scmType); + String newScmRepositoryUrl = this.scm.createScmUrlFromRequest(req); - this.scmRepositoryUrl = newScmRepositoryUrl; + this.scmRepositoryUrl = newScmRepositoryUrl; // If something changed, let's reinitialize repository in working directory ! repoInitializationRequired = newScmRepositoryUrl != null && !newScmRepositoryUrl.equals(oldScmRepositoryUrl); @@ -243,46 +243,48 @@ public void configure(StaplerRequest req, JSONObject formData) // 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>() { - 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>() { - public Iterable apply(ScmSyncStrategy strategy) { - return strategy.collect(fromSubDirectory); - }})); - } - - 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); - } + 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 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 ! @@ -307,123 +309,123 @@ public List getDefaultIncludes(){ 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); - } - - 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 + 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); + } + + 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 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 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 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 Descriptor getDescriptorForSCM(String scmName){ + return SCM.valueOf(scmName).getSCMDescriptor(); + } public void startThreadedTransaction(){ this.setTransaction(new ThreadedTransaction(synchronousTransactions)); @@ -454,14 +456,14 @@ protected void setTransaction(ScmTransaction transactionToRegister){ 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. * @@ -469,49 +471,49 @@ public boolean currentUserCannotPurgeFailLogs() { * @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(); - } - + 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. * @@ -519,17 +521,17 @@ public FormValidation doCheckGitUrl(@QueryParameter String value) { * @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; - } + 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 index 6fd10e48..056e35de 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/ScmSyncConfigurationStatusManager.java @@ -11,65 +11,65 @@ 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 File fail; - private 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()); - } - } + 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 index fcc2b99b..f0df1247 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/exceptions/LoggableException.java @@ -6,10 +6,10 @@ */ public class LoggableException extends RuntimeException { - private static final long serialVersionUID = 442135528912013310L; + private static final long serialVersionUID = 442135528912013310L; - Class clazz; - String methodName; + private final Class clazz; + private final String methodName; public LoggableException(String message, Class clazz, String methodName, Throwable cause) { super(message, cause); 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 index 4051e186..4a900706 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationFilter.java @@ -20,9 +20,11 @@ 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; @@ -40,6 +42,7 @@ public void doFilter(final ServletRequest request, final ServletResponse respons // 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 @@ -64,6 +67,7 @@ public Void call() throws Exception { } } + @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 index 642e8dd0..3a0cce4a 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationItemListener.java @@ -18,80 +18,80 @@ @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 + @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(); + } + + @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(); + 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 + } + + @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. + } + } 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 index 256320d6..d193dfdf 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationPageDecorator.java @@ -9,17 +9,17 @@ @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(); - } + @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() { + 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 index 37062f77..cd3709af 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationSaveableListener.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/extensions/ScmSyncConfigurationSaveableListener.java @@ -11,13 +11,13 @@ @Extension public class ScmSyncConfigurationSaveableListener extends SaveableListener{ - - @Override - public void onChange(Saveable o, XmlFile file) { - - super.onChange(o, file); - - ScmSyncConfigurationPlugin plugin = ScmSyncConfigurationPlugin.getInstance(); + + @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()); @@ -28,5 +28,5 @@ public void onChange(Saveable o, XmlFile file) { plugin.getTransaction().registerPath(path); } } - } + } } \ No newline at end of file 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 index ff736860..8682f5cc 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Commit.java @@ -39,43 +39,45 @@ public ScmContext getScmContext(){ } 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(); + 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())) { + 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(); + 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 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 index ab488172..462f933c 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/Path.java @@ -12,8 +12,8 @@ */ public class Path { - private String path; - private boolean isDirectory; + private final String path; + private final boolean isDirectory; public Path(String path){ this(JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(path)); @@ -59,29 +59,37 @@ public boolean 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; + 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; + 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; + if (isDirectory != path1.isDirectory) { + return false; + } + if (path != null ? !path.equals(path1.path) : path1.path != null) { + return false; + } return true; } 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 index 2db57c6b..b470cbdc 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/model/WeightedMessage.java @@ -7,15 +7,19 @@ * 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 index 2907a251..1c927270 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/SCM.java @@ -20,64 +20,64 @@ 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); + + 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()); - } + 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; + return null; } ScmProviderRepository scmRepo = repository.getProviderRepository(); @@ -87,81 +87,82 @@ public ScmRepository getConfiguredRepository(ScmManager scmManager, String scmRe // 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 ( 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() ); - } - } + 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]); - } - + } + + 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(); + return clazz.getName(); } - + public String getId(){ - return getId(getClass()); + return getId(getClass()); } } 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 index 8200e0d6..ff2354ed 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncGitSCM.java @@ -4,27 +4,30 @@ 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"); - } - - public String createScmUrlFromRequest(StaplerRequest req) { - String repoURL = req.getParameter("gitRepositoryUrl"); - if (repoURL == null) { - return null; - } else { - return SCM_URL_PREFIX + repoURL.trim(); - } - } - - public String extractScmUrlFrom(String scmUrl) { - return scmUrl.substring(SCM_URL_PREFIX.length()); - } - - public SCMCredentialConfiguration extractScmCredentials(String scmUrl) { - return null; - } - + 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/ScmSyncSubversionSCM.java b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java index c0840f93..7eae1a61 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/scms/ScmSyncSubversionSCM.java @@ -24,127 +24,130 @@ 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"); - } - - public String createScmUrlFromRequest(StaplerRequest req) { - String repoURL = req.getParameter("repositoryUrl"); - if (repoURL == null) { - return null; - } else { - return SCM_URL_PREFIX + repoURL.trim(); - } - } - - public String extractScmUrlFrom(String scmUrl) { - return scmUrl.substring(SCM_URL_PREFIX.length()); - } - - 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 }; - + 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 = 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; + LOGGER.throwing(SVNRepositoryFactory.class.getName(), "create", e); + LOGGER.severe("Error while creating SVNRepository : "+e.getMessage()); + return null; } - try { + 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); + 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); + 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); + 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){ + + } 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(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; - } + 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 index f239506b..54fd8dee 100644 --- 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 @@ -38,7 +38,7 @@ * and fixed to account for https://issues.apache.org/jira/browse/SCM-772 . */ public class FixedGitStatusConsumer - implements StreamConsumer +implements StreamConsumer { /** @@ -61,19 +61,19 @@ public class FixedGitStatusConsumer */ private static final Pattern RENAMED_PATTERN = Pattern.compile( "^R (.*) -> (.*)$" ); - private ScmLogger logger; + private final ScmLogger logger; - private File workingDirectory; + private final File workingDirectory; /** * Entries are relative to working directory, not to the repositoryroot */ - private List changedFiles = new ArrayList(); + private final List changedFiles = new ArrayList(); private String relativeRepositoryPath; - + private final File repositoryRoot; - + // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- @@ -82,20 +82,20 @@ public FixedGitStatusConsumer (ScmLogger logger, File workingDirectory, File rep 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; - } - } + 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; + 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. @@ -111,6 +111,7 @@ public FixedGitStatusConsumer (ScmLogger logger, File workingDirectory, File rep /** * {@inheritDoc} */ + @Override public void consumeLine( String line ) { if ( logger.isDebugEnabled() ) @@ -125,7 +126,7 @@ public void consumeLine( String line ) ScmFileStatus status = null; List files = new ArrayList(); - + Matcher matcher; if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() ) { @@ -150,23 +151,23 @@ else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() ) } else { - logger.warn( "Ignoring unrecognized line: " + line ); - return; + logger.warn( "Ignoring unrecognized line: " + line ); + return; } // If the file isn't a file; don't add it. if (files.isEmpty() || status == null) { - return; + 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; + // 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 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 index 9e05d09e..897f4b68 100644 --- 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 @@ -21,96 +21,98 @@ public class ScmSyncGitAddCommand extends GitAddCommand { - 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; - } + @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 index af3d73c5..2bf64e36 100644 --- 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 @@ -24,27 +24,23 @@ 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 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. + * @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 { - + protected CheckInScmResult executeCheckInCommand(ScmProviderRepository repo, ScmFileSet fileSet, String message, + ScmVersion version) + throws ScmException { GitScmProviderRepository repository = (GitScmProviderRepository) repo; CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); @@ -52,88 +48,84 @@ protected CheckInScmResult executeCheckInCommand( File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null); try { - FileUtils.fileWrite( messageFile.getAbsolutePath(), message ); + 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 ); + return new CheckInScmResult(null, "Error while making a temporary file for the commit message: " + + ex.getMessage(), null, false); } - try - { + 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()) { + 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); - } + 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(); + 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(); + 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() ); + 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)); + checkedInFiles.add(new ScmFile(changedFile.getPath(), ScmFileStatus.CHECKED_IN)); } return new CheckInScmResult(clCommit.toString(), checkedInFiles); } finally { try { - FileUtils.forceDelete( messageFile ); - } catch ( IOException ex ) { + 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" ); + 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!" ); + 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("origin"); - cl.createArg().setValue( branch + ":" + branch ); + cl.createArg().setValue(branch + ":" + branch); return cl; } 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 index bf57f24e..83011fd6 100644 --- 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 @@ -7,32 +7,32 @@ * 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 + + @Override protected GitCommand getCheckInCommand() { - // Push to origin (fcamblor) - // Handle quoted output from git, and fix relative path computations SCM-695, SCM-772 + // 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(); + // 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(); + // 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(); + // 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 index 06eedc62..15355af9 100644 --- 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 @@ -15,68 +15,58 @@ 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... + * 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. - - 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" ); + // 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() ); + 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 ); + 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() ); + return new RemoveScmResult(cl.toString(), consumer.getRemovedFiles()); } - public static Commandline createCommandLine( File workingDirectory, List files ) - throws ScmException - { - Commandline cl = GitCommandLineUtils.getBaseGitCommandLine( workingDirectory, "rm" ); + 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" ); + 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" ); + } 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 ); + 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 index 6f8c4c4a..3b4d2656 100644 --- 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 @@ -14,6 +14,7 @@ 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()); @@ -28,10 +29,6 @@ protected StatusScmResult executeStatusCommand(ScmProviderRepository repo, ScmFi return new StatusScmResult( cl.toString(), consumer.getChangedFiles() ); } - // ---------------------------------------------------------------------- - // - // ---------------------------------------------------------------------- - public static Commandline createCommandLine( GitScmProviderRepository repository, ScmFileSet fileSet ) { Commandline cl = GitCommandLineUtils.getBaseGitCommandLine( fileSet.getBasedir(), "status" ); 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 index abaade72..a1ece5d7 100644 --- 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 @@ -16,38 +16,38 @@ public final class ScmSyncGitUtils { - private ScmSyncGitUtils() { - // No instantiation - } - - public static File getRepositoryRootDirectory(File someDirectoryInTheRepo, ScmLogger logger) throws ScmException { - // Factored out from GitStatusCommand. + 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 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); - } + 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 + // 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. + // Fix for https://issues.apache.org/jira/browse/SCM-695 . Function copied from + // GitCommandLineUtils. if (files == null || files.isEmpty()) { return; } @@ -56,12 +56,12 @@ public static void addTarget(Commandline cl, List files) throws ScmExcepti { String canonicalWorkingDirectory = workingDirectory.getCanonicalPath(); if (!canonicalWorkingDirectory.endsWith(File.separator)) { - canonicalWorkingDirectory += File.separator; // Fixes SCM-695 + 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)) { @@ -75,119 +75,119 @@ public static void addTarget(Commandline cl, List files) throws ScmExcepti catch ( IOException ex ) { throw new ScmException( "Could not get canonical paths for workingDirectory = " - + workingDirectory + " or files=" + files, ex ); + + 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()); - } - } + 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); + 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; + 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 index 70085b3f..4723403f 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/AbstractScmSyncStrategy.java @@ -27,98 +27,109 @@ 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 ConfigurationEntityMatcher configEntityMatcher; - private List pageMatchers; - private CommitMessageFactory commitMessageFactory; - - protected AbstractScmSyncStrategy(ConfigurationEntityMatcher _configEntityMatcher, List _pageMatchers){ - this.configEntityMatcher = _configEntityMatcher; - this.pageMatchers = _pageMatchers; - } + 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; } - - 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 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; + 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() { - 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 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)); } - - public boolean isCurrentUrlApplicable(String url) { - return getPageMatcherMatching(url)!=null; - } + @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(); - } + 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 index 1c371e7f..ade4c617 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/ScmSyncStrategy.java @@ -16,48 +16,48 @@ public static interface CommitMessageFactory { 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(); + /** + * 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); - /** - * 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. */ 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 index 96bd3323..269e4b35 100644 --- 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 @@ -20,21 +20,25 @@ public class BasicPluginsConfigScmSyncStrategy extends AbstractScmSyncStrategy { "scm-sync-configuration.xml" }; - private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new PatternsEntityMatcher(PATTERNS); + private static final ConfigurationEntityMatcher CONFIG_ENTITY_MATCHER = new PatternsEntityMatcher(PATTERNS); - public BasicPluginsConfigScmSyncStrategy(){ - super(CONFIG_ENTITY_MATCHER, Collections.emptyList()); - } + 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); @@ -42,7 +46,8 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { }; } - public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { - return !wasDirectory && pathRelativeToRoot != null && CONFIG_ENTITY_MATCHER.matches(saveable, pathRelativeToRoot, false); - } + @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 index 2aaf73fe..fc14f1e1 100644 --- 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 @@ -16,40 +16,45 @@ 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 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); - } + 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 !"); } }; } - public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { - // Uh-oh... Jenkins config should never be deleted. - return false; - } + @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 index 6792c0ac..330e5d39 100644 --- 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 @@ -14,22 +14,26 @@ public class JobConfigScmSyncStrategy extends AbstractScmSyncStrategy { - private static final ConfigurationEntityMatcher CONFIG_MATCHER = new JobOrFolderConfigurationEntityMatcher(); - + private static final ConfigurationEntityMatcher CONFIG_MATCHER = new JobOrFolderConfigurationEntityMatcher(); + public JobConfigScmSyncStrategy(){ - super(CONFIG_MATCHER, Collections.singletonList(new PageMatcher("^(.*view/[^/]+/)?(job/[^/]+/)+configure$", "form[name='config']"))); - } + 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); @@ -37,7 +41,8 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { }; } - public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeFromRoot, boolean wasDirectory) { - return CONFIG_MATCHER.matches(saveable, pathRelativeFromRoot, wasDirectory); - } + @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 index ec1d7ed3..1c027d25 100644 --- 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 @@ -12,9 +12,9 @@ public class ManualIncludesScmSyncStrategy extends AbstractScmSyncStrategy { - public ManualIncludesScmSyncStrategy(){ - super(null, Collections.emptyList()); - } + public ManualIncludesScmSyncStrategy(){ + super(null, Collections.emptyList()); + } @Override protected ConfigurationEntityMatcher createConfigEntityMatcher(){ @@ -26,8 +26,9 @@ protected ConfigurationEntityMatcher createConfigEntityMatcher(){ return new PatternsEntityMatcher(includes); } - 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; - } + @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 index da3bf1ac..d860ba8d 100644 --- 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 @@ -18,32 +18,36 @@ 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']") - ); + 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[] { + 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); - } + 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); @@ -51,7 +55,8 @@ public WeightedMessage getMessageWhenItemDeleted(Item item) { }; } - public boolean mightHaveBeenApplicableToDeletedSaveable(Saveable saveable, String pathRelativeToRoot, boolean wasDirectory) { - return CONFIG_ENTITY_MATCHER.matches(saveable, pathRelativeToRoot, wasDirectory); - } + @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 index 94ff7015..a501a552 100644 --- 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 @@ -5,24 +5,25 @@ import java.io.File; public class ClassAndFileConfigurationEntityMatcher extends PatternsEntityMatcher { - - private Class saveableClazz; - - public ClassAndFileConfigurationEntityMatcher(Class clazz, String[] patterns){ - super(patterns); - this.saveableClazz = clazz; - } - - 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; - } + + 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 index 58a8defa..acce5387 100644 --- 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 @@ -11,35 +11,35 @@ * 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. - */ + + /** + * 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. * 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 index d7610d93..2da878c5 100644 --- 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 @@ -20,82 +20,87 @@ */ 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 }; + 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 + "/[^/]+/)+"; - public JobOrFolderConfigurationEntityMatcher() { - super(ANT_PATTERN); - // This pattern is only used for matchingFileFrom() below, and is augmented by a FileSelector enforcing the (jobs/someName/)+ pattern - } + 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); - 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; - } + private static final String[] ANT_PATTERN = new String[] { JOBS_DIR_NAME + "/**/" + CONFIG_FILE_NAME }; - 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; - } - - 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)"); - } + public JobOrFolderConfigurationEntityMatcher() { + super(ANT_PATTERN); + // This pattern is only used for matchingFileFrom() below, and is augmented by a FileSelector enforcing the (jobs/someName/)+ pattern + } - public String[] matchingFilesFrom(File rootDirectory, FileSelector selector) { - // Create a selector that enforces the (jobs/someName)+ pattern - final FileSelector originalSelector = selector; - FileSelector combinedSelector = new FileSelector() { - 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); - } + @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/PatternsEntityMatcher.java b/src/main/java/hudson/plugins/scm_sync_configuration/strategies/model/PatternsEntityMatcher.java index 3cf42e51..cbf74aa5 100644 --- 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 @@ -14,61 +14,65 @@ public class PatternsEntityMatcher implements ConfigurationEntityMatcher { - private String[] includesPatterns; + private final String[] includesPatterns; + + private static String SCM_WORKING_DIRECTORY = ScmSyncConfigurationBusiness.getScmDirectoryName(); + private static String WAR_DIRECTORY = "war"; - private static String SCM_WORKING_DIRECTORY = ScmSyncConfigurationBusiness.getScmDirectoryName(); - private static String WAR_DIRECTORY = "war"; - public PatternsEntityMatcher(String[] includesPatterns){ this.includesPatterns = includesPatterns; } - 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, File file) { + if (file == null) { + return false; + } + return matches(saveable, JenkinsFilesHelper.buildPathRelativeToHudsonRoot(file), file.isDirectory()); + } - 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 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.setSelectors(new FileSelector[] { selector}); } scanner.scan(); return scanner.getIncludedFiles(); 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 index 35e9d4bd..82bda3b7 100644 --- a/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java +++ b/src/main/java/hudson/plugins/scm_sync_configuration/transactions/ScmTransaction.java @@ -12,10 +12,10 @@ * @author fcamblor */ public abstract class ScmTransaction { - private ChangeSet changeset; + private final ChangeSet changeset; // Flag allowing to say if transaction will be asynchronous (default) or synchronous // Synchronous commit are useful during tests execution - private boolean synchronousCommit; + private final boolean synchronousCommit; protected ScmTransaction(){ this(false); @@ -35,9 +35,9 @@ public void commit(){ if (synchronousCommit && future != null) { // Synchronous transactions should wait for the future to be fully processed try { - future.get(); + future.get(); } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException(e); } } } @@ -51,19 +51,19 @@ public void registerPathForDeletion(String path) { } public void registerRenamedPath(String oldPath, String newPath){ - File newFile = JenkinsFilesHelper.buildFileFromPathRelativeToHudsonRoot(newPath); - if (newFile.isDirectory()) { + 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); - } + String pathRelativeToRoot = JenkinsFilesHelper.buildPathRelativeToHudsonRoot(f); + if (pathRelativeToRoot != null) { + this.changeset.registerPath(pathRelativeToRoot); + } } - } else { - this.changeset.registerPath(newPath); - } + } else { + this.changeset.registerPath(newPath); + } if (oldPath != null) { - this.changeset.registerPathForDeletion(oldPath); + this.changeset.registerPathForDeletion(oldPath); } } } 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 index 65466bf9..725876d0 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/basic/ScmSyncConfigurationBasicTest.java @@ -18,45 +18,45 @@ 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"); - } + 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/repository/HudsonExtensionsGitTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java index b88a7003..04830f61 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java @@ -4,10 +4,11 @@ public class HudsonExtensionsGitTest extends HudsonExtensionsTest { - public HudsonExtensionsGitTest() { - super(new ScmUnderTestGit()); - } + public HudsonExtensionsGitTest() { + super(new ScmUnderTestGit()); + } + @Override public void shouldJobRenameBeCorrectlyImpactedOnSCM() throws Throwable { super.shouldJobRenameBeCorrectlyImpactedOnSCM(); } 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 index 6110faa6..401c0c80 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsSubversionTest.java @@ -20,77 +20,77 @@ 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(); - } + 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 index 7555020e..c884c0f9 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsTest.java @@ -32,438 +32,438 @@ 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); + 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); + 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 { + 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)); - } - } + 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(){ + protected String getHudsonRootBaseTemplate(){ if("shouldFileWhichHaveToBeInSCM".equals(testName.getMethodName())){ return "HudsonExtensionsTest.shouldFileWhichHaveToBeInSCM/"; } - return "hudsonRootBaseTemplate/"; - } + 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"); + 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)); - } - } + 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/InitRepositoryTest.java b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java index 4de3fd9b..434a0cd0 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/InitRepositoryTest.java @@ -17,78 +17,78 @@ @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(); - } + + 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 index b953a83a..153223bc 100644 --- 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 @@ -29,22 +29,23 @@ public JobConfigScmSyncStrategyTest() { } @Before - public void initObjectsUnderTests() throws Throwable{ - this.sscConfigurationSaveableListener = new ScmSyncConfigurationSaveableListener(); + public void initObjectsUnderTests() throws Throwable{ + this.sscConfigurationSaveableListener = new ScmSyncConfigurationSaveableListener(); } + @Override protected String getHudsonRootBaseTemplate(){ - return "jobConfigStrategyTemplate/"; - } + return "jobConfigStrategyTemplate/"; + } // Reproducing JENKINS-17545 @Test public void shouldConfigInSubmodulesNotSynced() throws ComponentLookupException, PlexusContainerException, IOException { - // Initializing the repository... - createSCMMock(); + // Initializing the repository... + createSCMMock(); - // Synchronizing hudson config files - sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); + // Synchronizing hudson config files + sscBusiness.synchronizeAllConfigs(ScmSyncConfigurationPlugin.AVAILABLE_STRATEGIES); File subModuleConfigFile = new File(getCurrentHudsonRootDirectory() + "/jobs/fakeJob/modules/submodule/config.xml" ); @@ -54,9 +55,9 @@ public void shouldConfigInSubmodulesNotSynced() throws ComponentLookupException, sscConfigurationSaveableListener.onChange(mockedItem, new XmlFile(subModuleConfigFile)); - verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/"); + verifyCurrentScmContentMatchesHierarchy("expected-scm-hierarchies/JobConfigScmSyncStrategyTest.shouldConfigInSubmodulesNotSynced/"); - assertStatusManagerIsOk(); + 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 index 7d642191..2bef1afe 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/util/ScmSyncConfigurationBaseTest.java @@ -51,202 +51,202 @@ @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 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 + + @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); + 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{ + // 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); - } + 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); + 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); - + 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/"); - } - + } + + 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/"); + } + } From 046227ad254eee460d0b2255e541f532c11db908 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 7 Jul 2015 07:46:47 +0200 Subject: [PATCH 22/22] Cleanup: remove a pointless override in a test class. In fact, this override without the @Test annotation effectively switched off this test for git. It's re-enabled now. --- .../repository/HudsonExtensionsGitTest.java | 4 ---- 1 file changed, 4 deletions(-) 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 index 04830f61..89474b78 100644 --- a/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java +++ b/src/test/java/hudson/plugins/scm_sync_configuration/repository/HudsonExtensionsGitTest.java @@ -8,8 +8,4 @@ public HudsonExtensionsGitTest() { super(new ScmUnderTestGit()); } - @Override - public void shouldJobRenameBeCorrectlyImpactedOnSCM() throws Throwable { - super.shouldJobRenameBeCorrectlyImpactedOnSCM(); - } }