001package org.apache.maven.scm.provider.jazz.command.status; 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 022import org.apache.maven.scm.ScmFile; 023import org.apache.maven.scm.ScmFileStatus; 024import org.apache.maven.scm.log.ScmLogger; 025import org.apache.maven.scm.provider.ScmProviderRepository; 026import org.apache.maven.scm.provider.jazz.command.consumer.AbstractRepositoryConsumer; 027import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository; 028 029import java.util.ArrayList; 030import java.util.List; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033 034/** 035 * Consume the output of the scm command for the "status" operation. 036 * <p/> 037 * It is normally just used to build up a list of ScmFile objects that have 038 * their ScmFileStatus set. 039 * This class has been expanded so that the Workspace, Component and Baseline 040 * are also collected and set back in the JazzScmProviderRepository. 041 * The Workspace and Component names are needed for some other commands (list, 042 * for example), so we can easily get this information here. 043 * <p/> 044 * As this class has expanded over time, it has become more and more of a state 045 * machine, one that needs to parse the output of the "scm status --wide" command. 046 * If there are any issues with this provider, I would suggest this is a good 047 * place to start. 048 * 049 * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a> 050 */ 051public class JazzStatusConsumer 052 extends AbstractRepositoryConsumer 053{ 054// We have have a workspace with no flow targets (it points to itself) 055// 056// Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace" 057// Component: (1001) "BogusComponent" 058// Baseline: (1128) 27 "BogusTestJazz-3.0.0.40" 059// Unresolved: 060// d-- /BogusTest/pom.xml.releaseBackup 061// d-- /BogusTest/release.properties 062// 063// Or, we have have one that does have a flow target (ie a stream or another workspace). 064// 065// Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream" 066// Component: (1158) "GPDB" <-> (1157) "GPDBStream" 067// Baseline: (1159) 1 "Initial Baseline" 068// 069// Note the (%d) numbers are aliases and are only valid for the machine/instance that made the 070// remote calls to the server. They are not to be shared across machines (ie don't make them global, public 071// or persistent). 072// 073// We can also have a changeset with a work item associated with it: 074// 075// Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream" 076// Component: (1158) "GPDB" 077// Baseline: (2362) 48 "GPDB-1.0.50" 078// Outgoing: 079// Change sets: 080// (2366) *--@ 62 "Release the next release of GPDB." - "Man Created Changeset: X.Y.Z" 28-Apr-2015 07:55 PM 081// 082// Or not: 083// 084// Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream" 085// Component: (1158) "GPDB" 086// Baseline: (2362) 48 "GPDB-1.0.50" 087// Outgoing: 088// Change sets: 089// (2365) ---@ "This is my changeset comment." 26-Apr-2015 09:36 PM 090// 091// We can also have a multiple changesets. These will be seen when a JBE is used to perform 092// the release and has been instructed to create a baseline prior to starting the build. 093// Multiple changesets will also be seen when a maven release process fails (for whatever reason). 094// 095// Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream" 096// Component: (1158) "GPDB" 097// Baseline: (2362) 48 "GPDB-1.0.50" 098// Outgoing: 099// Change sets: 100// (2366) *--@ 62 "Release the next release of GPDB." - "Man Created Changeset: X.Y.Z" 28-Apr-2015 07:55 PM 101// (2365) ---@ "This is my changeset comment." 26-Apr-2015 09:36 PM 102// 103// We can also have Baselines, of which there may be more than one (especially true if an update (accept changes) 104// has not been done in a while. 105// 106// So the most complete/complex example I can find is something like this: 107// 108// Workspace: (1756) "Scott's GPDBWorkspace" <-> (1157) "GPDBStream" 109// Component: (1158) "GPDB" 110// Baseline: (1718) 25 "GPDB-1.0.25" 111// Unresolved: 112// -c- /GPDB/pom.xml 113// Outgoing: 114// Change sets: 115// (2389) *--@ "<No comment>" 23-May-2015 07:09 PM 116// Incoming: 117// Change sets: 118// (2385) ---$ Deb 62 "Release the next release of GPDB." - \ 119// + "[maven-release-plugin] prepare for next development itera..." 02-May-2015 11:01 PM 120// Baselines: 121// (2386) 52 "GPDB-1.0.53" 122// (2387) 51 "GPDB-1.0.52" 123// (2388) 50 "GPDB-1.0.51" 124// (2369) 49 "GPDB-MAN-1.0.50" 125// (2362) 48 "GPDB-1.0.50" 126// (2357) 47 "GPDB-1.0.49" 127// (2352) 46 "GPDB-1.0.48" 128// (2347) 45 "GPDB-1.0.47" 129// (2292) 44 "GPDB-1.0.46" 130// (2285) 42 "GPDB-1.0.42" 131// (2276) 41 "GPDB-1.0.41" 132// (2259) 40 "GPDB-1.0.40" 133// (2250) 39 "GPDB-1.0.39" 134// (2241) 38 "GPDB-1.0.38" 135// (2232) 37 "GPDB-1.0.37" 136// (2222) 36 "GPDB-1.0.36" 137// (2212) 35 "GPDB-1.0.35" 138// (2202) 34 "GPDB-1.0.34" 139// (2191) 33 "GPDB-1.0.33" 140// (2181) 32 "GPDB-1.0.32" 141// (2171) 31 "GPDB-1.0.31" 142// (2160) 30 "GPDB-1.0.30" 143// (2147) 29 "GPDB-1.0.29" 144// (2079) 28 "GPDB-1.0.28" 145// (1851) 27 "GPDB-1.0.27" 146// (1807) 26 "GPDB-1.0.26" 147// 148// Because the "Change sets:" line exists by itself, and it is followed by the changeset 149// lines, we need to implement a state machine... (seenIncomingChangeSets and seenOutgoingChangeSets) 150// 151// We can also have collisions: 152// 153// Workspace: (8551) "myNewWorkspace" <-> (8552) "stream19_test_max_results_1256765247692134" 154// Component: (8553) "Flux Capacitor" 155// Baseline: (8554) 1 "Initial Baseline" 156// Outgoing: 157// Change sets: 158// (8617) -#@ "Update from November planning meeting" 159// Changes: 160// -#-c /flux.capacitor/requirements.txt 161// Incoming: 162// Change sets: 163// (8616) -#$ "Results of initial trials" 164// Changes: 165// -#-c /flux.capacitor/requirements.txt 166 167 // Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace" 168 // Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream" 169 private static final Pattern WORKSPACE_PATTERN = 170 Pattern.compile( "\\((\\d+)\\) \"(.*)\" <-> \\((\\d+)\\) \"(.*)\"" ); 171 172 // Component: (1001) "BogusComponent" 173 private static final Pattern COMPONENT_PATTERN1 = Pattern.compile( "\\((\\d+)\\) \"(.*)\"" ); 174 175 // Component: (1158) "GPDB" <-> (1157) "GPDBStream" 176 // Component: (1002) "FireDragon" <-> (1005) "MavenR3Stream Workspace" (outgoing addition) 177 private static final Pattern COMPONENT_PATTERN2 = Pattern.compile( "\\((\\d+)\\) \"(.*)\" <.*>" ); 178 179 // Baseline: (1128) 27 "BogusTestJazz-3.0.0.40" 180 private static final Pattern BASELINE_PATTERN = Pattern.compile( "\\((\\d+)\\) (\\d+) \"(.*)\"" ); 181 182 // (2365) ---@ "This is my changeset comment." 26-Apr-2015 09:36 PM 183 private static final Pattern CHANGESET_PATTERN = Pattern.compile( "\\((\\d+)\\) (.*)" ); 184 185 // 186 // Additional data we collect. (eye catchers) 187 // 188 189 /** 190 * The "Status" command output line that contains the "Workspace" name. 191 */ 192 public static final String STATUS_CMD_WORKSPACE = "Workspace:"; 193 194 /** 195 * The "Status" command output line that contains the "Component" name. 196 */ 197 public static final String STATUS_CMD_COMPONENT = "Component:"; 198 199 /** 200 * The "Status" command output line that contains the "Baseline" name. 201 */ 202 public static final String STATUS_CMD_BASELINE = "Baseline:"; 203 204 /** 205 * The "Status" command output line that contains the "Outgoing" eye catcher. 206 */ 207 public static final String STATUS_CMD_OUTGOING = "Outgoing:"; 208 209 /** 210 * The "Status" command output line that contains the "Incoming" eye catcher. 211 */ 212 public static final String STATUS_CMD_INCOMING = "Incoming:"; 213 214 /** 215 * The "Status" command output line that contains the line "Change sets:". 216 * This will be followed by the change set lines themselves. 217 */ 218 public static final String STATUS_CMD_CHANGE_SETS = "Change sets:"; 219 220 /** 221 * The "Status" command output line that contains the "Baselines" eye catcher. 222 */ 223 public static final String STATUS_CMD_BASELINES = "Baselines:"; 224 225 // File Status Commands (eye catchers) 226 227 /** 228 * The "Status" command status flag for a resource that has been added. 229 */ 230 public static final String STATUS_CMD_ADD_FLAG = "a-"; 231 232 /** 233 * The "Status" command status flag for when the content or properties of 234 * a file have been modified, or the properties of a directory have changed. 235 */ 236 public static final String STATUS_CMD_CHANGE_FLAG = "-c"; 237 238 /** 239 * The "Status" command status flag for a resource that has been deleted. 240 */ 241 public static final String STATUS_CMD_DELETE_FLAG = "d-"; 242 243 /** 244 * The "Status" command status flag for a resource that has been renamed or moved. 245 */ 246 public static final String STATUS_CMD_MOVED_FLAG = "m-"; 247 248 /** 249 * A List of ScmFile objects that have their ScmFileStatus set. 250 */ 251 private List<ScmFile> fChangedFiles = new ArrayList<ScmFile>(); 252 253 /** 254 * Implement a simple state machine: Have we seen the "Change sets:" (outgoing) line or not? 255 */ 256 private boolean seenOutgoingChangeSets = false; 257 258 /** 259 * Implement a simple state machine: Have we seen the "Change sets:" (incoming) line or not? 260 */ 261 private boolean seenIncomingChangeSets = false; 262 263 /** 264 * Constructor for our "scm status" consumer. 265 * 266 * @param repo The JazzScmProviderRepository being used. 267 * @param logger The ScmLogger to use. 268 */ 269 public JazzStatusConsumer( ScmProviderRepository repo, ScmLogger logger ) 270 { 271 super( repo, logger ); 272 } 273 274 /** 275 * Process one line of output from the execution of the "scm status" command. 276 * 277 * @param line The line of output from the external command that has been pumped to us. 278 * @see org.codehaus.plexus.util.cli.StreamConsumer#consumeLine(java.lang.String) 279 */ 280 public void consumeLine( String line ) 281 { 282 super.consumeLine( line ); 283 if ( containsWorkspace( line ) ) 284 { 285 extractWorkspace( line ); 286 } 287 if ( containsComponent( line ) ) 288 { 289 extractComponent( line ); 290 } 291 if ( containsBaseline( line ) ) 292 { 293 extractBaseline( line ); 294 } 295 if ( containsStatusFlag( line ) ) 296 { 297 extractChangedFile( line ); 298 } 299 if ( containsOutgoing( line ) ) 300 { 301 // Now looking for outgoing, not incoming 302 seenOutgoingChangeSets = true; 303 seenIncomingChangeSets = false; 304 } 305 if ( containsIncoming( line ) ) 306 { 307 // Now looking for incoming, not outgoing 308 seenOutgoingChangeSets = false; 309 seenIncomingChangeSets = true; 310 } 311 if ( containsBaselines( line ) ) 312 { 313 // Got to baselines, stop looking for all changesets 314 seenOutgoingChangeSets = false; 315 seenIncomingChangeSets = false; 316 } 317 if ( seenOutgoingChangeSets ) 318 { 319 Integer changeSetAlias = extractChangeSetAlias( line ); 320 if ( changeSetAlias != null ) 321 { 322 // We are now supporting multiple change sets, as this allows 323 // us to cater for multiple changeset caused by previous failed 324 // release attempts. 325 // Our starting point should always be a clean slate of a workspace 326 // or sandbox, however, if something fails, then we will have some 327 // changesets already created, so we need to be able to deal with them effectively. 328 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository(); 329 jazzRepository.getOutgoingChangeSetAliases().add( new Integer( changeSetAlias ) ); 330 } 331 } 332 if ( seenIncomingChangeSets ) 333 { 334 Integer changeSetAlias = extractChangeSetAlias( line ); 335 if ( changeSetAlias != null ) 336 { 337 // We are now supporting multiple change sets, as this allows 338 // us to cater for multiple changeset caused by previous failed 339 // release attempts. 340 // Our starting point should always be a clean slate of a workspace 341 // or sandbox, however, if something fails, then we will have some 342 // changesets already created, so we need to be able to deal with them effectively. 343 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository(); 344 jazzRepository.getIncomingChangeSetAliases().add( new Integer( changeSetAlias ) ); 345 } 346 } 347 } 348 349 private boolean containsWorkspace( String line ) 350 { 351 return line.trim().startsWith( STATUS_CMD_WORKSPACE ); 352 } 353 354 private void extractWorkspace( String line ) 355 { 356 // With no stream (flow target): 357 // Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace" 358 // With a stream: 359 // Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream" 360 361 Matcher matcher = WORKSPACE_PATTERN.matcher( line ); 362 if ( matcher.find() ) 363 { 364 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository(); 365 366 int workspaceAlias = Integer.parseInt( matcher.group( 1 ) ); 367 String workspace = matcher.group( 2 ); 368 int streamAlias = Integer.parseInt( matcher.group( 3 ) ); 369 String stream = matcher.group( 4 ); 370 if ( getLogger().isDebugEnabled() ) 371 { 372 getLogger().debug( "Successfully parsed \"Workspace:\" line:" ); 373 getLogger().debug( " workspaceAlias = " + workspaceAlias ); 374 getLogger().debug( " workspace = " + workspace ); 375 getLogger().debug( " streamAlias = " + streamAlias ); 376 getLogger().debug( " stream = " + stream ); 377 } 378 jazzRepository.setWorkspaceAlias( workspaceAlias ); 379 jazzRepository.setWorkspace( workspace ); 380 jazzRepository.setFlowTargetAlias( streamAlias ); 381 jazzRepository.setFlowTarget( stream ); 382 } 383 } 384 385 private boolean containsComponent( String line ) 386 { 387 return line.trim().startsWith( STATUS_CMD_COMPONENT ); 388 } 389 390 private void extractComponent( String line ) 391 { 392 // With no stream (flow target): 393 // Component: (1001) "BogusComponent" 394 // With a stream: 395 // Component: (1158) "GPDB" <-> (1157) "GPDBStream" 396 // With some additional information: 397 // Component: (1002) "FireDragon" <-> (1005) "MavenR3Stream Workspace" (outgoing addition) 398 399 Matcher matcher = COMPONENT_PATTERN1.matcher( line ); 400 if ( matcher.find() ) 401 { 402 // Component: (1001) "BogusComponent" 403 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository(); 404 int componentAlias = Integer.parseInt( matcher.group( 1 ) ); 405 String component = matcher.group( 2 ); 406 if ( getLogger().isDebugEnabled() ) 407 { 408 getLogger().debug( "Successfully parsed \"Component:\" line:" ); 409 getLogger().debug( " componentAlias = " + componentAlias ); 410 getLogger().debug( " component = " + component ); 411 } 412 jazzRepository.setComponent( component ); 413 } 414 415 matcher = COMPONENT_PATTERN2.matcher( line ); 416 if ( matcher.find() ) 417 { 418 // Component: (1158) "GPDB" <-> (1157) "GPDBStream" 419 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository(); 420 int componentAlias = Integer.parseInt( matcher.group( 1 ) ); 421 String component = matcher.group( 2 ); 422 if ( getLogger().isDebugEnabled() ) 423 { 424 getLogger().debug( "Successfully parsed \"Component:\" line:" ); 425 getLogger().debug( " componentAlias = " + componentAlias ); 426 getLogger().debug( " component = " + component ); 427 } 428 jazzRepository.setComponent( component ); 429 } 430 } 431 432 private boolean containsBaseline( String line ) 433 { 434 return line.trim().startsWith( STATUS_CMD_BASELINE ); 435 } 436 437 private void extractBaseline( String line ) 438 { 439 // Baseline: (1128) 27 "BogusTestJazz-3.0.0.40" 440 441 Matcher matcher = BASELINE_PATTERN.matcher( line ); 442 if ( matcher.find() ) 443 { 444 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository(); 445 446 int baselineAlias = Integer.parseInt( matcher.group( 1 ) ); 447 int baselineId = Integer.parseInt( matcher.group( 2 ) ); 448 String baseline = matcher.group( 3 ); 449 if ( getLogger().isDebugEnabled() ) 450 { 451 getLogger().debug( "Successfully parsed \"Baseline:\" line:" ); 452 getLogger().debug( " baselineAlias = " + baselineAlias ); 453 getLogger().debug( " baselineId = " + baselineId ); 454 getLogger().debug( " baseline = " + baseline ); 455 } 456 jazzRepository.setBaseline( baseline ); 457 } 458 } 459 460 private boolean containsStatusFlag( String line ) 461 { 462 boolean containsStatusFlag = false; 463 464 if ( line.trim().length() > 2 ) 465 { 466 String flag = line.trim().substring( 0, 2 ); 467 if ( STATUS_CMD_ADD_FLAG.equals( flag ) || STATUS_CMD_CHANGE_FLAG.equals( flag ) 468 || STATUS_CMD_DELETE_FLAG.equals( flag ) ) 469 { 470 containsStatusFlag = true; 471 } 472 } 473 return containsStatusFlag; 474 } 475 476 private void extractChangedFile( String line ) 477 { 478 String flag = line.trim().substring( 0, 2 ); 479 String filePath = line.trim().substring( 3 ).trim(); 480 ScmFileStatus status = ScmFileStatus.UNKNOWN; 481 482 if ( STATUS_CMD_ADD_FLAG.equals( flag ) ) 483 { 484 status = ScmFileStatus.ADDED; 485 } 486 487 if ( STATUS_CMD_CHANGE_FLAG.equals( flag ) ) 488 { 489 status = ScmFileStatus.MODIFIED; 490 } 491 492 if ( STATUS_CMD_DELETE_FLAG.equals( flag ) ) 493 { 494 status = ScmFileStatus.DELETED; 495 } 496 497 if ( getLogger().isDebugEnabled() ) 498 { 499 getLogger().debug( " Extracted filePath : '" + filePath + "'" ); 500 getLogger().debug( " Extracted flag : '" + flag + "'" ); 501 getLogger().debug( " Extracted status : '" + status + "'" ); 502 } 503 504 fChangedFiles.add( new ScmFile( filePath, status ) ); 505 } 506 507 public List<ScmFile> getChangedFiles() 508 { 509 return fChangedFiles; 510 } 511 512 private boolean containsOutgoing( String line ) 513 { 514 return line.trim().startsWith( STATUS_CMD_OUTGOING ); 515 } 516 517 private boolean containsIncoming( String line ) 518 { 519 return line.trim().startsWith( STATUS_CMD_INCOMING ); 520 } 521 522 private boolean containsBaselines( String line ) 523 { 524 return line.trim().startsWith( STATUS_CMD_BASELINES ); 525 } 526 527 /** 528 * Extract and return an Integer of a change set alias, from both 529 * incoming and outgoing changesets. 530 * @param line The line to extract the change sets from. 531 * @return A parsed Integer value, or null if not able to parse. 532 */ 533 private Integer extractChangeSetAlias( String line ) 534 { 535 // (2365) ---@ "This is my changeset comment." 26-Apr-2015 09:36 PM 536 537 Matcher matcher = CHANGESET_PATTERN.matcher( line ); 538 if ( matcher.find() ) 539 { 540 int changeSetAlias = Integer.parseInt( matcher.group( 1 ) ); 541 if ( getLogger().isDebugEnabled() ) 542 { 543 getLogger().debug( "Successfully parsed post \"Change sets:\" line:" ); 544 getLogger().debug( " changeSetAlias = " + changeSetAlias ); 545 } 546 return new Integer( changeSetAlias ); 547 } 548 else 549 { 550 return null; 551 } 552 } 553}