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.manager; 020 021import java.io.File; 022import java.util.ArrayList; 023import java.util.Date; 024import java.util.List; 025import java.util.Map; 026import java.util.Optional; 027import java.util.concurrent.ConcurrentHashMap; 028 029import org.apache.maven.scm.CommandParameters; 030import org.apache.maven.scm.ScmBranch; 031import org.apache.maven.scm.ScmBranchParameters; 032import org.apache.maven.scm.ScmException; 033import org.apache.maven.scm.ScmFileSet; 034import org.apache.maven.scm.ScmTagParameters; 035import org.apache.maven.scm.ScmVersion; 036import org.apache.maven.scm.command.add.AddScmResult; 037import org.apache.maven.scm.command.blame.BlameScmRequest; 038import org.apache.maven.scm.command.blame.BlameScmResult; 039import org.apache.maven.scm.command.branch.BranchScmResult; 040import org.apache.maven.scm.command.changelog.ChangeLogScmRequest; 041import org.apache.maven.scm.command.changelog.ChangeLogScmResult; 042import org.apache.maven.scm.command.checkin.CheckInScmResult; 043import org.apache.maven.scm.command.checkout.CheckOutScmResult; 044import org.apache.maven.scm.command.diff.DiffScmResult; 045import org.apache.maven.scm.command.edit.EditScmResult; 046import org.apache.maven.scm.command.export.ExportScmResult; 047import org.apache.maven.scm.command.list.ListScmResult; 048import org.apache.maven.scm.command.mkdir.MkdirScmResult; 049import org.apache.maven.scm.command.remove.RemoveScmResult; 050import org.apache.maven.scm.command.status.StatusScmResult; 051import org.apache.maven.scm.command.tag.TagScmResult; 052import org.apache.maven.scm.command.unedit.UnEditScmResult; 053import org.apache.maven.scm.command.update.UpdateScmResult; 054import org.apache.maven.scm.provider.ScmProvider; 055import org.apache.maven.scm.provider.ScmProviderRepository; 056import org.apache.maven.scm.provider.ScmUrlUtils; 057import org.apache.maven.scm.repository.ScmRepository; 058import org.apache.maven.scm.repository.ScmRepositoryException; 059import org.apache.maven.scm.repository.UnknownRepositoryStructure; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import static java.util.Objects.requireNonNull; 064 065/** 066 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> 067 * @author <a href="mailto:brett@apache.org">Brett Porter</a> 068 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> 069 */ 070public abstract class AbstractScmManager implements ScmManager { 071 protected final Logger logger = LoggerFactory.getLogger(getClass()); 072 073 private final Map<String, ScmProvider> scmProviders = new ConcurrentHashMap<>(); 074 075 private final Map<String, String> userProviderTypes = new ConcurrentHashMap<>(); 076 077 protected void setScmProviders(Map<String, ScmProvider> providers) { 078 requireNonNull(providers); 079 this.scmProviders.clear(); 080 // first provider must not be overwritten by subsequent ones if they are registered for the same key 081 providers.forEach(this.scmProviders::putIfAbsent); 082 } 083 084 /** 085 * @param providerType the type of SCM, e.g. <code>svn</code>, <code>git</code> 086 * @param provider the provider that will be used for that SCM type 087 * @deprecated use {@link #setScmProvider(String, ScmProvider)} instead 088 */ 089 @Deprecated 090 protected void addScmProvider(String providerType, ScmProvider provider) { 091 setScmProvider(providerType, provider); 092 } 093 094 /** 095 * Set a provider to be used for a type of SCM. 096 * If there was already a designed provider for that type it will be replaced. 097 * 098 * @param providerType the type of SCM, e.g. <code>svn</code>, <code>git</code> 099 * @param provider the provider that will be used for that SCM type 100 */ 101 @Override 102 public void setScmProvider(String providerType, ScmProvider provider) { 103 requireNonNull(providerType); 104 requireNonNull(provider); 105 scmProviders.put(providerType, provider); 106 } 107 108 // ---------------------------------------------------------------------- 109 // ScmManager Implementation 110 // ---------------------------------------------------------------------- 111 112 /** 113 * {@inheritDoc} 114 */ 115 @Override 116 public ScmProvider getProviderByUrl(String scmUrl) throws ScmRepositoryException, NoSuchScmProviderException { 117 requireNonNull(scmUrl, "The scm url cannot be null."); 118 119 String providerType = ScmUrlUtils.getProvider(scmUrl); 120 121 return getProviderByType(providerType); 122 } 123 124 /** 125 * {@inheritDoc} 126 */ 127 @Override 128 public void setScmProviderImplementation(String providerType, String providerImplementation) { 129 requireNonNull(providerType); 130 requireNonNull(providerImplementation); 131 userProviderTypes.put(providerType, providerImplementation); 132 } 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override 138 public ScmProvider getProviderByType(String providerType) throws NoSuchScmProviderException { 139 String usedProviderType = System.getProperty("maven.scm.provider." + providerType + ".implementation"); 140 141 if (usedProviderType == null) { 142 usedProviderType = userProviderTypes.getOrDefault(providerType, providerType); 143 } 144 145 ScmProvider scmProvider = scmProviders.get(usedProviderType); 146 147 if (scmProvider == null) { 148 throw new NoSuchScmProviderException(usedProviderType); 149 } 150 151 return scmProvider; 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public ScmProvider getProviderByRepository(ScmRepository repository) throws NoSuchScmProviderException { 159 return getProviderByType(repository.getProvider()); 160 } 161 162 // ---------------------------------------------------------------------- 163 // Repository 164 // ---------------------------------------------------------------------- 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public ScmRepository makeScmRepository(String scmUrl) throws ScmRepositoryException, NoSuchScmProviderException { 171 requireNonNull(scmUrl, "The scm url cannot be null."); 172 173 char delimiter = ScmUrlUtils.getDelimiter(scmUrl).charAt(0); 174 175 String providerType = ScmUrlUtils.getProvider(scmUrl); 176 177 ScmProvider provider = getProviderByType(providerType); 178 179 String scmSpecificUrl = cleanScmUrl(scmUrl.substring(providerType.length() + 5)); 180 181 ScmProviderRepository providerRepository = provider.makeProviderScmRepository(scmSpecificUrl, delimiter); 182 183 return new ScmRepository(providerType, providerRepository); 184 } 185 186 /** 187 * Clean the SCM url by removing all ../ in path. 188 * 189 * @param scmUrl the SCM url 190 * @return the cleaned SCM url 191 */ 192 protected String cleanScmUrl(String scmUrl) { 193 requireNonNull(scmUrl, "The scm url cannot be null."); 194 195 String pathSeparator = ""; 196 197 int indexOfDoubleDot = -1; 198 199 // Clean Unix path 200 if (scmUrl.indexOf("../") > 1) { 201 pathSeparator = "/"; 202 203 indexOfDoubleDot = scmUrl.indexOf("../"); 204 } 205 206 // Clean windows path 207 if (scmUrl.indexOf("..\\") > 1) { 208 pathSeparator = "\\"; 209 210 indexOfDoubleDot = scmUrl.indexOf("..\\"); 211 } 212 213 if (indexOfDoubleDot > 1) { 214 int startOfTextToRemove = scmUrl.substring(0, indexOfDoubleDot - 1).lastIndexOf(pathSeparator); 215 216 String beginUrl = ""; 217 if (startOfTextToRemove >= 0) { 218 beginUrl = scmUrl.substring(0, startOfTextToRemove); 219 } 220 221 String endUrl = scmUrl.substring(indexOfDoubleDot + 3); 222 223 scmUrl = beginUrl + pathSeparator + endUrl; 224 225 // Check if we have other double dot 226 if (scmUrl.indexOf("../") > 1 || scmUrl.indexOf("..\\") > 1) { 227 scmUrl = cleanScmUrl(scmUrl); 228 } 229 } 230 231 return scmUrl; 232 } 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 public ScmRepository makeProviderScmRepository(String providerType, File path) 239 throws ScmRepositoryException, UnknownRepositoryStructure, NoSuchScmProviderException { 240 requireNonNull(providerType, "The provider type cannot be null."); 241 242 ScmProvider provider = getProviderByType(providerType); 243 244 ScmProviderRepository providerRepository = provider.makeProviderScmRepository(path); 245 246 return new ScmRepository(providerType, providerRepository); 247 } 248 249 @Override 250 public Optional<ScmRepository> makeProviderScmRepository(File workingDirectory) { 251 // 252 for (ScmProvider provider : scmProviders.values()) { 253 logger.debug( 254 "Checking if SCM provider {} is suitable for processing: {}", 255 provider.getScmType(), 256 workingDirectory); 257 try { 258 ScmProviderRepository providerRepository = provider.makeProviderScmRepository(workingDirectory); 259 return Optional.of(new ScmRepository(provider.getScmType(), providerRepository)); 260 } catch (ScmRepositoryException | UnknownRepositoryStructure e) { 261 logger.debug( 262 "SCM provider {} is not suitable for processing: {}", 263 provider.getScmType(), 264 workingDirectory, 265 e); 266 } 267 } 268 return Optional.empty(); 269 } 270 271 /** 272 * {@inheritDoc} 273 */ 274 @Override 275 public List<String> validateScmRepository(String scmUrl) { 276 List<String> messages = new ArrayList<>(ScmUrlUtils.validate(scmUrl)); 277 278 String providerType = ScmUrlUtils.getProvider(scmUrl); 279 280 ScmProvider provider; 281 282 try { 283 provider = getProviderByType(providerType); 284 } catch (NoSuchScmProviderException e) { 285 messages.add("No such provider installed '" + providerType + "'."); 286 287 return messages; 288 } 289 290 String scmSpecificUrl = cleanScmUrl(scmUrl.substring(providerType.length() + 5)); 291 292 List<String> providerMessages = provider.validateScmUrl( 293 scmSpecificUrl, ScmUrlUtils.getDelimiter(scmUrl).charAt(0)); 294 295 requireNonNull(providerMessages, "The SCM provider cannot return null from validateScmUrl()."); 296 297 messages.addAll(providerMessages); 298 299 return messages; 300 } 301 302 /** 303 * {@inheritDoc} 304 */ 305 @Override 306 public AddScmResult add(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 307 return this.getProviderByRepository(repository).add(repository, fileSet); 308 } 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override 314 public AddScmResult add(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException { 315 return this.getProviderByRepository(repository).add(repository, fileSet, message); 316 } 317 318 /** 319 * {@inheritDoc} 320 */ 321 @Override 322 public BranchScmResult branch(ScmRepository repository, ScmFileSet fileSet, String branchName) throws ScmException { 323 ScmBranchParameters scmBranchParameters = new ScmBranchParameters(""); 324 return this.getProviderByRepository(repository).branch(repository, fileSet, branchName, scmBranchParameters); 325 } 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override 331 public BranchScmResult branch(ScmRepository repository, ScmFileSet fileSet, String branchName, String message) 332 throws ScmException { 333 ScmBranchParameters scmBranchParameters = new ScmBranchParameters(message); 334 return this.getProviderByRepository(repository).branch(repository, fileSet, branchName, scmBranchParameters); 335 } 336 337 /** 338 * {@inheritDoc} 339 */ 340 @Override 341 public ChangeLogScmResult changeLog( 342 ScmRepository repository, ScmFileSet fileSet, Date startDate, Date endDate, int numDays, ScmBranch branch) 343 throws ScmException { 344 return this.getProviderByRepository(repository) 345 .changeLog(repository, fileSet, startDate, endDate, numDays, branch); 346 } 347 348 /** 349 * {@inheritDoc} 350 */ 351 @Override 352 public ChangeLogScmResult changeLog( 353 ScmRepository repository, 354 ScmFileSet fileSet, 355 Date startDate, 356 Date endDate, 357 int numDays, 358 ScmBranch branch, 359 String datePattern) 360 throws ScmException { 361 return this.getProviderByRepository(repository) 362 .changeLog(repository, fileSet, startDate, endDate, numDays, branch, datePattern); 363 } 364 365 /** 366 * {@inheritDoc} 367 */ 368 @Override 369 public ChangeLogScmResult changeLog(ChangeLogScmRequest scmRequest) throws ScmException { 370 return this.getProviderByRepository(scmRequest.getScmRepository()).changeLog(scmRequest); 371 } 372 373 /** 374 * {@inheritDoc} 375 */ 376 @Override 377 public ChangeLogScmResult changeLog( 378 ScmRepository repository, ScmFileSet fileSet, ScmVersion startVersion, ScmVersion endVersion) 379 throws ScmException { 380 return this.getProviderByRepository(repository).changeLog(repository, fileSet, startVersion, endVersion); 381 } 382 383 /** 384 * {@inheritDoc} 385 */ 386 @Override 387 public ChangeLogScmResult changeLog( 388 ScmRepository repository, 389 ScmFileSet fileSet, 390 ScmVersion startRevision, 391 ScmVersion endRevision, 392 String datePattern) 393 throws ScmException { 394 return this.getProviderByRepository(repository) 395 .changeLog(repository, fileSet, startRevision, endRevision, datePattern); 396 } 397 398 /** 399 * {@inheritDoc} 400 */ 401 @Override 402 public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException { 403 return this.getProviderByRepository(repository).checkIn(repository, fileSet, message); 404 } 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override 410 public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, ScmVersion revision, String message) 411 throws ScmException { 412 return this.getProviderByRepository(repository).checkIn(repository, fileSet, revision, message); 413 } 414 415 @Override 416 public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, CommandParameters commandParameters) 417 throws ScmException { 418 return this.getProviderByRepository(repository).checkIn(repository, fileSet, commandParameters); 419 } 420 421 /** 422 * {@inheritDoc} 423 */ 424 @Override 425 public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 426 return this.getProviderByRepository(repository).checkOut(repository, fileSet); 427 } 428 429 /** 430 * {@inheritDoc} 431 */ 432 @Override 433 public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet, ScmVersion version) 434 throws ScmException { 435 return this.getProviderByRepository(repository).checkOut(repository, fileSet, version); 436 } 437 438 /** 439 * {@inheritDoc} 440 */ 441 @Override 442 public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet, boolean recursive) 443 throws ScmException { 444 return this.getProviderByRepository(repository).checkOut(repository, fileSet, recursive); 445 } 446 447 /** 448 * {@inheritDoc} 449 */ 450 @Override 451 public CheckOutScmResult checkOut( 452 ScmRepository repository, ScmFileSet fileSet, ScmVersion version, boolean recursive) throws ScmException { 453 return this.getProviderByRepository(repository).checkOut(repository, fileSet, version, recursive); 454 } 455 456 /** 457 * {@inheritDoc} 458 */ 459 @Override 460 public DiffScmResult diff( 461 ScmRepository repository, ScmFileSet fileSet, ScmVersion startVersion, ScmVersion endVersion) 462 throws ScmException { 463 return this.getProviderByRepository(repository).diff(repository, fileSet, startVersion, endVersion); 464 } 465 466 /** 467 * {@inheritDoc} 468 */ 469 @Override 470 public EditScmResult edit(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 471 return this.getProviderByRepository(repository).edit(repository, fileSet); 472 } 473 474 /** 475 * {@inheritDoc} 476 */ 477 @Override 478 public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 479 return this.getProviderByRepository(repository).export(repository, fileSet); 480 } 481 482 /** 483 * {@inheritDoc} 484 */ 485 @Override 486 public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet, ScmVersion version) 487 throws ScmException { 488 return this.getProviderByRepository(repository).export(repository, fileSet, version); 489 } 490 491 /** 492 * {@inheritDoc} 493 */ 494 @Override 495 public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet, String outputDirectory) 496 throws ScmException { 497 return this.getProviderByRepository(repository).export(repository, fileSet, (ScmVersion) null, outputDirectory); 498 } 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override 504 public ExportScmResult export( 505 ScmRepository repository, ScmFileSet fileSet, ScmVersion version, String outputDirectory) 506 throws ScmException { 507 return this.getProviderByRepository(repository).export(repository, fileSet, version, outputDirectory); 508 } 509 510 /** 511 * {@inheritDoc} 512 */ 513 @Override 514 public ListScmResult list(ScmRepository repository, ScmFileSet fileSet, boolean recursive, ScmVersion version) 515 throws ScmException { 516 return this.getProviderByRepository(repository).list(repository, fileSet, recursive, version); 517 } 518 519 /** 520 * {@inheritDoc} 521 */ 522 @Override 523 public MkdirScmResult mkdir(ScmRepository repository, ScmFileSet fileSet, String message, boolean createInLocal) 524 throws ScmException { 525 return this.getProviderByRepository(repository).mkdir(repository, fileSet, message, createInLocal); 526 } 527 528 /** 529 * {@inheritDoc} 530 */ 531 @Override 532 public RemoveScmResult remove(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException { 533 return this.getProviderByRepository(repository).remove(repository, fileSet, message); 534 } 535 536 /** 537 * {@inheritDoc} 538 */ 539 @Override 540 public StatusScmResult status(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 541 return this.getProviderByRepository(repository).status(repository, fileSet); 542 } 543 544 /** 545 * {@inheritDoc} 546 */ 547 @Override 548 public TagScmResult tag(ScmRepository repository, ScmFileSet fileSet, String tagName) throws ScmException { 549 return this.tag(repository, fileSet, tagName, ""); 550 } 551 552 /** 553 * {@inheritDoc} 554 */ 555 @Override 556 public TagScmResult tag(ScmRepository repository, ScmFileSet fileSet, String tagName, String message) 557 throws ScmException { 558 ScmTagParameters scmTagParameters = new ScmTagParameters(message); 559 return this.getProviderByRepository(repository).tag(repository, fileSet, tagName, scmTagParameters); 560 } 561 562 /** 563 * {@inheritDoc} 564 */ 565 @Override 566 public UnEditScmResult unedit(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 567 return this.getProviderByRepository(repository).unedit(repository, fileSet); 568 } 569 570 /** 571 * {@inheritDoc} 572 */ 573 @Override 574 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet) throws ScmException { 575 return this.getProviderByRepository(repository).update(repository, fileSet); 576 } 577 578 /** 579 * {@inheritDoc} 580 */ 581 @Override 582 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version) 583 throws ScmException { 584 return this.getProviderByRepository(repository).update(repository, fileSet, version); 585 } 586 587 /** 588 * {@inheritDoc} 589 */ 590 @Override 591 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, boolean runChangelog) 592 throws ScmException { 593 return this.getProviderByRepository(repository).update(repository, fileSet, runChangelog); 594 } 595 596 /** 597 * {@inheritDoc} 598 */ 599 @Override 600 public UpdateScmResult update( 601 ScmRepository repository, ScmFileSet fileSet, ScmVersion version, boolean runChangelog) 602 throws ScmException { 603 return this.getProviderByRepository(repository).update(repository, fileSet, version, runChangelog); 604 } 605 606 /** 607 * {@inheritDoc} 608 */ 609 @Override 610 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, String datePattern) 611 throws ScmException { 612 return this.getProviderByRepository(repository).update(repository, fileSet, (ScmVersion) null, datePattern); 613 } 614 615 /** 616 * {@inheritDoc} 617 */ 618 @Override 619 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version, String datePattern) 620 throws ScmException { 621 return this.getProviderByRepository(repository).update(repository, fileSet, version, datePattern); 622 } 623 624 /** 625 * {@inheritDoc} 626 */ 627 @Override 628 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, Date lastUpdate) throws ScmException { 629 return this.getProviderByRepository(repository).update(repository, fileSet, (ScmVersion) null, lastUpdate); 630 } 631 632 /** 633 * {@inheritDoc} 634 */ 635 @Override 636 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version, Date lastUpdate) 637 throws ScmException { 638 return this.getProviderByRepository(repository).update(repository, fileSet, version, lastUpdate); 639 } 640 641 /** 642 * {@inheritDoc} 643 */ 644 @Override 645 public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, Date lastUpdate, String datePattern) 646 throws ScmException { 647 return this.getProviderByRepository(repository) 648 .update(repository, fileSet, (ScmVersion) null, lastUpdate, datePattern); 649 } 650 651 /** 652 * {@inheritDoc} 653 */ 654 @Override 655 public UpdateScmResult update( 656 ScmRepository repository, ScmFileSet fileSet, ScmVersion version, Date lastUpdate, String datePattern) 657 throws ScmException { 658 return this.getProviderByRepository(repository).update(repository, fileSet, version, lastUpdate, datePattern); 659 } 660 661 /** 662 * {@inheritDoc} 663 */ 664 @Override 665 public BlameScmResult blame(ScmRepository repository, ScmFileSet fileSet, String filename) throws ScmException { 666 return this.getProviderByRepository(repository).blame(repository, fileSet, filename); 667 } 668 669 @Override 670 public BlameScmResult blame(BlameScmRequest blameScmRequest) throws ScmException { 671 return this.getProviderByRepository(blameScmRequest.getScmRepository()).blame(blameScmRequest); 672 } 673}