View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.scmpublish;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.nio.file.Files;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeSet;
32  
33  import org.apache.commons.io.FileUtils;
34  import org.apache.commons.io.FilenameUtils;
35  import org.apache.commons.lang3.time.DurationFormatUtils;
36  import org.apache.maven.plugin.AbstractMojo;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugin.MojoFailureException;
39  import org.apache.maven.plugins.annotations.Component;
40  import org.apache.maven.plugins.annotations.Parameter;
41  import org.apache.maven.scm.CommandParameter;
42  import org.apache.maven.scm.CommandParameters;
43  import org.apache.maven.scm.ScmBranch;
44  import org.apache.maven.scm.ScmException;
45  import org.apache.maven.scm.ScmFileSet;
46  import org.apache.maven.scm.ScmResult;
47  import org.apache.maven.scm.command.add.AddScmResult;
48  import org.apache.maven.scm.command.checkin.CheckInScmResult;
49  import org.apache.maven.scm.manager.NoSuchScmProviderException;
50  import org.apache.maven.scm.manager.ScmManager;
51  import org.apache.maven.scm.provider.ScmProvider;
52  import org.apache.maven.scm.provider.ScmUrlUtils;
53  import org.apache.maven.scm.provider.svn.AbstractSvnScmProvider;
54  import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
55  import org.apache.maven.scm.repository.ScmRepository;
56  import org.apache.maven.scm.repository.ScmRepositoryException;
57  import org.apache.maven.settings.Server;
58  import org.apache.maven.settings.Settings;
59  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
60  import org.apache.maven.settings.crypto.SettingsDecrypter;
61  import org.apache.maven.settings.crypto.SettingsDecryptionRequest;
62  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
63  import org.apache.maven.shared.release.config.ReleaseDescriptor;
64  import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder;
65  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
66  import org.apache.maven.shared.utils.logging.MessageUtils;
67  
68  /**
69   * Base class for the scm-publish mojos.
70   */
71  public abstract class AbstractScmPublishMojo extends AbstractMojo {
72      // CHECKSTYLE_OFF: LineLength
73      /**
74       * Location of the scm publication tree:
75       * <code>scm:&lt;scm_provider&gt;&lt;delimiter&gt;&lt;provider_specific_part&gt;</code>.
76       * Example:
77       * <code>scm:svn:https://svn.apache.org/repos/infra/websites/production/maven/content/plugins/maven-scm-publish-plugin-LATEST/</code>
78       */
79      // CHECKSTYLE_ON: LineLength
80      @Parameter(
81              property = "scmpublish.pubScmUrl",
82              defaultValue = "${project.distributionManagement.site.url}",
83              required = true)
84      protected String pubScmUrl;
85  
86      /**
87       * If the checkout directory exists and this flag is activated, the plugin will try an SCM-update instead
88       * of delete then checkout.
89       */
90      @Parameter(property = "scmpublish.tryUpdate", defaultValue = "false")
91      protected boolean tryUpdate;
92  
93      // CHECKSTYLE_OFF: LineLength
94      /**
95       * Location where the scm check-out is done. By default, scm checkout is done in build (target) directory,
96       * which is deleted on every <code>mvn clean</code>. To avoid this and get better performance, configure
97       * this location outside build structure and set <code>tryUpdate</code> to <code>true</code>.
98       * See <a href="http://maven.apache.org/plugins/maven-scm-publish-plugin/various-tips.html#Improving_SCM_Checkout_Performance">
99       * Improving SCM Checkout Performance</a> for more information.
100      */
101     // CHECKSTYLE_ON: LineLength
102     @Parameter(
103             property = "scmpublish.checkoutDirectory",
104             defaultValue = "${project.build.directory}/scmpublish-checkout")
105     protected File checkoutDirectory;
106 
107     /**
108      * Location where the content is published inside the <code>${checkoutDirectory}</code>.
109      * By default, content is copyed at the root of <code>${checkoutDirectory}</code>.
110      */
111     @Parameter(property = "scmpublish.subDirectory")
112     protected String subDirectory;
113 
114     /**
115      * Display list of added, deleted, and changed files, but do not do any actual SCM operations.
116      */
117     @Parameter(property = "scmpublish.dryRun")
118     private boolean dryRun;
119 
120     /**
121      * Set this to 'true' to skip site deployment.
122      *
123      * @deprecated Please use {@link #skipDeployment}.
124      */
125     @Deprecated
126     @Parameter(defaultValue = "false")
127     private boolean skipDeployement;
128 
129     /**
130      * Set this to 'true' to skip site deployment.
131      */
132     @Parameter(property = "scmpublish.skipDeploy", alias = "maven.site.deploy.skip", defaultValue = "false")
133     private boolean skipDeployment;
134 
135     /**
136      * Run add and delete commands, but leave the actually checkin for the user to run manually.
137      */
138     @Parameter(property = "scmpublish.skipCheckin")
139     private boolean skipCheckin;
140 
141     /**
142      * SCM log/checkin comment for this publication.
143      */
144     @Parameter(property = "scmpublish.checkinComment", defaultValue = "Site checkin for project ${project.name}")
145     private String checkinComment;
146 
147     /**
148      * Patterns to exclude from the scm tree.
149      */
150     @Parameter
151     protected String excludes;
152 
153     /**
154      * Patterns to include in the scm tree.
155      */
156     @Parameter
157     protected String includes;
158 
159     /**
160      * List of SCM provider implementations.
161      * Key is the provider type, eg. <code>cvs</code>.
162      * Value is the provider implementation (the role-hint of the provider), eg. <code>cvs</code> or
163      * <code>cvs_native</code>.
164      * @see ScmManager.setScmProviderImplementation
165      */
166     @Parameter
167     private Map<String, String> providerImplementations;
168 
169     /**
170      * The SCM manager.
171      */
172     @Component
173     private ScmManager scmManager;
174 
175     /**
176      * Tool that gets a configured SCM repository from release configuration.
177      */
178     @Component
179     protected ScmRepositoryConfigurator scmRepositoryConfigurator;
180 
181     /**
182      * The serverId specified in the settings.xml, which should be used for the authentication.
183      */
184     @Parameter
185     private String serverId;
186 
187     /**
188      * The SCM username to use.
189      */
190     @Parameter(property = "username")
191     protected String username;
192 
193     /**
194      * The SCM password to use.
195      */
196     @Parameter(property = "password")
197     protected String password;
198 
199     /**
200      * Use a local checkout instead of doing a checkout from the upstream repository.
201      * <b>WARNING</b>: This will only work with distributed SCMs which support the file:// protocol.
202      * TODO: we should think about having the defaults for the various SCM providers provided via Modello!
203      */
204     @Parameter(property = "localCheckout", defaultValue = "false")
205     protected boolean localCheckout;
206 
207     /**
208      * The outputEncoding parameter of the site plugin. This plugin will corrupt your site
209      * if this does not match the value used by the site plugin.
210      */
211     @Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}")
212     protected String siteOutputEncoding;
213 
214     /**
215      * Do not delete files to the scm
216      */
217     @Parameter(property = "scmpublish.skipDeletedFiles", defaultValue = "false")
218     protected boolean skipDeletedFiles;
219 
220     /**
221      * Add each directory in a separated SCM command: this can be necessary if SCM does not support
222      * adding subdirectories in one command.
223      */
224     @Parameter(defaultValue = "false")
225     protected boolean addUniqueDirectory;
226 
227     /**
228      */
229     @Parameter(defaultValue = "${basedir}", readonly = true)
230     protected File basedir;
231 
232     /**
233      */
234     @Parameter(defaultValue = "${settings}", readonly = true, required = true)
235     protected Settings settings;
236 
237     @Component
238     private SettingsDecrypter settingsDecrypter;
239 
240     /**
241      * Collections of paths not to delete when checking content to delete.
242      * If your site has subdirectories published by an other mechanism/build
243      */
244     @Parameter
245     protected String[] ignorePathsToDelete;
246 
247     /**
248      * SCM branch to use. For github, you must configure with <code>gh-pages</code>.
249      */
250     @Parameter(property = "scmpublish.scm.branch")
251     protected String scmBranch;
252 
253     /**
254      * Configure svn automatic remote url creation.
255      */
256     @Parameter(property = "scmpublish.automaticRemotePathCreation", defaultValue = "true")
257     protected boolean automaticRemotePathCreation;
258 
259     /**
260      * Filename extensions of files which need new line normalization.
261      */
262     private static final String[] NORMALIZE_EXTENSIONS = {"html", "css", "js"};
263 
264     /**
265      * Extra file extensions to normalize line ending (will be added to default
266      * <code>html</code>,<code>css</code>,<code>js</code> list)
267      */
268     @Parameter
269     protected String[] extraNormalizeExtensions;
270 
271     private Set<String> normalizeExtensions;
272 
273     protected ScmProvider scmProvider;
274 
275     protected ScmRepository scmRepository;
276 
277     protected void logInfo(String format, Object... params) {
278         getLog().info(String.format(format, params));
279     }
280 
281     protected void logWarn(String format, Object... params) {
282         getLog().warn(String.format(format, params));
283     }
284 
285     protected void logError(String format, Object... params) {
286         getLog().error(String.format(format, params));
287     }
288 
289     private File relativize(File base, File file) {
290         return new File(base.toURI().relativize(file.toURI()).getPath());
291     }
292 
293     protected boolean requireNormalizeNewlines(File f) throws IOException {
294         if (normalizeExtensions == null) {
295             normalizeExtensions = new HashSet<>(Arrays.asList(NORMALIZE_EXTENSIONS));
296             if (extraNormalizeExtensions != null) {
297                 normalizeExtensions.addAll(Arrays.asList(extraNormalizeExtensions));
298             }
299         }
300 
301         return FilenameUtils.isExtension(f.getName(), normalizeExtensions);
302     }
303 
304     private ReleaseDescriptor setupScm() throws ScmRepositoryException, NoSuchScmProviderException {
305         String scmUrl;
306         if (localCheckout) {
307             // in the release phase we have to change the checkout URL
308             // to do a local checkout instead of going over the network.
309 
310             String provider = ScmUrlUtils.getProvider(pubScmUrl);
311             String delimiter = ScmUrlUtils.getDelimiter(pubScmUrl);
312 
313             String providerPart = "scm:" + provider + delimiter;
314 
315             // X TODO: also check the information from releaseDescriptor.getScmRelativePathProjectDirectory()
316             // X TODO: in case our toplevel git directory has no pom.
317             // X TODO: fix pathname once I understand this.
318             scmUrl = providerPart + "file://" + "target/localCheckout";
319             logInfo("Performing a LOCAL checkout from " + scmUrl);
320         }
321 
322         ReleaseDescriptorBuilder descriptorBuilder = new ReleaseDescriptorBuilder();
323         descriptorBuilder.setInteractive(settings.isInteractiveMode());
324 
325         if (username == null || password == null) {
326             for (Server server : settings.getServers()) {
327                 if (server.getId().equals(serverId)) {
328                     SettingsDecryptionRequest decryptionRequest = new DefaultSettingsDecryptionRequest(server);
329 
330                     SettingsDecryptionResult decryptionResult = settingsDecrypter.decrypt(decryptionRequest);
331 
332                     if (!decryptionResult.getProblems().isEmpty()) {
333                         // todo throw exception?
334                     }
335 
336                     if (username == null) {
337                         username = decryptionResult.getServer().getUsername();
338                     }
339 
340                     if (password == null) {
341                         password = decryptionResult.getServer().getPassword();
342                     }
343 
344                     break;
345                 }
346             }
347         }
348 
349         descriptorBuilder.setScmPassword(password);
350         descriptorBuilder.setScmUsername(username);
351 
352         descriptorBuilder.setWorkingDirectory(basedir.getAbsolutePath());
353         descriptorBuilder.setLocalCheckout(localCheckout);
354         descriptorBuilder.setScmSourceUrl(pubScmUrl);
355 
356         if (providerImplementations != null) {
357             for (Map.Entry<String, String> providerEntry : providerImplementations.entrySet()) {
358                 logInfo(
359                         "Changing the default '%s' provider implementation to '%s'.",
360                         providerEntry.getKey(), providerEntry.getValue());
361                 scmManager.setScmProviderImplementation(providerEntry.getKey(), providerEntry.getValue());
362             }
363         }
364 
365         ReleaseDescriptor releaseDescriptor = descriptorBuilder.build();
366         scmRepository = scmRepositoryConfigurator.getConfiguredRepository(releaseDescriptor, settings);
367 
368         scmProvider = scmRepositoryConfigurator.getRepositoryProvider(scmRepository);
369 
370         return releaseDescriptor;
371     }
372 
373     protected void checkoutExisting() throws MojoExecutionException {
374 
375         if (scmProvider instanceof AbstractSvnScmProvider) {
376             checkCreateRemoteSvnPath();
377         }
378 
379         logInfo(
380                 MessageUtils.buffer().strong("%s") + " the pub tree from "
381                         + MessageUtils.buffer().strong("%s") + " into %s",
382                 (tryUpdate ? "Updating" : "Checking out"),
383                 pubScmUrl,
384                 checkoutDirectory);
385 
386         if (checkoutDirectory.exists() && !tryUpdate) {
387 
388             try {
389                 FileUtils.deleteDirectory(checkoutDirectory);
390             } catch (IOException e) {
391                 logError(e.getMessage());
392 
393                 throw new MojoExecutionException("Unable to remove old checkout directory: " + e.getMessage(), e);
394             }
395         }
396 
397         boolean forceCheckout = false;
398 
399         if (!checkoutDirectory.exists()) {
400 
401             if (tryUpdate) {
402                 logInfo("TryUpdate is configured but no local copy currently available: forcing checkout.");
403             }
404             checkoutDirectory.mkdirs();
405             forceCheckout = true;
406         }
407 
408         try {
409             ScmFileSet fileSet = new ScmFileSet(checkoutDirectory, includes, excludes);
410 
411             ScmBranch branch = (scmBranch == null) ? null : new ScmBranch(scmBranch);
412 
413             ScmResult scmResult = null;
414             if (tryUpdate && !forceCheckout) {
415                 scmResult = scmProvider.update(scmRepository, fileSet, branch);
416             } else {
417                 int attempt = 0;
418                 while (scmResult == null) {
419                     try {
420                         scmResult = scmProvider.checkOut(scmRepository, fileSet, branch);
421                     } catch (ScmException e) {
422                         // give it max 2 times to retry
423                         if (attempt++ < 2) {
424                             try {
425                                 // wait 3 seconds
426                                 Thread.sleep(3 * 1000);
427                             } catch (InterruptedException ie) {
428                                 // noop
429                             }
430                         } else {
431                             throw e;
432                         }
433                     }
434                 }
435             }
436             checkScmResult(scmResult, "check out from SCM");
437         } catch (ScmException | IOException e) {
438             logError(e.getMessage());
439 
440             throw new MojoExecutionException("An error occurred during the checkout process: " + e.getMessage(), e);
441         }
442     }
443 
444     private void checkCreateRemoteSvnPath() throws MojoExecutionException {
445         getLog().debug("AbstractSvnScmProvider used, so we can check if remote url exists and eventually create it.");
446         AbstractSvnScmProvider svnScmProvider = (AbstractSvnScmProvider) scmProvider;
447 
448         try {
449             boolean remoteExists = svnScmProvider.remoteUrlExist(scmRepository.getProviderRepository(), null);
450 
451             if (remoteExists) {
452                 return;
453             }
454         } catch (ScmException e) {
455             throw new MojoExecutionException(e.getMessage(), e);
456         }
457 
458         String remoteUrl = ((SvnScmProviderRepository) scmRepository.getProviderRepository()).getUrl();
459 
460         if (!automaticRemotePathCreation) {
461             // olamy: return ?? that will fail during checkout IMHO :-)
462             logWarn("Remote svn url %s does not exist and automatic remote path creation disabled.", remoteUrl);
463             return;
464         }
465 
466         logInfo("Remote svn url %s does not exist: creating.", remoteUrl);
467 
468         File baseDir = null;
469         try {
470 
471             // create a temporary directory for svnexec
472             baseDir = Files.createTempDirectory("scm").toFile();
473 
474             // to prevent fileSet cannot be empty
475             ScmFileSet scmFileSet = new ScmFileSet(baseDir, new File(""));
476 
477             CommandParameters commandParameters = new CommandParameters();
478             commandParameters.setString(CommandParameter.SCM_MKDIR_CREATE_IN_LOCAL, Boolean.FALSE.toString());
479             commandParameters.setString(CommandParameter.MESSAGE, "Automatic svn path creation: " + remoteUrl);
480             svnScmProvider.mkdir(scmRepository.getProviderRepository(), scmFileSet, commandParameters);
481 
482             // new remote url so force checkout!
483             if (checkoutDirectory.exists()) {
484                 FileUtils.deleteDirectory(checkoutDirectory);
485             }
486         } catch (IOException | ScmException e) {
487             throw new MojoExecutionException(e.getMessage(), e);
488         } finally {
489             if (baseDir != null) {
490                 try {
491                     FileUtils.forceDeleteOnExit(baseDir);
492                 } catch (IOException e) {
493                     throw new MojoExecutionException(e.getMessage(), e);
494                 }
495             }
496         }
497     }
498 
499     public void execute() throws MojoExecutionException, MojoFailureException {
500         if (skipDeployment || skipDeployement) {
501             getLog().info("scmpublish.skipDeploy = true: Skipping site deployment");
502             return;
503         }
504 
505         // setup the scm plugin with help from release plugin utilities
506         try {
507             setupScm();
508         } catch (ScmRepositoryException | NoSuchScmProviderException e) {
509             throw new MojoExecutionException(e.getMessage(), e);
510         }
511 
512         boolean tmpCheckout = false;
513 
514         if (checkoutDirectory.getPath().contains("${project.")) {
515             try {
516                 tmpCheckout = true;
517                 checkoutDirectory = Files.createTempDirectory("maven-scm-publish" + ".checkout")
518                         .toFile();
519             } catch (IOException ioe) {
520                 throw new MojoExecutionException(ioe.getMessage(), ioe);
521             }
522         }
523 
524         try {
525             scmPublishExecute();
526         } finally {
527             if (tmpCheckout) {
528                 FileUtils.deleteQuietly(checkoutDirectory);
529             }
530         }
531     }
532 
533     /**
534      * Check-in content from scm checkout.
535      *
536      * @throws MojoExecutionException in case of issue
537      */
538     protected void checkinFiles() throws MojoExecutionException {
539         if (skipCheckin) {
540             return;
541         }
542 
543         ScmFileSet updatedFileSet = new ScmFileSet(checkoutDirectory);
544         try {
545             long start = System.currentTimeMillis();
546 
547             CheckInScmResult checkinResult = checkScmResult(
548                     scmProvider.checkIn(scmRepository, updatedFileSet, new ScmBranch(scmBranch), checkinComment),
549                     "check-in files to SCM");
550 
551             logInfo(
552                     "Checked in %d file(s) to revision %s in %s",
553                     checkinResult.getCheckedInFiles().size(),
554                     checkinResult.getScmRevision(),
555                     DurationFormatUtils.formatPeriod(start, System.currentTimeMillis(), "H' h 'm' m 's' s'"));
556         } catch (ScmException e) {
557             throw new MojoExecutionException("Failed to perform SCM checkin", e);
558         }
559     }
560 
561     protected void deleteFiles(Collection<File> deleted) throws MojoExecutionException {
562         if (skipDeletedFiles) {
563             logInfo("Deleting files is skipped.");
564             return;
565         }
566         List<File> deletedList = new ArrayList<>();
567         for (File f : deleted) {
568             deletedList.add(relativize(checkoutDirectory, f));
569         }
570         ScmFileSet deletedFileSet = new ScmFileSet(checkoutDirectory, deletedList);
571         try {
572             getLog().info("Deleting files: " + deletedList);
573 
574             checkScmResult(
575                     scmProvider.remove(scmRepository, deletedFileSet, "Deleting obsolete site files."),
576                     "delete files from SCM");
577         } catch (ScmException e) {
578             throw new MojoExecutionException("Failed to delete removed files to SCM", e);
579         }
580     }
581 
582     /**
583      * Add files to scm.
584      *
585      * @param added files to be added
586      * @throws MojoFailureException in case of issue
587      * @throws MojoExecutionException in case of issue
588      */
589     protected void addFiles(Collection<File> added) throws MojoFailureException, MojoExecutionException {
590         List<File> addedList = new ArrayList<>();
591         Set<File> createdDirs = new HashSet<>();
592         Set<File> dirsToAdd = new TreeSet<>();
593 
594         createdDirs.add(relativize(checkoutDirectory, checkoutDirectory));
595 
596         for (File f : added) {
597             for (File dir = f.getParentFile(); !dir.equals(checkoutDirectory); dir = dir.getParentFile()) {
598                 File relativized = relativize(checkoutDirectory, dir);
599                 //  we do the best we can with the directories
600                 if (createdDirs.add(relativized)) {
601                     dirsToAdd.add(relativized);
602                 } else {
603                     break;
604                 }
605             }
606             addedList.add(relativize(checkoutDirectory, f));
607         }
608 
609         if (addUniqueDirectory) { // add one directory at a time
610             for (File relativized : dirsToAdd) {
611                 try {
612                     ScmFileSet fileSet = new ScmFileSet(checkoutDirectory, relativized);
613                     getLog().info("scm add directory: " + relativized);
614                     AddScmResult addDirResult = scmProvider.add(scmRepository, fileSet, "Adding directory");
615                     if (!addDirResult.isSuccess()) {
616                         getLog().warn(" Error adding directory " + relativized + ": "
617                                 + addDirResult.getCommandOutput());
618                     }
619                 } catch (ScmException e) {
620                     //
621                 }
622             }
623         } else { // add all directories in one command
624             try {
625                 List<File> dirs = new ArrayList<>(dirsToAdd);
626                 ScmFileSet fileSet = new ScmFileSet(checkoutDirectory, dirs);
627                 getLog().info("scm add directories: " + dirs);
628                 AddScmResult addDirResult = scmProvider.add(scmRepository, fileSet, "Adding directories");
629                 if (!addDirResult.isSuccess()) {
630                     getLog().warn(" Error adding directories " + dirs + ": " + addDirResult.getCommandOutput());
631                 }
632             } catch (ScmException e) {
633                 //
634             }
635         }
636 
637         // remove directories already added !
638         addedList.removeAll(dirsToAdd);
639 
640         ScmFileSet addedFileSet = new ScmFileSet(checkoutDirectory, addedList);
641         getLog().info("scm add files: " + addedList);
642         try {
643             CommandParameters commandParameters = new CommandParameters();
644             commandParameters.setString(CommandParameter.MESSAGE, "Adding new site files.");
645             commandParameters.setString(CommandParameter.FORCE_ADD, Boolean.TRUE.toString());
646             checkScmResult(scmProvider.add(scmRepository, addedFileSet, commandParameters), "add new files to SCM");
647         } catch (ScmException e) {
648             throw new MojoExecutionException("Failed to add new files to SCM", e);
649         }
650     }
651 
652     private <T extends ScmResult> T checkScmResult(T result, String failure) throws MojoExecutionException {
653         if (!result.isSuccess()) {
654             String msg = "Failed to " + failure + ": " + result.getProviderMessage() + " " + result.getCommandOutput();
655             logError(msg);
656             throw new MojoExecutionException(msg);
657         }
658         return result;
659     }
660 
661     public boolean isDryRun() {
662         return dryRun;
663     }
664 
665     public abstract void scmPublishExecute() throws MojoExecutionException, MojoFailureException;
666 
667     public void setPubScmUrl(String pubScmUrl) {
668         // Fix required for Windows, which fit other OS as well
669         if (pubScmUrl.startsWith("scm:svn:")) {
670             pubScmUrl = pubScmUrl.replaceFirst("file:/[/]*", "file:///");
671         }
672 
673         this.pubScmUrl = pubScmUrl;
674     }
675 }