Scripting using the Rational Team Concert SCM Command Line Interface

Overview

The RTC SCM Command Line Interface (SCM CLI) tool allows users to load and accept items from the repository, commit and deliver changes to the repository. Most of the SCM CLI subcommands generate an output that can be consumed and used to peform follow up operations. Prior to 4.0, the output generated by the SCM CLI was plain space delimited text which was easy to view but difficult to parse by scripts. Also, whenever the subcommand output was enhanced to include more information it had the potential to break the scripts that relied on a certain output format. In order to alleviate this problem, there was a need to generate an output in a structured format that could be easily parsed and processed by scripts.

JSON output

JSON or JavaScript Object Notation, is a lightweight text-based open standard designed for data interchange. JSON is language independent as well as human readable and can be read/written easily. Almost all modern programming languages have built in support for JSON or provide JSON libraries to parse JSON formatted data.
Jsawk is an utility similar to awk but for JSON that could be used to parse and process JSON data from the command line. You work with an array of JSON objects read from stdin, filter them using JavaScript to produce a results array that is printed to stdout. You can use this as a filter to manipulate data, for example, in a shell script.

In 4.0, the SCM CLI provides support for generating output in JSON format. The subcommands can be instructed to output data in JSON format by specifying the -j/–json option. Not all subcommands support this option. The option is supported in only those subcommands that return some data back to the user.
For example: ‘lscm status –json’.

In this article we will show some Perl scripts which consume JSON output and perform certain operations that could be used in our day to day work. Readers should be familiar with RTC concepts and functionality; and comfortable scripting on the command line.

Sample output

Sample listing showing regular text output:

  [~/test]$ lscm status  Workspace: (1159) "Workspace1" <-> (1159) "Workspace1"    Component: (1160) "Comp1"      Baseline: (1161) 2 "SS1"      Unresolved:        -c- /Comp1/111/1.txt    Component: (1162) "Comp2"      Baseline: (1163) 2 "SS1"  

Sample listing showing jsonized output:

  [~/test]$ lscm status --json  {      "workspaces": [          {              "components": [                  {                      "baseline": {                          "id": 2,                          "name": "SS1",                          "uuid": "_tMTUUKsFEeGu6rQhbwU5dg"                      },                      "name": "Comp1",                      "unresolved": [                          {                              "path": "/Comp1/111/1.txt",                              "state": {                                  "add": false,                                  "content_change": true,                                  "delete": false,                                  "move": false,                                  "property_change": false                              },                              "uuid": "_jis80KsFEeGt8o_AYdWFYw"                          }                      ],                      "uuid": "_Uq0YAKsEEeGu6rQhbwU5dg"                  },                  {                      "baseline": {                          "id": 2,                          "name": "SS1",                          "uuid": "_tMaCAqsFEeGu6rQhbwU5dg"                      },                      "name": "Comp2",                      "uuid": "_XNAA4KsEEeGu6rQhbwU5dg"                  }              ],              "flow-target": {                  "name": "Workspace1",                  "type": "WORKSPACE",                  "url": "https://localhost:9443/jazz/",                  "uuid": "_Lm9f8KsGEeGu6rQhbwU5dg"              },              "name": "Workspace1",              "url": "https://localhost:9443/jazz/",              "userId": "ADMIN",              "uuid": "_Lm9f8KsGEeGu6rQhbwU5dg"          }      ]  }  

Example 1: Commit files to repository, set a comment and complete the change set

Using the SCM CLI there are various operations that can be performed on a changeset once the changes are committed to the repository such as setting the comment, associating a workitem and completing the changeset. At times, the user may want to combine some of these individual operations and execute it as a single operation.

The below sample script, will commit the changes, set a comment and complete the change set. Listed below are the subcommands that correspond to these individual operations:

  • ‘lscm checkin’
  • ‘lscm changeset comment’
  • ‘lscm changeset complete’

Perl script:

  # Script usage: commit.pl {comment} {file paths...}  # Script usage example: commit.pl "test comment" .    use JSON;        my $comment = $ARGV[0];            my $filePattern = "";      for ($count = 1; $count <= $#ARGV; $count++) {          if ($count > 1) {              $filePattern = $filePattern . " ";          }          $filePattern = $filePattern . $ARGV[$count];      }            # Commit the changes      my $result = 'lscm checkin $filePattern --json';      quitOnError($?, $result);            # Decode the result      my @json = @{decode_json($result)};        # Loop through the workspaces->components->outgoingChangesets      foreach $ws (@json) {          foreach my $component (@{$ws->{components}}) {              foreach my $outgoingChangeset (@{$component->{"outgoing-changes"}}) {                  $result = 'lscm changeset comment $outgoingChangeset->{"uuid"} "$comment"';                  quitOnError($?, $result);                                    $result = 'lscm changeset complete $outgoingChangeset->{"uuid"}';                  quitOnError($?, $result);              }                  }      }        	      #      # Routine that prints the error message and quits      #      sub quitOnError {          my $error_code = shift;          my $error = shift;                    if ($error_code != 0) {              print $error . "n";              exit($error_code);          }      }  

Download Example 1 – You’ll need to remove the .txt extension.

Note: In the above example we are committing all files below the current directory. If the script is run from the root of the sandbox there is a possibility that multiple changesets will be created depending upon whether there were unresolved changes in other components. To avoid this issue, run the script within a component directory or specify individual file paths.


Example 2: Diff files recursively

