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