View Javadoc

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