001 package org.apache.maven.scm.provider.hg; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import org.apache.maven.scm.ScmException; 023 import org.apache.maven.scm.ScmFileSet; 024 import org.apache.maven.scm.ScmFileStatus; 025 import org.apache.maven.scm.ScmResult; 026 import org.apache.maven.scm.log.DefaultLog; 027 import org.apache.maven.scm.log.ScmLogger; 028 import org.apache.maven.scm.provider.hg.command.HgCommandConstants; 029 import org.apache.maven.scm.provider.hg.command.HgConsumer; 030 import org.apache.maven.scm.provider.hg.command.inventory.HgChangeSet; 031 import org.apache.maven.scm.provider.hg.command.inventory.HgOutgoingConsumer; 032 import org.codehaus.plexus.util.cli.CommandLineException; 033 import org.codehaus.plexus.util.cli.CommandLineUtils; 034 import org.codehaus.plexus.util.cli.Commandline; 035 036 import java.io.File; 037 import java.util.ArrayList; 038 import java.util.HashMap; 039 import java.util.List; 040 import java.util.Map; 041 042 /** 043 * Common code for executing hg commands. 044 * 045 * @author <a href="mailto:thurner.rupert@ymono.net">thurner rupert</a> 046 * @version $Id: HgUtils.java 1378723 2012-08-29 20:57:26Z hboutemy $ 047 */ 048 public final class HgUtils 049 { 050 051 private HgUtils() 052 { 053 } 054 055 /** 056 * Map between command and its valid exit codes 057 */ 058 private static final Map<String, List<Integer>> EXIT_CODE_MAP = new HashMap<String, List<Integer>>(); 059 060 /** 061 * Default exit codes for entries not in exitCodeMap 062 */ 063 private static final List<Integer> DEFAULT_EXIT_CODES = new ArrayList<Integer>(); 064 065 /** Setup exit codes*/ 066 static 067 { 068 DEFAULT_EXIT_CODES.add( new Integer( 0 ) ); 069 070 //Diff is different 071 List<Integer> diffExitCodes = new ArrayList<Integer>( 3 ); 072 diffExitCodes.add( Integer.valueOf( 0 ) ); //No difference 073 diffExitCodes.add( Integer.valueOf( 1 ) ); //Conflicts in merge-like or changes in diff-like 074 diffExitCodes.add( Integer.valueOf( 2 ) ); //Unrepresentable diff changes 075 EXIT_CODE_MAP.put( HgCommandConstants.DIFF_CMD, diffExitCodes ); 076 //Outgoing is different 077 List<Integer> outgoingExitCodes = new ArrayList<Integer>( 2 ); 078 outgoingExitCodes.add( Integer.valueOf( 0 ) ); //There are changes 079 outgoingExitCodes.add( Integer.valueOf( 1 ) ); //No changes 080 EXIT_CODE_MAP.put( HgCommandConstants.OUTGOING_CMD, outgoingExitCodes ); 081 } 082 083 public static ScmResult execute( HgConsumer consumer, ScmLogger logger, File workingDir, String[] cmdAndArgs ) 084 throws ScmException 085 { 086 try 087 { 088 //Build commandline 089 Commandline cmd = buildCmd( workingDir, cmdAndArgs ); 090 if ( logger.isInfoEnabled() ) 091 { 092 logger.info( "EXECUTING: " + maskPassword( cmd ) ); 093 } 094 095 //Execute command 096 int exitCode = executeCmd( consumer, cmd ); 097 098 //Return result 099 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 }