=head1 NAME VCS::CMSynergy - Perl interface to Telelogic Synergy =head1 SYNOPSIS use VCS::CMSynergy; $ccm = VCS::CMSynergy->new(%attr); ($rc, $out, $err) = $ccm->ccm($ccm_command, @ccm_args); ($rc, $out, $err) = $ccm->any_ccm_command(@ccm_args); $ary_ref = $ccm->query(@ccm_args); $ary_ref = $ccm->query_arrayref($query, @keywords); $ary_ref = $ccm->query_hashref($query, @keywords); $ary_ref = $ccm->query_object($query, @keywords); $ary_ref = $ccm->finduse(@args); $path = $ccm->findpath($file_spec, $proj_vers); $ary_ref = $ccm->history(@ccm_args); $ary_ref = $ccm->history_arrayref($file_spec, @keywords); $ary_ref = $ccm->history_hashref($file_spec, @keywords); $ary_ref = $ccm->ls(@ccm_args); $ary_ref = $ccm->ls_object($file_spec); $ary_ref = $ccm->ls_arrayref($file_spec, @keywords); $ary_ref = $ccm->ls_hashref($file_spec, @keywords); $value = $ccm->get_attribute($attr_name, $file_spec); $ccm->set_attribute($attr_name, $file_spec, $value); $hash_ref = $ccm->list_attributes($file_spec); $delim = $ccm->delimiter; $database = $ccm->database; $ENV{CCM_ADDR} = $ccm->ccm_addr; This synopsis only lists the major methods. Methods that don't need a CM Synergy session are described in L. In fact, C is derived from C. Methods for administering users and their roles are described in L. =head1 DESCRIPTION use VCS::CMSynergy; my $ccm = VCS::CMSynergy->new(database => "/ccmdb/test/tut62/db"); $ccm->checkout(qw(foo/bar.c@foo~user -to test)) or die "checkout failed: ".$ccm->error; my $csrcs = $ccm->query_hashref("type = 'csrc'", qw(displayname modify_time)); if ($csrcs) { print "$_->{displayname} $_->{modify_time}\n" foreach (@$csrcs); } =head1 OPTIONS The following optional features can be enabled at compile time with the notation use VCS::CMSynergy ':option'; =head2 :cached_attributes This causes Ls to keep a cache of attribute names and values. The cache is only maintained for those attributes that are actually accessed by the program. See L for a list of methods perusing this cache. Note that this cache is only maintained if you use L methods (including the L) and will get inconsistent if you mix C and C calls on the same object. =head2 :tied_objects If this option is in effect. you can use a C in the same way you would use a hash reference. The available keys are the underlying CM Synergy object's attributes. See L for details. =head1 GENERAL METHODS =head2 new my $ccm = VCS::CMSynergy->new( database => "/ccmdb/foo/db" ) or die VCS::CMSynergy->error; Starts a new CM Synergy session. Returns a session handle if it succeeds. If it fails to start a session, it returns C. Use C<< VCS::CMSynergy->error >> to get the error string printed by CM Synergy. Multiple simultaneous sessions to multiple databases or with engines running on different hosts, even using different versions of CM Synergy, are supported. C issues a B command and remembers the C in the session object (together with other session state). The session is stopped (B) when the session object is destroyed (see L). C is called with an attribute hash. The following attributes are currently supported: =over 4 =item C (string) CM Synergy database path. This is the only attribute required on Unix systems. =item C (string) CM Synergy engine host to use. It defaults to the local host. =item C (string) User's initial CM Synergy role. It defaults to C. =item C (string) CM Synergy user. This attribute is available and required on Windows systems only. =item C (string) User's password. This attribute is required on Windows systems or when using ESD to connect to the CM Synergy engine. =item C (string) CM Synergy ini file to use. In contrast to the CM Synergy B command there is I default ini file consulted. (On Unix systems this is achieved by executing B with the option C<-f /dev/null>.) The reason is that we want scripts to behave in a reproducible way. Otherwise the script might accidentally work with the current contents of the current user's ini file, but might fail when invoked by another user. Or it might fail when invoked by the same user at a later time because of changes to her ini file (e.g. because of another session between invocations of the script). So if you really want to rely on an ini file, you have to supply it explicitly. =item C (string) Specifies the RFC address of an established CM Synergy session. If you specify this attribut L does not create a new session, but will attach to the one specified. Also, implicitly sets C to "on" so that destruction of the new session handle will not cause a B. However, setting C explicitly will take precedence. Note that there is no default value. In particular, L ignores the environment variable of the same name. =item C (string) Value of the C environment variable to use for this session. It defaults from the environment variable of the same name, i.e. C<$ENV{CCM_HOME}>. This is only of interest if you have multiple version of CM Synergy installed. You can have simultaneous sessions using different CM Synergy versions (the module takes care of setting the C variable appropriately before issuing any C commands). =item C (string) Specifies the path name to which your database information is copied when you are running a remote client session. This corresponds to the C<-u pathname> option for B. Note: This option is particularly useful for Windows clients. If L fails with something like Server Database Path ... is not accessible from this Client. Please specify a Client Database Path you should specify this option with a local directory path, e.g. my $ccm = VCS::CMSynergy->new(..., ui_database_dir => 'c:\\temp', ...); The value is what you would enter under "Client Information"/"Database Path" in the GUI's "Startup Info" window. Or you can set B in the [Options] section of the system ini file (note that setting it in your personal ini file won't do, as this file is I read by L by default). =item C (boolean) If the value is "on", it specifies that you want to start the CM Synergy session as a remote client. This corresponds to the C<-rc> option for B. This option is only useful on Unix systems. It defaults to "off". =item C (boolean) This attribute can be used to force errors to generate warnings (using L) in addition to returning error codes in the normal way. When set to true, any method which results in an error occuring will cause the corresponding C<< $ccm->error >> to be printed to stderr. It defaults to "on". Note: L and L below are stolen from the excellent L module. =item C (boolean) This attribute can be used to force errors to raise exceptions (using L) rather than simply return error codes in the normal way. When set to true, any method which results in an error will cause effectively a C with the actual C<< $ccm->error >> as the message. It defaults to "off". If you turn C on then you'd normally turn C off. If C is also on, then the C is done first (naturally). Typically C is used in conjunction with C to catch the exception that's been thrown and followed by an C block to handle the caught exception. If you want to temporarily turn C off (inside a library function that is likely to fail, for example), the recommended way is like this: { local $ccm->{RaiseError}; # localize and turn off for this block ... } The original value will automatically and reliably be restored by Perl, regardless of how the block is exited. The same logic applies to other attributes, including C. =item C (code ref) This attribute can be used to provide your own alternative behaviour in case of errors. If set to a reference to a subroutine then that subroutine is called when an error is detected (at the same point that L and L are handled). The subroutine is called with three parameters: the error message string that L and L would use, the C object being used, and the value being returned by the method that failed (typically undef). If the subroutine returns a false value then the L and/or L attributes are checked and acted upon as normal. Otherwise the error is considered "handled" and execution proceeds normally with a return from the method. For example, to "die" with a full stack trace for any error: use Carp; $ccm->{HandleError} = sub { confess(shift) }; =item C (boolean) If this attribute is "on" then destruction of the new session handle will not cause a B. This may be used if you want to create a new CM Synergy session in one program and then re-use it in another program (since session creation is a rather time consuming operation). In this case you should use C to extract the session's RFC address (after C returns) and somehow pass it on to the other program. It defaults to "off" unless you also specify C. =item C (boolean) This feature is highly experimental, B. B module installed to use this feature.> (Since L is not available for Win32 systems, C is ignored there.) If C is "off", C executes a separate C process whenever it invokes the CM Synergy CLI, e.g. $ccm->checkout('foo.c'); $ccm->set_attribute('color', 'foo.c', 'blue'); $csources = $ccm->query("name match '*.c'"); results in the execution of the following three processes: ccm checkout foo.c ccm attribute -modify color -value blue foo.c ccm query "name match '*.c'" In particular, we incur the startup overhead of B three times. This overhead is noticable, esp. if you are doing lots of CM Synergy operations. If C is "on", only one B process per CM Synergy session ever gets executed. The way it works is that C<< VCS::CMSynergy->new >> starts an "interactive" (i.e. one invoked without arguments) B process in the background. Later invocations of the CM Synergy CLI pipe their commands to its input and read back the output (up to the next C<< "ccm>" >> prompt). The actual command is then followed in the same way by C to retrieve the success status. Destruction of the session object will cause termination of this "coprocess" (via "stop" or "exit" depending on the setting of L). The "coprocess" method avoids the startup overhead, but may run into other problems: =over 4 =item * The "interactive" B imposes stricter limits on the length of one CLI command (experimentally put at ~2000 bytes) than the "batch" B (where the limit on the arguments of a process is typically imposed by the operating system). Moreover, it will silently truncate the command and not signal an error (unless the truncation causes a syntax error). =item * The current method to communicate with the "coprocess" does not allow for separation of its stdout and stderr. =item * C does not work under Win32 at all. =back The default value of C is "off". =back =head2 DESTROY $ccm->DESTROY; Stops the CM Synergy session represented by the session handle by executing B (unless the session has the C attribut set). You should never call this method explicitly, as it is invoked by the Perl runtime when the Perl process exits (either by calling C or because of a C). Hence, a script using the C module will not leave any CM Synergy sessions hanging around. Actually, the Perl runtime will call C when the last reference to a session handle goes out of scope, so in the following example each session will be stopped as soon as one loop through the C body is completed, i.e. there is at most one session in progress at any one time: my @databases = ...; # a list of CM Synergy databases foreach my $db (@databases) { my $ccm = VCS::CMSynergy->new( database => $db, ... ); ... # perform some operation on $db ... # session is stopped as "my" variable $ccm is about to go out of scope } Note: The correct way to explicitly stop a session is neither $ccm->stop; nor is it $ccm->DESTROY; Though both forms will execute B, the first form makes C<$ccm> a C object with an invalid RFC address (i.e. attribute CCM_ADDR), while the second form leaves you with an "empty" C object. Instead, you should rather say $ccm = undef; =head2 ccm ($rc, $out, $err) = $ccm->ccm($command, @args); This is the workhorse of the VCS::CMSynergy module. It executes B with command C<$command> and (optional) parameters C<@args>. In array context it returns a three-element array consisting of the (operating system) exit code of B, and what B printed on stdout and stderr. Note that the exit code is 0 if B operated successfully. On DOSish operating systems the (possibly multi-line) strings C<$out> and C<$err> have been read by Perl in "text" mode, i.e. contain LF characters instead of CRLF. In any case, C<$out> and C<$err> have been Ced. In scalar context C returns the "logical" exit code, i.e. C, so that you can write: $ccm->ccm('checkout', $file_spec) or die "checkout failed: ".$ccm->error; Note that you must pass every C argument or option as a single Perl argument. For literal arguments the C notation may come in handy, e.g. ($rc, $out, $err) = $ccm->ccm(qw(finduse -state working)); Most specialized methods in the VCS::CMSynergy module are ultimately implemented via the L method. Using it directly is only recommended for commands that perform some action, e.g. B, as opposed to query-like commands. For the latter, e.g. B, use one of the methods that return the information in structured form, e.g. L or L, instead of having to parse C<$out> yourself. In fact, there is a shortcut for "action" commands: if you call a non-existent method on a VCS::CMSynergy object, it tries to invoke the L method with the original method name as the C<$command> followed by the parameters of the original call, i.e. $ccm->checkout($file_spec); and $ccm->ccm('checkout', $file_spec); are equivalent (given that there is no real C method). Return values are those of L (depending on context). This is accomplished by a suitable C method. =head1 QUERY METHODS =head2 query $ary_ref = $ccm->query(@args); Executes the B command with the given C<@args> as parameters. The output (as formatted by the C<-format> option) is split into lines. These are Led and a reference to the resulting array of strings is returned. If there a no hits, a reference to an empty array is returned. (Note that B considers this an error, but VCS::CMSynergy does not.) If there was an error, C is returned. Note that you must pass every B argument or option as a single Perl argument. For literal arguments the C notation may come in handy. Example: $result = $ccm->query(qw(-t csrc -f), '%displayname %modify_time'); print "$_\n" foreach (@$result); If you are interested in the value of several attributes for the result set of the query, you should look at the L and L methods that return this information in structured form. If you are only interested in the identity of objects in the result set, you should look at the L method. Note that L will probably produce unpredictable results when the C<-format> option references attributes that can have multi-line values, e.g. C. L and L handle this case correctly. =head2 query_arrayref, query_hashref $ary_ref = $ccm->query_arrayref($query, @keywords); print "@$_\n" foreach @$ary_ref; $ary_ref = $ccm->query_hashref($query, @keywords); print "@$_{@keywords}\n" foreach @$ary_ref; C and C execute B with the query expression C<$query> asking for the values of the built-in keywords or attributes supplied in C<@keywords>. They both return a reference to an array of references, one per result row. C represents a row as an array containing the values of the keywords for that particular object in the result set (in the order given by C<@keywords>). C represents a row as a hash containing attribute and value pairs where the keys are the C<@keywords>. If the query returned no hits, both C and C return a reference to an empty array. If there was an error, C is returned. If the value of a keyword or an attribute is undefined or the attribute is not present, the actual value of the corresponding array or hash element is C (whereas B would print it as the string C<< "" >>). The following names may also be used as keywords though they are neither built-in nor attributes: =over 4 =item C The value is a C representing the object in the result set. =item C The value is a reference to a hash identifying in what parts of what projects the object is used. A key in the hash is the project's objectname. The hash value is the corresponding relative path (including the object's name) in the project. This information is the same as reported by B. In fact, if this keyword is given, L and L invoke B rather than B. Example: my $result = $ccm->query_arrayref( "name = 'main.c'", qw(objectname finduse)); returns (as formatted by L): $result = [ [ 'main.c-1:csrc:3', # objectname { # finduse 'guilib-1.0' => 'guilib/sources/main.c', 'guilib-int' => 'guilib/sources/main.c', 'guilib-darcy' => 'guilib/sources/main.c' } ], ... ]; =item C C actually I a built-in keyword. However, CM Synergy B returns the deprecated I (i.e. C) for certain model objects (e.g. try B) (but refuses to accept them as arguments later). Therefore C will rewrite these Is to correct Is before returning them from C or C. =item C The value is a reference to an array of C representing the tasks associated with the object. The value is C if there are no associated tasks. This keyword is implemented using the Synergy built-in keyword "%task". =item C The value is a reference to an array of C representing the change request associated with the object. The value is C if there are no associated change requests. This keyword is implemented using the Synergy built-in keyword "%change_request". =item C The value is a C representing the object's baseline project. The value is C if no baseline project exists. This keyword is implemented using the Synergy built-in keyword "%baseline". =item C The value is a C representing the object's baseline. The value is C if the object isn't in a baseline. This keyword is implemented using the Synergy built-in keyword "%in_baseline". =back Note the following differences from B: =over 4 =item * The keyword or attribute names given in C<@keywords> should I contain a leading C<%>. Example: my $result = $ccm->query_hashref("name match '*.c'", qw(displayname type modify_time); foreach my $row (@$result) { print "$row->{displayname} last modified at $row->{modify_time}\n"; ... } =item * These methods do I support any of the shortcut query options of the B command, e.g. B<-o owner> or B<-n name>. However, a different shortcut syntax is supported, see L. =item * C<$query> may contain newlines to improve the legibility of longish queries with whitespace and line breaks. Any whitespace in C<$query> will be replaced by a single blank before submitting it to B. =back =head2 query_object, query_object_with_attributes $ary_ref = $ccm->query_object($query); $ary_ref = $ccm->query_object_with_attributes($query, @keywords); Executes B with the query expression C<$query> and returns a reference to an array of Cs that satisfy the query. If there a no hits, a reference to an empty array is returned. If there was an error, C is returned. Note: This is a convenience method. It might be implemented using C: sub query_object { my ($self, $query) = @_; my $ary = $self->query_arrayref($query, 'object') or return; [ map { $_->[0] } @$ary ]; # project onto first (and only) column } C is only useful when L is in effect. It returns the same result as C, but the returned Cs have their attribute caches primed for the attributes listed in C<@keywords>. You could also view it as a fancy form of C where we don't store the attributes values of C<@keywords> in some anonymous hash, but rather in the corresponding object. Thus the loop for my $obj (@{ $ccm->query_object_with_attributes("...", qw(foo)) }) { print "$obj: foo=", $obj->get_attribute("foo"), "\n"; } issues a I B calls. Note: this example assumes use VCS::CMSynergy qw(:cached_attributes); =head2 query_count $n = $ccm->query_count($query); Returns the number of objects matched by C<$query>, 0 if nothing matched. This is the same as scalar @{ $ccm->query_object($query) } but it's implemented more efficiently (and also less prone to exhaust the 10 MB query result buffer in the Synergy engine). If there was an error, C is returned. =head2 shortcut query notation L, L, L, L and L support a shortcut notation for their common C<$query> parameter. To use this shortcut, supply a hash reference for C<$query> (instead of a simple string): $result = $ccm->query_hashref( { type => 'csrc', match => '*.cpp' }, qw(objectname status)); Every C<< key => value >> represents a simple query. Simple queries are combined with AND. The following simple queries are accepted: =over 4 =item "key" => $scalar This is translated to CMSynergy query syntax as C. Note the quotes around C<$scalar>. However, quotes are omitted if C<$scalar> is either the string C<"TRUE"> or C<"FALSE">. In general, C is the name of an attribute. The following keys are treated specially: =over 4 =item match C<< match => $scalar >> is short for C. =item task C<< task => $tasknr >> is short for C. This corresponds to CM Synergy's B. =back =item "key" => \@array This is translated as a call of a query function, i.e. C. Quoting is as described above. Example: $ccm->query_object( { hierarchy_project_members => [ 'toolkit-1.0:project:1', 'none' ] }); =item "key" => \%hash This is translated as a call of a query function with a nested query as parameter: Example: $rel = '6.0'; $ccm->query_object( { is_member_of => { release => $rel, match => '*web*' }); gets translated to "is_member_of(release='6.0' and name match '*web*')" =back =head2 history $ary_ref = $ccm->history(@args); Executes the B command with the given C<@args> as parameters. The output (probably formatted by the C<-format> option) is split into chunks at the divider line (a line consisting of lots of asterisks). A reference to the resulting array of (multi-line) strings is returned. If there was an error, C is returned. Note that you must pass every B argument or option as a single Perl argument. For literal arguments the C notation may come in handy. If you are interested in the successor or predecessor or certain attributes of an object in the history, you should look at the L and L methods that return this information in structured form. =head2 history_arrayref, history_hashref $ary_ref = $ccm->history_arrayref($file_spec, @keywords); $ary_ref = $ccm->history_hashref($file_spec, @keywords); C and C execute B for C<$file_spec> asking for the values of the built-in keywords or attributes supplied in C<@keywords>. The both return a reference to an array of references, one per history entry. C represents a history entry as an array containing the values of the keywords for that particular object in the history (in the order given by C<@keywords>). C represents a history entry as a hash containing attribute and value pairs where the keys are the C<@keywords>. If there was an error, C is returned. If the value of a keyword or an attribute is undefined or the attribute is not present, the actual value of the corresponding array or hash element is C (whereas B would print it as the string C<< "" >>). The following names may also be used as keywords though they are neither built-in nor attributes: =over 4 =item C The value returned is a reference to an array of Cs that represent the given object's predecessors. =item C The value returned is a reference to an array of Cs that represent the given object's successors. =item C, C, C For these pseudo keywords see the description of the L and L methods. =back Note the following differences from B: =over 4 =item * Only one C<$file_spec> is allowed. =item * There is no C<-p> (project) option. If you want to get the history of a project use the full objectname of the project for C<$file_spec>. =item * The keyword or attribute names given in C<@keywords> should I contain a leading C<%>. Example: my $result = $ccm->history_hashref( 'math.h-1:incl:1', qw(displayname modify_time successors)); foreach my $row (@$result) { print "$row->{displayname}: last modified at $row->{modify_time}\n"; print "\t$_\n" foreach (@{ $row->{successors} }); ... } =back =head2 finduse $ary_ref = $ccm->finduse(@args); Executes the B command with the given C<@args> as parameters. It returns a reference to an array of rows, usually one per C given in C<@args>, or one per query result if C<-query $query_expression> is present in C<@args>. Each row is a reference to an array of two elements. The first element is the description of the object. The second element is a reference to a hash identifying in what parts of what projects the object is used. A key in the hash is the project's objectname. The hash value is the corresponding relative path (including the object's name) in the project. If there are no uses of the object in the given scope the hash is empty. This usage information is in the same form as that for the pseudo keyword C of the L and L methods. If there was an error, C is returned. Note that you must pass every B argument or option as a single Perl argument. For literal arguments the C notation may come in handy. If you are interested in usage information for all objects matching a query you should look at the L and L methods, esp. the C keyword. Example (recreate the output of the B command): foreach (@{ $ccm->finduse(@args) }) { my ($desc, $uses) = @$_; print "$desc\n"; if (keys %$uses) { while (my ($proj_vers, $path) = each %$uses) { print "\t$path\@$proj_vers\n" } } else { print "\tObject is not used in scope.\n"; } } =head2 findpath $path = $ccm->findpath($file_spec, $proj_vers); This is a convenience function. It returns the relative pathname (including the objects's name) for the object C<$file_spec> within the project C<$proj_vers>. Returns C if C<$file_spec> is not used in C<$proj_vers> or if C<$file_spec> does not exist. Example: $ccm->findpath("main.c-1:csrc:3", "guilib-darcy"); returns "guilib/sources/main.c" =head2 relations_hashref $ccm->relations_hashref(%options); $ccm->relations_hashref( to => "bufcolor.c-2:csrc:1", from_attributes => [ qw/objecctname status owner/ ]); Executes B where C<%options> may contain any of the following keys: =over 4 =item from => $file_spec, to => $file_spec Restricts one or both ends of the relation. The option value may be any C<$file_spec> accepted by CM Synergy including a C. =item name => $string Restricts the return value to relations of type C<$string>, e.g. C or C. =item from_attributes => \@keywords, to_attributes => \@keywords The option value is a reference to an array of attributes that should be retrieved for the "from" and "to" objects of a relation, resp. =back The result is a reference to an array of hashes where each hash has exactly four keys describing a relation between two objects: =over 4 =item from The value describes the "from" object of the relation. If C was not specified the value is the I of the "from" object. If C was specified the value is a reference to a I, its keys given by C. The pseudo keywords C and C (see L) may be used in C. =item to The value describes the "to" object of the relation. The value depends on C as described for the "from" key. =item name The value is name of the relation. =item create_time The value is the time the relation was created. =back If there a no hits, a reference to an empty array is returned. If there was an error, C is returned. =head2 relations_object my $relations = $ccm->relations_object(%options); Executes B where C<%options> are the same as described for L. The result is a reference to an array of hashes where each hash has exactly four keys describing a relation between two objects: =over 4 =item from, to The value is a C. =item name The value is name of the relation. =item create_time The value is the time the relation was created. =back If there a no hits, a reference to an empty array is returned. If there was an error, C is returned. If C or C are specified, these are used as hints to prime the attribute caches of the "from" or "to" Cs, resp., similar to L. This is only useful when L is in effect. =head2 project_tree $hash = $ccm->project_tree(\%options, $project); $hash = $ccm->project_tree(\%options, $project1, $project2 ...); C traverses the given project(s) and constructs a mapping of path names to project members. It doesn't need a workarea. C<$project> may be any project specification (in project-version form, an objectname or a C). C returns a reference to a hash where the keys are the (relative workarea) path names of project members. If given one project (the first form above), hash values are C<$project>'s members, given as Cs. If given two or more projects (the second form), a hash value is an array of Cs where the first element is the member of C<$project1> mapped to the path key, the second element is the member of C<$project2> etc. If there is no member mapped to a path in a particular project, the corresponding element in the array is C. This form of C may be useful for comparing projects, see below for an example. If there was an error, C returns C. The first argument, C<\%options>, is either C or a hash reference of options for project traversal: =over 4 =item C (boolean) If this option is set, the mapping will recurse into subprojects. It is "off" by default. It corresponds to the option of the same name for L. =item C (array ref) This option is only useful if L is in effect. All returned Cs will have their attribute caches primed for the given attributes. See the description of the option of the same name for L. =item C (string) Use C as the separator for the path names (the keys of the returned hash). If you do not specify this, C uses the path separator appropriate for the operating system the script is running on. =item C (boolean) This option is "off" by default. If a path corresponds to a (sub) project, the correspondig hash value refers to the (sub) project's top level directory (a C with C<< ->cvtype >> "dir"); if this option is set, the hash value refers to the (sub) project itself (a C with C<< ->cvtype >> "project"). Note that in this case the top level directory is omitted from the mapping (because it has the same path as its project). =back The following example shows how to compute the "difference" between two projects expressed in the file system. Note that we suppress changes in "dir" objects because the effect of the change (added/deleted objects bound into the directory) will be reported anyway. See F for a complete working program. my $tree = $ccm->project_tree(undef, $proj1, $proj2); foreach my $path (sort keys %$tree) { my ($obj1, $obj2) = @{ $tree->{$path} }; print("added $path ($obj2)\n"), next unless defined $obj1; print("deleted $path ($obj1)\n"), next unless defined $obj2; print("changed $path ($obj1 -> $obj2)\n") unless $obj1 eq $obj2 || ($obj1->cvtype eq "dir" && $obj2->cvtype eq "dir"); } =head1 ATTRIBUTE METHODS =head2 get_attribute $value = $ccm->get_attribute($attr_name, $file_spec); Get the value of the attribute C<$attr_name> for C<$file_spec> (using B). If the attribute isn't defined for C<$file_spec>, C is returned. If C is not set and an error occurs (e.g. object C<$file_spec> doesn't exist), C will be returned. Note the following differences from B: =over 4 =item * Only one C<$file_spec> is allowed. =item * There is no C<-p> (project) option. If you want to get an attribute of a project use the full objectname of the project for C<$file_spec>. =item * It is I an error to get the value of an attribute that isn't defined for the particular object. Instead, check the return value of L with C as an attribute's value can never be C. =back =head2 set_attribute $ccm->set_attribute($attr_name, $file_spec, $value); Set the value of the attribute C<$attr_name> for C<$file_spec> to C<$value> (usually using B, but see below). Returns C<$value> on success. If C is not set and an error occurs (e.g. attribute C<$attr_name> does not exist on object C<$file_spec>), C will be returned. This works for B types of attributes, even those of type I (or derived from I) and with C<$value>s that consist of multiple lines, have arbitrary length or are empty strings. Note the following differences from B: =over 4 =item * If the attribute C<$attr_name>is inherited, B will fail. But C will retry with B in this case (thereby converting the attribute to a local attribute). I.e. whenever L indicates that an attribute exists (by returning something C), you can always set its value with C (given you have necessary permissions). =item * Only one C<$file_spec> is allowed. =item * There is no C<-p> (project) option. If you want to set an attribute of a project use the full objectname of the project for C<$file_spec>. =back =head2 create_attribute $ccm->create_attribute($attr_name, $type, $value, @file_specs); Create attribute C<$attr_name> of type C<$type> on all objects given by C<@file_specs> (using B). You must specify an initial value (something other than C) as C<$value>. Returns true on success and C on failure. Note the following differences from B: =over 4 =item * The initial value is mandatory. =item * There is no C<-p> (project) option. If you want to set an attribute of a project use the full objectname of the project for C<$file_spec>. =back =head2 delete_attribute $ccm->delete_attribute($attr_name, @file_specs); Delete attribute C<$attr_name> from all objects given by C<@file_specs> (using B). Returns true on success and C on failure. Note the following differences from B: =over 4 =item * There is no C<-p> (project) option. If you want to set an attribute of a project use the full objectname of the project for C<$file_spec>. =back =head2 copy_attribute $ccm->copy_attribute($attr_name, $from_file_spec, @to_file_specs); $ccm->copy_attribute($attr_name, $flags, $from_file_spec, @to_file_specs); Copy attribute C<$attr_name> from C<$from_file_spec> by objects given by C<@to_file_specs> (using B). Returns true on success and C on failure. You can specify multiple attributes to copy by passing a reference to an array of attribute names as C<$attr_name>. The optional C<$flags> must be reference to an array containing a subset of the following strings: C<"append">, C<"subproj">, C<"suball">, e.g. $ccm->copy_attribute($attr_name, [ qw(subproj suball) ], "proja-1.0:project:1", "projb-1.0:project:1"); Cf. the CM Synergy documentation on the I for the meaning of these flags. Note the following differences from B: =over 4 =item * There is no C<-p> (project) option. If you want to set an attribute of a project use the full objectname of the project for C<$file_spec>. =back =head2 list_attributes $hash_ref = $ccm->list_attributes($file_spec); Lists all attributes for C<$file_spec> (using B). Returns a reference to a hash containing pairs of attribute name and attribute type (e.g. C, C