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.IOUtils;
24  import org.apache.commons.io.filefilter.NameFileFilter;
25  import org.apache.commons.io.filefilter.NotFileFilter;
26  import org.apache.maven.plugin.MojoExecutionException;
27  import org.apache.maven.plugin.MojoFailureException;
28  import org.apache.maven.plugins.annotations.Component;
29  import org.apache.maven.plugins.annotations.Mojo;
30  import org.apache.maven.plugins.annotations.Parameter;
31  import org.apache.maven.project.MavenProject;
32  import org.codehaus.plexus.util.MatchPatterns;
33  
34  import java.io.BufferedReader;
35  import java.io.File;
36  import java.io.FileInputStream;
37  import java.io.FileOutputStream;
38  import java.io.IOException;
39  import java.io.InputStreamReader;
40  import java.io.OutputStreamWriter;
41  import java.io.PrintWriter;
42  import java.util.ArrayList;
43  import java.util.Arrays;
44  import java.util.Collections;
45  import java.util.Date;
46  import java.util.HashSet;
47  import java.util.List;
48  import java.util.Set;
49  
50  /**
51   * Publish a content to scm. By default, content is taken from default site staging directory
52   * <code>${project.build.directory}/staging</code>.
53   * Can be used without project, so usable to update any SCM with any content.
54   */
55  @Mojo ( name = "publish-scm", aggregator = true, requiresProject = false )
56  public class ScmPublishPublishScmMojo
57      extends AbstractScmPublishMojo
58  {
59      /**
60       * The content to be published.
61       */
62      @Parameter ( property = "scmpublish.content", defaultValue = "${project.build.directory}/staging" )
63      private File content;
64  
65      /**
66       */
67      @Component
68      protected MavenProject project;
69  
70      private List<File> deleted = new ArrayList<File>();
71  
72      private List<File> added = new ArrayList<File>();
73  
74      private List<File> updated = new ArrayList<File>();
75  
76      private int directories = 0;
77      private int files = 0;
78      private long size = 0;
79  
80      /**
81       * Update scm checkout directory with content.
82       *
83       * @param checkout        the scm checkout directory
84       * @param dir             the content to put in scm (can be <code>null</code>)
85       * @param doNotDeleteDirs directory names that should not be deleted from scm even if not in new content:
86       *                        used for modules, which content is available only when staging
87       * @throws IOException
88       */
89      private void update( File checkout, File dir, List<String> doNotDeleteDirs )
90          throws IOException
91      {
92          String[] files =
93              checkout.list( new NotFileFilter( new NameFileFilter( scmProvider.getScmSpecificFilename() ) ) );
94  
95          Set<String> checkoutContent = new HashSet<String>( Arrays.asList( files ) );
96          List<String> dirContent = ( dir != null ) ? Arrays.asList( dir.list() ) : Collections.<String>emptyList();
97  
98          Set<String> deleted = new HashSet<String>( checkoutContent );
99          deleted.removeAll( dirContent );
100 
101         MatchPatterns ignoreDeleteMatchPatterns = null;
102         List<String> pathsAsList = new ArrayList<String>( 0 );
103         if ( ignorePathsToDelete != null && ignorePathsToDelete.length > 0 )
104         {
105             ignoreDeleteMatchPatterns = MatchPatterns.from( ignorePathsToDelete );
106             pathsAsList = Arrays.asList( ignorePathsToDelete );
107         }
108 
109         for ( String name : deleted )
110         {
111             if ( ignoreDeleteMatchPatterns != null && ignoreDeleteMatchPatterns.matches( name, true ) )
112             {
113                 getLog().debug(
114                     name + " match one of the patterns '" + pathsAsList + "': do not add to deleted files" );
115                 continue;
116             }
117             getLog().debug( "file marked for deletion: " + name );
118             File file = new File( checkout, name );
119 
120             if ( ( doNotDeleteDirs != null ) && file.isDirectory() && ( doNotDeleteDirs.contains( name ) ) )
121             {
122                 // ignore directory not available
123                 continue;
124             }
125 
126             if ( file.isDirectory() )
127             {
128                 update( file, null, null );
129             }
130             this.deleted.add( file );
131         }
132 
133         for ( String name : dirContent )
134         {
135             File file = new File( checkout, name );
136             File source = new File( dir, name );
137 
138             if ( source.isDirectory() )
139             {
140                 directories++;
141                 if ( !checkoutContent.contains( name ) )
142                 {
143                     this.added.add( file );
144                     file.mkdir();
145                 }
146 
147                 update( file, source, null );
148             }
149             else
150             {
151                 if ( checkoutContent.contains( name ) )
152                 {
153                     this.updated.add( file );
154                 }
155                 else
156                 {
157                     this.added.add( file );
158                 }
159 
160                 copyFile( source, file );
161             }
162         }
163     }
164 
165     /**
166      * Copy a file content, normalizing newlines when necessary.
167      *
168      * @param srcFile  the source file
169      * @param destFile the destination file
170      * @throws IOException
171      * @see #requireNormalizeNewlines(File)
172      */
173     private void copyFile( File srcFile, File destFile )
174         throws IOException
175     {
176         if ( requireNormalizeNewlines( srcFile ) )
177         {
178             copyAndNormalizeNewlines( srcFile, destFile );
179         }
180         else
181         {
182             FileUtils.copyFile( srcFile, destFile );
183         }
184         files++;
185         size += destFile.length();
186     }
187 
188     /**
189      * Copy and normalize newlines.
190      *
191      * @param srcFile  the source file
192      * @param destFile the destination file
193      * @throws IOException
194      */
195     private void copyAndNormalizeNewlines( File srcFile, File destFile )
196         throws IOException
197     {
198         BufferedReader in = null;
199         PrintWriter out = null;
200         try
201         {
202             in = new BufferedReader( new InputStreamReader( new FileInputStream( srcFile ), siteOutputEncoding ) );
203             out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( destFile ), siteOutputEncoding ) );
204             String line;
205             while ( ( line = in.readLine() ) != null )
206             {
207                 if ( in.ready() )
208                 {
209                     out.println( line );
210                 }
211                 else
212                 {
213                     out.print( line );
214                 }
215             }
216         }
217         finally
218         {
219             IOUtils.closeQuietly( out );
220             IOUtils.closeQuietly( in );
221         }
222     }
223 
224     public void scmPublishExecute()
225         throws MojoExecutionException, MojoFailureException
226     {
227         if ( siteOutputEncoding == null )
228         {
229             getLog().warn( "No output encoding, defaulting to UTF-8." );
230             siteOutputEncoding = "utf-8";
231         }
232 
233         if ( !content.exists() )
234         {
235             throw new MojoExecutionException( "Configured content directory does not exist: " + content );
236         }
237 
238         if ( !content.canRead() )
239         {
240             throw new MojoExecutionException( "Can't read content directory: " + content );
241         }
242 
243         checkoutExisting();
244 
245         try
246         {
247             logInfo( "Updating checkout directory with actual content in %s", content );
248             update( checkoutDirectory, content, ( project == null ) ? null : project.getModel().getModules() );
249             String displaySize = org.apache.commons.io.FileUtils.byteCountToDisplaySize( size );
250             logInfo( "Content consists in %d directories and %d files = %s", directories, files, displaySize );
251         }
252         catch ( IOException ioe )
253         {
254             throw new MojoExecutionException( "Could not copy content to SCM checkout", ioe );
255         }
256 
257         logInfo( "Publishing content into SCM will result in %d addition(s), %d update(s), %d delete(s)", added.size(),
258                  updated.size(), deleted.size() );
259 
260         if ( isDryRun() )
261         {
262             int pos = checkoutDirectory.getAbsolutePath().length() + 1;
263             for ( File addedFile : added )
264             {
265                 logInfo( "- addition %s", addedFile.getAbsolutePath().substring( pos ) );
266             }
267             for ( File updatedFile : updated )
268             {
269                 logInfo( "- update   %s", updatedFile.getAbsolutePath().substring( pos ) );
270             }
271             for ( File deletedFile : deleted )
272             {
273                 logInfo( "- delete   %s", deletedFile.getAbsolutePath().substring( pos ) );
274             }
275             return;
276         }
277 
278         if ( !added.isEmpty() )
279         {
280             addFiles( added );
281         }
282 
283         if ( !deleted.isEmpty() )
284         {
285             deleteFiles( deleted );
286         }
287 
288         logInfo( "Checking in SCM, starting at " + new Date() + "..." );
289         checkinFiles();
290     }
291 }