View Javadoc

1   package org.apache.maven.scm.provider.hg;
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 org.apache.maven.scm.ScmException;
23  import org.apache.maven.scm.ScmFileSet;
24  import org.apache.maven.scm.ScmFileStatus;
25  import org.apache.maven.scm.ScmResult;
26  import org.apache.maven.scm.log.DefaultLog;
27  import org.apache.maven.scm.log.ScmLogger;
28  import org.apache.maven.scm.provider.hg.command.HgCommandConstants;
29  import org.apache.maven.scm.provider.hg.command.HgConsumer;
30  import org.apache.maven.scm.provider.hg.command.inventory.HgChangeSet;
31  import org.apache.maven.scm.provider.hg.command.inventory.HgOutgoingConsumer;
32  import org.codehaus.plexus.util.cli.CommandLineException;
33  import org.codehaus.plexus.util.cli.CommandLineUtils;
34  import org.codehaus.plexus.util.cli.Commandline;
35  
36  import java.io.File;
37  import java.util.ArrayList;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.Map;
41  
42  /**
43   * Common code for executing hg commands.
44   *
45   * @author <a href="mailto:thurner.rupert@ymono.net">thurner rupert</a>
46   * @version $Id: HgUtils.java 1378723 2012-08-29 20:57:26Z hboutemy $
47   */
48  public final class HgUtils
49  {
50  
51      private HgUtils()
52      {
53      }
54  
55      /**
56       * Map between command and its valid exit codes
57       */
58      private static final Map<String, List<Integer>> EXIT_CODE_MAP = new HashMap<String, List<Integer>>();
59  
60      /**
61       * Default exit codes for entries not in exitCodeMap
62       */
63      private static final List<Integer> DEFAULT_EXIT_CODES = new ArrayList<Integer>();
64  
65      /** Setup exit codes*/
66      static
67      {
68          DEFAULT_EXIT_CODES.add( new Integer( 0 ) );
69  
70          //Diff is different
71          List<Integer> diffExitCodes = new ArrayList<Integer>( 3 );
72          diffExitCodes.add( Integer.valueOf( 0 ) ); //No difference
73          diffExitCodes.add( Integer.valueOf( 1 ) ); //Conflicts in merge-like or changes in diff-like
74          diffExitCodes.add( Integer.valueOf( 2 ) ); //Unrepresentable diff changes
75          EXIT_CODE_MAP.put( HgCommandConstants.DIFF_CMD, diffExitCodes );
76          //Outgoing is different
77          List<Integer> outgoingExitCodes = new ArrayList<Integer>( 2 );
78          outgoingExitCodes.add( Integer.valueOf( 0 ) ); //There are changes
79          outgoingExitCodes.add( Integer.valueOf( 1 ) ); //No changes
80          EXIT_CODE_MAP.put( HgCommandConstants.OUTGOING_CMD, outgoingExitCodes );        
81      }
82  
83      public static ScmResult execute( HgConsumer consumer, ScmLogger logger, File workingDir, String[] cmdAndArgs )
84          throws ScmException
85      {
86          try
87          {
88              //Build commandline
89              Commandline cmd = buildCmd( workingDir, cmdAndArgs );
90              if ( logger.isInfoEnabled() )
91              {
92                  logger.info( "EXECUTING: " + maskPassword( cmd ) );
93              }
94  
95              //Execute command
96              int exitCode = executeCmd( consumer, cmd );
97  
98              //Return result
99              List<Integer> exitCodes = DEFAULT_EXIT_CODES;
100             if ( EXIT_CODE_MAP.containsKey( cmdAndArgs[0] ) )
101             {
102                 exitCodes = EXIT_CODE_MAP.get( cmdAndArgs[0] );
103             }
104             boolean success = exitCodes.contains( Integer.valueOf( exitCode ) );
105 
106             //On failure (and not due to exceptions) - run diagnostics
107             String providerMsg = "Execution of hg command succeded";
108             if ( !success )
109             {
110                 HgConfig config = new HgConfig( workingDir );
111                 providerMsg =
112                     "\nEXECUTION FAILED" + "\n  Execution of cmd : " + cmdAndArgs[0] + " failed with exit code: "
113                         + exitCode + "." + "\n  Working directory was: " + "\n    " + workingDir.getAbsolutePath()
114                         + config.toString( workingDir ) + "\n";
115                 if ( logger.isErrorEnabled() )
116                 {
117                     logger.error( providerMsg );
118                 }
119             }
120 
121             return new ScmResult( cmd.toString(), providerMsg, consumer.getStdErr(), success );
122         }
123         catch ( ScmException se )
124         {
125             String msg =
126                 "EXECUTION FAILED" + "\n  Execution failed before invoking the Hg command. Last exception:" + "\n    "
127                     + se.getMessage();
128 
129             //Add nested cause if any
130             if ( se.getCause() != null )
131             {
132                 msg += "\n  Nested exception:" + "\n    " + se.getCause().getMessage();
133             }
134 
135             //log and return
136             if ( logger.isErrorEnabled() )
137             {
138                 logger.error( msg );
139             }
140             throw se;
141         }
142     }
143 
144     static Commandline buildCmd( File workingDir, String[] cmdAndArgs )
145         throws ScmException
146     {
147         Commandline cmd = new Commandline();
148         cmd.setExecutable( HgCommandConstants.EXEC );
149         cmd.addArguments( cmdAndArgs );
150         if ( workingDir != null )
151         {
152             cmd.setWorkingDirectory( workingDir.getAbsolutePath() );
153 
154             if ( !workingDir.exists() )
155             {
156                 boolean success = workingDir.mkdirs();
157                 if ( !success )
158                 {
159                     String msg = "Working directory did not exist" + " and it couldn't be created: " + workingDir;
160                     throw new ScmException( msg );
161                 }
162             }
163         }
164         return cmd;
165     }
166 
167     static int executeCmd( HgConsumer consumer, Commandline cmd )
168         throws ScmException
169     {
170         final int exitCode;
171         try
172         {
173             exitCode = CommandLineUtils.executeCommandLine( cmd, consumer, consumer );
174         }
175         catch ( CommandLineException ex )
176         {
177             throw new ScmException( "Command could not be executed: " + cmd, ex );
178         }
179         return exitCode;
180     }
181 
182     public static ScmResult execute( File workingDir, String[] cmdAndArgs )
183         throws ScmException
184     {
185         ScmLogger logger = new DefaultLog();
186         return execute( new HgConsumer( logger ), logger, workingDir, cmdAndArgs );
187     }
188 
189     public static String[] expandCommandLine( String[] cmdAndArgs, ScmFileSet additionalFiles )
190     {
191         List<File> filesList = additionalFiles.getFileList();
192         String[] cmd = new String[filesList.size() + cmdAndArgs.length];
193 
194         // Copy command into array
195         System.arraycopy( cmdAndArgs, 0, cmd, 0, cmdAndArgs.length );
196 
197         // Add files as additional parameter into the array
198         int i = 0;
199         for ( File scmFile : filesList )
200         {
201             String file = scmFile.getPath().replace( '\\', File.separatorChar );
202             cmd[i + cmdAndArgs.length] = file;
203             i++;
204         }
205 
206         return cmd;
207     }
208 
209     public static int getCurrentRevisionNumber( ScmLogger logger, File workingDir )
210         throws ScmException
211     {
212 
213         String[] revCmd = new String[]{ HgCommandConstants.REVNO_CMD };
214         HgRevNoConsumer consumer = new HgRevNoConsumer( logger );
215         HgUtils.execute( consumer, logger, workingDir, revCmd );
216 
217         return consumer.getCurrentRevisionNumber();
218     }
219 
220     public static String getCurrentBranchName( ScmLogger logger, File workingDir )
221         throws ScmException
222     {
223         String[] branchnameCmd = new String[]{ HgCommandConstants.BRANCH_NAME_CMD };
224         HgBranchnameConsumer consumer = new HgBranchnameConsumer( logger );
225         HgUtils.execute( consumer, logger, workingDir, branchnameCmd );
226         return consumer.getBranchName();
227     }
228 
229     /**
230      * Get current (working) revision.
231      * <p/>
232      * Resolve revision to the last integer found in the command output.
233      */
234     private static class HgRevNoConsumer
235         extends HgConsumer
236     {
237 
238         private int revNo;
239 
240         HgRevNoConsumer( ScmLogger logger )
241         {
242             super( logger );
243         }
244 
245         public void doConsume( ScmFileStatus status, String line )
246         {
247             try
248             {
249                 revNo = Integer.valueOf( line ).intValue();
250             }
251             catch ( NumberFormatException e )
252             {
253                 // ignore
254             }
255         }
256 
257         int getCurrentRevisionNumber()
258         {
259             return revNo;
260         }
261     }
262 
263     /**
264      * Get current (working) branch name
265      */
266     private static class HgBranchnameConsumer
267         extends HgConsumer
268     {
269 
270         private String branchName;
271 
272         HgBranchnameConsumer( ScmLogger logger )
273         {
274             super( logger );
275         }
276 
277         public void doConsume( ScmFileStatus status, String trimmedLine )
278         {
279             branchName = String.valueOf( trimmedLine );
280         }
281 
282         String getBranchName()
283         {
284             return branchName;
285         }
286     }
287 
288 
289     /**
290      * Check if there are outgoing changes on a different branch. If so, Mercurial default behaviour
291      * is to block the push and warn using a 'push creates new remote branch !' message.
292      * We also warn, and return true if a different outgoing branch was found
293      * <p/>
294      * Method users should not stop the push on a negative return, instead, they should hg push -r(branch being released)
295      *
296      * @param logger            the logger
297      * @param workingDir        the working dir
298      * @param workingbranchName the working branch name
299      * @return true if a different outgoing branch was found
300      * @throws ScmException on outgoing command error
301      */
302     public static boolean differentOutgoingBranchFound( ScmLogger logger, File workingDir, String workingbranchName )
303         throws ScmException
304     {
305         String[] outCmd = new String[]{ HgCommandConstants.OUTGOING_CMD };
306         HgOutgoingConsumer outConsumer = new HgOutgoingConsumer( logger );
307         ScmResult outResult = HgUtils.execute( outConsumer, logger, workingDir, outCmd );
308         List<HgChangeSet> changes = outConsumer.getChanges();
309         if ( outResult.isSuccess() )
310         {
311             for ( HgChangeSet set : changes )
312             {
313                 if ( set.getBranch() != null )
314                 {
315                     logger.warn( "A different branch than " + workingbranchName
316                         + " was found in outgoing changes, branch name was " + set.getBranch()
317                         + ". Only local branch named " + workingbranchName + " will be pushed." );
318                     return true;
319                 }
320             }
321         }
322         return false;
323     }
324 
325     public static String maskPassword( Commandline cl )
326     {
327         String clString = cl.toString();
328 
329         int pos = clString.indexOf( '@' );
330 
331         if ( pos > 0 )
332         {
333             clString = clString.replaceAll( ":\\w+@", ":*****@" );
334         }
335 
336         return clString;
337     }
338 }