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.util.List;
25  
26  import org.apache.maven.project.MavenProject;
27  import org.apache.maven.scm.ScmException;
28  import org.apache.maven.scm.ScmFileSet;
29  import org.apache.maven.scm.ScmTag;
30  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
31  import org.apache.maven.scm.manager.NoSuchScmProviderException;
32  import org.apache.maven.scm.provider.ScmProvider;
33  import org.apache.maven.scm.repository.ScmRepository;
34  import org.apache.maven.scm.repository.ScmRepositoryException;
35  import org.apache.maven.shared.release.ReleaseExecutionException;
36  import org.apache.maven.shared.release.ReleaseFailureException;
37  import org.apache.maven.shared.release.ReleaseResult;
38  import org.apache.maven.shared.release.config.ReleaseDescriptor;
39  import org.apache.maven.shared.release.env.ReleaseEnvironment;
40  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
41  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
42  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
43  import org.apache.maven.shared.release.util.ReleaseUtil;
44  import org.codehaus.plexus.util.FileUtils;
45  import org.codehaus.plexus.util.StringUtils;
46  
47  /**
48   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
49   * @version $Id: CheckoutProjectFromScm.java 1430485 2013-01-08 20:20:17Z rfscholte $
50   * @plexus.component role="org.apache.maven.shared.release.phase.ReleasePhase" role-hint="checkout-project-from-scm"
51   */
52  public class CheckoutProjectFromScm
53      extends AbstractReleasePhase
54  {
55      /**
56       * Tool that gets a configured SCM repository from release configuration.
57       *
58       * @plexus.requirement
59       */
60      private ScmRepositoryConfigurator scmRepositoryConfigurator;
61  
62      /** {@inheritDoc}  */
63      public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
64                                    List<MavenProject> reactorProjects )
65              throws ReleaseExecutionException, ReleaseFailureException
66      {
67          ReleaseResult releaseResult = null;
68  
69          if ( releaseDescriptor.isLocalCheckout() )
70          {
71              // in the release phase we have to change the checkout URL
72              // to do a local checkout instead of going over the network.
73  
74              // the first step is a bit tricky, we need to know which provider! like e.g. "scm:jgit:http://"
75              // the offset of 4 is because 'scm:' has 4 characters...
76              String providerPart = releaseDescriptor.getScmSourceUrl()
77                      .substring( 0, releaseDescriptor.getScmSourceUrl().indexOf( ':', 4 ) );
78  
79              String scmPath = releaseDescriptor.getWorkingDirectory();
80  
81              // now we iteratively try to checkout.
82              // if the local checkout fails, then we might be in a subdirectory
83              // and need to walk a few directories up.
84              do
85              {
86                  try
87                  {
88                      if ( scmPath.startsWith( "/" ) )
89                      {
90                          // cut off the first '/'
91                          scmPath = scmPath.substring( 1 );
92                      }
93  
94                      String scmUrl = providerPart + ":file:///" + scmPath;
95                      releaseDescriptor.setScmSourceUrl( scmUrl );
96                      getLogger().info( "Performing a LOCAL checkout from " + releaseDescriptor.getScmSourceUrl() );
97  
98                      releaseResult = performCheckout( releaseDescriptor, releaseEnvironment, reactorProjects );
99                  }
100                 catch ( ScmException scmEx )
101                 {
102                     // the checkout from _this_ directory failed
103                     releaseResult = null;
104                 }
105 
106                 if ( releaseResult == null || releaseResult.getResultCode() == ReleaseResult.ERROR )
107                 {
108                     // this means that there is no SCM repo in this directory
109                     // thus we try to step one directory up
110                     releaseResult = null;
111 
112                     // remove last sub-directory path
113                     int lastSlashPos = scmPath.lastIndexOf( File.separator );
114                     if ( lastSlashPos > 0 )
115                     {
116                         scmPath = scmPath.substring( 0, lastSlashPos );
117                     }
118                     else
119                     {
120                         throw new ReleaseExecutionException( "could not perform a local checkout" );
121                     }
122                 }
123             }
124             while ( releaseResult == null );
125         }
126         else
127         {
128             // when there is no localCheckout, then we just do a standard SCM checkout.
129             try
130             {
131                 releaseResult =  performCheckout( releaseDescriptor, releaseEnvironment, reactorProjects );
132             }
133             catch ( ScmException e )
134             {
135                 releaseResult = new ReleaseResult();
136                 releaseResult.setResultCode( ReleaseResult.ERROR );
137                 logError( releaseResult, e.getMessage() );
138 
139                 throw new ReleaseExecutionException( "An error is occurred in the checkout process: "
140                                                      + e.getMessage(), e );
141             }
142 
143         }
144 
145         return releaseResult;
146     }
147 
148 
149     private ReleaseResult performCheckout( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
150                                   List<MavenProject> reactorProjects )
151         throws ReleaseExecutionException, ReleaseFailureException, ScmException
152     {
153         ReleaseResult result = new ReleaseResult();
154 
155         logInfo( result, "Checking out the project to perform the release ..." );
156 
157         ScmRepository repository;
158         ScmProvider provider;
159 
160         try
161         {
162             repository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
163                                                                             releaseEnvironment.getSettings() );
164 
165             provider = scmRepositoryConfigurator.getRepositoryProvider( repository );
166         }
167         catch ( ScmRepositoryException e )
168         {
169             result.setResultCode( ReleaseResult.ERROR );
170             logError( result, e.getMessage() );
171 
172             throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
173         }
174         catch ( NoSuchScmProviderException e )
175         {
176             result.setResultCode( ReleaseResult.ERROR );
177             logError( result, e.getMessage() );
178 
179             throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
180         }
181 
182         MavenProject rootProject = ReleaseUtil.getRootProject( reactorProjects );
183         // TODO: sanity check that it is not . or .. or lower
184         File checkoutDirectory;
185         if ( StringUtils.isEmpty( releaseDescriptor.getCheckoutDirectory() ) )
186         {
187             checkoutDirectory = new File( rootProject.getFile().getParentFile(), "target/checkout" );
188             releaseDescriptor.setCheckoutDirectory( checkoutDirectory.getAbsolutePath() );
189         }
190         else
191         {
192             checkoutDirectory = new File( releaseDescriptor.getCheckoutDirectory() );
193         }
194 
195         if ( checkoutDirectory.exists() )
196         {
197             try
198             {
199                 FileUtils.deleteDirectory( checkoutDirectory );
200             }
201             catch ( IOException e )
202             {
203                 result.setResultCode( ReleaseResult.ERROR );
204                 logError( result, e.getMessage() );
205 
206                 throw new ReleaseExecutionException( "Unable to remove old checkout directory: " + e.getMessage(), e );
207             }
208         }
209 
210         checkoutDirectory.mkdirs();
211 
212         CheckOutScmResult scmResult;
213 
214         scmResult = provider.checkOut( repository, new ScmFileSet( checkoutDirectory ),
215                                        new ScmTag( releaseDescriptor.getScmReleaseLabel() ) );
216 
217         if ( releaseDescriptor.isLocalCheckout() && !scmResult.isSuccess() )
218         {
219             // this is not beautiful but needed to indicate that the execute() method
220             // should continue in the parent directory
221             return null;
222         }
223 
224         String scmRelativePathProjectDirectory = scmResult.getRelativePathProjectDirectory();
225         if ( StringUtils.isEmpty( scmRelativePathProjectDirectory ) )
226         {
227             String basedir;
228             try
229             {
230                 basedir = ReleaseUtil.getCommonBasedir( reactorProjects );
231             }
232             catch ( IOException e )
233             {
234                 throw new ReleaseExecutionException( "Exception occurred while calculating common basedir: "
235                     + e.getMessage(), e );
236             }
237 
238             String rootProjectBasedir = rootProject.getBasedir().getAbsolutePath();
239             try
240             {
241                 if ( ReleaseUtil.isSymlink( rootProject.getBasedir() ) )
242                 {
243                     rootProjectBasedir = rootProject.getBasedir().getCanonicalPath();
244                 }
245             }
246             catch ( IOException e )
247             {
248                 throw new ReleaseExecutionException( e.getMessage(), e );
249             }
250             if ( rootProjectBasedir.length() > basedir.length() )
251             {
252                 scmRelativePathProjectDirectory = rootProjectBasedir.substring( basedir.length() + 1 );
253             }
254         }
255         releaseDescriptor.setScmRelativePathProjectDirectory( scmRelativePathProjectDirectory );
256 
257         if ( !scmResult.isSuccess() )
258         {
259             result.setResultCode( ReleaseResult.ERROR );
260             logError( result, scmResult.getProviderMessage() );
261 
262             throw new ReleaseScmCommandException( "Unable to checkout from SCM", scmResult );
263         }
264 
265         result.setResultCode( ReleaseResult.SUCCESS );
266 
267         return result;
268     }
269 
270     /** {@inheritDoc}  */
271     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
272                                    List<MavenProject> reactorProjects )
273         throws ReleaseExecutionException, ReleaseFailureException
274     {
275         ReleaseResult result = new ReleaseResult();
276 
277         if ( releaseDescriptor.isLocalCheckout() )
278         {
279             logInfo( result, "This would be a LOCAL check out to perform the release ..." );
280         }
281         else
282         {
283             logInfo( result, "The project would be checked out to perform the release ..." );
284         }
285 
286         result.setResultCode( ReleaseResult.SUCCESS );
287         return result;
288     }
289 }