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 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 099 */ 100 @Parameter(property = "username") 101 private String username; 102 103 /** 104 * The user password. 105 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 106 */ 107 @Parameter(property = "password") 108 private String password; 109 110 /** 111 * The private key. 112 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 113 */ 114 @Parameter(property = "privateKey") 115 private String privateKey; 116 117 /** 118 * The passphrase. 119 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 120 */ 121 @Parameter(property = "passphrase") 122 private String passphrase; 123 124 /** 125 * 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. 126 * If not set the default lookup uses the SCM URL to construct the server id like this: 127 * {@code server-id=scm-host[":"scm-port]}. 128 * <p> 129 * Currently the POM does not allow to specify a server id for the SCM section. 130 * <p> 131 * Explicit authentication information provided via {@link #username}, {@link #password} or {@link #privateKey} will take precedence. 132 * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a> 133 * @since 2.2.0 134 */ 135 @Parameter(property = "project.scm.id") 136 private String serverId; 137 138 /** 139 * The url of tags base directory (used by svn protocol). It is not 140 * necessary to set it if you use the standard svn layout 141 * (branches/tags/trunk). 142 */ 143 @Parameter(property = "tagBase") 144 private String tagBase; 145 146 /** 147 * Comma separated list of includes file pattern. 148 */ 149 @Parameter(property = "includes") 150 private String includes; 151 152 /** 153 * Comma separated list of excludes file pattern. 154 */ 155 @Parameter(property = "excludes") 156 private String excludes; 157 158 /** 159 * The base directory. 160 */ 161 @Parameter(property = "basedir", required = true) 162 private File basedir; 163 164 @Parameter(defaultValue = "${settings}", readonly = true) 165 private Settings settings; 166 167 /** 168 * List of System properties to set before executing the SCM command. 169 */ 170 @Parameter 171 private Properties systemProperties; 172 173 /** 174 * List of remapped provider implementations. Allows to bind a different implementation than the default one to a provider id. 175 * The key is the remapped provider id, the value is the default provider id the implementation is bound to. 176 * @see <a href="https://maven.apache.org/scm/scms-overview.html">Supported SCMs</a> 177 */ 178 @Parameter 179 private Map<String, String> providerImplementations; 180 181 /** 182 * Should distributed changes be pushed to the central repository? 183 * For many distributed SCMs like Git, a change like a commit 184 * is only stored in your local copy of the repository. Pushing 185 * the change allows your to more easily share it with other users. 186 * 187 * @since 1.4 188 */ 189 @Parameter(property = "pushChanges", defaultValue = "true") 190 private boolean pushChanges; 191 192 /** 193 * A workItem for SCMs like RTC, TFS etc, that may require additional 194 * information to perform a pushChange operation. 195 * 196 * @since 1.9.5 197 */ 198 @Parameter(property = "workItem") 199 @Deprecated 200 private String workItem; 201 202 private final ScmManager manager; 203 204 private final SettingsDecrypter settingsDecrypter; 205 206 protected AbstractScmMojo(ScmManager manager, SettingsDecrypter settingsDecrypter) { 207 this.manager = manager; 208 this.settingsDecrypter = settingsDecrypter; 209 } 210 211 /** {@inheritDoc} */ 212 public void execute() throws MojoExecutionException { 213 if (systemProperties != null) { 214 // Add all system properties configured by the user 215 Iterator<Object> iter = systemProperties.keySet().iterator(); 216 217 while (iter.hasNext()) { 218 String key = (String) iter.next(); 219 220 String value = systemProperties.getProperty(key); 221 222 System.setProperty(key, value); 223 } 224 } 225 226 if (providerImplementations != null && !providerImplementations.isEmpty()) { 227 for (Entry<String, String> entry : providerImplementations.entrySet()) { 228 String providerType = entry.getKey(); 229 String providerImplementation = entry.getValue(); 230 getLog().info("Change the default '" + providerType + "' provider implementation to '" 231 + providerImplementation + "'."); 232 getScmManager().setScmProviderImplementation(providerType, providerImplementation); 233 } 234 } 235 } 236 237 protected void setConnectionType(String connectionType) { 238 this.connectionType = connectionType; 239 } 240 241 public String getConnectionUrl() { 242 boolean requireDeveloperConnection = !"connection".equals(connectionType.toLowerCase()); 243 if ((connectionUrl != null && !connectionUrl.isEmpty()) && !requireDeveloperConnection) { 244 return connectionUrl; 245 } else if (developerConnectionUrl != null && !developerConnectionUrl.isEmpty()) { 246 return developerConnectionUrl; 247 } 248 if (requireDeveloperConnection) { 249 throw new NullPointerException("You need to define a developerConnectionUrl parameter"); 250 } else { 251 throw new NullPointerException("You need to define a connectionUrl parameter"); 252 } 253 } 254 255 public void setConnectionUrl(String connectionUrl) { 256 this.connectionUrl = connectionUrl; 257 } 258 259 public File getWorkingDirectory() { 260 if (workingDirectory == null) { 261 return basedir; 262 } 263 264 return workingDirectory; 265 } 266 267 public File getBasedir() { 268 return this.basedir; 269 } 270 271 public void setWorkingDirectory(File workingDirectory) { 272 this.workingDirectory = workingDirectory; 273 } 274 275 public ScmManager getScmManager() { 276 return manager; 277 } 278 279 public ScmFileSet getFileSet() throws IOException { 280 if (includes != null || excludes != null) { 281 return new ScmFileSet(getWorkingDirectory(), includes, excludes); 282 } else { 283 return new ScmFileSet(getWorkingDirectory()); 284 } 285 } 286 287 public ScmRepository getScmRepository() throws ScmException { 288 ScmRepository repository; 289 290 try { 291 repository = getScmManager().makeScmRepository(getConnectionUrl()); 292 293 ScmProviderRepository providerRepo = repository.getProviderRepository(); 294 295 providerRepo.setPushChanges(pushChanges); 296 297 if (!(workItem == null || workItem.isEmpty())) { 298 providerRepo.setWorkItem(workItem); 299 } 300 301 if (!(username == null || username.isEmpty())) { 302 providerRepo.setUser(username); 303 } 304 305 if (!(password == null || password.isEmpty())) { 306 providerRepo.setPassword(password); 307 } 308 309 if (repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost) { 310 ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); 311 312 loadInfosFromSettings(repo); 313 314 if (!(username == null || username.isEmpty())) { 315 repo.setUser(username); 316 } 317 318 if (!(password == null || password.isEmpty())) { 319 repo.setPassword(password); 320 } 321 322 if (!(privateKey == null || privateKey.isEmpty())) { 323 repo.setPrivateKey(privateKey); 324 } 325 326 if (!(passphrase == null || passphrase.isEmpty())) { 327 repo.setPassphrase(passphrase); 328 } 329 } 330 331 if (!(tagBase == null || tagBase.isEmpty()) 332 && repository.getProvider().equals("svn")) { 333 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); 334 335 svnRepo.setTagBase(tagBase); 336 } 337 } catch (ScmRepositoryException e) { 338 if (!e.getValidationMessages().isEmpty()) { 339 for (String message : e.getValidationMessages()) { 340 getLog().error(message); 341 } 342 } 343 344 throw new ScmException("Can't load the scm provider.", e); 345 } catch (Exception e) { 346 throw new ScmException("Can't load the scm provider.", e); 347 } 348 349 return repository; 350 } 351 352 /** 353 * Load username password from settings if user has not set them in JVM properties 354 * 355 * @param repo not null 356 */ 357 private void loadInfosFromSettings(ScmProviderRepositoryWithHost repo) { 358 if (username == null || password == null) { 359 String serverId = this.serverId; 360 if (serverId == null || serverId.isEmpty()) { 361 // construct server id from scm repository host and port 362 serverId = repo.getHost(); 363 int port = repo.getPort(); 364 if (port > 0) { 365 serverId += ":" + port; 366 } 367 } 368 369 Server server = this.settings.getServer(serverId); 370 371 if (server != null) { 372 server = decrypt(server); 373 374 if (username == null) { 375 username = server.getUsername(); 376 } 377 378 if (password == null) { 379 password = server.getPassword(); 380 } 381 382 if (privateKey == null) { 383 privateKey = server.getPrivateKey(); 384 } 385 386 if (passphrase == null) { 387 passphrase = server.getPassphrase(); 388 } 389 } 390 } 391 } 392 393 private Server decrypt(Server server) { 394 SettingsDecryptionResult result = settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(server)); 395 for (SettingsProblem problem : result.getProblems()) { 396 getLog().error(problem.getMessage(), problem.getException()); 397 } 398 399 return result.getServer(); 400 } 401 402 public void checkResult(ScmResult result) throws MojoExecutionException { 403 if (!result.isSuccess()) { 404 getLog().error("Provider message:"); 405 406 getLog().error(result.getProviderMessage() == null ? "" : result.getProviderMessage()); 407 408 getLog().error("Command output:"); 409 410 getLog().error(result.getCommandOutput() == null ? "" : result.getCommandOutput()); 411 412 throw new MojoExecutionException("Command failed: " + Objects.toString(result.getProviderMessage())); 413 } 414 } 415 416 public String getIncludes() { 417 return includes; 418 } 419 420 public void setIncludes(String includes) { 421 this.includes = includes; 422 } 423 424 public String getExcludes() { 425 return excludes; 426 } 427 428 public void setExcludes(String excludes) { 429 this.excludes = excludes; 430 } 431 432 public ScmVersion getScmVersion(String versionType, String version) throws MojoExecutionException { 433 if ((versionType == null || versionType.isEmpty()) && (version != null && !version.isEmpty())) { 434 throw new MojoExecutionException("You must specify the version type."); 435 } 436 437 if (version == null || version.isEmpty()) { 438 return null; 439 } 440 441 if (VERSION_TYPE_BRANCH.equals(versionType)) { 442 return new ScmBranch(version); 443 } 444 445 if (VERSION_TYPE_TAG.equals(versionType)) { 446 return new ScmTag(version); 447 } 448 449 if (VERSION_TYPE_REVISION.equals(versionType)) { 450 return new ScmRevision(version); 451 } 452 453 throw new MojoExecutionException("Unknown '" + versionType + "' version type."); 454 } 455 456 protected void handleExcludesIncludesAfterCheckoutAndExport(File checkoutDirectory) throws MojoExecutionException { 457 List<String> includes = new ArrayList<>(); 458 459 if (!StringUtils.isBlank(this.getIncludes())) { 460 String[] tokens = StringUtils.split(this.getIncludes(), ","); 461 for (int i = 0; i < tokens.length; ++i) { 462 includes.add(tokens[i]); 463 } 464 } 465 466 List<String> excludes = new ArrayList<>(); 467 468 if (!StringUtils.isBlank(this.getExcludes())) { 469 String[] tokens = StringUtils.split(this.getExcludes(), ","); 470 for (int i = 0; i < tokens.length; ++i) { 471 excludes.add(tokens[i]); 472 } 473 } 474 475 if (includes.isEmpty() && excludes.isEmpty()) { 476 return; 477 } 478 479 FileSetManager fileSetManager = new FileSetManager(); 480 481 FileSet fileset = new FileSet(); 482 fileset.setDirectory(checkoutDirectory.getAbsolutePath()); 483 fileset.setIncludes(excludes); // revert the order to do the delete 484 fileset.setExcludes(includes); 485 fileset.setUseDefaultExcludes(false); 486 487 try { 488 fileSetManager.delete(fileset); 489 } catch (IOException e) { 490 throw new MojoExecutionException( 491 "Error found while cleaning up output directory base on " + "excludes/includes configurations.", e); 492 } 493 } 494}