001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.scm.plugin; 020 021import java.io.File; 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.Objects; 029import java.util.Properties; 030 031import org.apache.commons.lang3.StringUtils; 032import org.apache.maven.plugin.AbstractMojo; 033import org.apache.maven.plugin.MojoExecutionException; 034import org.apache.maven.plugins.annotations.Parameter; 035import org.apache.maven.scm.ScmBranch; 036import org.apache.maven.scm.ScmException; 037import org.apache.maven.scm.ScmFileSet; 038import org.apache.maven.scm.ScmResult; 039import org.apache.maven.scm.ScmRevision; 040import org.apache.maven.scm.ScmTag; 041import org.apache.maven.scm.ScmVersion; 042import org.apache.maven.scm.manager.ScmManager; 043import org.apache.maven.scm.provider.ScmProviderRepository; 044import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; 045import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; 046import org.apache.maven.scm.repository.ScmRepository; 047import org.apache.maven.scm.repository.ScmRepositoryException; 048import org.apache.maven.settings.Server; 049import org.apache.maven.settings.Settings; 050import org.apache.maven.settings.building.SettingsProblem; 051import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; 052import org.apache.maven.settings.crypto.SettingsDecrypter; 053import org.apache.maven.settings.crypto.SettingsDecryptionResult; 054import org.apache.maven.shared.model.fileset.FileSet; 055import org.apache.maven.shared.model.fileset.util.FileSetManager; 056 057/** 058 * @author <a href="evenisse@apache.org">Emmanuel Venisse</a> 059 * @author Olivier Lamy 060 */ 061public abstract class AbstractScmMojo extends AbstractMojo { 062 063 protected static final String VERSION_TYPE_BRANCH = "branch"; 064 065 protected static final String VERSION_TYPE_REVISION = "revision"; 066 067 protected static final String VERSION_TYPE_TAG = "tag"; 068 069 protected static final String[] VALID_VERSION_TYPES = {VERSION_TYPE_BRANCH, VERSION_TYPE_REVISION, VERSION_TYPE_TAG 070 }; 071 072 /** 073 * The SCM connection URL. 074 */ 075 @Parameter(property = "connectionUrl", defaultValue = "${project.scm.connection}") 076 private String connectionUrl; 077 078 /** 079 * The SCM connection URL for developers. 080 */ 081 @Parameter(property = "developerConnectionUrl", defaultValue = "${project.scm.developerConnection}") 082 private String developerConnectionUrl; 083 084 /** 085 * The type of connection to use (connection or developerConnection). 086 */ 087 @Parameter(property = "connectionType", defaultValue = "connection") 088 private String connectionType; 089 090 /** 091 * The working directory. 092 */ 093 @Parameter(property = "workingDirectory") 094 private File workingDirectory; 095 096 /** 097 * The user name. 098 * 099 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 100 */ 101 @Parameter(property = "username") 102 private String username; 103 104 /** 105 * The user password. 106 * 107 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 108 */ 109 @Parameter(property = "password") 110 private String password; 111 112 /** 113 * The private key. 114 * 115 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 116 */ 117 @Parameter(property = "privateKey") 118 private String privateKey; 119 120 /** 121 * The passphrase. 122 * 123 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 124 */ 125 @Parameter(property = "passphrase") 126 private String passphrase; 127 128 /** 129 * The server id of the server which provides the credentials for the SCM in the <a href="https://maven.apache.org/settings.html">settings.xml</a> file. 130 * If not set the default lookup uses the SCM URL to construct the server id like this: 131 * {@code server-id=scm-host[":"scm-port]}. 132 * <p> 133 * Currently the POM does not allow to specify a server id for the SCM section. 134 * <p> 135 * Explicit authentication information provided via {@link #username}, {@link #password} or {@link #privateKey} will take precedence. 136 * 137 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 138 * @since 2.2.0 139 */ 140 @Parameter(property = "project.scm.id") 141 private String serverId; 142 143 /** 144 * The url of tags base directory (used by svn protocol). It is not 145 * necessary to set it if you use the standard svn layout 146 * (branches/tags/trunk). 147 */ 148 @Parameter(property = "tagBase") 149 private String tagBase; 150 151 /** 152 * Comma separated list of includes file pattern. 153 */ 154 @Parameter(property = "includes") 155 private String includes; 156 157 /** 158 * Comma separated list of excludes file pattern. 159 */ 160 @Parameter(property = "excludes") 161 private String excludes; 162 163 /** 164 * The base directory. 165 */ 166 @Parameter(property = "basedir", required = true) 167 private File basedir; 168 169 @Parameter(defaultValue = "${settings}", readonly = true) 170 private Settings settings; 171 172 /** 173 * List of System properties to set before executing the SCM command. 174 */ 175 @Parameter 176 private Properties systemProperties; 177 178 /** 179 * List of remapped provider implementations. Allows to bind a different implementation than the default one to a provider id. 180 * The key is the remapped provider id, the value is the default provider id the implementation is bound to. 181 * 182 * @see <a href="https://maven.apache.org/scm/scms-overview.html">Supported SCMs</a> 183 */ 184 @Parameter 185 private Map<String, String> providerImplementations; 186 187 /** 188 * Should distributed changes be pushed to the central repository? 189 * For many distributed SCMs like Git, a change like a commit 190 * is only stored in your local copy of the repository. Pushing 191 * the change allows your to more easily share it with other users. 192 * 193 * @since 1.4 194 */ 195 @Parameter(property = "pushChanges", defaultValue = "true") 196 private boolean pushChanges; 197 198 /** 199 * A workItem for SCMs like RTC, TFS etc, that may require additional 200 * information to perform a pushChange operation. 201 * 202 * @since 1.9.5 203 */ 204 @Parameter(property = "workItem") 205 @Deprecated 206 private String workItem; 207 208 private final ScmManager manager; 209 210 private final SettingsDecrypter settingsDecrypter; 211 212 protected AbstractScmMojo(ScmManager manager, SettingsDecrypter settingsDecrypter) { 213 this.manager = manager; 214 this.settingsDecrypter = settingsDecrypter; 215 } 216 217 /** 218 * {@inheritDoc} 219 */ 220 public void execute() throws MojoExecutionException { 221 if (systemProperties != null) { 222 // Add all system properties configured by the user 223 Iterator<Object> iter = systemProperties.keySet().iterator(); 224 225 while (iter.hasNext()) { 226 String key = (String) iter.next(); 227 228 String value = systemProperties.getProperty(key); 229 230 System.setProperty(key, value); 231 } 232 } 233 234 if (providerImplementations != null && !providerImplementations.isEmpty()) { 235 for (Entry<String, String> entry : providerImplementations.entrySet()) { 236 String providerType = entry.getKey(); 237 String providerImplementation = entry.getValue(); 238 getLog().debug("Change the default '" + providerType + "' provider implementation to '" 239 + providerImplementation + "'."); 240 getScmManager().setScmProviderImplementation(providerType, providerImplementation); 241 } 242 } 243 } 244 245 protected void setConnectionType(String connectionType) { 246 this.connectionType = connectionType; 247 } 248 249 public String getConnectionUrl() { 250 boolean requireDeveloperConnection = !"connection".equals(connectionType.toLowerCase()); 251 if ((connectionUrl != null && !connectionUrl.isEmpty()) && !requireDeveloperConnection) { 252 return connectionUrl; 253 } else if (developerConnectionUrl != null && !developerConnectionUrl.isEmpty()) { 254 return developerConnectionUrl; 255 } 256 if (requireDeveloperConnection) { 257 throw new NullPointerException("You need to define a developerConnectionUrl parameter"); 258 } else { 259 throw new NullPointerException("You need to define a connectionUrl parameter"); 260 } 261 } 262 263 public void setConnectionUrl(String connectionUrl) { 264 this.connectionUrl = connectionUrl; 265 } 266 267 public File getWorkingDirectory() { 268 if (workingDirectory == null) { 269 return basedir; 270 } 271 272 return workingDirectory; 273 } 274 275 public File getBasedir() { 276 return this.basedir; 277 } 278 279 public void setWorkingDirectory(File workingDirectory) { 280 this.workingDirectory = workingDirectory; 281 } 282 283 public ScmManager getScmManager() { 284 return manager; 285 } 286 287 public ScmFileSet getFileSet() throws IOException { 288 if (includes != null || excludes != null) { 289 return new ScmFileSet(getWorkingDirectory(), includes, excludes); 290 } else { 291 return new ScmFileSet(getWorkingDirectory()); 292 } 293 } 294 295 public ScmRepository getScmRepository() throws ScmException { 296 try { 297 ScmRepository repository = getScmManager().makeScmRepository(getConnectionUrl()); 298 299 ScmProviderRepository providerRepo = repository.getProviderRepository(); 300 301 providerRepo.setPushChanges(pushChanges); 302 303 if (!(workItem == null || workItem.isEmpty())) { 304 providerRepo.setWorkItem(workItem); 305 } 306 307 if (!(username == null || username.isEmpty())) { 308 providerRepo.setUser(username); 309 } 310 311 if (!(password == null || password.isEmpty())) { 312 providerRepo.setPassword(password); 313 } 314 315 if (repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost) { 316 ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); 317 318 loadInfosFromSettings(repo); 319 320 if (!(username == null || username.isEmpty())) { 321 repo.setUser(username); 322 } 323 324 if (!(password == null || password.isEmpty())) { 325 repo.setPassword(password); 326 } 327 328 if (!(privateKey == null || privateKey.isEmpty())) { 329 repo.setPrivateKey(privateKey); 330 } 331 332 if (!(passphrase == null || passphrase.isEmpty())) { 333 repo.setPassphrase(passphrase); 334 } 335 } 336 337 if (!(tagBase == null || tagBase.isEmpty()) 338 && repository.getProvider().equals("svn")) { 339 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); 340 341 svnRepo.setTagBase(tagBase); 342 } 343 344 return repository; 345 } catch (ScmRepositoryException e) { 346 if (!e.getValidationMessages().isEmpty()) { 347 for (String message : e.getValidationMessages()) { 348 getLog().error(message); 349 } 350 } 351 throw new ScmException("Can't load the scm provider.", e); 352 } catch (RuntimeException e) { 353 throw new ScmException("Can't load the scm provider.", e); 354 } 355 } 356 357 /** 358 * Load username password from settings if user has not set them in JVM properties. 359 * 360 * @param repo not null 361 */ 362 private void loadInfosFromSettings(ScmProviderRepositoryWithHost repo) { 363 if (username == null || password == null) { 364 String serverId = this.serverId; 365 if (serverId == null || serverId.isEmpty()) { 366 // construct server id from scm repository host and port 367 serverId = repo.getHost(); 368 int port = repo.getPort(); 369 if (port > 0) { 370 serverId += ":" + port; 371 } 372 } 373 374 Server server = this.settings.getServer(serverId); 375 376 if (server != null) { 377 server = decrypt(server); 378 379 if (username == null) { 380 username = server.getUsername(); 381 } 382 383 if (password == null) { 384 password = server.getPassword(); 385 } 386 387 if (privateKey == null) { 388 privateKey = server.getPrivateKey(); 389 } 390 391 if (passphrase == null) { 392 passphrase = server.getPassphrase(); 393 } 394 } 395 } 396 } 397 398 private Server decrypt(Server server) { 399 SettingsDecryptionResult result = settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(server)); 400 for (SettingsProblem problem : result.getProblems()) { 401 getLog().error(problem.getMessage(), problem.getException()); 402 } 403 404 return result.getServer(); 405 } 406 407 public void checkResult(ScmResult result) throws MojoExecutionException { 408 if (!result.isSuccess()) { 409 getLog().error("Provider message:"); 410 411 getLog().error(result.getProviderMessage() == null ? "" : result.getProviderMessage()); 412 413 getLog().error("Command output:"); 414 415 getLog().error(result.getCommandOutput() == null ? "" : result.getCommandOutput()); 416 417 throw new MojoExecutionException("Command failed: " + Objects.toString(result.getProviderMessage())); 418 } 419 } 420 421 public String getIncludes() { 422 return includes; 423 } 424 425 public void setIncludes(String includes) { 426 this.includes = includes; 427 } 428 429 public String getExcludes() { 430 return excludes; 431 } 432 433 public void setExcludes(String excludes) { 434 this.excludes = excludes; 435 } 436 437 public ScmVersion getScmVersion(String versionType, String version) throws MojoExecutionException { 438 if ((versionType == null || versionType.isEmpty()) && (version != null && !version.isEmpty())) { 439 throw new MojoExecutionException("You must specify the version type."); 440 } 441 442 if (version == null || version.isEmpty()) { 443 return null; 444 } 445 446 if (VERSION_TYPE_BRANCH.equals(versionType)) { 447 return new ScmBranch(version); 448 } 449 450 if (VERSION_TYPE_TAG.equals(versionType)) { 451 return new ScmTag(version); 452 } 453 454 if (VERSION_TYPE_REVISION.equals(versionType)) { 455 return new ScmRevision(version); 456 } 457 458 throw new MojoExecutionException("Unknown '" + versionType + "' version type."); 459 } 460 461 protected void handleExcludesIncludesAfterCheckoutAndExport(File checkoutDirectory) throws MojoExecutionException { 462 List<String> includes = new ArrayList<>(); 463 464 if (!StringUtils.isBlank(this.getIncludes())) { 465 String[] tokens = StringUtils.split(this.getIncludes(), ","); 466 for (int i = 0; i < tokens.length; ++i) { 467 includes.add(tokens[i]); 468 } 469 } 470 471 List<String> excludes = new ArrayList<>(); 472 473 if (!StringUtils.isBlank(this.getExcludes())) { 474 String[] tokens = StringUtils.split(this.getExcludes(), ","); 475 for (int i = 0; i < tokens.length; ++i) { 476 excludes.add(tokens[i]); 477 } 478 } 479 480 if (includes.isEmpty() && excludes.isEmpty()) { 481 return; 482 } 483 484 FileSetManager fileSetManager = new FileSetManager(); 485 486 FileSet fileset = new FileSet(); 487 fileset.setDirectory(checkoutDirectory.getAbsolutePath()); 488 fileset.setIncludes(excludes); // revert the order to do the delete 489 fileset.setExcludes(includes); 490 fileset.setUseDefaultExcludes(false); 491 492 try { 493 fileSetManager.delete(fileset); 494 } catch (IOException e) { 495 throw new MojoExecutionException( 496 "Error found while cleaning up output directory base on " + "excludes/includes configurations.", e); 497 } 498 } 499}