1 package org.apache.maven.scm.provider.jazz.command.status;
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.ScmFile;
23 import org.apache.maven.scm.ScmFileStatus;
24 import org.apache.maven.scm.log.ScmLogger;
25 import org.apache.maven.scm.provider.ScmProviderRepository;
26 import org.apache.maven.scm.provider.jazz.command.consumer.AbstractRepositoryConsumer;
27 import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
28
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 /**
35 * Consume the output of the scm command for the "status" operation.
36 * <p/>
37 * It is normally just used to build up a list of ScmFile objects that have
38 * their ScmFileStatus set.
39 * This class has been expanded so that the Workspace, Component and Baseline
40 * are also collected and set back in the JazzScmProviderRepository.
41 * The Workspace and Component names are needed for some other commands (list,
42 * for example), so we can easily get this information here.
43 * <p/>
44 * As this class has expanded over time, it has become more and more of a state
45 * machine, one that needs to parse the output of the "scm status --wide" command.
46 * If there are any issues with this provider, I would suggest this is a good
47 * place to start.
48 *
49 * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
50 */
51 public class JazzStatusConsumer
52 extends AbstractRepositoryConsumer
53 {
54 // We have have a workspace with no flow targets (it points to itself)
55 //
56 // Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace"
57 // Component: (1001) "BogusComponent"
58 // Baseline: (1128) 27 "BogusTestJazz-3.0.0.40"
59 // Unresolved:
60 // d-- /BogusTest/pom.xml.releaseBackup
61 // d-- /BogusTest/release.properties
62 //
63 // Or, we have have one that does have a flow target (ie a stream or another workspace).
64 //
65 // Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
66 // Component: (1158) "GPDB" <-> (1157) "GPDBStream"
67 // Baseline: (1159) 1 "Initial Baseline"
68 //
69 // Note the (%d) numbers are aliases and are only valid for the machine/instance that made the
70 // remote calls to the server. They are not to be shared across machines (ie don't make them global, public
71 // or persistent).
72 //
73 // We can also have a changeset with a work item associated with it:
74 //
75 // Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
76 // Component: (1158) "GPDB"
77 // Baseline: (2362) 48 "GPDB-1.0.50"
78 // Outgoing:
79 // Change sets:
80 // (2366) *--@ 62 "Release the next release of GPDB." - "Man Created Changeset: X.Y.Z" 28-Apr-2015 07:55 PM
81 //
82 // Or not:
83 //
84 // Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
85 // Component: (1158) "GPDB"
86 // Baseline: (2362) 48 "GPDB-1.0.50"
87 // Outgoing:
88 // Change sets:
89 // (2365) ---@ "This is my changeset comment." 26-Apr-2015 09:36 PM
90 //
91 // We can also have a multiple changesets. These will be seen when a JBE is used to perform
92 // the release and has been instructed to create a baseline prior to starting the build.
93 // Multiple changesets will also be seen when a maven release process fails (for whatever reason).
94 //
95 // Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
96 // Component: (1158) "GPDB"
97 // Baseline: (2362) 48 "GPDB-1.0.50"
98 // Outgoing:
99 // 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 }