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