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