1 package org.apache.maven.plugins.scmpublish;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
62
63 public abstract class AbstractScmPublishMojo
64 extends AbstractMojo
65 {
66
67
68
69 @Parameter ( property = "scmpublish.pubScmUrl", defaultValue = "${project.distributionManagement.site.url}",
70 required = true )
71 protected String pubScmUrl;
72
73
74
75
76 @Parameter ( property = "scmpublish.checkoutDirectory",
77 defaultValue = "${project.build.directory}/scmpublish-checkout" )
78 protected File checkoutDirectory;
79
80
81
82
83 @Parameter ( property = "scmpublish.dryRun" )
84 private boolean dryRun;
85
86
87
88
89 @Parameter ( property = "scmpublish.skipCheckin" )
90 private boolean skipCheckin;
91
92
93
94
95 @Parameter ( property = "scmpublish.checkinComment", defaultValue = "Site checkin for project ${project.name}" )
96 private String checkinComment;
97
98
99
100
101 @Parameter
102 protected String excludes;
103
104
105
106
107 @Parameter
108 protected String includes;
109
110
111
112
113
114
115
116 @Parameter
117 private Map<String, String> providerImplementations;
118
119
120
121
122 @Component
123 private ScmManager scmManager;
124
125
126
127
128 @Component
129 protected ScmRepositoryConfigurator scmRepositoryConfigurator;
130
131
132
133
134 @Parameter ( property = "username" )
135 protected String username;
136
137
138
139
140 @Parameter ( property = "password" )
141 protected String password;
142
143
144
145
146
147
148 @Parameter ( property = "localCheckout", defaultValue = "false" )
149 protected boolean localCheckout;
150
151
152
153
154
155 @Parameter ( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}" )
156 protected String siteOutputEncoding;
157
158
159
160
161
162 @Parameter ( property = "scmpublish.tryUpdate", defaultValue = "false" )
163 protected boolean tryUpdate;
164
165
166
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
183
184
185 @Parameter
186 protected String[] ignorePathsToDelete;
187
188
189
190
191 @Parameter ( property = "scmpublish.scm.branch" )
192 protected String scmBranch;
193
194
195
196
197 @Parameter ( property = "scmpublish.automaticRemotePathCreation", defaultValue = "true" )
198 protected boolean automaticRemotePathCreation;
199
200
201
202
203 private final static String[] NORMALIZE_EXTENSIONS = { "html", "css", "js" };
204
205
206
207
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
255
256
257
258
259 String providerPart = pubScmUrl.substring( 0, pubScmUrl.indexOf( ':', 4 ) );
260
261
262
263
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
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
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
408 baseDir = File.createTempFile( "scm", "tmp" );
409 baseDir.delete();
410 baseDir.mkdirs();
411
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
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
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
498
499
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
556
557
558
559
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
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
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 }