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