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 java.io.File;
23 import java.io.IOException;
24 import java.nio.file.Files;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.TreeSet;
33
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.lang3.time.DurationFormatUtils;
37 import org.apache.maven.plugin.AbstractMojo;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.MojoFailureException;
40 import org.apache.maven.plugins.annotations.Component;
41 import org.apache.maven.plugins.annotations.Parameter;
42 import org.apache.maven.scm.CommandParameter;
43 import org.apache.maven.scm.CommandParameters;
44 import org.apache.maven.scm.ScmBranch;
45 import org.apache.maven.scm.ScmException;
46 import org.apache.maven.scm.ScmFileSet;
47 import org.apache.maven.scm.ScmResult;
48 import org.apache.maven.scm.command.add.AddScmResult;
49 import org.apache.maven.scm.command.checkin.CheckInScmResult;
50 import org.apache.maven.scm.manager.NoSuchScmProviderException;
51 import org.apache.maven.scm.manager.ScmManager;
52 import org.apache.maven.scm.provider.ScmProvider;
53 import org.apache.maven.scm.provider.ScmUrlUtils;
54 import org.apache.maven.scm.provider.svn.AbstractSvnScmProvider;
55 import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
56 import org.apache.maven.scm.repository.ScmRepository;
57 import org.apache.maven.scm.repository.ScmRepositoryException;
58 import org.apache.maven.settings.Server;
59 import org.apache.maven.settings.Settings;
60 import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
61 import org.apache.maven.settings.crypto.SettingsDecrypter;
62 import org.apache.maven.settings.crypto.SettingsDecryptionRequest;
63 import org.apache.maven.settings.crypto.SettingsDecryptionResult;
64 import org.apache.maven.shared.release.config.ReleaseDescriptor;
65 import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
66 import org.apache.maven.shared.utils.logging.MessageUtils;
67
68
69
70
71 public abstract class AbstractScmPublishMojo
72 extends AbstractMojo
73 {
74
75
76
77
78
79
80
81
82 @Parameter ( property = "scmpublish.pubScmUrl", defaultValue = "${project.distributionManagement.site.url}",
83 required = true )
84 protected String pubScmUrl;
85
86
87
88
89
90 @Parameter ( property = "scmpublish.tryUpdate", defaultValue = "false" )
91 protected boolean tryUpdate;
92
93
94
95
96
97
98
99
100
101
102 @Parameter ( property = "scmpublish.checkoutDirectory",
103 defaultValue = "${project.build.directory}/scmpublish-checkout" )
104 protected File checkoutDirectory;
105
106
107
108
109
110 @Parameter ( property = "scmpublish.subDirectory" )
111 protected String subDirectory;
112
113
114
115
116 @Parameter ( property = "scmpublish.dryRun" )
117 private boolean dryRun;
118
119
120
121
122 @Parameter ( property = "scmpublish.skipDeploy", alias = "maven.site.deploy.skip", defaultValue = "false" )
123 private boolean skipDeployement;
124
125
126
127
128 @Parameter ( property = "scmpublish.skipCheckin" )
129 private boolean skipCheckin;
130
131
132
133
134 @Parameter ( property = "scmpublish.checkinComment", defaultValue = "Site checkin for project ${project.name}" )
135 private String checkinComment;
136
137
138
139
140 @Parameter
141 protected String excludes;
142
143
144
145
146 @Parameter
147 protected String includes;
148
149
150
151
152
153
154
155
156 @Parameter
157 private Map<String, String> providerImplementations;
158
159
160
161
162 @Component
163 private ScmManager scmManager;
164
165
166
167
168 @Component
169 protected ScmRepositoryConfigurator scmRepositoryConfigurator;
170
171
172
173
174 @Parameter
175 private String serverId;
176
177
178
179
180 @Parameter ( property = "username" )
181 protected String username;
182
183
184
185
186 @Parameter ( property = "password" )
187 protected String password;
188
189
190
191
192
193
194 @Parameter ( property = "localCheckout", defaultValue = "false" )
195 protected boolean localCheckout;
196
197
198
199
200
201 @Parameter ( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}" )
202 protected String siteOutputEncoding;
203
204
205
206
207 @Parameter ( property = "scmpublish.skipDeletedFiles", defaultValue = "false" )
208 protected boolean skipDeletedFiles;
209
210
211
212
213
214 @Parameter( defaultValue = "false" )
215 protected boolean addUniqueDirectory;
216
217
218
219 @Parameter ( defaultValue = "${basedir}", readonly = true )
220 protected File basedir;
221
222
223
224 @Parameter( defaultValue = "${settings}", readonly = true, required = true )
225 protected Settings settings;
226
227 @Component
228 private SettingsDecrypter settingsDecrypter;
229
230
231
232
233
234
235 @Parameter
236 protected String[] ignorePathsToDelete;
237
238
239
240
241 @Parameter ( property = "scmpublish.scm.branch" )
242 protected String scmBranch;
243
244
245
246
247 @Parameter ( property = "scmpublish.automaticRemotePathCreation", defaultValue = "true" )
248 protected boolean automaticRemotePathCreation;
249
250
251
252
253 private static final String[] NORMALIZE_EXTENSIONS = { "html", "css", "js" };
254
255
256
257
258
259 @Parameter
260 protected String[] extraNormalizeExtensions;
261
262 private Set<String> normalizeExtensions;
263
264 protected ScmProvider scmProvider;
265
266 protected ScmRepository scmRepository;
267
268 protected void logInfo( String format, Object... params )
269 {
270 getLog().info( String.format( format, params ) );
271 }
272
273 protected void logWarn( String format, Object... params )
274 {
275 getLog().warn( String.format( format, params ) );
276 }
277
278 protected void logError( String format, Object... params )
279 {
280 getLog().error( String.format( format, params ) );
281 }
282
283 private File relativize( File base, File file )
284 {
285 return new File( base.toURI().relativize( file.toURI() ).getPath() );
286 }
287
288 protected boolean requireNormalizeNewlines( File f )
289 throws IOException
290 {
291 if ( normalizeExtensions == null )
292 {
293 normalizeExtensions = new HashSet<String>( Arrays.asList( NORMALIZE_EXTENSIONS ) );
294 if ( extraNormalizeExtensions != null )
295 {
296 normalizeExtensions.addAll( Arrays.asList( extraNormalizeExtensions ) );
297 }
298 }
299
300 return FilenameUtils.isExtension( f.getName(), normalizeExtensions );
301 }
302
303 private ReleaseDescriptor setupScm()
304 throws ScmRepositoryException, NoSuchScmProviderException
305 {
306 String scmUrl;
307 if ( localCheckout )
308 {
309
310
311
312 String provider = ScmUrlUtils.getProvider( pubScmUrl );
313 String delimiter = ScmUrlUtils.getDelimiter( pubScmUrl );
314
315 String providerPart = "scm:" + provider + delimiter;
316
317
318
319
320 scmUrl = providerPart + "file://" + "target/localCheckout";
321 logInfo( "Performing a LOCAL checkout from " + scmUrl );
322 }
323
324 ReleaseDescriptor releaseDescriptor = new ReleaseDescriptor();
325 releaseDescriptor.setInteractive( settings.isInteractiveMode() );
326
327 if ( username == null || password == null )
328 {
329 for ( Server server : settings.getServers() )
330 {
331 if ( server.getId().equals( serverId ) )
332 {
333 SettingsDecryptionRequest decryptionRequest = new DefaultSettingsDecryptionRequest( server );
334
335 SettingsDecryptionResult decryptionResult = settingsDecrypter.decrypt( decryptionRequest );
336
337 if ( !decryptionResult.getProblems().isEmpty() )
338 {
339
340 }
341
342 if ( username == null )
343 {
344 username = decryptionResult.getServer().getUsername();
345 }
346
347 if ( password == null )
348 {
349 password = decryptionResult.getServer().getPassword();
350 }
351
352 break;
353 }
354 }
355 }
356
357 releaseDescriptor.setScmPassword( password );
358 releaseDescriptor.setScmUsername( username );
359
360 releaseDescriptor.setWorkingDirectory( basedir.getAbsolutePath() );
361 releaseDescriptor.setLocalCheckout( localCheckout );
362 releaseDescriptor.setScmSourceUrl( pubScmUrl );
363
364 if ( providerImplementations != null )
365 {
366 for ( Map.Entry<String, String> providerEntry : providerImplementations.entrySet() )
367 {
368 logInfo( "Changing the default '%s' provider implementation to '%s'.", providerEntry.getKey(),
369 providerEntry.getValue() );
370 scmManager.setScmProviderImplementation( providerEntry.getKey(), providerEntry.getValue() );
371 }
372 }
373
374 scmRepository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor, settings );
375
376 scmProvider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
377
378 return releaseDescriptor;
379 }
380
381 protected void checkoutExisting()
382 throws MojoExecutionException
383 {
384
385 if ( scmProvider instanceof AbstractSvnScmProvider )
386 {
387 checkCreateRemoteSvnPath();
388 }
389
390 logInfo( MessageUtils.buffer().strong( "%s" ) + " the pub tree from " + MessageUtils.buffer().strong( "%s" )
391 + " into %s", ( tryUpdate ? "Updating" : "Checking out" ), pubScmUrl, checkoutDirectory );
392
393 if ( checkoutDirectory.exists() && !tryUpdate )
394
395 {
396 try
397 {
398 FileUtils.deleteDirectory( checkoutDirectory );
399 }
400 catch ( IOException e )
401 {
402 logError( e.getMessage() );
403
404 throw new MojoExecutionException( "Unable to remove old checkout directory: " + e.getMessage(), e );
405 }
406 }
407
408 boolean forceCheckout = false;
409
410 if ( !checkoutDirectory.exists() )
411
412 {
413 if ( tryUpdate )
414 {
415 logInfo( "TryUpdate is configured but no local copy currently available: forcing checkout." );
416 }
417 checkoutDirectory.mkdirs();
418 forceCheckout = true;
419 }
420
421 try
422 {
423 ScmFileSet fileSet = new ScmFileSet( checkoutDirectory, includes, excludes );
424
425 ScmBranch branch = ( scmBranch == null ) ? null : new ScmBranch( scmBranch );
426
427 ScmResult scmResult = null;
428 if ( tryUpdate && !forceCheckout )
429 {
430 scmResult = scmProvider.update( scmRepository, fileSet, branch );
431 }
432 else
433 {
434 int attempt = 0;
435 while ( scmResult == null )
436 {
437 try
438 {
439 scmResult = scmProvider.checkOut( scmRepository, fileSet, branch );
440 }
441 catch ( ScmException e )
442 {
443
444 if ( attempt++ < 2 )
445 {
446 try
447 {
448
449 Thread.sleep( 3 * 1000 );
450 }
451 catch ( InterruptedException ie )
452 {
453
454 }
455 }
456 else
457 {
458 throw e;
459 }
460 }
461 }
462 }
463 checkScmResult( scmResult, "check out from SCM" );
464 }
465 catch ( ScmException e )
466 {
467 logError( e.getMessage() );
468
469 throw new MojoExecutionException( "An error occurred during the checkout process: " + e.getMessage(), e );
470 }
471 catch ( IOException e )
472 {
473 logError( e.getMessage() );
474
475 throw new MojoExecutionException( "An error occurred during the checkout process: " + e.getMessage(), e );
476 }
477 }
478
479 private void checkCreateRemoteSvnPath()
480 throws MojoExecutionException
481 {
482 getLog().debug( "AbstractSvnScmProvider used, so we can check if remote url exists and eventually create it." );
483 AbstractSvnScmProvider svnScmProvider = (AbstractSvnScmProvider) scmProvider;
484
485 try
486 {
487 boolean remoteExists = svnScmProvider.remoteUrlExist( scmRepository.getProviderRepository(), null );
488
489 if ( remoteExists )
490 {
491 return;
492 }
493 }
494 catch ( ScmException e )
495 {
496 throw new MojoExecutionException( e.getMessage(), e );
497 }
498
499 String remoteUrl = ( (SvnScmProviderRepository) scmRepository.getProviderRepository() ).getUrl();
500
501 if ( !automaticRemotePathCreation )
502 {
503
504 logWarn( "Remote svn url %s does not exist and automatic remote path creation disabled.",
505 remoteUrl );
506 return;
507 }
508
509 logInfo( "Remote svn url %s does not exist: creating.", remoteUrl );
510
511 File baseDir = null;
512 try
513 {
514
515
516 baseDir = Files.createTempDirectory( "scm" ).toFile();
517
518
519 ScmFileSet scmFileSet = new ScmFileSet( baseDir, new File( "" ) );
520
521 CommandParameters commandParameters = new CommandParameters();
522 commandParameters.setString( CommandParameter.SCM_MKDIR_CREATE_IN_LOCAL, Boolean.FALSE.toString() );
523 commandParameters.setString( CommandParameter.MESSAGE, "Automatic svn path creation: " + remoteUrl );
524 svnScmProvider.mkdir( scmRepository.getProviderRepository(), scmFileSet, commandParameters );
525
526
527 if ( checkoutDirectory.exists() )
528 {
529 FileUtils.deleteDirectory( checkoutDirectory );
530 }
531 }
532 catch ( IOException e )
533 {
534 throw new MojoExecutionException( e.getMessage(), e );
535 }
536 catch ( ScmException e )
537 {
538 throw new MojoExecutionException( e.getMessage(), e );
539 }
540 finally
541 {
542 if ( baseDir != null )
543 {
544 try
545 {
546 FileUtils.forceDeleteOnExit( baseDir );
547 }
548 catch ( IOException e )
549 {
550 throw new MojoExecutionException( e.getMessage(), e );
551 }
552 }
553 }
554 }
555
556 public void execute()
557 throws MojoExecutionException, MojoFailureException
558 {
559 if ( skipDeployement )
560 {
561 getLog().info( "scmpublish.skipDeploy = true: Skipping site deployment" );
562 return;
563 }
564
565
566 try
567 {
568 setupScm();
569 }
570 catch ( ScmRepositoryException e )
571 {
572 throw new MojoExecutionException( e.getMessage(), e );
573 }
574 catch ( NoSuchScmProviderException e )
575 {
576 throw new MojoExecutionException( e.getMessage(), e );
577 }
578
579 boolean tmpCheckout = false;
580
581 if ( checkoutDirectory.getPath().contains( "${project." ) )
582 {
583 try
584 {
585 tmpCheckout = true;
586 checkoutDirectory = File.createTempFile( "maven-scm-publish", ".checkout" );
587 checkoutDirectory.delete();
588 checkoutDirectory.mkdir();
589 }
590 catch ( IOException ioe )
591 {
592 throw new MojoExecutionException( ioe.getMessage(), ioe );
593 }
594 }
595
596 try
597 {
598 scmPublishExecute();
599 }
600 finally
601 {
602 if ( tmpCheckout )
603 {
604 FileUtils.deleteQuietly( checkoutDirectory );
605 }
606 }
607 }
608
609
610
611
612
613
614 protected void checkinFiles()
615 throws MojoExecutionException
616 {
617 if ( skipCheckin )
618 {
619 return;
620 }
621
622 ScmFileSet updatedFileSet = new ScmFileSet( checkoutDirectory );
623 try
624 {
625 long start = System.currentTimeMillis();
626
627 CheckInScmResult checkinResult =
628 checkScmResult( scmProvider.checkIn( scmRepository, updatedFileSet, new ScmBranch( scmBranch ),
629 checkinComment ), "check-in files to SCM" );
630
631 logInfo( "Checked in %d file(s) to revision %s in %s", checkinResult.getCheckedInFiles().size(),
632 checkinResult.getScmRevision(),
633 DurationFormatUtils.formatPeriod( start, System.currentTimeMillis(), "H' h 'm' m 's' s'" ) );
634 }
635 catch ( ScmException e )
636 {
637 throw new MojoExecutionException( "Failed to perform SCM checkin", e );
638 }
639 }
640
641 protected void deleteFiles( Collection<File> deleted )
642 throws MojoExecutionException
643 {
644 if ( skipDeletedFiles )
645 {
646 logInfo( "Deleting files is skipped." );
647 return;
648 }
649 List<File> deletedList = new ArrayList<File>();
650 for ( File f : deleted )
651 {
652 deletedList.add( relativize( checkoutDirectory, f ) );
653 }
654 ScmFileSet deletedFileSet = new ScmFileSet( checkoutDirectory, deletedList );
655 try
656 {
657 getLog().info( "Deleting files: " + deletedList );
658
659 checkScmResult( scmProvider.remove( scmRepository, deletedFileSet, "Deleting obsolete site files." ),
660 "delete files from SCM" );
661 }
662 catch ( ScmException e )
663 {
664 throw new MojoExecutionException( "Failed to delete removed files to SCM", e );
665 }
666 }
667
668
669
670
671
672
673
674
675 protected void addFiles( Collection<File> added )
676 throws MojoFailureException, MojoExecutionException
677 {
678 List<File> addedList = new ArrayList<File>();
679 Set<File> createdDirs = new HashSet<File>();
680 Set<File> dirsToAdd = new TreeSet<File>();
681
682 createdDirs.add( relativize( checkoutDirectory, checkoutDirectory ) );
683
684 for ( File f : added )
685 {
686 for ( File dir = f.getParentFile(); !dir.equals( checkoutDirectory ); dir = dir.getParentFile() )
687 {
688 File relativized = relativize( checkoutDirectory, dir );
689
690 if ( createdDirs.add( relativized ) )
691 {
692 dirsToAdd.add( relativized );
693 }
694 else
695 {
696 break;
697 }
698 }
699 addedList.add( relativize( checkoutDirectory, f ) );
700 }
701
702 if ( addUniqueDirectory )
703 {
704 for ( File relativized : dirsToAdd )
705 {
706 try
707 {
708 ScmFileSet fileSet = new ScmFileSet( checkoutDirectory, relativized );
709 getLog().info( "scm add directory: " + relativized );
710 AddScmResult addDirResult = scmProvider.add( scmRepository, fileSet, "Adding directory" );
711 if ( !addDirResult.isSuccess() )
712 {
713 getLog().warn( " Error adding directory " + relativized + ": "
714 + addDirResult.getCommandOutput() );
715 }
716 }
717 catch ( ScmException e )
718 {
719
720 }
721 }
722 }
723 else
724 {
725 try
726 {
727 List<File> dirs = new ArrayList<File>( dirsToAdd );
728 ScmFileSet fileSet = new ScmFileSet( checkoutDirectory, dirs );
729 getLog().info( "scm add directories: " + dirs );
730 AddScmResult addDirResult = scmProvider.add( scmRepository, fileSet, "Adding directories" );
731 if ( !addDirResult.isSuccess() )
732 {
733 getLog().warn( " Error adding directories " + dirs + ": " + addDirResult.getCommandOutput() );
734 }
735 }
736 catch ( ScmException e )
737 {
738
739 }
740 }
741
742
743 addedList.removeAll( dirsToAdd );
744
745 ScmFileSet addedFileSet = new ScmFileSet( checkoutDirectory, addedList );
746 getLog().info( "scm add files: " + addedList );
747 try
748 {
749 CommandParameters commandParameters = new CommandParameters();
750 commandParameters.setString( CommandParameter.MESSAGE, "Adding new site files." );
751 commandParameters.setString( CommandParameter.FORCE_ADD, Boolean.TRUE.toString() );
752 checkScmResult( scmProvider.add( scmRepository, addedFileSet, commandParameters ),
753 "add new files to SCM" );
754 }
755 catch ( ScmException e )
756 {
757 throw new MojoExecutionException( "Failed to add new files to SCM", e );
758 }
759 }
760
761 private <T extends ScmResult> T checkScmResult( T result, String failure )
762 throws MojoExecutionException
763 {
764 if ( !result.isSuccess() )
765 {
766 String msg = "Failed to " + failure + ": " + result.getProviderMessage() + " " + result.getCommandOutput();
767 logError( msg );
768 throw new MojoExecutionException( msg );
769 }
770 return result;
771 }
772
773 public boolean isDryRun()
774 {
775 return dryRun;
776 }
777
778 public abstract void scmPublishExecute()
779 throws MojoExecutionException, MojoFailureException;
780
781 public void setPubScmUrl( String pubScmUrl )
782 {
783
784 if ( pubScmUrl.startsWith( "scm:svn:" ) )
785 {
786 pubScmUrl = pubScmUrl.replaceFirst( "file:/[/]*", "file:///" );
787 }
788
789 this.pubScmUrl = pubScmUrl;
790 }
791
792 }