View Javadoc
1   package org.apache.maven.shared.release.phase;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.file.LinkOption;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.List;
28  
29  import org.apache.maven.project.MavenProject;
30  import org.apache.maven.scm.CommandParameter;
31  import org.apache.maven.scm.CommandParameters;
32  import org.apache.maven.scm.ScmException;
33  import org.apache.maven.scm.ScmFileSet;
34  import org.apache.maven.scm.ScmTag;
35  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
36  import org.apache.maven.scm.manager.NoSuchScmProviderException;
37  import org.apache.maven.scm.provider.ScmProvider;
38  import org.apache.maven.scm.repository.ScmRepository;
39  import org.apache.maven.scm.repository.ScmRepositoryException;
40  import org.apache.maven.shared.release.ReleaseExecutionException;
41  import org.apache.maven.shared.release.ReleaseFailureException;
42  import org.apache.maven.shared.release.ReleaseResult;
43  import org.apache.maven.shared.release.config.ReleaseDescriptor;
44  import org.apache.maven.shared.release.env.ReleaseEnvironment;
45  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
46  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
47  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
48  import org.apache.maven.shared.release.util.ReleaseUtil;
49  import org.codehaus.plexus.component.annotations.Component;
50  import org.codehaus.plexus.component.annotations.Requirement;
51  import org.codehaus.plexus.util.FileUtils;
52  import org.codehaus.plexus.util.StringUtils;
53  
54  /**
55   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
56   */
57  @Component( role = ReleasePhase.class, hint = "checkout-project-from-scm" )
58  public class CheckoutProjectFromScm
59      extends AbstractReleasePhase
60  {
61      /**
62       * Tool that gets a configured SCM repository from release configuration.
63       */
64      @Requirement
65      private ScmRepositoryConfigurator scmRepositoryConfigurator;
66  
67      @Override
68      public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
69                                    List<MavenProject> reactorProjects )
70              throws ReleaseExecutionException, ReleaseFailureException
71      {
72          ReleaseResult releaseResult = null;
73  
74          if ( releaseDescriptor.isLocalCheckout() )
75          {
76              // in the release phase we have to change the checkout URL
77              // to do a local checkout instead of going over the network.
78  
79              // the first step is a bit tricky, we need to know which provider! like e.g. "scm:jgit:http://"
80              // the offset of 4 is because 'scm:' has 4 characters...
81              String providerPart = releaseDescriptor.getScmSourceUrl()
82                      .substring( 0, releaseDescriptor.getScmSourceUrl().indexOf( ':', 4 ) );
83  
84              String scmPath = releaseDescriptor.getWorkingDirectory();
85  
86              // now we iteratively try to checkout.
87              // if the local checkout fails, then we might be in a subdirectory
88              // and need to walk a few directories up.
89              do
90              {
91                  try
92                  {
93                      if ( scmPath.startsWith( "/" ) )
94                      {
95                          // cut off the first '/'
96                          scmPath = scmPath.substring( 1 );
97                      }
98  
99                      String scmUrl = providerPart + ":file:///" + scmPath;
100                     releaseDescriptor.setScmSourceUrl( scmUrl );
101                     getLogger().info( "Performing a LOCAL checkout from " + releaseDescriptor.getScmSourceUrl() );
102 
103                     releaseResult = performCheckout( releaseDescriptor, releaseEnvironment, reactorProjects );
104                 }
105                 catch ( ScmException scmEx )
106                 {
107                     // the checkout from _this_ directory failed
108                     releaseResult = null;
109                 }
110 
111                 if ( releaseResult == null || releaseResult.getResultCode() == ReleaseResult.ERROR )
112                 {
113                     // this means that there is no SCM repo in this directory
114                     // thus we try to step one directory up
115                     releaseResult = null;
116 
117                     // remove last sub-directory path
118                     int lastSlashPos = scmPath.lastIndexOf( File.separator );
119                     if ( lastSlashPos > 0 )
120                     {
121                         scmPath = scmPath.substring( 0, lastSlashPos );
122                     }
123                     else
124                     {
125                         throw new ReleaseExecutionException( "could not perform a local checkout" );
126                     }
127                 }
128             }
129             while ( releaseResult == null );
130         }
131         else
132         {
133             // when there is no localCheckout, then we just do a standard SCM checkout.
134             try
135             {
136                 releaseResult =  performCheckout( releaseDescriptor, releaseEnvironment, reactorProjects );
137             }
138             catch ( ScmException e )
139             {
140                 releaseResult = new ReleaseResult();
141                 releaseResult.setResultCode( ReleaseResult.ERROR );
142                 logError( releaseResult, e.getMessage() );
143 
144                 throw new ReleaseExecutionException( "An error is occurred in the checkout process: "
145                                                      + e.getMessage(), e );
146             }
147 
148         }
149 
150         return releaseResult;
151     }
152 
153 
154     private ReleaseResult performCheckout( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
155                                   List<MavenProject> reactorProjects )
156         throws ReleaseExecutionException, ReleaseFailureException, ScmException
157     {
158         ReleaseResult result = new ReleaseResult();
159 
160         logInfo( result, "Checking out the project to perform the release ..." );
161 
162         ScmRepository repository;
163         ScmProvider provider;
164 
165         try
166         {
167             repository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
168                                                                             releaseEnvironment.getSettings() );
169 
170             provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
171         }
172         catch ( ScmRepositoryException e )
173         {
174             result.setResultCode( ReleaseResult.ERROR );
175             logError( result, e.getMessage() );
176 
177             throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
178         }
179         catch ( NoSuchScmProviderException e )
180         {
181             result.setResultCode( ReleaseResult.ERROR );
182             logError( result, e.getMessage() );
183 
184             throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
185         }
186 
187         MavenProject rootProject = ReleaseUtil.getRootProject( reactorProjects );
188 
189         // TODO: sanity check that it is not . or .. or lower
190         File checkoutDirectory =
191             FileUtils.resolveFile( rootProject.getBasedir(), releaseDescriptor.getCheckoutDirectory() );
192 
193         if ( checkoutDirectory.exists() )
194         {
195             try
196             {
197                 FileUtils.deleteDirectory( checkoutDirectory );
198             }
199             catch ( IOException e )
200             {
201                 result.setResultCode( ReleaseResult.ERROR );
202                 logError( result, e.getMessage() );
203 
204                 throw new ReleaseExecutionException( "Unable to remove old checkout directory: " + e.getMessage(), e );
205             }
206         }
207 
208         checkoutDirectory.mkdirs();
209 
210         CommandParameters commandParameters = new CommandParameters();
211         commandParameters.setString( CommandParameter.SHALLOW, Boolean.TRUE.toString() );
212 
213         CheckOutScmResult scmResult = provider.checkOut( repository, new ScmFileSet( checkoutDirectory ),
214                            new ScmTag( releaseDescriptor.getScmReleaseLabel() ), commandParameters );
215 
216         if ( releaseDescriptor.isLocalCheckout() && !scmResult.isSuccess() )
217         {
218             // this is not beautiful but needed to indicate that the execute() method
219             // should continue in the parent directory
220             return null;
221         }
222 
223         String scmRelativePathProjectDirectory = scmResult.getRelativePathProjectDirectory();
224         if ( StringUtils.isEmpty( scmRelativePathProjectDirectory ) )
225         {
226             Path workingDirectory = Paths.get( releaseDescriptor.getWorkingDirectory() );
227 
228             Path rootProjectBasedir;
229             try
230             {
231                 rootProjectBasedir = rootProject.getBasedir().toPath().toRealPath( LinkOption.NOFOLLOW_LINKS );
232             }
233             catch ( IOException e )
234             {
235                 throw new ReleaseExecutionException( e.getMessage(), e );
236             }
237             
238             scmRelativePathProjectDirectory = workingDirectory.relativize( rootProjectBasedir ).toString();
239         }
240         releaseDescriptor.setScmRelativePathProjectDirectory( scmRelativePathProjectDirectory );
241 
242         if ( !scmResult.isSuccess() )
243         {
244             result.setResultCode( ReleaseResult.ERROR );
245             logError( result, scmResult.getProviderMessage() );
246 
247             throw new ReleaseScmCommandException( "Unable to checkout from SCM", scmResult );
248         }
249 
250         result.setResultCode( ReleaseResult.SUCCESS );
251 
252         return result;
253     }
254 
255     @Override
256     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
257                                    List<MavenProject> reactorProjects )
258         throws ReleaseExecutionException, ReleaseFailureException
259     {
260         ReleaseResult result = new ReleaseResult();
261 
262         if ( releaseDescriptor.isLocalCheckout() )
263         {
264             logInfo( result, "This would be a LOCAL check out to perform the release ..." );
265         }
266         else
267         {
268             logInfo( result, "The project would be checked out to perform the release ..." );
269         }
270 
271         result.setResultCode( ReleaseResult.SUCCESS );
272         return result;
273     }
274 }