blob: 37d41fcc01532140cb5d95946be94aeb802a3abc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.shared.release.phase;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.apache.maven.project.MavenProject;
import org.apache.maven.scm.CommandParameter;
import org.apache.maven.scm.CommandParameters;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmTag;
import org.apache.maven.scm.command.checkout.CheckOutScmResult;
import org.apache.maven.scm.manager.NoSuchScmProviderException;
import org.apache.maven.scm.provider.ScmProvider;
import org.apache.maven.scm.repository.ScmRepository;
import org.apache.maven.scm.repository.ScmRepositoryException;
import org.apache.maven.shared.release.ReleaseExecutionException;
import org.apache.maven.shared.release.ReleaseFailureException;
import org.apache.maven.shared.release.ReleaseResult;
import org.apache.maven.shared.release.config.ReleaseDescriptor;
import org.apache.maven.shared.release.env.ReleaseEnvironment;
import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
import org.apache.maven.shared.release.util.ReleaseUtil;
import org.codehaus.plexus.util.FileUtils;
import static java.util.Objects.requireNonNull;
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
/**
* @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
*/
@Singleton
@Named("checkout-project-from-scm")
public class CheckoutProjectFromScm extends AbstractReleasePhase {
/**
* Tool that gets a configured SCM repository from release configuration.
*/
private final ScmRepositoryConfigurator scmRepositoryConfigurator;
@Inject
public CheckoutProjectFromScm(ScmRepositoryConfigurator scmRepositoryConfigurator) {
this.scmRepositoryConfigurator = requireNonNull(scmRepositoryConfigurator);
}
@Override
public ReleaseResult execute(
ReleaseDescriptor releaseDescriptor,
ReleaseEnvironment releaseEnvironment,
List<MavenProject> reactorProjects)
throws ReleaseExecutionException, ReleaseFailureException {
ReleaseResult releaseResult;
if (releaseDescriptor.isLocalCheckout()) {
// in the release phase we have to change the checkout URL
// to do a local checkout instead of going over the network.
// the first step is a bit tricky, we need to know which provider! like e.g. "scm:jgit:http://"
// the offset of 4 is because 'scm:' has 4 characters...
String providerPart = releaseDescriptor
.getScmSourceUrl()
.substring(0, releaseDescriptor.getScmSourceUrl().indexOf(':', 4));
String scmPath = releaseDescriptor.getWorkingDirectory();
// now we iteratively try to checkout.
// if the local checkout fails, then we might be in a subdirectory
// and need to walk a few directories up.
do {
try {
if (scmPath.startsWith("/")) {
// cut off the first '/'
scmPath = scmPath.substring(1);
}
String scmUrl = providerPart + ":file:///" + scmPath;
releaseDescriptor.setScmSourceUrl(scmUrl);
getLogger().info("Performing a LOCAL checkout from " + releaseDescriptor.getScmSourceUrl());
releaseResult = performCheckout(releaseDescriptor, releaseEnvironment, reactorProjects);
} catch (ScmException scmEx) {
// the checkout from _this_ directory failed
releaseResult = null;
}
if (releaseResult == null || releaseResult.getResultCode() == ReleaseResult.ERROR) {
// this means that there is no SCM repo in this directory
// thus we try to step one directory up
releaseResult = null;
// remove last sub-directory path
int lastSlashPos = scmPath.lastIndexOf(File.separator);
if (lastSlashPos > 0) {
scmPath = scmPath.substring(0, lastSlashPos);
} else {
throw new ReleaseExecutionException("could not perform a local checkout");
}
}
} while (releaseResult == null);
} else {
// when there is no localCheckout, then we just do a standard SCM checkout.
try {
releaseResult = performCheckout(releaseDescriptor, releaseEnvironment, reactorProjects);
} catch (ScmException e) {
releaseResult = new ReleaseResult();
releaseResult.setResultCode(ReleaseResult.ERROR);
logError(releaseResult, e.getMessage());
throw new ReleaseExecutionException(
"An error is occurred in the checkout process: " + e.getMessage(), e);
}
}
return releaseResult;
}
private ReleaseResult performCheckout(
ReleaseDescriptor releaseDescriptor,
ReleaseEnvironment releaseEnvironment,
List<MavenProject> reactorProjects)
throws ReleaseExecutionException, ReleaseFailureException, ScmException {
ReleaseResult result = new ReleaseResult();
logInfo(result, "Checking out the project to perform the release ...");
ScmRepository repository;
ScmProvider provider;
try {
repository = scmRepositoryConfigurator.getConfiguredRepository(
releaseDescriptor, releaseEnvironment.getSettings());
provider = scmRepositoryConfigurator.getRepositoryProvider(repository);
} catch (ScmRepositoryException e) {
result.setResultCode(ReleaseResult.ERROR);
logError(result, e.getMessage());
throw new ReleaseScmRepositoryException(e.getMessage(), e.getValidationMessages());
} catch (NoSuchScmProviderException e) {
result.setResultCode(ReleaseResult.ERROR);
logError(result, e.getMessage());
throw new ReleaseExecutionException("Unable to configure SCM repository: " + e.getMessage(), e);
}
MavenProject rootProject = ReleaseUtil.getRootProject(reactorProjects);
// TODO: sanity check that it is not . or .. or lower
File checkoutDirectory =
FileUtils.resolveFile(rootProject.getBasedir(), releaseDescriptor.getCheckoutDirectory());
if (checkoutDirectory.exists()) {
try {
FileUtils.deleteDirectory(checkoutDirectory);
} catch (IOException e) {
result.setResultCode(ReleaseResult.ERROR);
logError(result, e.getMessage());
throw new ReleaseExecutionException("Unable to remove old checkout directory: " + e.getMessage(), e);
}
}
checkoutDirectory.mkdirs();
CommandParameters commandParameters = new CommandParameters();
commandParameters.setString(
CommandParameter.SHALLOW,
Boolean.valueOf(releaseDescriptor.isScmShallowClone()).toString());
CheckOutScmResult scmResult = provider.checkOut(
repository,
new ScmFileSet(checkoutDirectory),
new ScmTag(releaseDescriptor.getScmReleaseLabel()),
commandParameters);
if (releaseDescriptor.isLocalCheckout() && !scmResult.isSuccess()) {
// this is not beautiful but needed to indicate that the execute() method
// should continue in the parent directory
return null;
}
String scmRelativePathProjectDirectory = scmResult.getRelativePathProjectDirectory();
if (scmRelativePathProjectDirectory == null || scmRelativePathProjectDirectory.isEmpty()) {
Path workingDirectory = Paths.get(releaseDescriptor.getWorkingDirectory());
Path rootProjectBasedir;
try {
rootProjectBasedir = rootProject.getBasedir().toPath().toRealPath(LinkOption.NOFOLLOW_LINKS);
} catch (IOException e) {
throw new ReleaseExecutionException(e.getMessage(), e);
}
scmRelativePathProjectDirectory =
workingDirectory.relativize(rootProjectBasedir).toString();
}
releaseDescriptor.setScmRelativePathProjectDirectory(scmRelativePathProjectDirectory);
if (!scmResult.isSuccess()) {
result.setResultCode(ReleaseResult.ERROR);
logError(result, scmResult.getProviderMessage());
throw new ReleaseScmCommandException("Unable to checkout from SCM", scmResult);
}
result.setResultCode(ReleaseResult.SUCCESS);
return result;
}
@Override
public ReleaseResult simulate(
ReleaseDescriptor releaseDescriptor,
ReleaseEnvironment releaseEnvironment,
List<MavenProject> reactorProjects)
throws ReleaseExecutionException, ReleaseFailureException {
ReleaseResult result = new ReleaseResult();
MavenProject rootProject = ReleaseUtil.getRootProject(reactorProjects);
File checkoutDirectory =
FileUtils.resolveFile(rootProject.getBasedir(), releaseDescriptor.getCheckoutDirectory());
if (releaseDescriptor.isLocalCheckout()) {
logInfo(
result,
"The project would have a " + buffer().strong("local") + " check out to perform the release from "
+ checkoutDirectory + "...");
} else {
logInfo(
result,
"The project would be checked out to perform the release from " + checkoutDirectory + "...");
}
result.setResultCode(ReleaseResult.SUCCESS);
return result;
}
}