| package org.apache.maven.plugins.scmpublish; |
| |
| /* |
| * 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.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.nio.file.Files; |
| import java.nio.file.LinkOption; |
| import java.nio.file.StandardCopyOption; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.io.filefilter.NameFileFilter; |
| import org.apache.commons.io.filefilter.NotFileFilter; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.shared.utils.logging.MessageUtils; |
| import org.codehaus.plexus.util.MatchPatterns; |
| |
| /** |
| * Publish a content to scm. By default, content is taken from default site staging directory |
| * <code>${project.build.directory}/staging</code>. |
| * Can be used without project, so usable to update any SCM with any content. |
| */ |
| @Mojo ( name = "publish-scm", aggregator = true, requiresProject = false ) |
| public class ScmPublishPublishScmMojo |
| extends AbstractScmPublishMojo |
| { |
| /** |
| * The content to be published. |
| */ |
| @Parameter ( property = "scmpublish.content", defaultValue = "${project.build.directory}/staging" ) |
| private File content; |
| |
| /** |
| */ |
| @Parameter( defaultValue = "${project}", readonly = true, required = true ) |
| protected MavenProject project; |
| |
| private List<File> deleted = new ArrayList<File>(); |
| |
| private List<File> added = new ArrayList<File>(); |
| |
| private List<File> updated = new ArrayList<File>(); |
| |
| private int directories = 0; |
| private int files = 0; |
| private long size = 0; |
| |
| /** |
| * Update scm checkout directory with content. |
| * |
| * @param checkout the scm checkout directory |
| * @param dir the content to put in scm (can be <code>null</code>) |
| * @param doNotDeleteDirs directory names that should not be deleted from scm even if not in new content: |
| * used for modules, which content is available only when staging |
| * @throws IOException |
| */ |
| private void update( File checkout, File dir, List<String> doNotDeleteDirs ) |
| throws IOException |
| { |
| String scmSpecificFilename = scmProvider.getScmSpecificFilename(); |
| String[] files = scmSpecificFilename != null |
| ? checkout.list( new NotFileFilter( new NameFileFilter( scmSpecificFilename ) ) ) |
| : checkout.list(); |
| |
| Set<String> checkoutContent = new HashSet<String>( Arrays.asList( files ) ); |
| List<String> dirContent = ( dir != null ) ? Arrays.asList( dir.list() ) : Collections.<String>emptyList(); |
| |
| Set<String> deleted = new HashSet<String>( checkoutContent ); |
| deleted.removeAll( dirContent ); |
| |
| MatchPatterns ignoreDeleteMatchPatterns = null; |
| List<String> pathsAsList = new ArrayList<String>( 0 ); |
| if ( ignorePathsToDelete != null && ignorePathsToDelete.length > 0 ) |
| { |
| ignoreDeleteMatchPatterns = MatchPatterns.from( ignorePathsToDelete ); |
| pathsAsList = Arrays.asList( ignorePathsToDelete ); |
| } |
| |
| for ( String name : deleted ) |
| { |
| if ( ignoreDeleteMatchPatterns != null && ignoreDeleteMatchPatterns.matches( name, true ) ) |
| { |
| getLog().debug( |
| name + " match one of the patterns '" + pathsAsList + "': do not add to deleted files" ); |
| continue; |
| } |
| getLog().debug( "file marked for deletion: " + name ); |
| File file = new File( checkout, name ); |
| |
| if ( ( doNotDeleteDirs != null ) && file.isDirectory() && ( doNotDeleteDirs.contains( name ) ) ) |
| { |
| // ignore directory not available |
| continue; |
| } |
| |
| if ( file.isDirectory() ) |
| { |
| update( file, null, null ); |
| } |
| this.deleted.add( file ); |
| } |
| |
| for ( String name : dirContent ) |
| { |
| File file = new File( checkout, name ); |
| File source = new File( dir, name ); |
| |
| if ( Files.isSymbolicLink( source.toPath() ) ) |
| { |
| if ( !checkoutContent.contains( name ) ) |
| { |
| this.added.add( file ); |
| } |
| |
| // copy symbolic link (Java 7 only) |
| copySymLink( source, file ); |
| } |
| else if ( source.isDirectory() ) |
| { |
| directories++; |
| if ( !checkoutContent.contains( name ) ) |
| { |
| this.added.add( file ); |
| file.mkdir(); |
| } |
| |
| update( file, source, null ); |
| } |
| else |
| { |
| if ( checkoutContent.contains( name ) ) |
| { |
| this.updated.add( file ); |
| } |
| else |
| { |
| this.added.add( file ); |
| } |
| |
| copyFile( source, file ); |
| } |
| } |
| } |
| |
| /** |
| * Copy a symbolic link. |
| * |
| * @param srcFile the source file (expected to be a symbolic link) |
| * @param destFile the destination file (which will be a symbolic link) |
| * @throws IOException |
| */ |
| private void copySymLink( File srcFile, File destFile ) |
| throws IOException |
| { |
| Files.copy( srcFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING, |
| StandardCopyOption.COPY_ATTRIBUTES, LinkOption.NOFOLLOW_LINKS ); |
| } |
| |
| /** |
| * Copy a file content, normalizing newlines when necessary. |
| * |
| * @param srcFile the source file |
| * @param destFile the destination file |
| * @throws IOException |
| * @see #requireNormalizeNewlines(File) |
| */ |
| private void copyFile( File srcFile, File destFile ) |
| throws IOException |
| { |
| if ( requireNormalizeNewlines( srcFile ) ) |
| { |
| copyAndNormalizeNewlines( srcFile, destFile ); |
| } |
| else |
| { |
| FileUtils.copyFile( srcFile, destFile ); |
| } |
| files++; |
| size += destFile.length(); |
| } |
| |
| /** |
| * Copy and normalize newlines. |
| * |
| * @param srcFile the source file |
| * @param destFile the destination file |
| * @throws IOException |
| */ |
| private void copyAndNormalizeNewlines( File srcFile, File destFile ) |
| throws IOException |
| { |
| BufferedReader in = null; |
| PrintWriter out = null; |
| try |
| { |
| in = new BufferedReader( new InputStreamReader( new FileInputStream( srcFile ), siteOutputEncoding ) ); |
| out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( destFile ), siteOutputEncoding ) ); |
| |
| for ( String line = in.readLine(); line != null; line = in.readLine() ) |
| { |
| if ( in.ready() ) |
| { |
| out.println( line ); |
| } |
| else |
| { |
| out.print( line ); |
| } |
| } |
| |
| out.close(); |
| out = null; |
| in.close(); |
| in = null; |
| } |
| finally |
| { |
| IOUtils.closeQuietly( out ); |
| IOUtils.closeQuietly( in ); |
| } |
| } |
| |
| public void scmPublishExecute() |
| throws MojoExecutionException, MojoFailureException |
| { |
| if ( siteOutputEncoding == null ) |
| { |
| getLog().warn( "No output encoding, defaulting to UTF-8." ); |
| siteOutputEncoding = "utf-8"; |
| } |
| |
| if ( !content.exists() ) |
| { |
| throw new MojoExecutionException( "Configured content directory does not exist: " + content ); |
| } |
| |
| if ( !content.canRead() ) |
| { |
| throw new MojoExecutionException( "Can't read content directory: " + content ); |
| } |
| |
| checkoutExisting(); |
| |
| final File updateDirectory; |
| if ( subDirectory == null ) |
| { |
| updateDirectory = checkoutDirectory; |
| } |
| else |
| { |
| updateDirectory = new File( checkoutDirectory, subDirectory ); |
| |
| // Security check for subDirectory with .. inside |
| if ( !updateDirectory.toPath().normalize().startsWith( checkoutDirectory.toPath().normalize() ) ) |
| { |
| logError( "Try to acces outside of the checkout directory with sub-directory: %s", subDirectory ); |
| return; |
| } |
| |
| if ( !updateDirectory.exists() ) |
| { |
| updateDirectory.mkdirs(); |
| } |
| |
| logInfo( "Will copy content in sub-directory: %s", subDirectory ); |
| } |
| |
| try |
| { |
| logInfo( "Updating checkout directory with actual content in %s", content ); |
| update( updateDirectory, content, ( project == null ) ? null : project.getModel().getModules() ); |
| String displaySize = org.apache.commons.io.FileUtils.byteCountToDisplaySize( size ); |
| logInfo( "Content consists of " + MessageUtils.buffer().strong( "%d directories and %d files = %s" ), |
| directories, files, displaySize ); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new MojoExecutionException( "Could not copy content to SCM checkout", ioe ); |
| } |
| |
| logInfo( "Publishing content to SCM will result in " |
| + MessageUtils.buffer().strong( "%d addition(s), %d update(s), %d delete(s)" ), added.size(), |
| updated.size(), deleted.size() ); |
| |
| if ( isDryRun() ) |
| { |
| int pos = checkoutDirectory.getAbsolutePath().length() + 1; |
| for ( File addedFile : added ) |
| { |
| logInfo( "- addition %s", addedFile.getAbsolutePath().substring( pos ) ); |
| } |
| for ( File updatedFile : updated ) |
| { |
| logInfo( "- update %s", updatedFile.getAbsolutePath().substring( pos ) ); |
| } |
| for ( File deletedFile : deleted ) |
| { |
| logInfo( "- delete %s", deletedFile.getAbsolutePath().substring( pos ) ); |
| } |
| return; |
| } |
| |
| if ( !added.isEmpty() ) |
| { |
| addFiles( added ); |
| } |
| |
| if ( !deleted.isEmpty() ) |
| { |
| deleteFiles( deleted ); |
| } |
| |
| logInfo( "Checking in SCM, starting at " + new Date() + "..." ); |
| checkinFiles(); |
| } |
| } |