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