The subcommand ‘lscm diff file’ is used to list the differences between an individual file in the local file system against that in the repository. The below sample script will demonstrate how to recursively diff files in the current folder and it’s child folders. The script first executes the ‘lscm status’ subcommand to find all the unresolved files in the local file system and picks the files that have content changes before diff’ing each of those files.

Perl script:

  # Script usage: rdiff.pl  # Script usage example: rdiff.pl    use JSON;  use Cwd;        # Get the status      my $result = 'lscm status --json';      quitOnError($?, $result);            # Decode the result      my $json = decode_json($result);        # Get the sandbox root      # This is required becuase of defect 213514 - 'lscm diff file' does not accept alias/item id      my $sandboxRoot = getSandboxRoot();        # Loop through all the workspaces, components and unresolved changes      foreach my $ws (@{$json->{workspaces}}) {          my $repoUri = $ws->{"url"};          foreach my $component (@{$ws->{components}}) {              foreach my $unresolvedChange (@{$component->{"unresolved"}}) {                  my $state = $unresolvedChange->{"state"};                  if ($state->{"content_change"} eq JSON::true) {                      my $filePath = $sandboxRoot . $unresolvedChange->{"path"};                      diffFile($filePath, $repoUri);                  }              }                  }      }            # Find the sandbox root based on the current directory      sub getSandboxRoot {          my $sandboxRoot = cwd();                    while (true) {              $sandboxMetaDir = $sandboxRoot . "/.jazz5";              if (-d $sandboxMetaDir) {                 return $sandboxRoot;               }                            # Remove the last segment              my $index = rindex($sandboxRoot, "/");              if ($index == -1) {                  last;                              }                            $sandboxRoot = substr($sandboxRoot, 0, $index);          }                    print "Could not locate the sandbox rootn";          exit(1);      }            sub diffFile() {          my $filePath = shift;          my $repoUri = shift;            while (true) {              print "Diff file: " . $filePath . " (y|n|q): ";              chomp(my $input = <STDIN>);                            if ($input =~ /^[Y]$/i) {                  my $result = 'lscm diff file -r $repoUri $filePath';                  print $result . "n";                  last;              } elsif ($input =~ /^[N]$/i) {                  last;              } elsif ($input =~ /^[Q]$/i) {                  exit (0);              }          }      }      	      #      # Routine that prints the error message and quits      #      sub quitOnError {          my $error_code = shift;          my $error = shift;                    if ($error_code != 0) {              print $error . "n";              exit($error_code);          }      }  

Download Example 2 – You’ll need to remove the .txt extension.


Example 3: Find a file in the workspace or stream

The subcommand ‘lscm list remote-files’ provides the capability to list files in the workspace but there is a limitation that it only lists files one component at a time. The below sample script will list the files in all the components by first finding the components of a workspace/stream and then listing the files for each of those components.

Perl script:

  # Script usage: findfile.pl {file pattern} {workspace name/alias/uuid} {repoUri}  # Script usage example: findfile.pl my*file.txt myWorkspace1 repoUri    use JSON;        my $filePattern = $ARGV[0];      my $workspace = $ARGV[1];      my $repoUri = $ARGV[2];            # Get all the components for the workspace      my $result = 'lscm list components $workspace -r $repoUri --json';      quitOnError($?, $result);            # Decode the result      my $json = decode_json($result);        # Loop through the components      my $ws = $json->{workspaces}[0];      foreach my $component (@{$ws->{components}}) {          my $componentId = $component->{"uuid"};          findPatternInComponent($filePattern, $workspace, $componentId, $repoUri);      }            #      # Routine that prints the matching file/folder found in the component      #      sub findPatternInComponent {          my $filePattern = shift;          my $workspace = shift;          my $component = shift;          my $repoUri = shift;                    # Get all the files/folders for the component                 my $result = 'lscm list remotefiles $workspace $component -r $repoUri --depth - --json';          quitOnError($?, $result);                    # Decode the result          my $json = decode_json($result);                    # Loop through the file/folders          foreach my $entry (@{$json->{"remote-files"}}) {              # Get the file/folder name              if ($entry->{"path"} =~ "([^/]+)/?$") {                  # match the file pattern                  if ($1 =~ $filePattern) {                      print $entry->{"path"} . "n";                  }              }          }      }  	      #      # Routine that prints the error message and quits      #      sub quitOnError {          my $error_code = shift;          my $error = shift;                    if ($error_code != 0) {              print $error . "n";              exit($error_code);          }      }  

Download Example 3 – You’ll need to remove the .txt extension.

Shell script using jsawk:

  # Script usage: findfile.pl {file pattern} {workspace name/alias/uuid} {repoUri}  # Script usage example: findfile.sh my*file.txt myWorkspace1 repoUri    compArray='lscm list components $2 -r $3 --json | jsawk "return Q('$..components..uuid', this)" | jsawk -a 'return this.join(" ")''  for comp in $compArray  do      fileArray='lscm list remotefiles $2 $comp -r $3 --depth - --json | jsawk "return Q('..path', this)" | jsawk -a 'return this.join(" ")''      for file in $fileArray      do          fname='basename $file'                    # match the file pattern          if [[ $fname = $1 ]]          then              echo $fname          fi      done  done  

Download Example 4


Summary

In this article we took a look at how to process the JSON output generated by one subcommand and use it in subsequent subcommands. The above scripts touched upon basic developer usecases but more complex scripts can be written to target build/release usecases.

Related articles


Dashboards and work items are no longer publicly available, so some links may be invalid. We now provide similar information through other means. Learn more here.
Feedback
Was this information helpful? Yes No 5 people rated this as helpful.