{ ## A clean lexical environment with no strictures in main:: sub SteamRoller::_eval{ my $dest = pop; warn ">>>evalling $dest<<<\n" if $ENV{DEBUGSTEAMROLLEDSCRIPT}; eval shift( @_ ) . "; 1;" or die $@; $INC{$dest} = "evalled" if length $dest; }; } { package SteamRoller; use strict; use Cwd; use File::Spec; use File::Basename qw( fileparse ); ## The BEGINs are so the monster strings below get parsed, used, and ## deallocated one at a time. Not using __END__/__DATA__ to allow ## adding user code that does so. my $original_cwd; my $parent_dir; my $dont_spew; my $digest; my $id_fn; my @lib_dirs; my $command_path; BEGIN { $digest = "U+3Il95PuUZjOPm4gh/GCA koNyikaCdb48uSB1d9I6JA 9aS1yB6P+BZxFgIl38fRmQ KrSOwNYqG88nUsCoIlw1Nw csKy4dpP4udEys0tfBoaIw NBoj/WZbC6hl1riX2CAHWw 3Zo45NhA/hfHSErz0FVT0g nWyNveGQCju+YVFLl1tp+Q 3LHYgY/QymZHLngPo5mt5g r3XVhIZdl5Yuvqla4WACyQ kVp+nnUYEYeg72WIiQ3XoQ ae11ArUeZjEgw9Cw2qWOvg SVdc7WSV+1Lum3DVD26Kqg AOLmE8S16mWoCaM6ete8HA F7SXgIxhaD6DSGoRc5yqNg eE8V9xWOxhQuOXRpUBWP/g DFV9gR1bTIgXj9p3I/n/oQ bzXtGKZHQrFIcYkpmf2Oeg P4IFprUlnmVlrt4ng3Qzkg tu6kDDp97NSnFE2xsc0pHg 2vImopU09qJOgA05kdMvag /WYEmKX81WQFvCKyOdHcog +OD0Rara1WcGGkrvxi37Lg hMgYmW/qIocLwlbVl1hVMg HoTJQBnEH+acvNCaPUlwAw 6K+hnrMf/oFchCAOKK2kKA gM2rSnIXKuWhKJ/d9LfWCg blurZ+885Zy3lnAH8feuLw RCtKz+bZJ5dF85KsZq2EZw ZH0TFtaZ+tGJGEj3PR32Lw C7fwA5E6Rz1uWHqYBJHyUg WBXpgiX8KtT6LwKfPPABtw +IJiErqM0mIO6F3KByI8Dw L9fxX8JPNV+P6yqAtTXrog IOzQ3YMz2IFWHh/7o8aHaA UsiBKr5dYaoiB3Fg2RiGRA +w8ZTtbUKLvqt0/5xbCTTw jBrXXC6qIEmwRhVBR9G/ow iqOGDi9mrGgXFRY0yntUIA cqeaH3KdK7RW04dKvw/U0g X0Mty+qK7LG++VfJCjkfSw QpZLmtoKzRegmpBzHTzLSg APluJvz0fzgY/YpYKYsK9A Op9N0gyzQbwJwnmY8fIiTQ 01qQNAnqaj60Ewx+SzFSyg /Rw6EN6yWVyuZ35WyE9+Eg APibHxbMXmZriSVVsGdxBw 3RgqwYH96lh+OoQrl6Somg wjJx6IsIGDhBLMYn9rdizw Dk2W0uyR+fEg0EMuHHkfVQ 9hVDLzTlHILDutbV3CQCFQ"; $id_fn = "id_M_eO__d2w5wa27w_2URRPQ"; @lib_dirs = ( 'lib' ); $command_path = ""; $original_cwd = cwd; sub _spew { return if $dont_spew; my $dest = pop; warn ">>>spewing $dest<<<\n" if $ENV{DEBUGSTEAMROLLEDSCRIPT}; $dest = File::Spec->catfile( $parent_dir, $dest ); my $dest_parent = (fileparse( $dest ))[1]; ## 0775: Trust in the user's umask "a bit", but don't allow writing mkpath( [ $dest_parent ], 0, 0775 ) unless -d $dest_parent; sysopen F, $dest, O_CREAT()|O_WRONLY(), 0775 or die "$!: $dest\n"; push @_, pop; ## make read/writable chomp $_[-1]; print F @_ or die "$!: $dest\n"; close F or die "$!: $dest\n"; } ## Search for an existing copy. ## This is more to prevent overwriting an old (put possibly still ## in service) copy than to speed things up. spewing a bunch ## of files like we do is pretty quick. my $payload_number = 0; while () { $parent_dir = File::Spec->catdir( File::Spec->tmpdir, scalar fileparse( $0 ) . "-tmp-$payload_number" ); last unless -d $parent_dir; my $id_file = File::Spec->catdir( $parent_dir, $id_fn ); if ( -f $id_file ) { my $id = do $id_file; if ( $id eq $digest ) { $dont_spew = 1; last; } } ++$payload_number; } eval <catdir( $parent_dir, $_ ), @lib_dirs ); } #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <, L,\nL, along with the tags ':all' and ':debug'. Use the latter\nto head off future namespace pollution in case :all gets expanded in the\nfuture..\n\n=head1 METHODS\n\n=over\n\n=cut\n\nuse strict ;\nuse File::Spec ;\nuse File::Path ;\nuse VCP::Debug ;\nuse vars qw( \$VERSION ) ;\n\n\$VERSION = 0.1 ;\n\nrequire VCP::Source ;\nrequire VCP::Dest ;\n\nuse fields (\n 'SOURCE', # The VCP::Source to pull data from\n 'DEST', # The RevML::Writer instance\n) ;\n\n\n=item new\n\n \$ex = VCP->new( \$source, \$dest ) ;\n\nwhere\n\n \$source is an instance of VCP::Source\n \$dest is an instance of VCP::Dest\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my ( \$source, \$dest ) = \@_ ;\n\n my VCP \$self ;\n {\n no strict 'refs' ;\n \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n \$self->{SOURCE} = \$source ;\n \$self->{DEST} = \$dest ;\n\n return \$self ;\n}\n\n\n=item dest\n\n \$dest = \$vcp->dest ;\n\nGets the dest object. This object is set by passing it to\nnew(), so there's no need to set it.\n\n=cut\n\nsub dest {\n my VCP \$self = shift ;\n return \$self->{DEST} ;\n}\n\n\n=item copy_all\n\n \$vcp->copy_all( \$header, \$footer ) ;\n\nCalls \$source->handle_header, \$source->copy_revs, and \$source->handle_footer.\n\n=cut\n\nsub copy_all {\n my VCP \$self = shift ;\n\n my ( \$header, \$footer ) = \@_ ;\n\n my VCP::Source \$s = \$self->source ;\n \$s->dest( \$self->dest ) ;\n\n \$s->handle_header( \$header ) ;\n \$s->copy_revs() ;\n \$s->handle_footer( \$footer ) ;\n\n ## Removing this link allows the dest to be cleaned up earlier by perl,\n ## which keeps VCP::Rev from complaining about undeleted revs.\n \$s->dest( undef ) ;\n return ;\n}\n\n\n=item source\n\n \$source = \$vcp->source ;\n\nGets the source object. This object is set by passing it to\nnew(), so there's no need to set it.\n\n=cut\n\nsub source {\n my VCP \$self = shift ;\n return \$self->{SOURCE} ;\n}\n\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAABW #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <SUPER::new( \@_ ) ;\n\n ## rev_id is here in case the change id isn't,\n ## name is here for VSS deletes, which have no other data.\n \$self->set_sort_spec( "presort,change,time,avgcommenttime,comment,name,rev_id" ) ;\n\n return \$self ;\n}\n\n\n###############################################################################\n\n=head1 SUBCLASSING\n\nThis class uses the fields pragma, so you'll need to use base and \npossibly fields in any subclasses.\n\n=head2 SUBCLASS API\n\nThese methods are intended to support subclasses.\n\n=over\n\n=item digest\n\n \$self->digest( "/tmp/readers" ) ;\n\nReturns the Base64 MD5 digest of the named file. Used to compare a base\nrev (which is the revision *before* the first one we want to transfer) of\na file from the source repo to the existing head rev of a dest repo.\n\nThe Base64 version is returned because that's what RevML uses and we might\nwant to cross-check with a .revml file when debugging.\n\n=cut\n\nsub digest {\n shift ; ## selfless little bugger, isn't it?\n my ( \$path ) = \@_ ;\n\n require Digest::MD5 ;\n my \$d= Digest::MD5->new ;\n open DEST_P4_F, "<\$path" or die "\$!: \$path" ;\n \$d->addfile( \\*DEST_P4_F ) ;\n\n my \$digest = \$d->b64digest ;\n close DEST_P4_F ;\n return \$digest ;\n}\n\n\n=item compare_base_revs\n\n \$self->compare_base_revs( \$rev ) ;\n\nChecks out the indicated revision fromt the destination repository and\ncompares it (using digest()) to the file from the source repository\n(as indicated by \$rev->work_path). Dies with an error message if the\nbase revisions do not match.\n\nCalls \$self->checkout_file( \$rev ), which the subclass must implement.\n\n=cut\n\nsub compare_base_revs {\n my VCP::Dest \$self = shift ;\n my ( \$rev ) = \@_ ;\n\n ## This block should only be run when transferring an incremental rev.\n ## from a "real" repo. If it's from a .revml file, the backfill will\n ## already be done for us.\n ## Grab it and see if it's the same...\n my \$source_digest = \$self->digest( \$rev->work_path ) ;\n \n my \$dest_digest = \$self->digest( \$self->checkout_file( \$rev ) ) ;\n die( "vcp: base revision\\n",\n \$rev->as_string, "\\n",\n "differs from the last version in the destination p4 repository.\\n",\n " source digest: \$source_digest\\n",\n " dest. digest: \$dest_digest\\n"\n ) unless \$source_digest eq \$dest_digest ;\n}\n\n\n=item header\n\nGets/sets the \$header passed to handle_header().\n\nGenerally not overridden: all error checking is done in new(), and\nno output should be generated until output() is called.\n\n=cut\n\nsub header {\n my VCP::Dest \$self = shift ;\n \$self->{DEST_HEADER} = shift if \@_ ;\n return \$self->{DEST_HEADER} ;\n}\n\n=back\n\n=head2 SUBCLASS OVERLOADS\n\nThese methods are overloaded by subclasses.\n\n=over\n\n=item backfill\n\n \$dest->backfill( \$rev ) ;\n\nChecks the file indicated by VCP::Rev \$rev out of the target repository if\nthis destination supports backfilling. Currently, only the revml destination\ndoes not support backfilling.\n\nThe \$rev->{workpath} must be set to the filename the backfill was put\nin.\n\nThis is used when doing an incremental update, where the first revision of\na file in the update is encoded as a delta from the prior version. A digest\nof the prior version is sent along before the first version delta to\nverify it's presence in the database.\n\nSo, the source calls backfill(), which returns TRUE on success, FALSE if the\ndestination doesn't support backfilling, and dies if there's an error in\nprocuring the right revision.\n\nIf FALSE is returned, then the revisions will be sent through with no\nworking path, but will have a delta record.\n\nMUST BE OVERRIDDEN.\n\n=cut\n\nsub backfill {\n my VCP::Dest \$self = shift ;\n die ref( \$self ) . "::backfill() not found, Oops.\\n" ;\n}\n\n\n=item handle_footer\n\n \$dest->handle_footer( \$footer ) ;\n\nDoes any cleanup necessary. Not required. Don't call this from the override.\n\n=cut\n\nsub handle_footer {\n my VCP::Dest \$self = shift ;\n return ;\n}\n\n=item handle_header\n\n \$dest->handle_header( \$header ) ;\n\nStows \$header in \$self->header. This should only rarely be overridden,\nsince the first call to handle_rev() should output any header info.\n\n=cut\n\nsub handle_header {\n my VCP::Dest \$self = shift ;\n\n my ( \$header ) = \@_ ;\n\n \$self->header( \$header ) ;\n\n return ;\n}\n\n=item handle_rev\n\n \$dest->handle_rev( \$rev ) ;\n\nOutputs the item referred to by VCP::Rev \$rev. If this is the first call,\nthen \$self->none_seen will be TRUE and any preamble should be emitted.\n\nMUST BE OVERRIDDEN. Don't call this from the override.\n\n=cut\n\nsub handle_rev {\n my VCP::Dest \$self = shift ;\n die ref( \$self ) . "::handle_rev() not found, Oops.\\n" ;\n}\n\n\n=head3 Sorting\n\n=over\n\n=item set_sort_spec\n\n \$dest->set_sort_spec( \@key_names ) ;\n\n\@key_names specifies the list of fields to sort by. Each element in the array\nmay be a comma separated list. Such elements are treated as though each name\nwas passed in it's own element; so C<( "a", "b,c" )> is equivalent to C<("a",\n"b", "c")>. This eases command line parameter parsing.\n\nSets the sort specification, checking to make sure that the field_names\nhave corresponding parse_sort_field_... handlers in this object.\n\nLegal field names include: name, change, change_id, rev, rev_id, comment,\ntime.\n\nIf a field is missing from all revs, it is ignored, however at\nleast one of rev_id, change, or time *must* be used.\n\nDefault ordering is by\n\n - change_id (compared numerically using <=>, for now)\n - time (commit time: simple numeric, since this is a simple number)\n - comment (alphabetically, case sensitive)\n\nThis ordering benefits change number oriented systems while preserving\ncommit order for non-change number oriented systems.\n\nIf change_id is undefined in either rev, it is not used.\n\nIf time is undefined in a rev, the value "-1" is used. This causes\nbase revisions (ie digest-only) to precede real revisions.\n\nThat's not always good, though: one of commit time or change number should\nbe defined! \n\nChange ids are compared numerically, times by date order (ie numerically, since\ntime-since-the-epoch is used internally). Comments are compared alphabetically.\n\nEach sort field is split in to one or more segments, see the appropriate\nparse_sort_field_... documentation.\n\nHere's the sorting rules:\n - Revisions are compared field by field.\n - The first non-equal field determines sort order.\n - Fields are compared segment by segment.\n - The first non-equal segment determines sort order.\n - A not-present segment compares as less-than any other segment, so\n fields that are leading substrings of longer fields come first, and\n not-present fields come before all present fields, including empty\n fields.\n\n=cut\n\nsub set_sort_spec {\n my VCP::Dest \$self = shift ;\n\n my \@spec = split ',', join ',', \@_ ;\n\n for ( \@spec ) {\n next if \$_ eq "presort";\n next if \$self->can( "parse_sort_field_\$_" ) ;\n croak "Sort specification \$_ is not available in ",\n ref( \$self ) =~ /.*:(.*)/ ;\n }\n\n debug "vcp: sort spec: ", join ",", \@spec\n if explicitly_debugging "sort" || debugging \$self ;\n \$self->{DEST_SORT_SPEC} = \\\@spec ;\n return undef ;\n}\n\n\n=item parse_sort_field_name\n\n push \@sort_key_segs, \$self->parse_sort_field_name( \$rev ) ;\n\nSplits the C of the revision in to segments suitable for sorting.\n\n=cut\n\nsub parse_sort_field_name {\n my VCP::Dest \$self = shift ;\n my VCP::Rev \$rev ;\n ( \$rev ) = \@_ ;\n\n for ( \$rev->name ) {\n return () unless defined ;\n return ("") unless length ;\n return split "/" ;\n }\n}\n\n=item parse_sort_field_rev\n=item parse_sort_field_rev_id\n=item parse_sort_field_revision\n=item parse_sort_field_revision_id\n=item parse_sort_field_change\n=item parse_sort_field_change_id\n\n push \@sort_key_segs, \$self->parse_sort_field_name( \$rev ) ;\n\nThese split the C or C of the revision in to segments\nsuitable for sorting. Several spellings of each method are provided for user\nconvenience; all spellings for each field work the same way. This is because\nusers may think of different names for each field depending on how much RevML\nthey know (the _id variants come from RevML), or whether they like to spell\n"revision" or "rev".\n\nThe splits occur at the following points:\n\n 1. Before and after each substring of consecutive digits\n 2. Before and after each substring of consecutive letters\n 3. Before and after each non-alpha-numeric character\n\nThe substrings are greedy: each is as long as possible and non-alphanumeric\ncharacters are discarded. So "11..22aa33" is split in to 5 segments:\n( 11, "", 22, "aa", 33 ).\n\nIf a segment is numeric, it is left padded with 50 NUL characters.\n\nThis algorithm makes 1.52 be treated like revision 1, minor revision 52, not\nlike a floating point C<1.52>. So the following sort order is maintained:\n\n 1.0\n 1.0b1\n 1.0b2\n 1.0b10\n 1.0c\n 1.1\n 1.2\n 1.10\n 1.11\n 1.12\n\nThe substring "pre" might be treated specially at some point.\n\n(At least) the following cases are not handled by this algorithm:\n\n 1. floating point rev_ids: 1.0, 1.1, 1.11, 1.12, 1.2\n 2. letters as "prereleases": 1.0a, 1.0b, 1.0, 1.1a, 1.1\n\nNever returns (), since C is a required field.\n\n=cut\n\n## This function's broken out to be shared.\nsub _pad_number {\n for ( \$_[0] ) {\n return () unless defined ;\n return ( "\\x00" x ( 50 - length ) ) . \$_[0] ;\n }\n}\n\nsub _pad_rev_id {\n map /^\\d+\\z/ ? _pad_number \$_ : \$_ , \@_ ;\n}\n\n\n## This function's broken out to be shared.\nsub _clean_text_field {\n for ( \$_[0] ) {\n return () unless defined ;\n return (\$_) ;\n }\n}\n\n## This function (not method) is broken out for testing purposes. Perhaps\n## later, it can be made in to a method to allow subclassing.\nsub _split_rev_id {\n for ( \$_[0] ) {\n return () unless defined ;\n return ( "" ) unless length ;\n\n return split /(?:\n\x09 (?<=[[:alpha:]])(?=[^[:alpha:]])\n\x09 |(?<=[[:digit:]])(?=[^[:digit:]])\n\x09 |[^[:alnum:]]+\n )/x ;\n }\n}\n\nsub _pad_decimal_number {\n for ( \$_[0] ) {\n return () unless defined ;\n return sprintf( "%0100.50f", \$_[0] );\n }\n}\n\n*parse_sort_field_rev_id = \\&parse_sort_field_rev ;\n*parse_sort_field_revision = \\&parse_sort_field_rev ;\n*parse_sort_field_revision_id = \\&parse_sort_field_rev ;\nsub parse_sort_field_rev {\n my VCP::Dest \$self = shift ;\n my ( \$rev ) = \@_ ;\n return _pad_rev_id _split_rev_id \$rev->rev_id\n}\n\n\n*parse_sort_field_change_id = \\&parse_sort_field_change ;\nsub parse_sort_field_change {\n my VCP::Dest \$self = shift ;\n my ( \$rev ) = \@_ ;\n return _pad_rev_id _split_rev_id \$rev->change_id ;\n}\n\n=item parse_sort_field_time\n\nPads and returns the seconds-since-epoch value that is the time.\n\n=cut\n\nsub parse_sort_field_time {\n my VCP::Dest \$self = shift ;\n my ( \$rev ) = \@_ ;\n ## We default time to 0 if it's a base rev, so it can be used\n ## as a sort key.\n return _pad_number \$rev->time || ( \$rev->is_base_rev ? 0 : undef ) ;\n}\n\n=item parse_sort_field_avgcommenttime\n\nPads and returns the seconds-since-epoch value that is the average\ntimestamp for all revs with this rev's comment. This allows apparent\nchanges that occur across time period boundaries (seconds, minutes,\ndays: whatever the source RCS gives out in terms of time value\nresolution) so that revs with identical comments will be grouped near\nthe time change between two time boundaries.\n\n=cut\n\nsub parse_sort_field_avgcommenttime {\n my VCP::Dest \$self = shift ;\n my ( \$rev ) = \@_ ;\n ## We default time to 0 if it's a base rev, so it can be used\n ## as a sort key.\n return _pad_decimal_number\n defined \$rev->comment\n ? \$self->{DEST_COMMENT_TIMES}->{\$rev->comment}\n : \$rev->is_base_rev ? 0 : undef;\n}\n\n=item parse_sort_field_comment\n\nJust returns the comment, undef, or maybe "" (see the source).\n\n=cut\n\nsub parse_sort_field_comment {\n my VCP::Dest \$self = shift ;\n my ( \$rev ) = \@_ ;\n return _clean_text_field\n defined \$rev->comment\n ? \$rev->comment\n : \$rev->is_base_rev\n ? ""\n : \$self->{DEST_DEFAULT_COMMENT};\n}\n\n\nsub _calc_sort_rec {\n my VCP::Dest \$self = shift ;\n my ( \$rev, \$spec ) = \@_ ;\n my \@fields = ( \$rev );\n\n for my \$spec ( \@\$spec ) {\n my \$sub = \$self->can( "parse_sort_field_\$spec" ) ;\n\x09die "Can't sort by \$spec, no parse_sort_field_\$spec found"\n\x09 unless \$sub ;\n\x09my \@segments = \$sub->( \$self, \$rev ) ;\n\x09confess \$rev->as_string, " contains an sort key"\n\x09 if grep !defined, \@segments ;\n push \@fields, \\\@segments ;\n }\n return \\\@fields ;\n}\n\n## The sort routine\nsub _rev_cmp {\n my \@a_fields = \@\$a ;\n my \@b_fields = \@\$b ;\n\n # The first "field" is the revision itself.\n my \$reva = shift \@a_fields;\n my \$revb = shift \@b_fields;\n confess "\\\$a[0] is a '\$reva', not a VCP::Rev" unless isa( \$reva, "VCP::Rev" );\n confess "\\\$b[0] is a '\$revb', not a VCP::Rev" unless isa( \$revb, "VCP::Rev" );\n\n debug "vcp cmp: ", \$reva->as_string, "\\n : ", \$revb->as_string\n if explicitly_debugging "sort" ;\n\n while ( \@a_fields && \@b_fields ) {\n my \$result ;\n my \@a_segments = \@{shift \@a_fields} ;\n my \@b_segments = \@{shift \@b_fields} ;\n unless ( \@a_segments && \@b_segments ) {\n debug "vcp cmp: pass" if \$result && explicitly_debugging "sort" ;\n next;\n }\n\n while ( \@a_segments && \@b_segments ) {\n my \$a_segment = shift \@a_segments;\n my \$b_segment = shift \@b_segments;\n\x09 debug "vcp cmp: ",\n defined \$a_segment ? \$a_segment : "",\n " cmp ",\n defined \$b_segment ? \$b_segment : ""\n\x09 if explicitly_debugging "sort" ;\n\n unless ( defined \$a_segment && defined \$b_segment ) {\n debug "vcp cmp: pass" if \$result && explicitly_debugging "sort" ;\n next;\n }\n\n\x09 \$result = \$a_segment cmp \$b_segment;\n\x09 debug "vcp cmp: \$result" if \$result && explicitly_debugging "sort" ;\n\x09 return \$result if \$result ;\n }\n debug "vcp cmp: " . \@a_segments . " <=> " . \@b_segments . " ( segment length)"\n\x09 if explicitly_debugging "sort" ;\n \$result = \@a_segments <=> \@b_segments ;\n debug "vcp cmp: \$result" if \$result && explicitly_debugging "sort" ;\n return \$result if \$result ;\n }\n\n confess "revs have different numbers of sort key fields:",\n \$reva->as_string, "\\n",\n \$revb->as_string \n if \@a_fields || \@b_fields ;\n\n debug "vcp cmp equal:", \$reva->as_string, "\\n :", \$revb->as_string\n if explicitly_debugging "sort" ;\n return 0 ;\n}\n\n=item sort_revs\n\n \$source->dest->sort_revs( \$source->revs ) ;\n\nThis sorts the revisions that the source has identified in to whatever order\nis needed by the destination. The default ordering is set by L.\n\nSorting is normally done in two passes. Each file's revisions are sorted\nby change, rev_id, or time, then the resulting lists of revisions are\nsorted in to one long list by pulling the "least" revision off the head\nof each list based on change, time, comment, and name.\n\nThis two-phased approach ensures that revisions of a file are always in\nproper order no matter what order they are provided, and furthermore in an\norder that enables change number aggregation (which is useful even if the\ndestination does not provide change numbers if only to do batch submits\nor get commit timestamps in a sensible order).\n\nThe sort specification "presort" is used to engage the presort-and-merge\nalgorithm (it is engaged by default). There is currently no way to\naffect the sort order in the presort phase.\n\n=cut\n\nsub _calc_sort_recs {\n my VCP::Dest \$self = shift ;\n my ( \$sort_recs, \$spec ) = \@_;\n\n debug "vcp sort key: ", join ", ", \@\$spec\n if debugging "sort" ;\n\n if ( grep /avgcommenttime/, \@\$spec ) {\n \$self->{DEST_COMMENT_TIMES} = {};\n for ( \@\$sort_recs ) {\n my \$r = \$_->[0];\n my \$comment = defined \$r->comment\n ? \$r->comment\n : \$r->is_base_rev ? "" : undef;\n my \$time = defined \$r->time\n ? \$r->time\n : \$r->is_base_rev ? 0 : undef;\n next unless defined \$comment && defined \$time;\n push \@{\$self->{DEST_COMMENT_TIMES}->{\$comment}}, \$time;\n }\n\n for ( values %{\$self->{DEST_COMMENT_TIMES}} ) {\n next unless \@\$_;\n my \$sum;\n \$sum += \$_ for \@\$_;\n \$_ = \$sum / \@\$_;\n }\n }\n\n for ( \@\$sort_recs) {\n my \$r = \$_->[0];\n my \$sort_rec = \$self->_calc_sort_rec( \$r, \$spec );\n ## Modify the sort rec, don't replace it\n \@\$_ = \@\$sort_rec;\n }\n}\n\nsub _remove_sporadics {\n my VCP::Dest \$self = shift;\n my ( \$sort_recs, \$spec ) = \@_;\n\n my \@not_seen = my \@seen = ( 0 ) x \@\$spec;\n my \@sporadics;\n my \@keepers = ( 0 ); ## Always keep the rev (\$sort_rec[0])\n my \$its_ok;\n\n for ( \@\$sort_recs) {\n for my \$i ( 1..\$#\$_ ) {\n if ( \@{\$_->[\$i]} && defined \$_->[\$i]->[0] ) {\n ++\$seen[\$i];\n }\n else {\n ++\$not_seen[\$i];\n }\n }\n }\n for my \$i ( 1..(\$#seen > \$#not_seen ? \$#seen : \$#not_seen) ) {\n if ( \$seen[\$i] && \$not_seen[\$i] ) {\n push \@sporadics, \$i - 1;\n next;\n }\n push \@keepers, \$i;\n\n if (\n \$seen[\$i]\n && \$spec->[\$i-1] =~ /^(change|rev|time)/\n ) {\n ## One of the quantitative ordering fields is present.\n \$its_ok = 1;\n }\n }\n\n if ( \@sporadics ) {\n my \@sp_desc = map \n "\$spec->[\$_] (seen \$seen[\$_] times, missing \$not_seen[\$_])\\n",\n \@sporadics;\n unless ( \$its_ok ) {\n die "missing sort key",\n \@sp_desc == 1 ? () : "s",\n " while sorting revisions:",\n \@sp_desc == 1\n ? ( " ", \@sp_desc )\n : ( "\\n", map " \$_", \@sp_desc ),\n "sort keys are ",\n join( ", ", \@\$spec ),\n "\\n";\n }\n\n debug "removing sporadic sort key",\n \@sp_desc == 1\n ? ( ": ", \@sp_desc )\n : ( "s:\\n", map " \$_", \@sp_desc )\n if debugging;\n\nconfess "not keeping revs" unless \$keepers[0] == 0;\n\n \@\$_ = \@{\$_}[\@keepers] for \@\$sort_recs;\n }\n}\n\n\nsub _presort_revs {\n my VCP::Dest \$self = shift ;\n my ( \$revs, \$sort_spec ) = \@_;\n\n my %p;\n for my \$r ( \$revs->get ) {\n push \@{\$p{\$r->name}}, \$self->_calc_sort_rec( \$r, \$sort_spec );\n }\n\n return [ map [ sort _rev_cmp \@\$_ ], values %p ];\n}\n\n\nsub _merge_presorted_revs {\n my VCP::Dest \$self = shift ;\n my ( \$presorted, \$spec ) = \@_;\n\n debug "merging presorted revisions by ", join ", ", \@\$spec\n if debugging ;\n\n my \@result;\n my \@p = \@\$presorted;\n\n my \@sort_recs = map \@\$_, \@p;\n\n \$self->{DEST_DEFAULT_COMMENT} = "";\n \$self->_calc_sort_recs( \\\@sort_recs, \$spec );\n \$self->{DEST_DEFAULT_COMMENT} = undef;\n\n ## Fill in missing time values as best as we can for things like\n ## VSS.\n for ( 0..\$#\$spec ) {\n next unless \$spec->[\$_] eq "time";\n my \$time_index = \$_ + 1; # Ignore the rev in \$_->[0]\n for ( \@p ) {\n my \$prev_t = [ "9" x 50 ];\n for ( reverse \@\$_ ) {\n if ( \@{\$_->[\$time_index]} ) {\n \$prev_t = \$_->[\$time_index];\n }\n else {\n \$_->[\$time_index] = \$prev_t;\n }\n }\n }\n }\n\n \$self->_remove_sporadics( \\\@sort_recs, \$spec );\n\n while ( \@p ) {\n local \$a = \$p[0]->[0];\n my \$which = 0;\n local \$b;\n for my \$i ( 1..\$#p ) {\n \$b = \$p[\$i]->[0];\n if ( _rev_cmp() > 0 ) {\n \$which = \$i;\n \$a = \$b;\n }\n }\n\n push \@result, (shift \@{\$p[\$which]})->[0];\n splice \@p, \$which, 1 unless \@{\$p[\$which]};\n }\n\n \@result;\n}\n\n\nsub sort_revs {\n my VCP::Dest \$self = shift ;\n my ( \$revs ) = \@_ ;\n\n my \@spec = \@{\$self->{DEST_SORT_SPEC}};\n\n if ( substr( \$spec[0], 0, 7 ) eq "presort" ) {\n my \@prespec = ( shift \@spec ) =~ m/presort\\((?:\\s*(\\w+)[,)])*\\)/;\n \@prespec = qw( change rev_id time ) unless \@prespec;\n\n debug "presorting revisions by ", join ", ", \@prespec\n if debugging ;\n\n my \$presorted = \$self->_presort_revs( \$revs, \\\@prespec );\n\n \$revs->set( \$self->_merge_presorted_revs( \$presorted, \\\@spec ) );\n\n return;\n }\n\n debug "sorting revisions" if debugging ;\n\n my \$sort_recs = [ map [ \$_ ], \$revs->get ];\n \$self->_calc_sort_recs( \$sort_recs, \\\@spec );\n\n \$self->_remove_sporadics( \$sort_recs, \\\@spec );\n\n \$revs->set( map \$_->[0], sort _rev_cmp \@\$sort_recs ) ;\n}\n\n=back\n\n\n=back\n\n=head1 NOTES\n\nSeveral fields are jury rigged for "base revisions": these are fake\nrevisions used to start off incremental, non-bootstrap transfers with\nthe MD5 digest of the version that must be the last version in the\ntarget repository. Since these are "faked", they don't contain\ncomments or timestamps, so the comment and timestamp fields are treated as\n"" and 0 by the sort routines.\n\nThere is a special sortkey C that allows revisions within\nthe same time period (second, minute, day) to be sorted according to the\naverage time of the comment for the revision (across all revisions with\nthat comment). This causes changes that span more than one time period\nto still be grouped properly.\n\n=cut\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAABX #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <"\n +a/deeply/buried/file, revision 2, char 0x09=" "\n\n=item *\n\nFor a several line file with multiple changes:\n\nHere are the source and result files side-by-side:\n\n Source\x09Result\n ======\x09======\n\n 1 1\n 2\x09\x092\n 3\x09\x093\n 4\x09\x094\n 5d\x09\x095a\n 6\x09\x096\n 7\x09\x097\n 8\x09\x098\n 9\x09\x099\n 10\x09\x099a\n 11\x09\x0910\n 11d\x09\x0911\n 12\x09\x0912\n 13\x09\x0913\n\nThe "patch" to transform the source in to the result can be expressed in\nseveral ways, depending on the amount of context. VCP requires no context\nsince the result is checked with an MD5 checksum. Context is, however,\nsometimes used to make the RevML a bit more human readable, though this can\nvary.\n\n=over\n\n=item 0 context (C):\n\n \@\@ -5 +5 \@\@\n -5d\n +5a\n \@\@ -9,0 +10 \@\@\n +9a\n \@\@ -12 +12,0 \@\@\n -11d\n\n=item 1 line of context (C):\n\n --- A Sat Aug 25 00:05:26 2001\n +++ B Sat Aug 25 00:05:26 2001\n \@\@ -4,3 +4,3 \@\@\n 4\n -5d\n +5a\n 6\n \@\@ -9,5 +9,5 \@\@\n 9\n +9a\n 10\n 11\n -11d\n 12\n\n=item 3 lines of context (C or C)\n\n --- A Sat Aug 25 00:05:26 2001\n +++ B Sat Aug 25 00:05:26 2001\n \@\@ -2,13 +2,13 \@\@\n 2\n 3\n 4\n -5d\n +5a\n 6\n 7\n 8\n 9\n +9a\n 10\n 11\n -11d\n 12\n 13\n\n=back\n\n=back\n\n=head1 Functions\n\n=over\n\n=cut\n\n\@ISA = qw( Exporter ) ;\n\@EXPORT = qw( vcp_patch ) ;\n\nuse strict ;\nuse Carp ;\nuse VCP::Debug ':debug' ;\nuse Exporter ;\n\n=item vcp_patch\n\nTakes a patch file name, a source file name, and a result file name and\nperforms the patch. Called from VCP::Source::revml to reconstitute revisions\ngiven by delta records.\n\nWill die on error, always returns true.\n\n=cut\n\nsub vcp_patch {\n my ( \$source_fn, \$result_fn, \$patch_fn ) = \@_ ;\n\n debug "patching \$source_fn -> \$result_fn using \$patch_fn" if debugging ;\n\n open PATCH, "<\$patch_fn" or croak "\$!: \$source_fn" ;\n open SOURCE, "<\$source_fn" or croak "\$!: \$source_fn" ;\n open RESULT, ">\$result_fn" or croak "\$!: \$result_fn" ;\n\n ## We'll need to make sure the diff's line endings match up with the\n ## source files' somehow.\n binmode PATCH;\n binmode SOURCE;\n binmode RESULT;\n\n my \$source_pos = 1;\n\n while ( =~ /(.)(.*?\\n)/ ) {\n my ( \$fchar, \$patch_line ) = ( \$1, \$2 );\n if ( \$fchar eq '\@' ) {\n \$patch_line =~ /^\\\@ -(\\d+)(?:,\\d+)? [+-]\\d+(,\\d+)? \\\@\\\@/\n or croak "Can't parse line: '\$fchar\$patch_line'.";\n my \$first_source_line = \$1;\n while ( \$source_pos < \$first_source_line ) {\n my \$source_line = ;\n croak "Ran off end of source file at line \$source_pos"\n unless defined \$source_line;\n print RESULT \$source_line;\n ++\$source_pos;\n }\n }\n elsif ( \$fchar eq '-' ) {\n my \$source_line = ;\n croak "Ran off end of source file at line \$source_pos"\n unless defined \$source_line;\n \$source_line =~ s/[\\r\\n]+\\z//;\n \$patch_line =~ s/[\\r\\n]+\\z//;\n unless ( \$source_line eq \$patch_line ) {\n \$source_line =~ s/([\\000-\\037])/sprintf "\\\\x%02x", ord \$1/ge;\n \$patch_line =~ s/([\\000-\\037])/sprintf "\\\\x%02x", ord \$1/ge;\n croak "Patch line disagrees with source line \$source_pos:\\n",\n "source:\\"", \$source_line, "\\"\\n",\n "patch :\\"", \$patch_line , "\\"\\n";\n }\n ++\$source_pos;\n }\n elsif ( \$fchar eq ' ' ) {\n my \$source_line = ;\n croak "Ran off end of source file at line \$source_pos"\n unless defined \$source_line;\n print RESULT \$source_line;\n ++\$source_pos;\n }\n elsif ( \$fchar eq '+' ) {\n print RESULT \$patch_line;\n ++\$source_pos;\n }\n else {\n croak "Unknown line type '\$fchar' in diff line '\$fchar\$patch_line'";\n }\n }\n\n print RESULT ;\n\n close SOURCE or croak "\$!: \$source_fn" ;\n close RESULT or croak "\$!: \$result_fn" ;\n close PATCH or croak "\$!: \$patch_fn" ;\n return 1 ;\n}\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nSean McCune \n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAABY #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < 10 } if \$in ne \$out ;\n}\n\n=item slurp\n\n \$guts = slurp \$filename ;\n\n=cut\n\nsub slurp {\n my ( \$fn ) = \@_ ;\n open F, "<\$fn" or die "\$!: \$fn" ;\n binmode F ;\n local \$/ ;\n return ;\n}\n\n\n=item perl_cmd\n\n \@perl = perl_cmd\n\nReturns a list containing the Perl executable and some options to reproduce\nthe current Perl options , like -I.\n\n=cut\n\nsub perl_cmd {\n my %seen ;\n return (\n \$^X,\n (\n\x09 map {\n\x09 my \$s = \$_ ;\n\x09 \$s = File::Spec->rel2abs( \$_ ) ;\n\x09 "-I\$s" ;\n\x09 } grep ! \$seen{\$_}++, \@INC\n )\n ) ;\n}\n\n\n=item vcp_cmd\n\n \@vcp = vcp_cmd\n\nReturns a list containing the Perl executable and some options to reproduce\nthe current Perl options , like -I.\n\nvcp_cmd assumes it is called from within the main distro directory or one\nsubdir under it, since it looks for "bin/vcp" and "../bin/vcp". This should be\nadequate for almost all uses.\n\nvcp_cmd caches it's results to allow it to be run from other directories after\nthe first time it's called. (this is not a significant performance improvement;\nrunning the vcp process takes several orders of magnitude longer than the quick\nchecks vcp_cmd does).\n\n=cut\n\nmy \@vcp_cmd ;\n\nsub vcp_cmd {\n unless ( \@vcp_cmd ) {\n ## We always run vcp by doing a \@perl, vcp, to make sure that vcp runs under\n ## the same version of perl that we are running under.\n my \$vcp = 'vcp' ;\n \$vcp = "bin/\$vcp" if -e "bin/\$vcp" ;\n \$vcp = "../bin/\$vcp" if -e "../bin/\$vcp" ;\n\n \$vcp = File::Spec->rel2abs( \$vcp ) ;\n\n \@vcp_cmd = ( perl_cmd, \$vcp ) ;\n }\n return \@vcp_cmd ;\n}\n\n\n=item get_vcp_output\n\n \@vcp = get_vcp_output "foo:", "-bar" ;\n\nDoes a:\n\n run [ vcp_cmd, \@_, "revml:", ... ], \\undef, \\\$out\n or croak "`vcp blahdy blah` returned \$?";\n\nand returns \$out. The "..." refers to whatever output options are needed\nto make the test output agree with C's test files\n(t/test-*.revml).\n\n=cut\n\nsub get_vcp_output {\n my \$out ;\n my \@args = ( \@_, "revml:", "--sort-by=name,rev_id" ) ;\n run [ vcp_cmd, \@args ], \\undef, \\\$out\n or croak "`vcp ", join( " ", \@_ ), " returned \$?\\n" ;\n return \$out ;\n}\n\n=cut\n\nsub vcp_cmd {\n ## We always run vcp by doing a \@perl, vcp, to make sure that vcp runs under\n ## the same version of perl that we are running under.\n my \$vcp = 'vcp' ;\n \$vcp = "bin/\$vcp" if -x "bin/\$vcp" ;\n \$vcp = "../bin/\$vcp" if -x "../bin/\$vcp" ;\n\n \$vcp = File::Spec->rel2abs( \$vcp ) ;\n\n return ( perl_cmd, \$vcp ) ;\n}\n\n\n=back\n\n=head1 XML "cleanup" functions\n\nThese are used to get rid of content or elements that are known to differ\nwhen comparing the revml fed in to a repository with the revml that\ncomes out.\n\n=over\n\n=item s_content\n\n s_content\n \$elt_type1, \$elt_type2, ..., \\\$string1, \\\$string2, ..., \$new_content ;\n\nChanges the contents of the elements, since some things, like suer id or\nmod_time can't be the same after going through a repository.\n\nIf \$new_val is not supplied, a constant string is used.\n\n=cut\n\nsub s_content {\n my \$new_val = pop if \@_ && ! ref \$_[-1] ;\n \$new_val = "" unless defined \$new_val ;\n\n my \$elt_type_re = do {\n my \@a ;\n push \@a, quotemeta shift while \@_ && ! ref \$_[0] ;\n join "|", \@a ;\n } ;\n\n \$\$_ =~ s{(<(\$elt_type_re)[^>]*?>).*?()}\n\x09 {\$1\$new_val\$3}sg\n for \@_ ;\n\n \$\$_ =~ s{(<(\$elt_type_re)[^>]*?>).*?()}{\$1\$new_val\$3}sg\n for \@_ ;\n}\n\n\n=item rm_elts\n\n rm_elts \$elt_type1, \$elt_type2, ..., \\\$string1, \\\$string2\n rm_elts \$elt_type1, \$elt_type2, ..., qr/\$content_re/, \\\$string1, \\\$string2\n\nRemoves the specified elements from the strings, including leading whitespace\nand trailing line separators. If the optional \$content_re regular expression\nis provided, then only elements containing that pattern will be removed.\n\n=cut\n\nsub rm_elts {\n my \$elt_type_re = do {\n my \@a ;\n push \@a, quotemeta shift while \@_ && ! ref \$_[0] ;\n join "|", \@a ;\n } ;\n\n my \$content_re = \@_ && ref \$_[0] eq "Regexp" ? shift : qr/.*?/s ;\n my \$re = qr{^\\s*<(\$elt_type_re)\\b[^>]*?>\$content_re\\r?\\n}sm ;\n\n \$\$_ =~ s{\$re}{}g for \@_ ;\n}\n\n\n=head1 p4 repository mgmt functions\n\n=over\n\n=item p4_borken\n\nReturns true if the p4 is missing or too old (< 99.2).\n\n=cut\n\nsub p4d_borken {\n my \$p4dV = `p4d -V` || 0 ;\n return "p4d not found" unless \$p4dV ;\n\n my ( \$p4d_version ) = \$p4dV =~ m{^Rev[^/]*/[^/]*/([^/]*)}m ;\n\n my \$min_version = 99.2 ;\n return "p4d version too old, need at least \$min_version"\n unless \$p4d_version >= \$min_version ;\n return "" ;\n}\n\n=item launch_p4d\n\n launch_p4d "prefix_" ;\n\nCreates an empty repository and launches a p4d for it. The p4d will be killed\nand it's repository deleted on exit. Returns the options needed to access\nthe repository.\n\n=cut\n\n#sub launch_p4d {\n# my \$prefix = shift || "" ;\n#\n# {\n# my \$borken = p4d_borken ;\n# croak \$borken if \$borken ;\n# }\n#\n# my \$tmp = File::Spec->tmpdir ;\n# my \$repo = File::Spec->catdir( \$tmp, "vcp\${\$}_\${prefix}p4repo" ) ;\n# mk_tmp_dir \$repo ;\n#\n# ## Ok, this is wierd: we need to fork & run p4d in foreground mode so that\n# ## we can capture it's PID and kill it later. There doesn't seem to be\n# ## the equivalent of a 'p4d.pid' file. If we let it daemonize, then I\n# ## don't know how to get it's PID.\n#\n# my \$port ;\n# my \$p4d_pid ;\n# my \$tries ;\n# while () {\n# ## 30_000 is because I vaguely recall some TCP stack that had problems\n# ## with listening on really high ports. 2048 is because I vaguely recall\n# ## that some OS required root privs up to 2047 instead of 1023.\n# \$port = ( rand( 65536 ) % 30_000 ) + 2048 ;\n# my \@p4d = ( 'p4d', '-f', '-r', \$repo, '-p', \$port ) ;\n# print "# Running ", join( " ", \@p4d ), "\\n" ;\n# \$p4d_pid = fork ;\n# unless ( \$p4d_pid ) {\n#\x09 ## Ok, there's a tiny chance that this will fail due to a port\n#\x09 ## collision. Oh, well.\n#\x09 exec \@p4d ;\n#\x09 die "\$!: p4d" ;\n# }\n# sleep 1 ;\n# ## Wait for p4d to start. 'twould be better to wait for P4PORT to\n# ## be seen.\n# select( undef, undef, undef, 0.250 ) ;\n#\n# last if kill 0, \$p4d_pid ;\n# die "p4d failed to start after \$tries tries, aborting\\n"\n# if ++\$tries >= 3 ;\n# warn "p4d failed to start, retrying\\n" ;\n# }\n#\n# END {\n# return unless defined \$p4d_pid ;\n# kill 'INT', \$p4d_pid or die "\$! \$p4d_pid" ;\n# my \$t0 = time ;\n# my \$dead_child ;\n# while ( \$t0 + 15 > time ) {\n# select undef, undef, undef, 0.250 ;\n#\x09 \$dead_child = waitpid \$p4d_pid, WNOHANG ;\n#\x09 warn "\$!: \$p4d_pid" if \$dead_child == -1 ;\n#\x09 last if \$dead_child ;\n# }\n# unless ( defined \$dead_child && \$dead_child > 0 ) {\n#\x09 print "terminating \$p4d_pid\\n" ;\n#\x09 kill 'TERM', \$p4d_pid or die "\$! \$p4d_pid" ;\n#\x09 \$t0 = time ;\n#\x09 while ( \$t0 + 15 > time ) {\n#\x09 select undef, undef, undef, 0.250 ;\n#\x09 \$dead_child = waitpid \$p4d_pid, WNOHANG ;\n#\x09 warn "\$!: \$p4d_pid" if \$dead_child == -1 ;\n#\x09 last if \$dead_child ;\n#\x09 }\n# }\n# unless ( defined \$dead_child && \$dead_child > 0 ) {\n#\x09 print "killing \$p4d_pid\\n" ;\n#\x09 kill 'KILL', \$p4d_pid or die "\$! \$p4d_pid" ;\n# }\n# }\n#\n# return {\n# user => "\${prefix}t_user",\n# port => \$port,\n# } ;\n#}\nsub launch_p4d {\n my \$prefix = shift || "" ;\n\n {\n my \$borken = p4d_borken ;\n croak \$borken if \$borken ;\n }\n\n my \$tmp = File::Spec->tmpdir ;\n my \$repo = File::Spec->catdir( \$tmp, "vcp\${\$}_\${prefix}p4repo" ) ;\n mk_tmp_dir \$repo ;\n\n ## Ok, this is wierd: we need to fork & run p4d in foreground mode so that\n ## we can capture it's PID and kill it later. There doesn't seem to be\n ## the equivalent of a 'p4d.pid' file. If we let it daemonize, then I\n ## don't know how to get it's PID.\n\n my \$port ;\n my \$tries ;\n my \$h ;\n while () {\n ## 30_000 is because I vaguely recall some TCP stack that had problems\n ## with listening on really high ports. 2048 is because I vaguely recall\n ## that some OS required root privs up to 2047 instead of 1023.\n \$port = ( rand( 65536 ) % 30_000 ) + 2048 ;\n my \@p4d = ( "p4d", "-f", "-r", \$repo, "-p", \$port ) ;\n print "# Running ", join( " ", \@p4d ), "\\n" ;\n \$h = start \\\@p4d ;\n ## Wait for p4d to start. 'twould be better to wait for P4PORT to\n ## be seen.\n sleep 1 ;\n\n ## The child process will have died if the port is taken or due\n ## to other errors.\n last if \$h->pumpable;\n finish \$h;\n die "p4d failed to start after \$tries tries, aborting\\n"\n if ++\$tries >= 3 ;\n warn "p4d failed to start, retrying\\n" ;\n }\n\n END {\n return unless \$h;\n \$h->kill_kill;\n \$? = 0; ## p4d exits with a "15", which becomes our exit code\n ## if we don't clear this.\n }\n\n return {\n user => "\${prefix}t_user",\n port => \$port,\n } ;\n}\n\n=back\n\n=head1 CVS mgmt functions\n\n=over\n\n=item cvs_borken\n\nReturns true if cvs -v works and outputs "Concurrent Versions System".\n\n=cut\n\nsub cvs_borken {\n my \$cvsV = `cvs -v` || 0 ;\n return "cvs command not found" unless \$cvsV ;\n return "cvs command does not appear to be for CVS: '\$cvsV'"\n unless \$cvsV =~ /Concurrent Versions System/;\n\n return "" ;\n}\n\n=item init_cvs\n\n my \$cvs_options = init_cvs \$prefix, \$module_name ;\n\nCreates a CVS repository containing an empty module. Also sets\n\$ENV{LOGNAME} if it notices that we're running as root, so CVS won't give\na "cannot commit files as 'root'" error. Tries "nobody", then "guest".\n\nReturns the options needed to access the cvs repository.\n\n=cut\n\nsub init_cvs {\n my ( \$prefix , \$module ) = \@_ ;\n\n my \$tmp = File::Spec->tmpdir ;\n my \$options = {\n repo => File::Spec->catdir( \$tmp, "vcp\${\$}_\${prefix}cvsroot" ),\n work => File::Spec->catdir( \$tmp, "vcp\${\$}_\${prefix}cvswork" ),\n } ;\n\n my \$cwd = cwd ;\n ## Give vcp ... cvs:... a repository to work with. Note that it does not\n ## use \$cvswork, just this test script does.\n\n \$ENV{CVSROOT} = \$options->{repo} ;\n\n ## CVS does not like root to commit files. So, try to fool it.\n ## CVS calls geteuid() to determine rootness (so does perl's \$>).\n ## If root, CVS calls getlogin() first, then checks the LOGNAME and USER\n ## environment vars.\n ##\n ## What this means is: if the user is actually logged in on a physical\n ## terminal as 'root', getlogin() will return "root" to cvs and we can't\n ## fool CVS.\n ##\n ## However, if they've used "su", a very common occurence, then getlogin()\n ## will return failure (NULL in C, undef in Perl) and we can spoof CVS\n ## using \$ENV{LOGNAME}.\n if ( ! \$> && \$^O !~ /Win32/ ) {\n my \$login = getlogin ;\n if ( ( ! defined \$login || ! getpwnam \$login )\n && ( ! exists \$ENV{LOGNAME} || ! getpwnam \$ENV{LOGNAME} )\n ) {\n\x09 for ( qw( nobody guest ) ) {\n\x09 my \$uid = getpwnam \$_ ;\n\x09 next unless defined \$uid ;\n\x09 ( \$ENV{LOGNAME}, \$> ) = ( \$_, \$uid ) ;\n\x09 last ;\n\x09 }\n\x09 ## Must set uid, too, to keep perl (and thus vcp) from bombing\n\x09 ## out when running setuid and given a -I option. This happens\n\x09 ## a lot in the test suite, since the tests often call vcp\n\x09 ## using "perl", "-Iblib/lib", "bin/vcp", ... to recreate the\n\x09 ## appropriate operating environment for Perl. If this becomes\n\x09 ## a problem, perhaps we can hack in a "run as user" option to\n\x09 ## VCP::Utils::cvs so that only the cvs subcommands are run\n\x09 ## setuid, or perhaps we can avoid passing "-I" to the perls.\n\x09 \$< = \$> ;\n\x09 \n\x09 warn\n\x09 "# Setting real & eff. uids=",\n\x09 \$>,\n\x09 "(",\n\x09 \$ENV{LOGNAME},\n\x09 qq{) to quell "cvs: cannot commit files as 'root'"\\n} ;\n }\n }\n\n mk_tmp_dir \$options->{repo} ;\n\n run [ qw( cvs init ) ] or die "cvs init failed" ;\n\n mk_tmp_dir \$options->{work} ;\n chdir \$options->{work} or die "\$!: \$options->{work}" ;\n\n mkdir \$module, 0770 or die "\$!: \$module" ;\n chdir \$module or die "\$!: \$module" ;\n run [ qw( cvs import -m ), "\$module import", \$module, "\${module}_vendor", "\${module}_release" ]\n or die "cvs import failed" ;\n chdir \$cwd or die "\$!: \$cwd" ;\n\n delete \$ENV{CVSROOT} ;\n# chdir ".." or die "\$! .." ;\n#\n# system qw( cvs checkout CVSROOT/modules ) and die "cvs checkout failed" ;\n#\n# open MODULES, ">>CVSROOT/modules" or die "\$!: CVSROOT/modules" ;\n# print MODULES "\\n\$module \$module/\\n" or die "\$!: CVSROOT/modules" ;\n# close MODULES or die "\$!: CVSROOT/modules" ;\n#\n# system qw( cvs commit -m foo CVSROOT/modules )\n# and die "cvs commit failed" ;\n return \$options ;\n}\n\n=back\n\n=head1 VSS mgmt functions\n\n=over\n\n=item vss_borken\n\nfails unless \$ENV{SSUSER} is defined and the command C runs and\nreturns what looks like a username.\n\nMay lock up if the ss.exe command prompts for a password.\n\nThis is because I can't figure out a reliable way to detect if the "ss" command\nruns well without risking a lock up, since it has a habit of prompting for\na password that I can't break it of without initalizing a custom Source Safe\nrepository.\n\n=cut\n\nsub vss_borken {\n return "SSUSER not in the environment" unless defined \$ENV{SSUSER};\n\n my \$user = `ss Whoami` ;\n return "ss command not found" unless defined \$user && length \$user;\n return "ss command did not return just a username"\n unless \$user =~ /\\A\\S+\$/m;\n\n return "" ;\n}\n\n=back\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAABZ #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < "VCP::DiffFormat" };\n\n=head1 DESCRIPTION\n\nThis is a plugin output formatter for Text::Diff that generates "unified" style\ndiffs without headers. VCP::Dest::revml uses this to output differences for\nseveral reasons:\n\n=over\n\n=item *\n\nThe Unix C command is not available on all platforms by default,\nspecifically WinNT.\n\n=item *\n\nThe two line "file header" is not needed in RevML, since the meta information\nis captured elsewhere in the element, and the name and mtime\nof the files being compared is irrellevant; they're just some temporary files\nsomewhere\n\n=item *\n\nBecause RevML offers MD5 hashes of the file to verify that a diff was applied\nproperly, all of the "-" lines present in a normal unified diff are not\nnecesssary. They are left in now for ease of debugging with RevML files, but\nmay be stripped out to conserve space.\n\n=cut\n\n\@ISA = qw( Text::Diff::Unified );\n\nuse strict;\nuse Text::Diff;\nuse Carp;\n\nsub file_header { "" }\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACA #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <, L,\nL\nL, along with the tags ':all' and ':debug'. Use the latter\nto head off future namespace pollution in case :all gets expanded in the\nfuture..\n\nA warning will be emitted on program exit for any specs that aren't used,\nto help you make sure that you are using sensible specs.\n\n=over\n\n=cut\n\nuse strict ;\nuse vars qw( \$VERSION \@ISA \@EXPORT_OK %EXPORT_TAGS ) ;\nuse Exporter ;\n\n\@ISA = qw( Exporter ) ;\n\@EXPORT_OK = qw(\n debug\n enable_debug\n disable_debug\n debugging\n explicitly_debugging\n) ;\n%EXPORT_TAGS = (\n 'all' => \\\@EXPORT_OK,\n 'debug' => \\\@EXPORT_OK,\n) ;\n\n\$VERSION = 0.1 ;\n\n# TODO:\n#=item use\n#=item import\n#\n#In addition to all of the routines and tags that C and C normally\n#take (see above), you may also pass in pairwise debugging definitions like\n#so:\n#\n# use VCP::debug (\n# ":all",\n# DEBUGGING_FOO => "foo,bar",\n# ) ;\n#\n#Any all caps export import requests are created as subroutines that may well be\n#optimized away at compile time if "enable_debugging" has not been called. This\n#requires a conspiracy between the author of a module and the author of the main\n#program to call enable_debugging I Cing any modules that leverage\n#this feature, otherwise compile-time optimizations won't occur.\n#\n\n=item debug\n\n debug \$foo if debugging \$self ;\n\nEmits a line of debugging (a "\\n" will be appended). Use debug_some\nto avoid the "\\n". Any undefined parameters will be displayed as\nCundefE>.\n\n=cut\n\nmy \$dump_undebugged ;\nmy \$reported_specs ;\nmy \@debug_specs ;\nmy %used_specs ;\nmy %debugging ;\n\nEND {\n \$used_specs{'##NEVER_MATCH##'} = 1 ;\n my \@unused = grep ! \$used_specs{\$_}, \@debug_specs ;\n\n warn "vcp: Unused debug specs: ", join( ', ', map "/\$_/", \@unused ), "\\n"\n if \@unused ;\n\n if ( \@unused || \$dump_undebugged ) {\n my \@undebugged = grep {\n\x09 my \$name = \$_ ;\n\x09 ! grep \$name =~ /\$_/i, keys %used_specs\n } map lc \$_, sort keys %debugging ;\n\n if ( \@undebugged ) {\n\x09 warn "vcp: Undebugged things: ", join( ', ', \@undebugged ), "\\n" ;\n }\n else {\n\x09 warn "vcp: No undebugged things\\n" ;\n }\n }\n}\n\nsub debug {\n return unless \@debug_specs ;\n if ( \@_ ) {\n my \$t = join( '', map defined \$_ ? \$_ : "", \@_ ) ;\n if ( length \$t ) {\n\x09 print STDERR \$t, substr( \$t, -1 ) eq "\\n" ? () : "\\n" ;\n }\n }\n}\n\n\nsub debug_some {\n return unless \@debug_specs ;\n print STDERR map defined \$_ ? \$_ : "", \@_ if \@_ ;\n}\n\n\n=item debugging\n\n debug "blah" if debugging ;\n\nReturns TRUE if the caller's module is being debugged\n\n debug "blah" if debugging \$self ;\n debug "blah" if debugging \$other, \$self ; ## ORs the arguments together\n\nReturns TRUE if any of the arguments are being debugged. Plain\nstrings can be passed or blessed references.\n\n=cut\n\nsub _report_specs {\n my \@report = grep ! /##NEVER_MATCH##/, \@debug_specs ;\n print STDERR "Debugging ",join( ', ', map "/\$_/", \@report ),"\\n"\n if \@report ;\n \$reported_specs = 1 ;\n}\n\n\nsub debugging {\n return undef unless \@debug_specs ;\n\n my \$result ;\n my \@missed ;\n for my \$where ( \@_ ? map ref \$_ || \$_, \@_ : scalar caller ) {\n if ( ! exists \$debugging{\$where} ) {\n# print STDERR "missed \$where\\n" ;\n\x09 ## If this is the first miss, then these may not have been reported.\n\x09 _report_specs unless \$reported_specs ;\n\n\x09 ## We go ahead and evaluate all specs instead of returning when the\n\x09 ## first is found so that we can set \$used_specs for all specs that\n\x09 ## match.\n\x09 \$debugging{\$where} = 0 ;\n\x09 for my \$spec ( \@debug_specs ) {\n\x09 next if \$spec eq '##NEVER_MATCH##' ;\n# print STDERR " /\$spec/:\\n" ;\n\x09 if ( \$where =~ /\$spec/i ) {\n\x09 \$debugging{\$where} = 1 ;\n\x09 \$used_specs{\$spec} = 1 ;\n\x09 \$result = 1 ;\n\x09 ## no last: we want to build up %used_specs. There\n\x09 ## aren't usually many specs anyway.\n\x09 }\n\x09 else {\n# print STDERR " ! /\$spec/\\n" ;\n }\n\x09 }\n }\n# print STDERR "\$where ", \$debugging{\$where} ? 'yes' : 'no', "\\n" ;\n return 1 if \$debugging{\$where} ;\n }\n\n return \$result ;\n}\n\n=item explicitly_debugging\n\n debug "blah" if explicitly_debugging ;\n\nReturns TRUE if the caller's module is being debugged by a literal match\ninstead of a pattern match. This is used when debugging output would normally\nbe congested with too much crap from a particular subsystem when using a\nwildcard debug spec (like ".*"), but you want the ability to turn on debugging\nfor that subsystem:\n\n debug "blah" if explicitly_debugging "VCP::Dest::sort" ;\n\nrequires an explicit C to be given in the debug specs.\n\n debug "blah" if explicitly_debugging \$self ;\n debug "blah" if explicitly_debugging \$other, \$self ; ## ORs the args\n\nReturns TRUE if any of the arguments are being debugged. Plain\nstrings can be passed or blessed references.\n\n=cut\n\nmy %explicitly_debugging ;\n\nsub explicitly_debugging {\n return undef unless \@debug_specs ;\n\n my \$result ;\n my \@missed ;\n for my \$where ( \@_ ? map ref \$_ || \$_, \@_ : scalar caller ) {\n if ( ! exists \$explicitly_debugging{\$where} ) {\n# print STDERR "missed \$where\\n" ;\n\x09 ## If this is the first miss, then these may not have been reported.\n\x09 _report_specs unless \$reported_specs ;\n\n\x09 ## We go ahead and evaluate all specs instead of returning when the\n\x09 ## first is found so that we can set \$used_specs for all specs that\n\x09 ## match.\n\x09 \$explicitly_debugging{\$where} = 0 ;\n\x09 for my \$spec ( \@debug_specs ) {\n\x09 next if \$spec eq '##NEVER_MATCH##' ;\n# print STDERR " /\$spec/:\\n" ;\n\x09 if ( lc \$where eq lc \$spec ) {\n\x09 \$explicitly_debugging{\$where} = 1 ;\n\x09 \$used_specs{\$spec} = 1 ;\n\x09 \$result = 1 ;\n\x09 ## no last: we want to build up %used_specs. There\n\x09 ## aren't usually many specs anyway.\n\x09 }\n\x09 else {\n# print STDERR " ! /\$spec/\\n" ;\n }\n\x09 }\n }\n# print STDERR "\$where ", \$debugging{\$where} ? 'yes' : 'no', "\\n" ;\n return 1 if \$explicitly_debugging{\$where} ;\n }\n\n return \$result ;\n}\n\n=item disable_debug\n\nDisable all debugging.\n\n=cut\n\nsub disable_debug() {\n \@debug_specs = () ;\n return ;\n}\n\n=item enable_debug\n\n enable_debug ;\n enable_debug( ...debug specs... ) ;\n\nA debug spec is a regular expression that matches the name of a module.\n\n=cut\n\nsub enable_debug {\n my %specs = map { ( \$_ => 1 ) } \@debug_specs, \@_ ;\n my \@new_debug_specs = %specs \n ? keys %specs \n : qr/^/ ;\n _report_specs\n if \$reported_specs && \@debug_specs != \@new_debug_specs ;\n \@debug_specs = map(\n /^what\$/i && ( \$dump_undebugged = 1 ) ? '##NEVER_MATCH##' : \$_,\n \@new_debug_specs\n ) ;\n return ;\n}\n\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACB #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <work_root( \$self->tmp_dir ) ;\n rmtree \$self->work_root if ! \$ENV{VCPNODELETE} && -e \$self->work_root ;\n\n \$self->{SEEN} = {} ;\n\n \$self->{COMMAND_OK_RESULT_CODES} = { 0 => undef } ;\n\n \$self->command_chdir( \$self->work_path ) ;\n\n return \$self ;\n}\n\n\n###############################################################################\n\n=head1 SUBCLASSING\n\nThis class uses the fields pragma, so you'll need to use base and \npossibly fields in any subclasses.\n\n=head2 SUBCLASS API\n\nThese methods are intended to support subclasses.\n\n=over\n\n=item seen\n\n \$old_rev = \$self->seen( \$new_rev ) ;\n \$old_r = \$self->seen( \$name ) ;\n\nCalled to register the fact that \$new_rev has been seen, and\nto return the last request for the same resource, which refers to the\nprevious version of the resource. If a plain scalar is passed, simply\nreturns the last rev structure that was seen for that filename (but\ndoes not mark that filename as having been seen if it hasn't).\n\nThis is one of the few accessor methods in VCP's implementation that returns\nthe previous value.\n\n=cut\n\nsub seen {\n my VCP::Plugin \$self = shift ;\n my ( \$arg ) = \@_ ;\n\n confess "SEEN not initted: need to call SUPER::new?"\n unless defined \$self->{SEEN} ;\n\n if ( ref \$arg ) {\n my VCP::Rev \$r = \$arg ;\n my \$old_r = \$self->{SEEN}->{\$r->name} ;\n \$self->{SEEN}->{\$r->name} = \$arg ;\n return \$old_r ;\n }\n else {\n return exists \$self->{SEEN}->{\$arg} && \$self->{SEEN}->{\$arg} ;\n }\n}\n\n\n=item delete_seen\n\nDeletes the last seen revision for a file. Returns nothing.\n\n=cut\n\nsub delete_seen {\n my VCP::Plugin \$self = shift ;\n my ( \$arg ) = \@_ ;\n\n confess "SEEN not initted: need to call SUPER::new?"\n unless defined \$self->{SEEN} ;\n\n delete \$self->{SEEN}->{\$arg->name} ;\n return ;\n}\n\n=item none_seen\n\nReturns TRUE if \$dest->seen( \$r ) has not yet been called.\n\n=cut\n\nsub none_seen {\n my VCP::Plugin \$self = shift ;\n\n ## This can happen if a subclass forgets to init it's base class(es).\n confess "Oops" unless defined \$self->{SEEN} ;\n\n return ! %{\$self->{SEEN}} ;\n}\n\n\n=item parse_repo_spec\n\n my \$spec = \$self->split_repo_spec( \$spec ) ;\n\nThis splits a repository spec in one of the following formats:\n\n scheme:user:passwd\@server:filespec\n scheme:user\@server:filespec\n scheme::passwd\@server:filespec\n scheme:server:filespec\n scheme:filespec\n\nin to the indicated fields, which are stored in \$self and may be\naccessed and altered using L, L, L,\nL, and L. Some sources and destinations may\nadd additional fields. The p4 drivers create an L,\nfor instance, and parse the repo_user field to fill it in. See\nL for details.\n\nThe spec is parsed from the ends towars the middle in this order:\n\n 1. SCHEME (up to first ':')\n 2. FILESPEC (after last ':')\n 3. USER, PASSWORD (before first '\@')\n 4. SERVER (everything left).\n\nThis approach allows the FILESPEC string to contain '\@', and the SERVER\nstring to contain ':' and '\@'. USER can contain ':'. Funky, but this\nworks well, at least for cvs and p4.\n\nIf a section of the repo spec is not present, the corresponding entry\nin \$hash will not exist.\n\nThe attributes repo_user, repo_password and repo_server are set\nautomatically by this method. It does not store the SCHEME anyware\nsince the SCHEME is usually ignored by the plugin (the plugin is\nselected using the scheme, so it knows the scheme implicitly), and\nthe FILES setting often needs extra manipulation, so there's no point\nin storing it.\n\n=cut\n\nsub parse_repo_spec {\n my VCP::Plugin \$self = shift ;\n\n my ( \$spec ) = \@_ ;\n\n my \$result ;\n\n for ( \$spec ) {\n return \$result unless s/^([^:]*)(?::|\$)// ;\n \$result->{SCHEME} = \$1 ;\n \$self->repo_scheme( \$1 ) ;\n\n return \$result unless s/(?:^|:)([^:]*)\$// ;\n \$result->{FILES} = \$1 ;\n \$self->repo_filespec( \$1 ) ;\n\n if ( s/^([^\\\@]*?)(?::([^\\\@:]*))?\@// ) {\n if ( defined \$1 ) {\n\x09 \$result->{USER} = \$1 ;\n\x09 \$self->repo_user( \$1 ) ;\n\x09 }\n\n if ( defined \$2 ) {\n\x09 \$result->{PASSWORD} = \$2 ;\n\x09 \$self->repo_password( \$2 ) ;\n\x09 }\n }\n\n return \$result unless length \$spec ;\n \$result->{SERVER} = \$spec ;\n \$self->repo_server( \$spec ) ;\n }\n\n ## TODO: Return nothing. Callers need to come to use the\n ## accessors.\n return \$result\n}\n\n\n\n=item usage_and_exit\n\n GetOptions( ... ) or \$self->usage_and_exit ;\n\nUsed by subclasses to die if unknown options are passed in.\n\nRequires Pod::Usage when called.\n\n=cut\n\nsub usage_and_exit {\n my VCP::Plugin \$self = shift ;\n\n require Pod::Usage ;\n my \$f = ref \$self ;\n \$f =~ s{::}{/}g ;\n \$f .= '.pm' ;\n\n for ( \@INC ) {\n my \$af = File::Spec->catfile( \$_, \$f ) ;\n if ( -f \$af ) {\n\x09 Pod::Usage::pod2usage(\n\x09 -input => \$af,\n\x09 -verbose => 0,\n\x09 -exitval => 2,\n\x09 ) ;\n\x09 confess ;\n }\n }\n\n die "can't locate '\$f' to print usage.\\n" ;\n}\n\n\n=item tmp_dir\n\nReturns the temporary directory this plugin should use, usually something\nlike "/tmp/vcp123/dest-p4".\n\n=cut\n\nmy %tmp_dirs ;\n\nEND {\n return unless keys %tmp_dirs;\n ## This delay seems to be required to give NT a chance\n ## to clean up the tmpdir, otherwise we get a\n ## "permission denied error on Win32.\n select undef, undef, undef, 0.01 if \$^O =~ /Win32/ ;\n rmtree [ reverse sort { length \$a <=> length \$b } keys %tmp_dirs ]\n if ! \$ENV{VCPNODELETE} && %tmp_dirs ;\n}\n\nsub tmp_dir {\n my VCP::Plugin \$self = shift ;\n my \$plugin_dir = ref \$self ;\n \$plugin_dir =~ tr/A-Z/a-z/ ;\n \$plugin_dir =~ s/^VCP:://i ;\n \$plugin_dir =~ s/::/-/g ;\n my \$tmp_dir_root = File::Spec->catdir( File::Spec->tmpdir, "vcp\$\$" ) ;\n\n ## Make sure no old tmpdir is there to mess us up in case\n ## a previous run crashed before cleanup or \$ENV{VCPNODELETE} is set.\n if ( ! \$tmp_dirs{\$tmp_dir_root} && -e \$tmp_dir_root ) {\n warn "Removing previous working directory \$tmp_dir_root\\n";\n rmtree [\$tmp_dir_root ], 0;\n }\n\n \$tmp_dirs{\$tmp_dir_root} = 1 ;\n return File::Spec->catdir( \$tmp_dir_root, \$plugin_dir, \@_ ) ;\n}\n\n\n=item work_path\n\n \$full_path = \$self->work_path( \$filename, \$rev ) ;\n\nReturns the full path to the working copy of the local filename.\n\nEach VCP::Plugin gets thier own hierarchy to use, usually rooted at\na directory named /tmp/vcp\$\$/plugin-source-foo/ for a module\nVCP::Plugin::Source::foo. \$\$ is vcp's process ID.\n\nThis is typically \$work_root/\$filename/\$rev, but this may change.\n\$rev is put last instead of first in order to minimize the overhead of\ncreating lots of directories.\n\nIt *must* be under \$work_root in order for rm_work_path() to fully\nclean.\n\nAll directories will be created as needed, so you should be able\nto create the file easily after calling this. This is only\ncalled by subclasses, and is optional: a subclass could create it's\nown caching system.\n\nDirectories are created mode 0775 (rwxrwxr-x), subject to modification\nby umask or your local operating system. This will be modifiable in\nthe future.\n\n=cut\n\nsub work_path {\n my VCP::Plugin \$self = shift ;\n\n my \$path = File::Spec->canonpath(\n File::Spec->catfile( \$self->work_root, \@_ )\n ) ;\n\n return \$path ;\n}\n\n\n=item mkdir\n\n \$self->mkdir( \$filename ) ;\n \$self->mkdir( \$filename, \$mode ) ;\n\nMakes a directory and any necessary parent directories.\n\nThe default mode is 770. Does some debug logging if any directories are\ncreated.\n\nReturns nothing.\n\n=cut\n\nsub mkdir {\n my VCP::Plugin \$self = shift ;\n\n my ( \$path, \$mode ) = \@_ ;\n\n unless ( -d \$path ) {\n \$mode = 0770 unless defined \$mode ;\n debug "vcp: mkdir \$path, ", sprintf "%04o", \$mode if debugging \$self ;\n mkpath [ \$path ], 0, \$mode\n or die "vcp: failed to create \$path with mode \$mode\\n" ;\n }\n\n return ;\n}\n\n\n=item mkpdir\n\n \$self->mkpdir( \$filename ) ;\n \$self->mkpdir( \$filename, \$mode ) ;\n\nMakes the parent directory of a filename and all directories down to it.\n\nThe default mode is 770. Does some debug logging if any directories are\ncreated.\n\nReturns the path of the parent directory.\n\n=cut\n\nsub mkpdir {\n my VCP::Plugin \$self = shift ;\n\n my ( \$path, \$mode ) = \@_ ;\n\n my ( undef, \$dir ) = fileparse( \$path ) ;\n\n \$self->mkdir( \$dir, \$mode ) ;\n\n return \$dir ;\n}\n\n\n=item rm_work_path\n\n \$self->rm_work_path( \$filename, \$rev ) ;\n \$self->rm_work_path( \$dirname ) ;\n\nRemoves a directory or file from the work. Also removes any and\nall directories that become empty as a result up to the\nwork root (/tmp on Unix).\n\n=cut\n\nsub rm_work_path {\n my VCP::Plugin \$self = shift ;\n\n my \$path = \$self->work_path( \@_ ) ;\n\n if ( defined \$path && -e \$path ) {\n debug "vcp: rmtree \$path" if debugging \$self ;\n if ( ! \$ENV{VCPNODELETE} ) {\n rmtree \$path or warn "\$!: \$path"\n }\n else {\n warn "Not removing working directory \$path due to VCPNODELETE\\n";\n }\n }\n\n my \$root = \$self->work_root ;\n\n if ( substr( \$path, 0, length \$root ) eq \$root ) {\n while ( length \$path > length \$root ) {\n\x09 ( undef, \$path ) = fileparse( \$path ) ;\n\x09 ## TODO: More discriminating error handling. But the error emitted\n\x09 ## when a directory is not empty may differ from platform\n\x09 ## to platform, not sure.\n\x09 last unless rmdir \$path ;\n }\n }\n}\n\n\n=item work_root\n\n \$root = \$self->work_root ;\n \$self->work_root( \$new_root ) ;\n \$self->work_root( \$new_root, \$dir1, \$dir2, .... ) ;\n\nGets/sets the work root. This defaults to\n\n File::Spec->tmpdir . "/vcp\$\$/" . \$plugin_name\n\nbut may be altered. If set to a relative path, the current working\ndirectory is prepended. The returned value is always absolute, and will\nnot change if you chdir(). Depending on the operating system, however,\nit might not be located on to the current volume. If not, it's a bug,\nplease patch away.\n\n=cut\n\nsub work_root {\n my VCP::Plugin \$self = shift ;\n\n if ( \@_ ) {\n if ( defined \$_[0] ) {\n\x09 \$self->{WORK_ROOT} = File::Spec->catdir( \@_ ) ;\n\x09 debug "vcp: work_root set to '",\$self->work_root,"'"\n\x09 if debugging \$self ;\n\x09 unless ( File::Spec->file_name_is_absolute( \$self->{WORK_ROOT} ) ) {\n\x09 require Cwd ;\n\x09 \$self->{WORK_ROOT} = File::Spec->catdir( Cwd::cwd, \@_ ) ;\n\x09 }\n }\n else {\n \$self->{WORK_ROOT} = undef ;\n }\n }\n\n return \$self->{WORK_ROOT} ;\n}\n\n\n=item command_chdir\n\nSets/gets the directory to chdir into before running the default command.\n\n=cut\n\nsub command_chdir {\n my VCP::Plugin \$self = shift ;\n if ( \@_ ) {\n \$self->{COMMAND_CHDIR} = shift ;\n debug "vcp: command_chdir set to '", \$self->command_chdir, "'"\n if debugging \$self ;\n }\n return \$self->{COMMAND_CHDIR} ;\n}\n\n\n=item command_stderr_filter\n\n \$self->command_stderr_filter( qr/^cvs add: use 'cvs commit'.*\\n/m ) ;\n \$self->command_stderr_filter( sub { my \$t = shift ; \$\$t =~ ... } ) ;\n\nSome commands--cough*cvs*cough--just don't seem to be able to shut up\non stderr. Other times we need to watch stderr for some meaningful output.\n\nThis allows you to filter out expected whinging on stderr so that the command\nappears to run cleanly and doesn't cause \$self->cmd(...) to barf when it sees\nexpected output on stderr.\n\nThis can also be used to filter out intermittent expected errors that\naren't errors in all contexts when they aren't actually errors.\n\n=cut\n\nsub command_stderr_filter {\n my VCP::Plugin \$self = shift ;\n \$self->{COMMAND_STDERR_FILTER} = \$_[0] if \@_ ;\n return \$self->{COMMAND_STDERR_FILTER} ;\n}\n\n\n=item command_ok_result_codes\n\n \$self->command_ok_result_codes( 0, 1 ) ;\n\nOccasionally, a non-zero result is Ok. this method lets you set a list\nof acceptable result codes.\n\n=cut\n\nsub command_ok_result_codes {\n my VCP::Plugin \$self = shift ;\n\n if ( \@_ ) {\n %{\$self->{COMMAND_OK_RESULT_CODES}} = () ;\n \@{\$self->{COMMAND_OK_RESULT_CODES}}{\@_} = () ;\n }\n\n return unless defined wantarray ;\n return keys %{\$self->{COMMAND_STDERR_FILTER}} ;\n}\n\n\n=item repo_scheme\n\n \$self->repo_scheme( \$scheme_name ) ;\n \$scheme_name = \$self->repo_scheme ;\n\nSets/gets the scheme specified ("cvs", "p4", "revml", etc). This is normally\nsuperfluous, since the scheme name is peeked at in order to load the\ncorrect VCP::{Source,Dest}::* class, which then calls this.\n\nThis is usually set automatically by L.\n\n=cut\n\nsub repo_scheme {\n my VCP::Plugin \$self = shift ;\n \$self->{REPO_SCHEME} = \$_[0] if \@_ ;\n return \$self->{REPO_SCHEME} ;\n}\n\n\n=item repo_user\n\n \$self->repo_user( \$user_name ) ;\n \$user_name = \$self->repo_user ;\n\nSets/gets the user name to log in to the repository with. Some plugins\nignore this, like revml, while others, like p4, use it.\n\nThis is usually set automatically by L.\n\n=cut\n\nsub repo_user {\n my VCP::Plugin \$self = shift ;\n \$self->{REPO_USER} = \$_[0] if \@_ ;\n return \$self->{REPO_USER} ;\n}\n\n\n=item repo_password\n\n \$self->repo_password( \$password ) ;\n \$password = \$self->repo_password ;\n\nSets/gets the password to log in to the repository with. Some plugins\nignore this, like revml, while others, like p4, use it.\n\nThis is usually set automatically by L.\n\n=cut\n\nsub repo_password {\n my VCP::Plugin \$self = shift ;\n \$self->{REPO_PASSWORD} = \$_[0] if \@_ ;\n return \$self->{REPO_PASSWORD} ;\n}\n\n\n=item repo_server\n\n \$self->repo_server( \$server ) ;\n \$server = \$self->repo_server ;\n\nSets/gets the repository to log in to. Some plugins\nignore this, like revml, while others, like p4, use it.\n\nThis is usually set automatically by L.\n\n=cut\n\nsub repo_server {\n my VCP::Plugin \$self = shift ;\n \$self->{REPO_SERVER} = \$_[0] if \@_ ;\n return \$self->{REPO_SERVER} ;\n}\n\n\n=item repo_filespec\n\n \$self->repo_filespec( \$filespec ) ;\n \$filespec = \$self->repo_filespec ;\n\nSets/gets the filespec.\n\nThis is usually set automatically by L.\n\n=cut\n\nsub repo_filespec {\n my VCP::Plugin \$self = shift ;\n \$self->{REPO_FILESPEC} = \$_[0] if \@_ ;\n return \$self->{REPO_FILESPEC} ;\n}\n\n\n=item rev_root\n\n \$self->rev_root( 'depot' ) ;\n \$rr = \$self->rev_root ;\n\nThe rev_root is the root of the tree being sourced. See L\nfor automated extraction.\n\nRoot values should have neither a leading or trailing directory separator.\n\n'/' and '\\' are recognized as directory separators and runs of these\nare converted to single '/' characters. Leading and trailing '/'\ncharacters are then removed.\n\n=cut\n\nsub _slash_hack {\n for ( my \$spec = shift ) {\n confess "undef arg" unless defined \$spec ;\n s{[/\\\\]+}{/}g ;\n s{^/}{}g ;\n s{/\\Z}{}g ;\n return \$_ ;\n }\n}\n\nsub rev_root {\n my VCP::Plugin \$self = shift ;\n\n if ( \@_ ) {\n \$self->{REV_ROOT} = &_slash_hack ;\n debug "vcp: rev_root set to '\$self->{REV_ROOT}'" if debugging \$self ;\n }\n return \$self->{REV_ROOT} ;\n}\n\n\n=item deduce_rev_root\n\n \$self->deduce_rev_root ;\n print \$self->rev_root ;\n\nThis is used in most plugins to deduce the rev_root from the filespec portion\nof the source or destination spec if the user did not specify a rev_root as\nan option.\n\nThis function sets the rev_root to be the portion of the filespec up to (but\nnot including) the first file/directory name with a wildcard.\n\n'/' and '\\' are recognized as directory separators, and '*', '?', and '...'\nas wildcard sequences. Runs of '/' and '\\' characters are reduced to\nsingle '/' characters.\n\n=cut\n\nsub deduce_rev_root {\n my VCP::Plugin \$self = shift ;\n\n my ( \$spec ) = &_slash_hack ;\n my \@dirs ;\n my \$wildcard_found ;\n for ( split( /[\\\\\\/]+/, \$spec ) ) {\n if ( /[*?]|\\.\\.\\./ ) {\n\x09 \$wildcard_found = 1 ;\n last ;\n }\n push \@dirs, \$_ ;\n }\n\n my \$dirs = \$wildcard_found || \@dirs < 2 ? \$#dirs : \$#dirs - 1 ;\n \$self->rev_root( join( '/', \@dirs[0..\$dirs] ) ) ;\n}\n\n\n=item normalize_name\n\n \$fn = \$self->normalize_name( \$fn ) ;\n\nNormalizes the filename by converting runs of '\\' and '/' to '/', removing\nleading '/' characters, and removing a leading rev_root. Dies if the name\ndoes not begin with rev_root.\n\n=cut\n\nsub normalize_name {\n my VCP::Plugin \$self = shift ;\n\n my ( \$spec ) = &_slash_hack ;\n\n my \$rr = \$self->rev_root ;\n\n return \$spec unless length \$rr ;\n confess "'\$spec' does not begin with rev_root '\$rr'"\n unless substr( \$spec, 0, length \$rr ) eq \$rr ;\n \n return substr( \$spec, length( \$rr ) + 1 ) ;\n}\n\n\n=item denormalize_name\n\n \$fn = \$self->denormalize_name( \$fn ) ;\n\nDenormalizes the filename by prepending the rev_root. May do more in\nsubclass overloads. For instance, does not prepend a '//' by default for\ninstance, but p4 overloads do that.\n\n=cut\n\nsub denormalize_name {\n my VCP::Plugin \$self = shift ;\n\n return join( '/', \$self->rev_root, shift ) ;\n}\n\n\n=item run\n\nDEPRECATED: use run_safely instead.\n\n \$self->run( [\@cmd_and_args], \\\$stdout, \\\$stderr ) ;\n\nA wrapper around L, which integrates debugging support and\ndisables stdin by default.\n\n=cut\n\n## output command lines using " quoting on Win32 so we can cut & paste.\nmy \$q = \$^O =~ /Win32|OS2/ ? '"' : "'";\n\nsub run {\n my VCP::Plugin \$self = shift ;\n my \$cmd = shift ;\n\n\n debug "vcp: running ", join( ' ', map "\$q\$_\$q", \@\$cmd )\n if debugging \$self ;\n \n return IPC::Run::run( \$cmd, \\undef, \@_ ) ;\n}\n\n=item run_safely\n\nRuns a command "safely", first chdiring in to the proper directory and\nthen running it while examining STDERR through an optional filter and\nlooking at the result codes to see if the command exited acceptably.\n\nMost often called from VCP::Utils::foo methods.\n\n=cut\n\nsub run_safely {\n my VCP::Plugin \$self = shift ;\n my \$cmd = shift ;\n\n my \$cmd_path = \$cmd->[0] ;\n my \$cmd_name = basename( \$cmd_path ) ;\n\n ## Prefix succinct mode args with '>', etc.\n my \$childs_stderr = '' ;\n my \@redirs ;\n my \$fd = 1 ;\n while ( \@_ ) {\n last unless ref \$_[0] ;\n push \@redirs, "\$fd>", shift ;\n ++\$fd ;\n }\n\n my \$stderr_filter;\n while ( \@_ ) {\n if ( \$_[-1] eq "stderr_filter" ) {\n shift;\n \$stderr_filter = shift;\n next;\n }\n push \@redirs, shift \@_ ;\n }\n \$stderr_filter = \$self->command_stderr_filter\n unless defined \$stderr_filter;\n\n ## Put it on the beginning so that later redirects specified by the client\n ## can override our redirect. This is necessary in case the client does\n ## a '2>&1' or some other subtle thing.\n unshift \@redirs, '2>', \\\$childs_stderr\n unless grep \$_ eq '2>', \@redirs ;\n\n unshift \@redirs, '<', \\undef\n unless grep \$_ eq '<', \@redirs ;\n\n debug "vcp: running ", join( ' ', map "\$q\$_\$q", \@\$cmd ),\n " in ", defined \$self->{COMMAND_CHDIR}\n ? \$self->{COMMAND_CHDIR}\n\x09 : "undef"\n if debugging \$self, join( '::', ref \$self, \$cmd->[0] ) ;\n\n my \$cwd ;\n\n if ( defined \$self->command_chdir ) {\n \$self->mkdir( \$self->command_chdir )\n\x09 unless -e \$self->command_chdir ;\n\n \$cwd = cwd;\n\n chdir \$self->command_chdir or die "\$!: ", \$self->command_chdir ;\n cwd;\n# debug "now in ", cwd if debugging ;\n }\n \n my \$h = IPC::Run::harness( \$cmd, \@redirs ) ;\n \$h->run ;\n\n if ( defined \$cwd ) {\n chdir \$cwd or die "\$!: \$cwd" ;\n# debug "now in ", cwd if debugging ;\n }\n\n my \@errors ;\n\n if ( length \$childs_stderr ) {\n if ( debugging \$self ) {\n my \$t = \$childs_stderr ;\n\x09 \$t =~ s/^/\$cmd_name: /gm ;\n\x09 debug \$t ;\n }\n if ( ref \$stderr_filter eq 'Regexp' ) {\n \$childs_stderr =~ s/\$stderr_filter//mg ;\n }\n elsif ( ref \$stderr_filter eq 'CODE' ) {\n \$stderr_filter->( \\\$childs_stderr ) ;\n }\n\n if ( length \$childs_stderr ) {\n\x09 \$childs_stderr =~ s/^/\$cmd_name: /gm ;\n\x09 \$childs_stderr .= "\\n" unless substr( \$childs_stderr, -1 ) eq "\\n" ;\n\x09 push (\n\x09 \@errors,\n\x09 "vcp: unexpected stderr from '\$cmd_name':\\n",\n\x09 \$childs_stderr,\n\x09 ) ;\n }\n }\n\n ## In checking the result code, we assume the first one is the important\n ## one. This is done because a few callers pipe the first child's output\n ## in to a perl sub that then does a kill 9,\$\$ to effectively exit without\n ## calling DESTROY.\n ## TODO: Look at all of the result codes if we can get rid of kill 9, \$\$.\n push(\n \@errors,\n "vcp: ",\n join( ' ', \@\$cmd ),\n " returned ",\n \$h->full_result( 0 ),\n " not ",\n join( ', ', keys %{\$self->{COMMAND_OK_RESULT_CODES}} ),\n "\\n"\n )\n unless exists \$self->{COMMAND_OK_RESULT_CODES}->{\$h->full_result( 0 )} ;\n\n die join( '', \@errors ) if \@errors ;\n\n Carp::cluck "Result of `", join( ' ', \@\$cmd ), "` checked"\n if defined wantarray ;\n}\n\n\nsub DESTROY {\n my VCP::Plugin \$self = shift ;\n\n if ( defined \$self->work_root ) {\n local \$\@ ;\n eval { \$self->rm_work_path() ; } ;\n\n warn "Unable to remove work directory '", \$self->work_root, "'\\n"\n\x09 if ! \$ENV{VCPNODELETE} && -d \$self->work_root ;\n }\n}\n\n=back\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACC #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <{REVS} = [] ;\n \$self->{SEEN} = {} ;\n\n return \$self ;\n}\n\n\n=item add\n\n \$revs->add( \$rev ) ;\n \$revs->add( \$rev1, \$rev2, ... ) ;\n\nAdds a revision or revisions to the collection.\n\n=cut\n\nsub add {\n my VCP::Revs \$self = CORE::shift ;\n\n if ( debugging \$self || debugging scalar caller ) {\n debug( "vcp: queuing ", \$_->as_string ) for \@_ ;\n }\n\n for my \$r ( \@_ ) {\n my \$key = \$r->name . "#" . \$r->rev_id ;\n croak "Can't add same revision twice: '" . \$r->as_string\n if \$self->{SEEN}->{\$key} ;\n \$self->{SEEN}->{\$key} = 1 ;\n push \@{\$self->{REVS}}, \$r ;\n }\n}\n\n\n=item set\n\n \$revs->set( \$rev ) ;\n \$revs->set( \$rev1, \$rev2, ... ) ;\n\nSets the list of revs.\n\n=cut\n\nsub set {\n my VCP::Revs \$self = CORE::shift ;\n\n Carp::confess "undef passed" if grep !defined, \@_;\n\n if ( debugging \$self || debugging scalar caller ) {\n require UNIVERSAL;\n Carp::confess "unblessed ref passed" if grep !UNIVERSAL::can( \$_, "as_string" ), \@_;\n debug( "vcp: queuing ", \$_->as_string ) for \@_ ;\n }\n\n \@{\$self->{REVS}} = \@_ ;\n}\n\n\n=item get\n\n \@revs = \$revs->get ;\n\nGets the list of revs.\n\n=cut\n\nsub get {\n my VCP::Revs \$self = CORE::shift ;\n\n return \@{\$self->{REVS}} ;\n}\n\n\n=item sort\n\n # Using a custom sort function:\n \$revs->sort( sub { ... } ) ;\n\nNote: Don't use \$a and \$b in your sort function. They're package globals\nand that's not your package. See L for more details.\n\n=cut\n\nsub sort {\n my VCP::Revs \$self = CORE::shift ;\n\n my ( \$sort_func ) = \@_ ;\n\n \@{\$self->{REVS}} = sort \$sort_func, \@{\$self->{REVS}} ;\n}\n\n\n=item shift\n\n while ( \$r = \$revs->shift ) {\n ...\n }\n\nCall L before calling this :-).\n\n=cut\n\nsub shift {\n my VCP::Revs \$self = CORE::shift ;\n\n return shift \@{\$self->{REVS}} ;\n}\n\n\n=item as_array_ref\n\nReturns an ARRAY ref of all revs.\n\n=cut\n\nsub as_array_ref {\n my VCP::Revs \$self = CORE::shift ;\n\n return \$self->{REVS} ;\n}\n\n\n=head1 SUBCLASSING\n\nThis class uses the fields pragma, so you'll need to use base and \npossibly fields in any subclasses.\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACD #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <{\$_} = {} ;\n\x09\x09 \\\@{\\\$self->{\$_}}{\\\@_} = (undef) x \\\@_ ;\n\x09 }\n\x09 return \\\$self->{\$_} ? sort keys \\%{\\\$self->{\$_}} : () ;\n\x09 }\n\x09 } ;\n }\n else {\n\x09 eval qq{\n\x09 sub \$f {\n\x09 my VCP::Rev \\\$self = shift ;\n\x09 confess "too many parameters passed" if \\\@_ > 1 ;\n\x09 \\\$self->{\$_} = shift if \\\@_ == 1 ;\n\x09 return \\\$self->{\$_} ;\n\x09 }\n\x09 } ;\n }\n die \$\@ if \$\@ ;\n }\n}\n\n\n## We never, ever want to delete a file that has revs referring to it.\n## So, we put a cleanup object in %files_to_delete and manually manage a\n## reference count on it. The hash is keyed on filename and contains\n## a count value. When the count reaches 0, it is cleaned. We add a warning\n## about undeleted files, which is a great PITA. The reason there's a\n## warning is that we could be using gobs of disk space for temporary files\n## if there's some bug preventing VCP::Rev objects from being DESTROYed\n## soon enough. It's a PITA because it means that the source and\n## destination object really must be dereferenced ASAP, so their SEEN\n## arrays get cleaned up, and every once in awhile I screw it up somehow.\nmy %files_to_delete ;\n\nEND {\n if ( debugging && ! \$ENV{VCPNODELETE} ) {\n for ( sort keys %files_to_delete ) {\n\x09 if ( -e \$_ ) {\n\x09 warn "\$_ not deleted" ;\n\x09 }\n }\n }\n}\n\n\n=item new\n\nCreates an instance, see subclasses for options.\n\n my VCP::Rev \$rev = VCP::Rev->new(\n name => 'foo',\n time => \$commit_time,\n ...\n ) ;\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Rev \$self ;\n\n {\n no strict 'refs' ;\n \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n while ( \@_ ) {\n my \$key = shift ;\n \$self->{uc(\$key)} = shift ;\n }\n\n if ( \$self->{LABELS} ) {\n \$self->labels( \@{\$self->{LABELS}} ) if ref \$self->{LABELS} eq "ARRAY" ;\n }\n else {\n \$self->{LABELS} = {} unless \$self->{LABELS} ;\n }\n\n return \$self ;\n}\n\n\n=item is_base_rev\n\nReturns TRUE if this is a base revision. This is the case if no\naction is defined. A base revision is a revision that is being\ntransferred merely to check it's contents against the destination\nrepository's contents. It's usually a digest and the actual bosy\nof the revision is 'backfilled' from the destination repository and\nchecked against the digest. This cuts down on transfer size, since\nthe full body of the file never need be sent with incremental updates.\n\nSee L as well.\n\n=cut\n\nsub is_base_rev {\n my VCP::Rev \$self = shift ;\n\n return ! defined \$self->{ACTION} ;\n}\n\n\n=item base_revify\n\nConverts a "normal" rev in to a base rev.\n\n=cut\n\nsub base_revify {\n my VCP::Rev \$self = shift ;\n\n \$self->{\$_} = undef for qw(\n P4_INFO\n CVS_INFO\n STATE\n TIME\n MOD_TIME\n USER_ID\n LABELS\n COMMENT\n ACTION\n );\n}\n\n\n=item work_path, dest_work_path\n\nThese set/get the name of the working file for sources and destinations,\nrespectively. These files are automatically cleaned up when all VCP::Rev\ninstances that refer to them are DESTROYED or have their work_path or\ndest_work_path set to other files or undef.\n\n=cut\n\nsub _set_work_path {\n my VCP::Rev \$self = shift ;\n\n my ( \$field, \$fn ) = \@_ ;\n my \$doomed = \$self->{\$field} ;\n if ( defined \$doomed\n && \$files_to_delete{\$doomed}\n && --\$files_to_delete{\$doomed} < 1\n && -e \$doomed\n ) {\n if ( debugging \$self ) {\n my \@details ;\n\x09 my \$i = 2 ;\n\x09 do { \@details = caller(2) } until \$details[0] ne __PACKAGE__ ;\n\x09 debug "vcp: \$self unlinking '\$doomed' in "\n\x09 . join( '|', \@details[0,1,2,3]) ;\n }\n unlink \$doomed or warn "\$! unlinking \$doomed\\n"\n unless \$ENV{VCPNODELETE};\n }\n\n \$self->{\$field} = \$fn ;\n ++\$files_to_delete{\$self->{\$field}} if defined \$self->{\$field} ;\n}\n\n\nsub work_path {\n my VCP::Rev \$self = shift ;\n confess "too many parameters passed" if \@_ > 1 ;\n \$self->_set_work_path( 'WORK_PATH', \@_ ) if \@_ ;\n return \$self->{WORK_PATH} ;\n}\n\n\nsub dest_work_path {\n my VCP::Rev \$self = shift ;\n confess "too many parameters passed" if \@_ > 1 ;\n \$self->_set_work_path( 'DEST_WORK_PATH', \@_ ) if \@_ ;\n return \$self->{DEST_WORK_PATH} ;\n}\n\n\n=item labels\n\n \$r->labels( \@labels ) ;\n \@labels = \$r->labels ;\n\nSets/gets labels associated with a revision. If a label is applied multiple\ntimes, it will only be returned once. This feature means that the automatic\nlabel generation code for r_... revision and ch_... change labels won't add\nadditional copies of labels that were already applied to this revision in the\nsource repository.\n\nReturns labels in an unpredictible order, which happens to be sorted for\nnow. This sorting is purely for logging purposes and may disappear at\nany moment.\n\n=item add_label\n\n \$r->add_label( \$label ) ;\n \$r->add_label( \@labels ) ;\n\nMarks one or more labels as being associated with this revision of a file.\n\n=cut\n\nsub add_label {\n my VCP::Rev \$self = shift ;\n \@{\$self->{LABELS}}{\@_} = (undef) x \@_ ;\n return ;\n}\n\n\nsub as_string {\n my VCP::Rev \$self = shift ;\n\n my \@v = map(\n defined \$_ ? \$_ : "",\n \$self->is_base_rev\n\x09 ? map \$self->\$_(), qw( name rev_id change_id type )\n\x09 : map(\n\x09 \$_ eq 'time' && defined \$self->\$_()\n ? scalar localtime \$self->\$_()\n\x09 : \$_ eq 'comment' && defined \$self->\$_()\n ? do {\n my \$c = \$self->\$_();\n \$c =~ s/\\n/\\\\n/g;\n \$c =~ s/\\r/\\\\r/g;\n \$c =~ s/\\t/\\\\t/g;\n \$c =~ s/\\l/\\\\l/g;\n \$c = substr( \$c, 0, 32 )\n if length( \$c ) > 32;\n \$c;\n }\n : \$self->\$_(),\n\x09 qw(name rev_id change_id type action time user_id comment )\n\x09 )\n ) ;\n\n return \$self->is_base_rev\n ? sprintf( qq{%s#%s \@%s (%s) -- base rev --}, \@v )\n : sprintf( qq{%s#%s \@%s (%s) %s %s %s "%s"}, \@v ) ;\n}\n\nsub DESTROY {\n return if \$ENV{VCPNODELETE};\n my VCP::Rev \$self = shift ;\n \$self->work_path( undef ) ;\n \$self->dest_work_path( undef ) ;\n my \$doomed = \$self->work_path ;\n if ( defined \$doomed && -e \$doomed ) {\n debug "vcp: \$self unlinking '\$doomed'" if debugging \$self ;\n unlink \$doomed or warn "\$! unlinking \$doomed\\n";\n }\n}\n\n\n=back\n\n=head1 SUBCLASSING\n\nThis class uses the fields pragma, so you'll need to use base and \npossibly fields in any subclasses.\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACE #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <SUPER::new( \@_ ) ;\n\n \$self->{BOOTSTRAP_REGEXPS} = [] ;\n \$self->{REVS} = VCP::Revs->new ;\n\n return \$self ;\n}\n\n\n###############################################################################\n\n=head1 SUBCLASSING\n\nThis class uses the fields pragma, so you'll need to use base and \npossibly fields in any subclasses. See L for methods\noften needed in subclasses.\n\n=head2 Subclass utility API\n\n=over\n\n=item dest\n\nSets/Gets a reference to the VCP::Dest object. The source uses this to\ncall handle_header(), handle_rev(), and handle_end() methods.\n\n=cut\n\nsub dest {\n my VCP::Source \$self = shift ;\n\n \$self->{DEST} = shift if \@_ ;\n return \$self->{DEST} ;\n}\n\n=back\n\n=head1 SUBCLASS OVERLOADS\n\nThese methods should be overridded in any subclasses.\n\n=over\n\n=item dest_expected\n\nReturns TRUE if a destination is expected given the parameters passed\nto new().\n\nSome sources can have configuration options that cause side effects.\nThe only one that does this so far is the revml source, which can\noutput the RevML doctype as a .pm file.\n\n=cut\n\nsub dest_expected {\n return 1 ;\n}\n\n\n=item copy\n\nREQUIRED OVERLOAD.\n\n \$source->copy_revs() ;\n\nCalled by L to do the entire export process. This is passed a\npartially filled-in header structure.\n\nThe subclass should call\n\n \$self->dest->handle_rev( \$rev_meta ) ;\n\nThe subclass needs to make sure the \$rev_meta hash contains the metadata for\nthe file and a work_path that points to the work location of the\nfile:\n\n \$rev_meta = VCP::Rev->new(\n work_path => '/tmp/revex/4/depot/perl/perl.c',\n name => 'depot/perl/perl.c',\n rev_id => '4',\n change_id => '22',\n labels => [ 'v0_003', 'v0_004' ],\n ) ;\n\n=cut\n\nsub copy_revs {\n my VCP::Source \$self = shift ;\n\n confess "ERROR: copy_revs not overloaded by class '",\n ref \$self, "'. Oops.\\n" ;\n}\n\n\n=item handle_header\n\nREQUIRED OVERLOAD.\n\nSubclasses must add all repository-specific info to the \$header, at least\nincluding rep_type and rep_desc.\n\n \$header->{rep_type} => 'p4',\n \$self->p4( ['info'], \\\$header->{rep_desc} ) ;\n\nThe subclass must call the superclass method to pass the \$header on to\nthe dest:\n\n \$self->SUPER::handle_header( \$header ) ;\n\n=cut\n\nsub handle_header {\n my VCP::Source \$self = shift ;\n\n my ( \$header ) = \@_ ;\n\n confess "ERROR: copy not overloaded by class '", ref \$self, "'. Oops.\\n"\n if \$self->can( 'handle_header' ) eq \\&handle_header ;\n\n \$self->dest->handle_header( \$header ) ;\n}\n\n\n=item handle_footer\n\nNot a required overload, as the footer carries no useful information at\nthis time. Overriding methods must call this method to pass the\n\$footer on:\n\n \$self->SUPER::handle_footer( \$footer ) ;\n\n=cut\n\nsub handle_footer {\n my VCP::Source \$self = shift ;\n\n my ( \$footer ) = \@_ ;\n\n \$self->dest->handle_footer( \$footer ) ;\n}\n\n\n=item parse_time\n\n \$time = \$self->parse_time( \$timestr ) ;\n\nParses "[cc]YY/MM/DD[ HH[:MM[:SS]]]".\n\nWill add ability to use format strings in future.\nHH, MM, and SS are assumed to be 0 if not present.\n\nReturns a time suitable for feeding to localtime or gmtime.\n\nAssumes local system time, so no good for parsing times in revml, but that's\nnot a common thing to need to do, so it's in VCP::Source::revml.pm.\n\n=cut\n\nsub parse_time {\n my VCP::Source \$self = shift ;\n my ( \$timestr ) = \@_ ;\n\n ## TODO: Get parser context here & give file, line, and column. filename\n ## and rev, while we're scheduling more work for the future.\n confess "Malformed time value \$timestr\\n"\n unless \$timestr =~ /^(\\d\\d)?\\d?\\d(\\D\\d?\\d){2,5}/ ;\n my \@f = split( /\\D/, \$timestr ) ;\n --\$f[1] ; # Month of year needs to be 0..11\n push \@f, ( 0 ) x ( 6 - \@f ) ;\n return timelocal( reverse \@f ) ;\n}\n\n\n=item revs\n\n \$self->revs( VCP::Revs->new ) ;\n \$self->revs->add( \$r ) ; # Many times\n \$self->dest->sort_revs( \$self->revs ) ;\n my VCP::Rev \$r ;\n while ( \$r = \$self->revs->pop ) {\n ## ...checkout the source reve & set \$r->work_path() to refer to it's loc.\n \$self->dest->handle_rev( \$r ) ;\n }\n\nSets/gets the revisions member. This is used by most sources to accumulate\nthe set of revisions to be copied.\n\nThis member should be set by the child in copy_revs(). It should then be\npassed to the destination\n\n=cut\n\nsub revs {\n my VCP::Source \$self = shift ;\n\n \$self->{REVS} = \$_[0] if \@_ ;\n return \$self->{REVS} ;\n}\n\n\n=item bootstrap\n\nUsually called from within call to GetOptions in subclass' new():\n\n GetOptions(\n 'b|bootstrap:s' => sub {\n\x09 my ( \$name, \$val ) = \@_ ;\n\x09 \$self->bootstrap( \$val ) ;\n },\n 'r|rev-root' => \\\$rev_root,\n ) or \$self->usage_and_exit ;\n\nCan be called plain:\n\n \$self->bootstrap( \$bootstrap_spec ) ;\n\nSee the command line documentation for the format of \$bootstrap_spec.\n\nReturns nothing useful, but L does.\n\n=cut\n\nsub bootstrap {\n my VCP::Source \$self = shift ;\n my ( \$val ) = \@_ ;\n \$self->{BOOTSTRAP_REGEXPS} = \$val eq ''\n ? [ compile_shellish( '**' ) ]\n : [ map compile_shellish( \$_ ), split /,+/, \$val ] ;\n\n return ;\n}\n\n\n#=item bootstrap_regexps\n#\n# \$self->bootstrap_regexps( \$re1, \$re1, ... ) ;\n# \$self->bootstrap_regexps( undef ) ; ## clears the list\n# \@res = \$self->bootstrap_regexps ;\n#\n#Sets/gets the list of regular expressions defining what files are in bootstrap\n#mode. This is usually set by L, though.\n#\n#=cut\n#\n#sub bootstrap_regexps {\n# my VCP::Source \$self = shift ;\n# \$self->{BOOTSTRAP_REGEXPS} = [ \@_ == 1 && ! defined \$_[0] ? () : \@_ ]\n# if \@_ ;\n# return \@{\$self->{BOOTSTRAP_REGEXPS}} ;\n#}\n#\n=item is_bootstrap_mode\n\n ... if \$self->is_bootstrap_mode( \$file ) ;\n\nCompares the filename passed in against the list of bootstrap regular\nexpressions set by L.\n\nThe file should be in a format similar to the command line spec for\nwhatever repository is passed in, and not relative to rev_root, so\n"//depot/foo/bar" for p4, or "module/foo/bar" for cvs.\n\nThis is typically called in the subbase class only after looking at the\nrevision number to see if it's a first revision (in which case the\nsubclass should automatically put it in bootstrap mode).\n\n=cut\n\nsub is_bootstrap_mode {\n my VCP::Source \$self = shift ;\n my ( \$file ) = \@_ ;\n\n my \$result = grep \$file =~ \$_, \@{\$self->{BOOTSTRAP_REGEXPS}} ;\n\n debug (\n "vcp: \$file ",\n ( \$result ? "=~ " : "!~ " ),\n "[ ", join( ', ', map "qr/\$_/", \@{\$self->{BOOTSTRAP_REGEXPS}} ), " ] (",\n ( \$result ? "not in " : "in " ),\n "bootstrap mode)"\n ) if debugging \$self ;\n\n return \$result ;\n}\n\n=back\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACF #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <|vcp> to extract versions form a vss\nrepository.\n\nThe source specification for VSS looks like:\n\n vss:filespec []\n\nC may contain trailing wildcards, like C to extract\nan entire directory tree (this is the normal case).\n\nNOTE: This does not support incremental exports, see LIMITATIONS.\n\n=head1 OPTIONS\n\n=over\n\n=item -b, --bootstrap\n\n -b ...\n --bootstrap=...\n -b file1[,file2[, etc.]]\n --bootstrap=file1[,file2[, etc. ]]\n\n(the C<...> there is three periods, a\nL wildcard borrowed from C\npath syntax).\n\nForces bootstrap mode for an entire export (C<-b ...>) or for certain\nfiles. Filenames may contain wildcards, see L for\ndetails on what wildcards are accepted.\n\nControls how the first revision of a file is exported. A bootstrap\nexport contains the entire contents of the first revision in the\nrevision range. This should only be necessary when exporting for the\nfirst time.\n\nAn incremental export contains a digest of the revision preceding the\nfirst revision in the revision range, followed by a delta record between\nthat revision and the first revision in the range. This allows the\ndestination import function to make sure that the incremental export\nbegins where the last export left off.\n\nThe default is decided on a per-file basis: if the first revision in the\nrange is revision #.1, the full contents are exported. Otherwise an\nincremental export is done for that file.\n\nThis option is necessary when exporting only more recent revisions from\na repository.\n\n=item --cd\n\nUsed to set the VSS working directory. VCP::Source::vss will cd to this\ndirectory before calling vss, and won't initialize a VSS workspace of\nit's own (normally, VCP::Source::vss does a "vss checkout" in a\ntemporary directory).\n\nThis is an advanced option that allows you to use a VSS workspace you\nestablish instead of letting vcp create one in a temporary directory\nsomewhere. This is useful if you want to read from a VSS branch or if\nyou want to delete some files or subdirectories in the workspace.\n\nIf this option is a relative directory, then it is treated as relative\nto the current directory.\n\n=item --rev-root\n\nB.\n\nFalsifies the root of the source tree being extracted; files will\nappear to have been extracted from some place else in the hierarchy.\nThis can be useful when exporting RevML, the RevML file can be made\nto insert the files in to a different place in the eventual destination\nrepository than they existed in the source repository.\n\nThe default C is the file spec up to the first path segment\n(directory name) containing a wildcard, so\n\n vss:/a/b/c...\n\nwould have a rev-root of C.\n\nIn direct repository-to-repository transfers, this option should not be\nnecessary, the destination filespec overrides it.\n\n=cut\n\n#=item -V\n#\n# -V 5\n# -V 5~3\n#\n#Passed to C.\n\n=back\n\n=head2 Files that aren't tagged\n\nVSS has one peculiarity that this driver works around.\n\nIf a file does not contain the tag(s) used to select the source files,\nC outputs the entire life history of that file. We don't want\nto capture the entire history of such files, so L goes\nignores any revisions before and after the oldest and newest tagged file\nin the range.\n\n=head1 LIMITATIONS\n\n "What we have here is a failure to communicate!"\n - The warden in Cool Hand Luke\n\nVSS does not try to protect itself from people checking in things that\nlook like snippets of VSS log file: they come out exactly like they\nwent in, confusing the log file parser.\n\nSo, if a repository contains messages in the log file that look like the \noutput from some other "vss log" command, things will likely go awry.\n\nAt least one vss repository out there has multiple revisions of a single file\nwith the same rev number. The second and later revisions with the same rev\nnumber are ignored with a warning like "Can't add same revision twice:...".\n\n=cut\n\n\$VERSION = 1.2 ;\n\n# Removed docs for -f, since I now think it's overcomplicating things...\n#Without a -f This will normally only replicate files which are tagged. This\n#means that files that have been added since, or which are missing the tag for\n#some reason, are ignored.\n#\n#Use the L option to force files that don't contain the tag to be\n#=item -f\n#\n#This option causes vcp to attempt to export files that don't contain a\n#particular tag but which occur in the date range spanned by the revisions\n#specified with -r. The typical use is to get all files from a certain\n#tag to now.\n#\n#It does this by exporting all revisions of files between the oldest and\n#newest files that the -r specified. Without C<-f>, these would\n#be ignored.\n#\n#It is an error to specify C<-f> without C<-r>.\n#\n#exported.\n\nuse strict ;\n\nuse Carp ;\nuse Getopt::Long ;\nuse File::Basename;\nuse Regexp::Shellish qw( :all ) ;\nuse VCP::Rev ;\nuse VCP::Debug ':debug' ;\nuse VCP::Source ;\nuse VCP::Utils::vss ;\n\nuse base qw( VCP::Source VCP::Utils::vss ) ;\nuse fields (\n 'VSS_CUR', ## The current change number being processed\n 'VSS_BOOTSTRAP', ## Forces bootstrap mode\n 'VSS_IS_INCREMENTAL', ## Hash of filenames, 0->bootstrap, 1->incremental\n 'VSS_INFO', ## Results of the 'vss --version' command and VSSROOT\n 'VSS_LABEL_CACHE', ## ->{\$name}->{\$rev} is a list of labels for that rev\n 'VSS_LABELS', ## Array of labels from 'p4 labels'\n 'VSS_MAX', ## The last change number needed\n 'VSS_MIN', ## The first change number needed\n 'VSS_VER_SPEC', ## The revision spec to pass to `ss History`\n\n 'VSS_NAME_REP_NAME', ## A mapping of names to repository names\n\n 'VSS_K_OPTION', ## Which of the VSS/RCS "-k" options to use, if any\n\n 'VSS_LOG_CARRYOVER', ## The unparsed bit of the history file\n 'VSS_LOG_STATE', ## Parser state machine state\n 'VSS_LOG_REV', ## The revision being parsed (a hash)\n\n 'VSS_NEEDS_BASE_REV', ## What base revisions are needed. Base revs are\n ## needed for incremental (ie non-bootstrap) updates,\n\x09\x09\x09 ## which is decided on a per-file basis by looking\n\x09\x09\x09 ## at VCP::Source::is_bootstrap_mode( \$file ) and\n\x09\x09\x09 ## the file's rev number (ie does it end in .1).\n 'VSS_HIGHEST_VERSION', ## A HASH keyed on filename that contains the\n ## last rev_id seen for a file. This allows\n ## file deletions (which aren't tracked by\n ## VSS in a file's history) to be given a\n ## pretend revision number.\n 'VSS_REV_ID_OFFSET', ## After a busy day processing a deleted file,\n ## it's time to relax and process the not-deleted\n ## file of the same name. In order to keep\n ## from reusing the same version numbers for\n ## the not-deleted file, this variable contains\n ## an offset to add to the revisions. It's the\n ## value of VSS_HIGHEST_VERSION reached while\n ## reading the deleted file.\n\n 'VSS_FILES', ## Managed by VSS::Utils::vss\n 'VSS_LOG_LAZY_COMMIT_PENDING', ## Multiple VSS revisions get compressed\n ## in to a single VCP revision. This\n ## flag is set when a revision is parsed\n ## that is not immediately converted in\n ## to a VCP::Rev; right now this applies\n ## to "Labeled" revisions because we\n ## accumulate labels in to one\n ## VCP::Rev.\n 'VSS_LOG_PRELIM_FIELDS', ## When reading ahead to see if the current\n ## pending lazy commit needs to be committed,\n ## accumulated data is held here.\n 'VSS_LOG_OLDEST_VERSION', ## The oldest rev parsed for this file.\n) ;\n\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Source::vss \$self = \$class->SUPER::new( \@_ ) ;\n\n ## Parse the options\n my ( \$spec, \$options ) = \@_ ;\n\n ## Make it look like a Unix path.\n \$spec =~ s{^\\\$//}{};\n \$spec =~ s{\\\$}{}g;\n \$spec =~ s{\\\\}{/}g;\n\n \$self->parse_repo_spec( \$spec ) ;\n\n my \$work_dir ;\n my \$rev_root ;\n my \$ver_spec ;\n\n GetOptions(\n "b|bootstrap:s" => sub {\n\x09 my ( \$name, \$val ) = \@_ ;\n\x09 \$self->{VSS_BOOTSTRAP} = \$val eq ""\n\x09 ? [ compile_shellish( "..." ) ]\n\x09 : [ map compile_shellish( \$_ ), split /,+/, \$val ] ;\n },\n "cd=s" => \\\$work_dir,\n "rev-root=s" => \\\$rev_root,\n "V=s" => sub { \$ver_spec = "-V\$_[1]" },\n "k=s" => sub { warn \$self->{VSS_K_OPTION} = \$_[1] } ,\n "kb" => sub { warn \$self->{VSS_K_OPTION} = "b" } ,\n ) or \$self->usage_and_exit ;\n\n \$self->{VSS_VER_SPEC} = \$ver_spec;\n\n my \$files = \$self->repo_filespec ;\n unless ( defined \$rev_root ) {\n \$self->deduce_rev_root( \$files ) ;\n }\n# else {\n# \$files = "\$rev_root/\$files" ;\n# }\n#\n### TODO: Figure out whether we should make rev_root merely set the rev_root\n### in the header. I think we probably should do it that way, as it's more\n### flexible and less confusing.\n\n ## Don't normalize the filespec.\n \$self->repo_filespec( \$files ) ;\n\n unless ( defined \$work_dir ) {\n \$self->create_vss_workspace ;\n }\n else {\n \$self->work_root( File::Spec->rel2abs( \$work_dir ) ) ; \n \$self->command_chdir( \$self->work_path ) ;\n }\n\n ## May need to run again with -D to list deleted files\n ## This generates the list of all files we want to scan\n \$self->get_vss_file_list(\n \$self->repo_filespec,\n );\n\n {\n my ( \$out, \$err );\n ## Dirty trick: send a known bad parm *just* to get ss.exe to\n ## print it's banner without popping open a help screen.\n \$self->ss( [ "help", "/illegal arg" ], ">", \\\$out, "2>", \\\$err );\n \$self->{VSS_INFO} = \$out;\n }\n\n return \$self ;\n}\n\n\nsub is_incremental {\n my VCP::Source::vss \$self= shift ;\n my ( \$file, \$first_rev ) = \@_ ;\n\n \$first_rev =~ s/\\.\\d+//; ## Trim down rev_ids\n\n my \$bootstrap_mode = \$first_rev <= "1"\n || ( \$self->{VSS_BOOTSTRAP}\n && grep \$file =~ \$_, \@{\$self->{VSS_BOOTSTRAP}}\n ) ;\n\n return \$bootstrap_mode ? 0 : "incremental" ;\n}\n\n\nsub denormalize_name {\n my VCP::Source::vss \$self = shift ;\n return '/' . \$self->SUPER::denormalize_name( \@_ ) ;\n}\n\n\nsub handle_header {\n my VCP::Source::vss \$self = shift ;\n my ( \$header ) = \@_ ;\n\n \$header->{rep_type} = 'vss' ;\n \$header->{rep_desc} = \$self->{VSS_INFO} ;\n \$header->{rev_root} = \$self->rev_root ;\n\n \$self->dest->handle_header( \$header ) ;\n return ;\n}\n\n\nsub get_rev {\n my VCP::Source::vss \$self = shift ;\n\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n my \$wp = \$self->work_path( "revs", \$r->name, \$r->rev_id ) ;\n \$r->work_path( \$wp ) ;\n \$self->mkpdir( \$wp ) ;\n\n my ( \$fn, \$dir ) = fileparse( \$wp );\n\n my \$ignored_stdout;\n\nconfess "Shouldn't be get_rev()ing a rev with no rev_id" unless defined \$r->rev_id;\n\n if ( \$self->vss_file_is_deleted( \$r->source_name ) ) {\n my \$rev_id = \$r->rev_id;\n \$rev_id -= \$self->{VSS_REV_ID_OFFSET}->{\$r->source_name}\n if \$rev_id > \$self->{VSS_REV_ID_OFFSET}->{\$r->source_name};\n \$self->_swap_in_deleted_file_and(\n \$r->source_name,\n "ss",\n [ "Get",\n "\\\$/" . \$r->source_name,\n "-V" . \$rev_id,\n "-GL" . \$dir,\n "-GN", ## Newlines only, please\n ],\n ">", \\\$ignored_stdout\n ) ;\n }\n else {\n \$self->ss(\n [ "Get",\n "\\\$/" . \$r->source_name,\n "-V" . \$r->rev_id,\n "-GL" . \$dir,\n "-GN", ## Newlines only, please\n ],\n ">", \\\$ignored_stdout\n );\n }\n\n my \$temp_fn = fileparse( \$r->source_name );\n\n rename "\$dir/\$temp_fn", "\$dir/\$fn" or die "\$! renaming \$temp_fn to \$fn\\n";\n}\n\n\n## History report Parser states\n## The code below does things like grep for "commit" and "skip to next"\n## in these strings. Plus, they make debug output easier to read.\nuse constant SKIP_TO_NEXT => "skip to next";\nuse constant SKIP_TO_NEXT_COMMIT_AT_END => "skip to next and commit at end";\nuse constant ENTRY_START => "entry start";\nuse constant READ_ACTION => "read action";\nuse constant READ_COMMENT_AND_COMMIT => "read comment and commit";\nuse constant READ_REST_OF_COMMENT_AND_COMMIT => "read rest of comment and commit";\n\nsub _reset_log_parser {\n my VCP::Source::vss \$self = shift ;\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT;\n \$self->{VSS_LOG_CARRYOVER} = '' ;\n \$self->{VSS_LOG_REV} = {} ;\n \$self->{VSS_LOG_OLDEST_VERSION} = undef ;\n \$self->{VSS_LOG_LAZY_COMMIT_PENDING} = 0;\n \$self->{VSS_LOG_PRELIM_FIELDS} = {};\n\n}\n\nsub _get_file_metadata {\n my VCP::Source::vss \$self = shift ;\n my ( \$filename ) = \@_;\n\n my \$ss_fn = "\\\$/\$filename";\n\n my \$filetype;\n\n \$self->ss( [ "FileType", \$ss_fn ], ">", \\\$filetype );\n\n \$filetype =~ s/\\A.*\\s(\\S+)\\r?\\n.*/\$1/ms\n or die "Can't parse filetype from '\$filetype'";\n \$filetype = lc \$filetype;\n\n my \$tmp_f;\n my \$result = 1;\n if ( defined \$self->{VSS_VER_SPEC} ) {\n \$tmp_f = \$self->command_stderr_filter;\n \$self->command_stderr_filter(\n sub {\n my ( \$err_text_ref ) = \@_ ;\n \$\$err_text_ref =~ s{^Version not found\\r?\\n\\r?}[\n \$result = 0;\n '' ;\n ]mei ;\n }\n );\n }\n\n \$self->_reset_log_parser;\n \$self->ss( [\n "History",\n "\\\$/\$filename",\n defined \$self->{VSS_VER_SPEC}\n ? \$self->{VSS_VER_SPEC}\n : (),\n ],\n '>', sub { \$self->parse_log_file( \$filename, \$filetype, \@_ ) },\n ) ;\n\n ## Keep scanning until we get the actual checkin, so we get\n ## any intervening labels and the correct metadata for the\n ## checkin. A LAZY_COMMIT_PENDING means that the History\n ## output did not end on a checkin, it ended on a label or\n ## something.\n if ( defined \$self->{VSS_LOG_OLDEST_VERSION} ) {\n if ( substr( \$self->{VSS_LOG_STATE}, -6 ) eq "commit" ) {\n \$self->add_rev_from_log_parser;\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT;\n }\n\n my \$oldest = \$self->{VSS_LOG_OLDEST_VERSION};\n if ( \$self->{VSS_LOG_LAZY_COMMIT_PENDING} ) {\n debug "vcp: scanning back to checkin"\n if debugging \$self;\n die "Must be in SKIP_TO_NEXT... not \$self->{VSS_LOG_STATE}"\n unless 0 == index \$self->{VSS_LOG_STATE}, SKIP_TO_NEXT;\n \$self->_find_checkin( \$filename, \$filetype, \$oldest );\n\n if ( substr( \$self->{VSS_LOG_STATE}, -6 ) eq "commit" ) {\n \$self->add_rev_from_log_parser;\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT;\n }\n \$oldest = \$self->{VSS_LOG_OLDEST_VERSION};\n }\n\n if ( \$self->is_incremental( \$filename, \$oldest ) ) {\n debug "vcp: scanning back to base rev"\n if debugging \$self;\n ## Skip the banner\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT;\n \$self->_find_checkin( \$filename, \$filetype, \$oldest );\n if ( substr( \$self->{VSS_LOG_STATE}, -6 ) eq "commit" ) {\n \$self->add_rev_from_log_parser;\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT;\n }\n \$self->revs->as_array_ref->[-1]->base_revify;\n }\n }\n \$self->parse_log_file( \$filename, \$filetype, undef );\n\n \$self->command_stderr_filter( \$tmp_f )\n if defined \$self->{VSS_VER_SPEC};\n\n return \$result;\n}\n\n\nsub _swap_in_deleted_file_and {\n my VCP::Source::vss \$self = shift ;\n my ( \$filename, \$method, \@args ) = \@_;\n\n my \$ss_fn = "\\\$/\$filename";\n\n my \$ignored_stdout;\n\n my \$renamed_active;\n if ( \$self->vss_file_is_active( \$filename ) ) {\n my \$i = "";\n while (1) {\n \$renamed_active = "\$ss_fn.vcp_bak\$i";\n last unless \$self->vss_file( \$renamed_active );\n \$i ||= 0;\n ++\$i;\n }\n \$self->ss( [ "Rename", \$ss_fn, \$renamed_active ] );\n }\n\n my \$result;\n\n my \$ok = eval {\n##TODO: not ignore this output!\n \$self->ss( [ "Recover", \$ss_fn ], ">", \\\$ignored_stdout ) ;\n\n my \$ok = eval { \$result = \$self->\$method( \@args ); 1 };\n\n my \$x = \$\@;\n \$self->{VSS_REV_ID_OFFSET}->{\$filename} =\n \$self->{VSS_HIGHEST_VERSION}->{\$filename} || 0;\n \$ok = eval {\n##TODO: not ignore this output!\n \$self->ss( [ "Delete", \$ss_fn ], ">", \\\$ignored_stdout ) ;\n 1;\n } && \$ok;\n \$x = "" unless defined \$x;\n die \$x.\$\@ unless \$ok;\n };\n\n\n my \$x = \$\@;\n\n if ( defined \$renamed_active ) {\n my \$myok = eval {\n \$self->ss( [ "Rename", \$renamed_active, \$ss_fn ] );\n 1;\n };\n if ( ! \$myok ) {\n \$x .= \$\@;\n \$ok = 0;\n };\n }\n\n die \$x unless \$ok;\n\n return \$result;\n}\n\n\nsub copy_revs {\n my VCP::Source::vss \$self = shift ;\n\n ## Get a list of all files we need to worry about\n \$self->get_vss_file_list( \$self->repo_filespec );\n\n \$self->revs( VCP::Revs->new ) ;\n\n for my \$filename ( \$self->vss_files ) {\n \$self->{VSS_REV_ID_OFFSET}->{\$filename} = 0;\n\n my \$found_deleted;\n if ( \$self->vss_file_is_deleted( \$filename ) ) {\n \$found_deleted = \$self->_swap_in_deleted_file_and(\n \$filename, "_get_file_metadata", \$filename\n );\n\n my VCP::Rev \$r = VCP::Rev->new(\n source_name => \$filename,\n name => \$self->normalize_name( \$filename ),\n action => "delete",\n ## Make up a fictional rev number that will allow the\n ## receiver's sort algorithm to put this delete in the\n ## right place and that will be documented in the\n ## receiving repository as a label.\n rev_id => "\$self->{VSS_REV_ID_OFFSET}->{\$filename}.1",\n ## Deletes are not logged, no user data, time, etc.\n ) ;\n\n \$self->revs->add( \$r );\n }\n\n my \$found_active;\n if ( \$self->vss_file_is_active( \$filename ) ) {\n my \$tmp_ver_spec;\n if ( \$found_deleted ) {\n ## If we happen to have been looking for a label and it was\n ## found in the deleted version, then make sure we get all\n ## the revs from the active file.\n \$tmp_ver_spec = \$self->{VSS_VER_SPEC};\n \$self->{VSS_VER_SPEC} = undef;\n }\n\n \$found_active = \$self->_get_file_metadata( \$filename );\n\n \$self->{VSS_VER_SPEC} = \$tmp_ver_spec\n if \$found_deleted;\n }\n\n if ( defined \$self->{VSS_VER_SPEC}\n && ! ( \$found_deleted || \$found_active )\n ) {\n warn "vcp: \$self->{VSS_VER_SPEC} did not match any revisions of \$filename, not transferring\\n";\n }\n \n if ( keys %{\$self->{VSS_LOG_REV}} ) {\n require Data::Dumper;\n die "Data left over in VSS_LOG_REV, state \$self->{VSS_LOG_STATE}:\\n",\n Data::Dumper::Dumper(\n \$self->{VSS_LOG_REV}\n );\n }\n }\n\n \$self->dest->sort_revs( \$self->revs ) ;\n\n my VCP::Rev \$r ;\n while ( \$r = \$self->revs->shift ) {\n \$self->get_rev( \$r ) if ! \$r->action || \$r->action ne "delete";\n \$self->dest->handle_rev( \$r ) ;\n }\n}\n\n\n# Here's a typical history\n#\n###############################################################################\n##D:\\src\\vcp>ss history\n#History of \$/90vss.t ...\n#\n#***************** Version 9 *****************\n#User: Admin Date: 3/05/02 Time: 9:32\n#readd recovered\n#\n#***** a_big_file *****\n#Version 3\n#User: Admin Date: 3/05/02 Time: 9:32\n#Checked in \$/90vss.t\n#Comment: comment 3\n#\n#\n#***** binary *****\n#Version 3\n#User: Admin Date: 3/05/02 Time: 9:32\n#Checked in \$/90vss.t\n#Comment: comment 3\n#\n#\n#***************** Version 8 *****************\n#User: Admin Date: 3/05/02 Time: 9:32\n#readd deleted\n#\n#***** binary *****\n#Version 2\n#User: Admin Date: 3/05/02 Time: 9:32\n#Checked in \$/90vss.t\n#Comment: comment 2\n#\n#\n#***************** Version 7 *****************\n#User: Admin Date: 3/05/02 Time: 9:32\n#readd added\n#\n#***** a_big_file *****\n#Version 2\n#User: Admin Date: 3/05/02 Time: 9:32\n#Checked in \$/90vss.t\n#Comment: comment 2\n#\n#\n#***************** Version 6 *****************\n#User: Admin Date: 3/05/02 Time: 9:32\n#\$del added\n#\n#***************** Version 5 *****************\n#User: Admin Date: 3/05/02 Time: 9:32\n#binary added\n#\n#***************** Version 4 *****************\n#User: Admin Date: 3/05/02 Time: 9:31\n#\$add added\n#\n#***************** Version 3 *****************\n#User: Admin Date: 3/05/02 Time: 9:31\n#a_big_file added\n#\n#***************** Version 2 *****************\n#User: Admin Date: 3/05/02 Time: 9:31\n#\$a added\n#\n#***************** Version 1 *****************\n#User: Admin Date: 3/05/02 Time: 9:31\n#Created\n#\n#\n#D:\\src\\vcp>ss dir /r\n#\$/90vss.t:\n#\$a\n#\$add\n#\$del\n#a_big_file\n#binary\n#readd\n#\n#\$/90vss.t/a:\n#\$deeply\n#\n#\$/90vss.t/a/deeply:\n#\$buried\n#\n#\$/90vss.t/a/deeply/buried:\n#file\n#\n#\$/90vss.t/add:\n#f1\n#f2\n#f3\n#\n#\$/90vss.t/del:\n#f4\n#\n#13 item(s)\n#\n#D:\\src\\vcp>\n#\n###############################################################################\n\n\nsub _is_rev_a_checkin {\n my ( \$self, \$fn, \$filetype, \$rev_id ) = \@_;\n\n \$rev_id -= \$self->{VSS_REV_ID_OFFSET}->{\$fn}\n if \$rev_id > \$self->{VSS_REV_ID_OFFSET}->{\$fn};\n\n \$self->ss(\n [ "History", "\\\$/\$fn", "-V\$rev_id", "-#1" ],\n ">", \\my \$history\n );\n\n \$self->parse_log_file( \$fn, \$filetype, \$history );\n\n ## Note: similar regexp in parse_log_file\n return \$history =~ /^(Checked in .*|Created|.* recovered)\$/m ? 1 : 0;\n}\n\n\nsub _find_checkin {\n my \$self = shift;\n my ( \$fn, \$filetype, \$rev_id ) = \@_;\n\n \$rev_id =~ s/\\.\\d+//; # ignore faked-up revs.\n return if \$rev_id <= 1;\n\n while ( --\$rev_id ) {\n warn \$fn, " ", \$rev_id, " ", \$self->{VSS_REV_ID_OFFSET}->{\$fn};\n if ( \$rev_id <= \$self->{VSS_REV_ID_OFFSET}->{\$fn} ) {\n last if \$self->_swap_in_deleted_file_and(\n \$fn,\n "_is_rev_a_checkin",\n \$fn,\n \$filetype,\n \$rev_id\n );\n }\n else {\n last if \$self->_is_rev_a_checkin( \$fn, \$filetype, \$rev_id );\n }\n }\n}\n\n\nsub parse_log_file {\n my ( \$self, \$filename, \$filetype, \$input ) = \@_ ;\n\n if ( defined \$input ) {\n \$self->{VSS_LOG_CARRYOVER} .= \$input ;\n }\n else {\n ## Last call...\n ## There can only be leftovers if they don't end in a "\\n". I've never\n ## seen that happen, but given large comments, I could be surprised...\n \$self->{VSS_LOG_CARRYOVER} .= "\\n" if length \$self->{VSS_LOG_CARRYOVER} ;\n }\n\n my \$p = \$self->{VSS_LOG_REV};\n\n local \$_ ;\n\n ## DOS, Unix, Mac lineends spoken here.\n while ( \$self->{VSS_LOG_CARRYOVER} =~ s/^(.*(?:\\r\\n|\\n\\r|\\n))// ) {\n \$_ = \$1 ;\n if ( debugging \$self ) {\n my \$foo = \$1;\n chomp \$foo;\n debug "[\$foo] \$self->{VSS_LOG_STATE}\\n";\n }\n\n ## This is crude, but effective: it sets the values every time\n \$p->{Name} = \$filename;\n \$p->{Type} = \$filetype;\n\n if ( /^\\*{17} Version (\\d+) +\\*{17}/ ) {\n \$self->add_rev_from_log_parser\n if substr( \$self->{VSS_LOG_STATE}, -6 ) eq "commit";\n \$self->{VSS_LOG_STATE} = ENTRY_START;\n\n ## This will overwrite the newer/higher version number\n ## with the lower/older one until we reach the check-in\n ## we want\n \$self->{VSS_LOG_OLDEST_VERSION} = \$p->{Version} = \$1;\n next;\n }\n\n if ( /^\\*{5}\\s+(.*?)\\s+\\*{5}\$/ ) {\n \$self->add_rev_from_log_parser\n if substr( \$self->{VSS_LOG_STATE}, -6 ) eq "commit";\n \$self->{VSS_LOG_STATE} = ENTRY_START;\n \$p->{_banner_name} = \$1;\n next;\n }\n\n next if 0 == index \$self->{VSS_LOG_STATE}, SKIP_TO_NEXT;\n\n if ( \$self->{VSS_LOG_STATE} eq ENTRY_START ) {\n if ( /^Label:\\s*"([^"]+)"/ ) {\n ## Unshift because we're reading from newest to oldest yet\n ## we want oldest first so vss->vss is relatively consistent\n unshift \@{\$p->{Labels}}, \$1;\n next;\n }\n if ( /^User:\\s+(.*?)\\s+Date:\\s+(.*?)\\s+Time:\\s+(\\S+)/ ) {\n \$self->{VSS_LOG_PRELIM_FIELDS}->{User} = \$1;\n \$self->{VSS_LOG_PRELIM_FIELDS}->{Date} = \$2;\n \$self->{VSS_LOG_PRELIM_FIELDS}->{Time} = \$3;\n \$self->{VSS_LOG_STATE} = READ_ACTION;\n next;\n }\n }\n\n if ( \$self->{VSS_LOG_STATE} eq READ_ACTION ) {\n if ( /Labeled/ ) {\n ## It's a label-add only, ignore the rest.\n ## for incremental exports, we'll need to commit at the\n ## end of the log if the last thing was a "Labeled"\n ## version. We don't want to commit after each "Labeled"\n ## because we want to aggregate labels.\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT_COMMIT_AT_END;\n \$p->{Action} = "edit";\n \$p->{\$_} = delete \$self->{VSS_LOG_PRELIM_FIELDS}->{\$_}\n for keys %{\$self->{VSS_LOG_PRELIM_FIELDS}};\n \$self->{VSS_LOG_LAZY_COMMIT_PENDING} = 1;\n next;\n }\n\n ## Note: similar regexp in is_rev_a_checkin\n if ( /^(Checked in .*|Created|.* recovered)\$/ ) {\n \$self->{VSS_LOG_STATE} = READ_COMMENT_AND_COMMIT;\n \$p->{\$_} = delete \$self->{VSS_LOG_PRELIM_FIELDS}->{\$_}\n for keys %{\$self->{VSS_LOG_PRELIM_FIELDS}};\n \$p->{Action} = "edit";\n next;\n }\n }\n\n if ( \$self->{VSS_LOG_STATE} eq READ_COMMENT_AND_COMMIT ) {\n if ( s/Comment: // ) {\n \$p->{Comment} = \$_;\n \$self->{VSS_LOG_STATE} = READ_REST_OF_COMMENT_AND_COMMIT;\n next;\n }\n }\n\n if ( \$self->{VSS_LOG_STATE} eq READ_REST_OF_COMMENT_AND_COMMIT ) {\n \$p->{Comment} .= \$_;\n next;\n }\n\n require Data::Dumper;\n local \$Data::Dumper::Indent = 1;\n local \$Data::Dumper::Quotekeys = 0;\n local \$Data::Dumper::Terse = 1;\n\n die\n "unhandled VSS log line '\$_' in state \$self->{VSS_LOG_STATE} for:\\n",\n Data::Dumper::Dumper( \$self->{VSS_LOG_REV} );\n }\n\n if ( ! defined \$input ) {\n \$self->add_rev_from_log_parser\n if 0 <= index( \$self->{VSS_LOG_STATE}, "commit" )\n || \$self->{VSS_LOG_LAZY_COMMIT_PENDING};\n\n \$self->{VSS_LOG_STATE} = SKIP_TO_NEXT;\n }\n}\n\n\n# Here's a (probably out-of-date by the time you read this) dump of the args\n# for _add_rev:\n#\n###############################################################################\n#\$file = {\n# 'WORKING' => 'src/Eesh/eg/synopsis',\n# 'SELECTED' => '2',\n# 'LOCKS' => 'strict',\n# 'TOTAL' => '2',\n# 'ACCESS' => '',\n# 'RCS' => '/var/vss/vssroot/src/Eesh/eg/synopsis,v',\n# 'KEYWORD' => 'kv',\n# 'RTAGS' => {\n# '1.1' => [\n# 'Eesh_003_000',\n# 'Eesh_002_000'\n# ]\n# },\n# 'HEAD' => '1.2',\n# 'TAGS' => {\n# 'Eesh_002_000' => '1.1',\n# 'Eesh_003_000' => '1.1'\n# },\n# 'BRANCH' => ''\n#};\n#\$rev = {\n# 'DATE' => '2000/04/21 17:32:16',\n# 'MESSAGE' => 'Moved a bunch of code from eesh, then deleted most of it.\n#',\n# 'STATE' => 'Exp',\n# 'AUTHOR' => 'barries',\n# 'REV' => '1.1'\n#};\n###############################################################################\n\nsub _add_rev {\n my VCP::Source::vss \$self = shift ;\n my ( \$rev_data, \$is_base_rev ) = \@_ ;\n\n if ( debugging \$self ) {\n require Data::Dumper;\n debug "ADDING: ", Data::Dumper::Dumper( \$rev_data );\n }\n\n my \$action = \$rev_data->{Action};\n\n \$rev_data->{Type} ||= "text";\n\n#debug map "\$_ => \$rev_data->{\$_}, ", sort keys %{\$rev_data} ;\n\n my \$filename = \$rev_data->{Name};\n\n my VCP::Rev \$r = VCP::Rev->new(\n source_name => \$filename,\n name => \$self->normalize_name( \$rev_data->{Name} ),\n rev_id =>\n \$rev_data->{Version} + \$self->{VSS_REV_ID_OFFSET}->{\$filename},\n type => \$rev_data->{Type},\n# ! \$is_base_rev\n#\x09 ? (\n\x09 action => \$action,\n\x09 time =>\n \$self->parse_time( \$rev_data->{Date} . " " . \$rev_data->{Time} ),\n\x09 user_id => \$rev_data->{User},\n\x09 comment => \$rev_data->{Comment},\n\x09 state => \$rev_data->{STATE},\n\x09 labels => \$rev_data->{Labels},\n#\x09 )\n#\x09 : (),\n ) ;\n\n \$self->{VSS_NAME_REP_NAME}->{\$rev_data->{Name}} = \$rev_data->{RCS} ;\n\n eval {\n \$self->revs->add( \$r ) ;\n } ;\n if ( \$\@ ) {\n if ( \$\@ =~ /Can't add same revision twice/ ) {\n warn \$\@ ;\n }\n else {\n die \$\@ ;\n }\n }\n}\n\nsub add_rev_from_log_parser {\n my ( \$self ) = \@_;\n\n my \$rev_data = \$self->{VSS_LOG_REV};\n\n \$rev_data->{Comment} = ''\n unless defined \$rev_data->{Comment};\n\n \$rev_data->{Comment} =~ s/\\r\\n|\\n\\r/\\n/g ;\n chomp \$rev_data->{Comment};\n chomp \$rev_data->{Comment};\n\n \$self->_add_rev( \$rev_data );\n\n my \$name = \$rev_data->{Name};\n\n \$self->{VSS_HIGHEST_VERSION}->{\$name} = \$rev_data->{Version}\n if ! defined \$self->{VSS_HIGHEST_VERSION}->{\$name}\n || \$rev_data->{Version} > \$self->{VSS_HIGHEST_VERSION}->{\$name};\n\n %\$rev_data = () ;\n \$self->{VSS_LOG_LAZY_COMMIT_PENDING} = 0;\n} ;\n\n=head1 VSS NOTES\n\nWe lose comments attached to labels: labels are added to the last\n"real" (ie non-label-only) revision and the comments are ignored.\nThis can be changed, contact me.\n\nWe assume a file has always been text or binary, don't think this is\nstored per-version in VSS.\n\nLooks for deleted files: recovers them if found just long enough to\ncope with them, then deletes them again.\n\nVSS does not track renames by version, so a previous name for a file is lost.\n\nVSS lets you add a new file after deleting an old one. This module\nrenames the current file, restores the old one, issues its revisions,\nthen deletes the old on and renames the current file back. In this\ncase, the Cs from the current file start at the highest\nC for the deleted file and continue up.\n\nNOTE: when recovering a deleted file and using it, the current version\ntakes a "least opportunity to screw up the source repository" approach:\nit renames the not-deleted version (if any), restores the deleted one,\ndoes the History or Get, and then deletes it and renames the not-deleted\nversion back.\n\nThis is so that if something (the OS, the hardware, AC mains, or even\nVCP code) crashes, the source repository is left as close to the\noriginal state as is possible. This does mean that this module can\nissue many more commands than minimally necessary; perhaps there should\nbe a --speed-over-safety option.\n\nNo incremental export is supported. VSS' -V~Lfoo option, which says\n"all versions since this label" does not actually cause the C command to emit the indicated checkin. We'll need to make the\nhistory command much smarter to implement that.\n\n=head1 SEE ALSO\n\nL, L, L.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACG #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < to extract files from a\nL repository.\n\nNote that not all metadata is extracted: users, clients and job tracking\ninformation is not exported, and only label names are exported.\n\nAlso, the 'time' and 'mod_time' attributes will lose precision, since\np4 doesn't report them down to the minute. Hmmm, seems like p4 never\nsets a true mod_time. It gets set to either the submit time or the\nsync time. From C:\n\n modtime Causes 'p4 sync' to force modification time \n\x09\x09 to when the file was submitted.\n\n nomodtime * Leaves modification time set to when the\n\x09\x09 file was fetched.\n\n=head1 OPTIONS\n\n=over\n\n=item -b, --bootstrap\n\n -b '...'\n --bootstrap='...'\n -b file1[,file2[,...]]\n --bootstrap=file1[,file2[,...]]\n\n(the C<...> there is three periods, a\nL wildcard borrowed from C\npath syntax).\n\nForces bootstrap mode for an entire export (-b '...') or for certain\nfiles. Filenames may contain wildcards, see L for\ndetails on what wildcards are accepted.\n\nControls how the first revision of a file is exported. A bootstrap\nexport contains the entire contents of the first revision in the revision\nrange. This should only be necessary when exporting for the first time.\n\nAn incremental export contains a digest of the revision preceding the first\nrevision in the revision range, followed by a delta record between that\nrevision and the first revision in the range. This allows the destination\nimport function to make sure that the incremental export begins where the\nlast export left off.\n\nThe default is decided on a per-file basis: if the first revision in the\nrange is revision #1, the full contents are exported. Otherwise an\nincremental export is done for that file.\n\nThis option is necessary when exporting only more recent revisions from\na repository.\n\n=item -r, --rev-root\n\nB.\n\nFalsifies the root of the source tree being extracted; files will\nappear to have been extracted from some place else in the hierarchy.\nThis can be useful when exporting RevML, the RevML file can be made\nto insert the files in to a different place in the eventual destination\nrepository than they existed in the source repository.\n\nThe default C is the file spec up to the first path segment\n(directory name) containing a wildcard, so\n\n p4:/a/b/c...\n\nwould have a rev-root of C.\n\nIn direct repository-to-repository transfers, this option should not be\nnecessary, the destination filespec overrides it.\n\n=back\n\n=head1 METHODS\n\n=over\n\n=cut\n\n\$VERSION = 1.0 ;\n\nuse strict ;\n\nuse Carp ;\nuse Getopt::Long ;\nuse Fcntl qw( O_WRONLY O_CREAT ) ;\nuse VCP::Debug ":debug" ;\nuse Regexp::Shellish qw( :all ) ;\nuse VCP::Rev ;\nuse IPC::Run qw( run io timeout new_chunker ) ;\n\nuse base qw( VCP::Source VCP::Utils::p4 ) ;\nuse fields (\n 'P4_REPO_CLIENT', ## Set by p4_parse_repo_spec in VCP::Utils::p4\n 'P4_INFO', ## Results of the 'p4 info' command\n 'P4_LABEL_CACHE', ## ->{\$name}->{\$rev} is a list of labels for that rev\n# 'P4_LABELS', ## Array of labels from 'p4 labels'\n 'P4_MAX', ## The last change number needed\n 'P4_MIN', ## The first change number needed\n) ;\n\n=item new\n\nCreates a new instance of a VCP::Source::p4. Contacts the p4d using the p4\ncommand and gets some initial information ('p4 info' and 'p4 labels').\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Source::p4 \$self = \$class->SUPER::new( \@_ ) ;\n\n ## Parse the options\n my ( \$spec, \$options ) = \@_ ;\n\n \$self->parse_p4_repo_spec( \$spec ) ;\n\n my \$rev_root ;\n\n GetOptions(\n 'b|bootstrap:s' => sub {\n\x09 my ( \$name, \$val ) = \@_ ;\n\x09 \$self->bootstrap( \$val ) ;\n },\n 'r|rev-root=s' => \\\$rev_root,\n ) or \$self->usage_and_exit ;\n\n\n my \$name = \$self->repo_filespec ;\n unless ( defined \$rev_root ) {\n if ( length \$name >= 2 && substr( \$name, 0, 2 ) ne '//' ) {\n ## No depot on the command line, default it to the only depot\n\x09 ## or error if more than one.\n\x09 my \$depots ;\n\x09 \$self->p4( ['depots'], \\\$depots ) ;\n\x09 \$depots = 'depot' unless length \$depots ;\n\x09 my \@depots = split( /^/m, \$depots ) ;\n\x09 die "vcp: p4 has more than one depot, can't assume //depot/...\\n"\n\x09 if \@depots > 1 ;\n\x09 debug "vcp: defaulting depot to '\$depots[0]'" if debugging \$self ;\n\x09 \$name = join( '/', '/', \$depots[0], \$name ) ;\n }\n \$self->deduce_rev_root( \$name ) ;\n }\n else {\n \$self->rev_root( \$rev_root ) ;\n }\n\n die "no depot name specified for p4 source '\$name'\\n"\n unless \$name =~ m{^//[^/]+/} ;\n \$self->repo_filespec( \$name ) ;\n\n \$self->load_p4_info ;\n \$self->load_p4_labels ;\n\n return \$self ;\n}\n\n\nsub load_p4_info {\n my VCP::Source::p4 \$self = shift ;\n\n my \$errors = '' ;\n \$self->p4( ['info'], \\\$self->{P4_INFO} ) ;\n}\n\n\nsub is_incremental {\n my VCP::Source::p4 \$self= shift ;\n\n my ( \$file, \$first_rev ) = \@_ ;\n\n my \$bootstrap_mode = \$first_rev == 1 || \$self->is_bootstrap_mode( \$file ) ;\n\n return ! \$bootstrap_mode ;\n}\n\n# A typical entry in the filelog looks like\n#-------8<-------8<------\n#//revengine/revml.dtd\n#... #6 change 11 edit on 2000/08/28 by barries\@barries (text)\n#\n# Rev 0.008: Added some modules and tests and fixed lots of bugs.\n#\n#... #5 change 10 edit on 2000/08/09 by barries\@barries (text)\n#\n# Got Dest/cvs working, lots of small changes elsewhere\n#\n#-------8<-------8<------\n# And, from a more tangled source tree, perl itself:\n#-------8<-------8<------\n#... ... branch into //depot/ansiperl/x2p/a2p.h#1\n#... ... ignored //depot/maint-5.004/perl/x2p/a2p.h#1\n#... ... copy into //depot/oneperl/x2p/a2p.h#3\n#... ... copy into //depot/win32/perl/x2p/a2p.h#2\n#... #2 change 18 integrate on 1997/05/25 by mbeattie\@localhost (text)\n#\n# First stab at 5.003 -> 5.004 integration.\n#\n#... ... branch into //depot/lexwarn/perl/x2p/a2p.h#1\n#... ... branch into //depot/oneperl/x2p/a2p.h#1\n#... ... copy from //depot/relperl/x2p/a2p.h#2\n#... ... branch into //depot/win32/perl/x2p/a2p.h#1\n#... #1 change 1 add on 1997/03/28 by mbeattie\@localhost (text)\n#\n# Perl 5.003 check-in\n#\n#... ... branch into //depot/mainline/perl/x2p/a2p.h#1\n#... ... branch into //depot/relperl/x2p/a2p.h#1\n#... ... branch into //depot/thrperl/x2p/a2p.h#1\n#-------8<-------8<------\n#\n# This next regexp is used to parse the lines beginning "... #"\n\nmy \$filelog_rev_info_re = qr{\n \\G # Use with /gc!!\n ^\\.\\.\\.\\s+\n \\#(\\d+)\\s+ # Revision\n change\\s+(\\d+)\\s+ # Change nubmer\n (\\S+)\\s+ # Action\n \\S+\\s+ ### 'on '\n (\\S+)\\s+ # date\n \\S+\\s+ ### 'by '\n (\\S(?:.*?\\S))\\s+ # user id. Undelimited, so hope for best\n \\((\\S+?)\\) # type\n .*\\r?\\n\n}mx ;\n\n# And this one grabs the comment\nmy \$filelog_comment_re = qr{\n \\G\n ^\\r?\\n\n ((?:^[^\\S\\r\\n].*\\r?\\n)*)\n ^\\r?\\n\n}mx ;\n\n\nsub scan_filelog {\n my VCP::Source::p4 \$self = shift ;\n\n my ( \$first_change_id, \$last_change_id ) = \@_ ;\n\n my \$log = '' ;\n\n my \$delta = \$last_change_id - \$first_change_id + 1 ;\n\n my \$spec = join( '', \$self->repo_filespec . '\@' . \$last_change_id ) ;\n my \$temp_f = \$self->command_stderr_filter ;\n \$self->command_stderr_filter(\n qr{//\\S* - no file\\(s\\) at that changelist number\\.\\s*\\r?\\n}\n ) ;\n\n my %oldest_revs ;\n {\n my \$log_state = "need_file" ;\n\n my VCP::Rev \$r ;\n my \$name ;\n my \$comment ;\n\n my \$p4_filelog_parser = sub {\n\x09 local \$_ = shift ;\n\n REDO_LINE:\n\x09 if ( \$log_state eq "need_file" ) {\n\x09 die "\\\$r defined" if defined \$r ;\n\x09 die "vcp: p4 filelog parser: file name expected, got '\$_'"\n\x09 unless m{^//(.*?)\\r?\\n\\r?} ;\n\n\x09 \$name = \$1 ;\n\x09 \$log_state = "revs" ;\n\x09 }\n\x09 elsif ( \$log_state eq "revs" ) {\n\x09 return if m{^\\.\\.\\.\\s+\\.\\.\\..*\\r?\\n\\r?} ;\n\x09 unless ( m{\$filelog_rev_info_re} ) {\n\x09 \$log_state = "need_file" ;\n\x09 goto REDO_LINE ;\n\x09 }\n\n\x09 my \$change_id = \$2 ;\n\x09 if ( \$change_id < \$self->min ) {\n\x09 undef \$r ;\n\x09 \$log_state = "need_comment" ;\n\x09 return ;\n\x09 }\n\n\x09 my \$type = \$6 ;\n\n\x09 my \$norm_name = \$self->normalize_name( \$name ) ;\n\x09 die "\\\$r defined" if defined \$r ;\n\x09 \$r = VCP::Rev->new(\n\x09 name => \$norm_name,\n\x09 rev_id => \$1,\n\x09 change_id => \$change_id,\n\x09 action => \$3,\n\x09 time => \$self->parse_time( \$4 ),\n\x09 user_id => \$5,\n\x09 p4_info => \$_,\n\x09 comment => '',\n\x09 ) ;\n\n\x09 my \$is_binary = \$type =~ /^(?:u?x?binary|x?tempobj|resource)/ ;\n\x09 \$r->type( \$is_binary ? "binary" : "text" ) ;\n\n\x09 \$r->labels( \$self->get_p4_file_labels( \$name, \$r->rev_id ) );\n\n\x09 ## Filelogs are in newest...oldest order, so this should catch\n\x09 ## the oldest revision of each file.\n\x09 \$oldest_revs{\$name} = \$r ;\n\n\x09 \$log_state = "need_comment" ;\n\x09 }\n\x09 elsif ( \$log_state eq "need_comment" ) {\n\x09 unless ( /^\$/ ) {\n\x09 die\n "vcp: p4 filelog parser: expected a blank line before a comment, got '\$_'" ;\n\x09 }\n\x09 \$log_state = "comment_accum" ;\n\x09 }\n\x09 elsif ( \$log_state eq "comment_accum" ) {\n\x09 if ( /^\$/ ) {\n\x09 if ( defined \$r ) {\n\x09\x09 \$r->comment( \$comment ) ;\n\x09\x09 \$self->revs->add( \$r ) ;\n\x09\x09 \$r = undef ;\n\x09 }\n\x09 \$comment = undef ;\n\x09 \$log_state = "revs" ;\n\x09 return ;\n\x09 }\n\x09 unless ( s/^\\s// ) {\n\x09 die "vcp: p4 filelog parser: expected a comment line, got '\$_'" ;\n\x09 }\n\x09 \$comment .= \$_ ;\n\x09 }\n\x09 else {\n\x09 die "unknown log_state '\$log_state'" ;\n\x09 }\n } ;\n\n \$self->p4(\n\x09 [qw( filelog -m ), \$delta, "-l", \$spec ],\n\x09 '>', new_chunker, \$p4_filelog_parser\n ) ;\n \$self->command_stderr_filter( \$temp_f ) ;\n\n die "\\\$r defined" if defined \$r ;\n }\n\n my \@base_rev_specs ;\n for my \$name ( sort keys %oldest_revs ) {\n my \$r = \$oldest_revs{\$name} ;\n my \$rev_id = \$r->rev_id ;\n if ( \$self->is_incremental( "//\$name", \$r->rev_id ) ) {\n\x09 \$rev_id -= 1 ;\n\x09 push \@base_rev_specs, "//\$name#\$rev_id" ;\n }\n else {\n\x09 debug "vcp: bootstrapping '", \$r->name, "#", \$r->rev_id, "'"\n\x09 if debugging \$self ;\n }\n \$oldest_revs{\$name} = undef ;\n }\n\n if ( \@base_rev_specs ) {\n undef \$log ;\n \$self->command_stderr_filter(\n\x09 qr{//\\S* - no file\\(s\\) at that changelist number\\.\\s*\\r?\\n}\n ) ;\n \$self->p4( [qw( filelog -m 1 -l ), \@base_rev_specs ], \\\$log ) ;\n \$self->command_stderr_filter( \$temp_f ) ;\n\n while ( \$log =~ m{\\G(.*?)^//(.*?)\\r?\\n\\r?}gmsc ) {\n\x09 warn "vcp: Ignoring '\$1' in p4 filelog output\\n" if length \$1 ;\n\x09 my \$name = \$2 ;\n\n\x09 my \$norm_name = \$self->normalize_name( \$name ) ;\n\x09 while () {\n\x09 next if \$log =~ m{\\G^\\.\\.\\.\\s+\\.\\.\\..*\\r?\\n\\r?}gmc ;\n\n\x09 last unless \$log =~ m{\$filelog_rev_info_re}gc ;\n\n\x09 my VCP::Rev \$br = VCP::Rev->new(\n\x09 name => \$norm_name,\n\x09 rev_id => \$1,\n\x09 change_id => \$2,\n # Don't send these on a base rev for incremental changes:\n #\x09 action => \$3,\n #\x09 time => \$self->parse_time( \$4 ),\n #\x09 user_id => \$5,\n\x09\x09type => \$6,\n #\x09 comment => '',\n\x09 ) ;\n\n\x09 \$self->revs->add( \$br ) ;\n\n\x09 \$log =~ m{\$filelog_comment_re}gc ;\n\x09 }\n }\n }\n}\n\n\nsub min {\n my VCP::Source::p4 \$self = shift ;\n \$self->{P4_MIN} = shift if \@_ ;\n return \$self->{P4_MIN} ;\n}\n\n\nsub max {\n my VCP::Source::p4 \$self = shift ;\n \$self->{P4_MAX} = shift if \@_ ;\n return \$self->{P4_MAX} ;\n}\n\n\nsub load_p4_labels {\n my VCP::Source::p4 \$self = shift ;\n\n my \$labels = '' ;\n my \$errors = '' ;\n \$self->p4( ['labels'], \\\$labels ) ;\n\n my \@labels = map(\n /^Label\\s*(\\S*)/ ? \$1 : (),\n split( /^/m, \$labels )\n ) ;\n\n \$self->command_ok_result_codes( 0, 1 ) ;\n\n my \$marker = "//.../NtLkly" ;\n my \$p4_files_args =\n join(\n "",\n\x09 ( map {\n\x09 ( "\$marker\\n", "//...\\\@\$_\\n" ) ;\n\x09 } \@labels ),\n ) ;\n my \$files ;\n \$self->p4( [ qw( -x - -s files) ], "<", \\\$p4_files_args, ">", \\\$files ) ;\n\n my \$label ;\n for my \$spec ( split /\\n/m, \$files ) {\n last if \$spec =~ /^exit:/ ;\n if ( \$spec =~ /^error: \$marker/o ) {\n\x09 \$label = shift \@labels ;\n\x09 next ;\n }\n next if \$spec =~ m{^error: //\\.\\.\\.\\\@.+ file(\\(s\\))? not in label.\$} ;\n \$spec =~ /^.*?: *\\/\\/(.*)#(\\d+)/\n\x09 or die "Couldn't parse name & rev from '\$spec' in '\$files'" ;\n\n debug "vcp: p4 label '\$label' => '\$1#\$2'" if debugging \$self ;\n push \@{\$self->{P4_LABEL_CACHE}->{\$1}->{\$2}}, \$label ;\n }\n\n \$self->command_ok_result_codes( 0 ) ;\n\n return ;\n}\n\n\nsub denormalize_name {\n my VCP::Source::p4 \$self = shift ;\n return '//' . \$self->SUPER::denormalize_name( \@_ ) ;\n}\n\n\nsub get_p4_file_labels {\n my VCP::Source::p4 \$self = shift ;\n\n my \$name ;\n my VCP::Rev \$rev ;\n ( \$name, \$rev ) = \@_ ;\n\n return (\n ( exists \$self->{P4_LABEL_CACHE}->{\$name}\n && exists \$self->{P4_LABEL_CACHE}->{\$name}->{\$rev}\n )\n\x09 ? \@{\$self->{P4_LABEL_CACHE}->{\$name}->{\$rev}}\n\x09 : ()\n ) ;\n}\n\n\nmy \$filter_prog = <<'EOPERL' ;\n use strict ;\n my ( \$name, \$working_path ) = ( shift, shift ) ;\n }\nEOPERL\n\n\nsub get_rev {\n my VCP::Source::p4 \$self = shift ;\n\n my VCP::Rev \$r ;\n\n ( \$r ) = \@_ ;\n\n return if defined \$r->action && \$r->action eq "delete" ;\n my \$fn = \$r->name ;\n my \$rev = \$r->rev_id ;\n \$r->work_path( \$self->work_path( \$fn, \$rev ) ) ;\n my \$wp = \$r->work_path ;\n \$self->mkpdir( \$wp ) ;\n\n my \$denormalized_name = \$self->denormalize_name( \$fn ) ;\n my \$rev_spec = "\$denormalized_name#\$rev" ;\n\n sysopen( WP, \$wp, O_CREAT | O_WRONLY )\n or die "\$!: \$wp" ;\n\n binmode WP ;\n\n my \$re = quotemeta( \$rev_spec ) . " - .* change \\\\d+ \\\\((.+)\\\\)";\n\n ## TODO: look for "+x" in the (...) and pass an executable bit\n ## through the rev structure.\n \$self->p4( \n [ "print", \$rev_spec ],\n ">", sub {\n\x09 \$_ = shift ;\n\x09 s/\\A\$re\\r?\\n//m if \$re ;\n\x09 print WP or die "\$! writing to \$wp" ;\n\x09 \$re = undef ;\n },\n ) ;\n\n close WP or die "\$! closing wp" ;\n\n return ;\n}\n\n\nsub handle_header {\n my VCP::Source::p4 \$self = shift ;\n my ( \$header ) = \@_ ;\n\n \$header->{rep_type} = 'p4' ;\n \$header->{rep_desc} = \$self->{P4_INFO} ;\n \$header->{rev_root} = \$self->rev_root ;\n\n \$self->dest->handle_header( \$header ) ;\n return ;\n}\n\n\nsub copy_revs {\n my VCP::Source::p4 \$self = shift ;\n\n \$self->revs( VCP::Revs->new ) ;\n\n \$self->scan_filelog( \$self->min, \$self->max ) ;\n \$self->dest->sort_revs( \$self->revs ) ;\n\n ## Discard the revs so they'll be DESTROYed and thus\n ## clean up after themselves.\n while ( my VCP::Rev \$r = \$self->revs->shift ) {\n \$self->get_rev( \$r ) ;\n \$self->dest->handle_rev( \$r ) ;\n }\n}\n\n=head1 SEE ALSO\n\nL, L.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACH #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <]\n vcp revml[:] --dtd \n\nWhere is a filename for input; or missing or '-' for STDIN.\n\nTo compile a DTD in to a perl module:\n\n revml: --dtd --save-doctype\n\n=head1 DESCRIPTION\n\nThis source driver allows L to read a RevML file.\n\nFor now, all revisions are fully reconstituted in the working\ndirectory in order to make sure that all of the patches apply cleanly.\nThis can require a huge amount of disk space, but it works (optimizing\nthis is on the TODO).\n\n=cut\n\nuse strict ;\n\nuse Carp ;\nuse Fcntl ;\nuse Getopt::Long ;\nuse Digest::MD5 ;\nuse MIME::Base64 ;\nuse RevML::Doctype ;\nuse Symbol ;\nuse UNIVERSAL qw( isa ) ;\nuse XML::Parser ;\nuse Time::Local qw( timegm ) ;\nuse VCP::Debug ':debug' ;\nuse VCP::Patch ;\nuse VCP::Rev ;\n\nuse vars qw( \$VERSION \$debug ) ;\n\n\$VERSION = 0.1 ;\n\n\$debug = 0 ;\n\nuse base 'VCP::Source' ;\n\nuse fields (\n 'COPY_MODE', ## TRUE to do a copy, FALSE if not (like when writing .pm DTD)\n 'DOCTYPE',\n 'HEADER', ## The \$header is held here until the first is read\n 'IN_FH', ## The handle of the input revml file\n 'IN_NAME', ## The name of the input revml file, or '-' for stdout\n 'WORK_NAME', ## The name of the working file (diff or content)\n 'WORK_FH', ## The filehandle of working file\n 'REV', ## The VCP::Rev containing all of this rev's meta info\n 'STACK', ## A stack of currently open elements\n 'UNDECODED_CONTENT', ## Base64 content waiting to be decoded.\n) ;\n\n\n#=item new\n#\n#Creates a new instance. The only parameter is '-dtd', which overrides\n#the default DTD found by searching for modules matching RevML::DTD:v*.pm.\n#\n#Attempts to open the input file if one is specified.\n#\n#If the option '--save-doctype' is passed, then no copying of resources\n#is done (queue_all returns nothing to copy) and the doctype is saved\n#as a .pm file. See L for details.\n#\n#=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Source::revml \$self = \$class->SUPER::new( \@_ ) ;\n\n \$self->{COPY_MODE} = 1 ;\n\n my ( \$spec, \$args ) = \@_ ;\n\n my \$parsed_spec = \$self->parse_repo_spec( \$spec ) ;\n\n my \$save_doctype ;\n {\n local *ARGV = \$args ;\n GetOptions(\n\x09 'dtd|version' => sub {\n\x09 \$self->{DOCTYPE} = RevML::Doctype->new( shift \@\$args ) ;\n\x09 },\n\x09 'save-doctype' => \\\$save_doctype,\n ) or \$self->usage_and_exit ;\n }\n\n \$self->{DOCTYPE} = RevML::Doctype->new\n unless \$self->{DOCTYPE} ;\n\n if ( \$save_doctype ) {\n \$self->{COPY_MODE} = 0 ;\n \$self->{DOCTYPE}->save_as_pm ;\n }\n my \@errors ;\n\n my \$files = \$parsed_spec->{FILES} ;\n\n \$self->{IN_NAME} = defined \$files && length \$files\n ? \$files\n : '-' ;\n\n if ( \$self->{IN_NAME} eq '-' ) {\n \$self->{IN_FH} = \\*STDIN ;\n ## TODO: Check IN_FH for writability when it's set to STDIN\n }\n else {\n require Symbol ;\n \$self->{IN_FH} = Symbol::gensym ;\n open( \$self->{IN_FH}, "<\$self->{IN_NAME}" )\n or die "\$!: \$self->{IN_NAME}\\n" ;\n }\n\n \$self->{WORK_FH} = Symbol::gensym ;\n\n die join( '', \@errors ) if \@errors ;\n\n return \$self ;\n}\n\n\nsub dest_expected {\n my VCP::Source::revml \$self = shift ;\n\n return \$self->{COPY_MODE} ;\n}\n\n\nsub handle_header {\n my VCP::Source::revml \$self = shift ;\n\n ## Save this off until we get our first rev from the input\n \$self->{HEADER} = shift ;\n return ;\n}\n\n\nsub get_rev {\n my VCP::Source::revml \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n}\n\n\nsub copy_revs {\n my VCP::Source::revml \$self = shift ;\n\n \$self->revs( VCP::Revs->new ) ;\n \$self->parse_revml_file ;\n\n \$self->dest->sort_revs( \$self->revs ) ;\n\n my VCP::Rev \$r ;\n while ( \$r = \$self->revs->shift ) {\n \$self->get_rev( \$r ) ;\n \$self->dest->handle_rev( \$r ) ;\n }\n}\n\n\nsub parse_revml_file {\n my VCP::Source::revml \$self = shift ;\n\n my \@stack ;\n \$self->{STACK} = \\\@stack ;\n\n my \$char_handler = sub {\n my \$expat = shift ;\n my \$pelt = \$stack[-1] ; ## parent element\n my \$tag = \$pelt->{NAME} ;\n my \$content = \$pelt->{CONTENT} ;\n if ( defined \$content ) {\n\x09 if ( \@\$content && \$content->[-1]->{TYPE} eq 'PCDATA' ) {\n\x09 \$content->[-1]->{PCDATA} .= \$_[0] ;\n\x09 }\n\x09 else {\n\x09 push \@\$content, { TYPE => 'PCDATA', PCDATA => \$_[0] } ;\n\x09 }\n }\n my \$sub = "\${tag}_characters" ;\n \$self->\$sub( \@_ ) if \$self->can( \$sub ) ;\n } ;\n\n my \$p = XML::Parser->new(\n Handlers => {\n Start => sub {\n\x09 my \$expat = shift ;\n\x09 my \$tag = shift ;\n\n\x09 if ( \$tag eq "char" ) {\n\x09 while ( \@_ ) {\n\x09 my ( \$attr, \$value ) = ( shift, shift ) ;\n#print STDERR \$value, "=" ;\n\x09\x09 if ( \$attr eq "code" ) {\n\x09\x09 if ( \$value =~ s{^0x}{} ) {\n\x09\x09\x09\$value = chr( hex( \$value ) ) ;\n\x09\x09 }\n\x09\x09 else {\n\x09\x09\x09\$value = chr( \$value ) ;\n\x09\x09 }\n#print STDERR ord \$value, "\\n" ;\n\x09\x09 \$char_handler->( \$expat, \$value ) ;\n\x09\x09 }\n\x09 }\n\x09 return ;\n\x09 }\n\n#print STDERR "<\$tag>\\n" ;\n\x09 push \@stack, {\n\x09 NAME => \$tag,\n\x09 ATTRS => {\@_},\n\x09 CONTENT => ! \$self->can( "\${tag}_characters" ) ? [] : undef,\n\x09 } ;\n\x09 my \$sub = "start_\$tag" ;\n\x09 \$self->\$sub( \@_ ) if \$self->can( \$sub ) ;\n\x09 },\n\n\x09 End => sub {\n\x09 my \$expat = shift ;\n\x09 my \$tag = shift ;\n\x09 return if \$tag eq "char" ;\n\n#print STDERR "\\n" ;\n\x09 die "Unexpected , expected \\n"\n\x09 unless \$tag eq \$stack[-1]->{NAME} ;\n\x09 my \$sub = "end_\$tag" ;\n\x09 \$self->\$sub( \@_ ) if \$self->can( \$sub ) ;\n\x09 my \$elt = pop \@stack ;\n\n\x09 if ( \@stack\n\x09 && \$stack[-1]->{NAME} =~ /^rev(ml)?\$/\n\x09 && defined \$elt->{CONTENT}\n\x09 ) {\n#print STDERR "\\n" ;\n\x09 ## Save all the meta fields for start_content() or start_diff()\n\x09 if ( \$tag eq 'label' ) {\n\x09 push \@{\$stack[-1]->{labels}}, \$elt ;\n\x09 }\n\x09 else {\n\x09\x09 \$stack[-1]->{\$tag} = \$elt ;\n\x09 }\n\x09 }\n\x09 },\n\n\x09 Char => \$char_handler,\n },\n ) ;\n \$p->parse( \$self->{IN_FH} ) ;\n}\n\n\nsub start_rev {\n my VCP::Source::revml \$self = shift ;\n\n ## We now have all of the header info parsed, save it off\n\n ## TODO: Demystify this hairy wart. Better yet, simplify all the code\n ## in this module. It needs to decode the fields as they come in and\n ## stick them in the header and the rev_meta \n for ( map(\n \$self->{STACK}->[-2]->{\$_},\n grep /^[a-z_0-9]+\$/, keys %{\$self->{STACK}->[-2]}\n ) ) {\n \$self->{HEADER}->{\$_->{NAME}} = \$_->{CONTENT}->[0]->{PCDATA} ;\n }\n\n ## Make sure no older rev is lying around to confuse us.\n \$self->{REV} = undef ;\n}\n\n## RevML is contstrained so that the diff and content tags are after all of\n## the meta info for a revision. And we really don't want to hold\n## the entire content of a file in memory, in case it's large. So we\n## intercept start_content and start_diff and initialize the REV\n## member as well as opening a place to catch all of the data that gets\n## extracted from the file.\nsub init_rev_meta {\n my VCP::Source::revml \$self = shift ;\n\n my \$rev_elt = \$self->{STACK}->[-2] ;\n my VCP::Rev \$r = VCP::Rev->new() ;\n ## All revml tag naes are lc, all internal data member names are uc\n#require Data::Dumper ; print Data::Dumper::Dumper( \$self->{STACK} ) ;\n\n for ( grep /^[a-z_0-9]+\$/, keys %\$rev_elt ) {\n if ( \$_ eq 'labels' ) {\n \$r->labels(\n\x09 map \$_->{CONTENT}->[0]->{PCDATA}, \@{\$rev_elt->{labels}}\n\x09 ) ;\n }\n else {\n ## We know that all kids *in use today* of are pure PCDATA\n\x09 ## Later, we'll need sub-attributes.\n\x09 ## TODO: Flatten the element tree by preficing attribute names\n\x09 ## with, I dunno, say '_' or by adding '_attr' to them.\n\x09 my \$out_key = \$_ ;\n \$r->\$out_key( \$rev_elt->{\$_}->{CONTENT}->[0]->{PCDATA} ) ;\n }\n }\n#require Data::Dumper ; print Data::Dumper::Dumper( \$r ) ;\n\n \$r->work_path( \$self->work_path( \$r->name, \$r->rev_id ) ) ;\n\n \$self->mkpdir( \$r->work_path ) ;\n\n \$self->{REV} = \$r ;\n return ;\n}\n\n\nsub start_delete {\n my VCP::Source::revml \$self = shift ;\n\n \$self->init_rev_meta ;\n \$self->{REV}->action( "delete" ) ;\n ## Clear the work_path so that VCP::Rev doesn't try to delete it.\n \$self->{REV}->work_path( undef ) ;\n}\n\n\nsub start_move {\n my VCP::Source::revml \$self = shift ;\n\n \$self->init_rev_meta ;\n \$self->{REV}->action( "move" ) ;\n ## Clear the work_path so that VCP::Rev doesn't try to delete it.\n \$self->{REV}->work_path( undef ) ;\n die " unsupported" ;\n}\n\n\nsub start_content {\n my VCP::Source::revml \$self = shift ;\n\n \$self->init_rev_meta ;\n#require Data::Dumper ; print Data::Dumper::Dumper( \$self->{REV} ) ;\n \$self->{REV}->action( "edit" ) ;\n \$self->{WORK_NAME} = \$self->{REV}->work_path ;\n \$self->{UNDECODED_CONTENT} = "" ;\n sysopen \$self->{WORK_FH}, \$self->{WORK_NAME}, O_WRONLY | O_CREAT | O_TRUNC\n or die "\$!: \$self->{WORK_NAME}" ;\n ## The binmode here is to make sure we don't convert \\n to \\r\\n and\n ## to allow ^Z out the door (^Z is EOF on windows, and they take those\n ## things rather more seriously there than on Unix).\n binmode \$self->{WORK_FH};\n}\n\n\nsub content_characters {\n my VCP::Source::revml \$self = shift ;\n if ( \$self->{STACK}->[-1]->{ATTRS}->{encoding} eq "base64" ) {\n \$self->{UNDECODED_CONTENT} .= shift ;\n if ( \$self->{UNDECODED_CONTENT} =~ s{(.*\\n)}{} ) {\n\x09 syswrite( \$self->{WORK_FH}, decode_base64( \$1 ) )\n\x09 or die "\$! writing \$self->{WORK_NAME}" ;\n }\n }\n elsif ( \$self->{STACK}->[-1]->{ATTRS}->{encoding} eq "none" ) {\n# print STDERR map( sprintf( " %02x=\$_", ord ), \$_[0] =~ m/(.)/gs ), "\\n" ;\n syswrite \$self->{WORK_FH}, \$_[0]\n or die "\$! writing \$self->{WORK_NAME}" ;\n }\n else {\n die "vcp: unknown encoding '\$self->{STACK}->[-1]->{ATTRS}->{encoding}'\\n";\n }\n return ;\n}\n\nsub end_content {\n my VCP::Source::revml \$self = shift ;\n \n if ( length \$self->{UNDECODED_CONTENT} ) {\n syswrite( \$self->{WORK_FH}, decode_base64( \$self->{UNDECODED_CONTENT} ) )\n\x09 or die "\$! writing \$self->{WORK_NAME}" ;\n }\n close \$self->{WORK_FH} or die "\$! closing \$self->{WORK_NAME}" ;\n\n if ( \$self->none_seen ) {\n#require Data::Dumper ; print Data::Dumper::Dumper( \$self->{HEADER} ) ;\n \$self->dest->handle_header( \$self->{HEADER} ) ;\n }\n\n \$self->seen( \$self->{REV} ) ;\n}\n\nsub start_delta {\n my VCP::Source::revml \$self = shift ;\n\n \$self->init_rev_meta ;\n my \$r = \$self->{REV} ;\n \$r->action( 'edit' ) ;\n \$self->{WORK_NAME} = \$self->work_path( \$r->name, 'delta' ) ;\n sysopen \$self->{WORK_FH}, \$self->{WORK_NAME}, O_WRONLY | O_CREAT | O_TRUNC\n or die "\$!: \$self->{WORK_NAME}" ;\n ## See comment in start_content :)\n binmode \$self->{WORK_FH};\n}\n\n\n## TODO: Could keep deltas in memory if they're small.\n*delta_characters = \\&content_characters ;\n## grumble...name used once warning...grumble\n*delta_characters = \\&content_characters ;\n\nsub end_delta {\n my VCP::Source::revml \$self = shift ;\n\n close \$self->{WORK_FH} or die "\$! closing \$self->{WORK_NAME}" ;\n\n#print STDERR `hexdump -cx \$self->{WORK_NAME}` ;\n\n my VCP::Rev \$r = \$self->{REV} ;\n\n ## Delay sending handle_header to dest until patch succeeds.\n my \$is_first = \$self->none_seen ;\n\n my VCP::Rev \$saw = \$self->seen( \$r ) ;\n\n die "No original content to patch for ", \$r->name, ",",\n " revision ", \$r->rev_id\n unless defined \$saw ;\n\n if ( -s \$self->{WORK_NAME} ) {\n ## source fn, result fn, patch fn\n vcp_patch( \$saw->work_path, \$r->work_path, \$self->{WORK_NAME} );\n unless ( \$ENV{VCPNODELETE} ) {\n unlink \$self->{WORK_NAME} or warn "\$! unlinking \$self->{WORK_NAME}\\n" ;\n }\n }\n else {\n ## TODO: Don't assume working link()\n debug "vcp: linking ", \$saw->work_path, ", ", \$r->work_path\n if debugging \$self ;\n\n link \$saw->work_path, \$r->work_path\n or die "vcp: \$! linking ", \$saw->work_path, ", ", \$r->work_path\n }\n\n if ( \$is_first ) {\n#require Data::Dumper ; print Data::Dumper::Dumper( \$self->{HEADER} ) ;\n \$self->dest->handle_header( \$self->{HEADER} ) ;\n }\n\n}\n\n\n## Convert ISO8601 UTC time to local time since the epoch\nsub end_time {\n my VCP::Source::revml \$self = shift ;\n\n my \$timestr = \$self->{STACK}->[-1]->{CONTENT}->[0]->{PCDATA} ;\n ## TODO: Get parser context here & give file, line, and column. filename\n ## and rev, while we're scheduling more work for the future.\n confess "Malformed time value \$timestr\\n"\n unless \$timestr =~ /^\\d\\d\\d\\d(\\D\\d\\d){5}/ ;\n confess "Non-UTC time value \$timestr\\n" unless substr \$timestr, -1 eq 'Z' ;\n my \@f = split( /\\D/, \$timestr ) ;\n --\$f[1] ; # Month of year needs to be 0..11\n \$self->{STACK}->[-1]->{CONTENT}->[0]->{PCDATA} = timegm( reverse \@f ) ;\n}\n\n# double assign => avoid used once warning\n*end_mod_time = *end_mod_time = \\&end_time ;\n\n\n## TODO: Verify that we should be using a Base64 encoded MD5 digest,\n## according to 's attributes. Oh, and same goes for 's\n## encoding.\n\n## TODO: workaround backfilling if the destination is revml, since\n## it can't put the original content in place. We'll need to flag\n## some kind of special pass-through mode for that.\n\nsub end_digest {\n my VCP::Source::revml \$self = shift ;\n\n \$self->init_rev_meta unless defined \$self->{REV} ;\n my \$r = \$self->{REV} ;\n\n my \$original_digest = \$self->{STACK}->[-1]->{CONTENT}->[0]->{PCDATA} ;\n my \$d = Digest::MD5->new() ;\n\n if ( \$r->is_base_rev ) {\n \$self->dest->handle_header( \$self->{HEADER} ) if \$self->none_seen ;\n\n ## Don't bother checking the digest if the destination returns\n ## FALSE, meaning that a backfill is not possible with that destination.\n ## VCP::Dest::revml does this.\n return unless \$self->dest->backfill( \$r ) ;\n my VCP::Rev \$saw = \$self->seen( \$r ) ;\n warn "I've seen ", \$r->name, " before" if \$saw ;\n }\n my \$work_path = \$r->work_path ;\n\n sysopen F, \$work_path, O_RDONLY\n or die "vcp: \$! opening '\$work_path' for digestion\\n" ;\n ## See comment for binmode in start_content :)\n binmode F;\n \$d->addfile( \\*F ) ;\n close F ;\n my \$reconstituted_digest = \$d->b64digest ;\n\n ## TODO: provide an option to turn this in to a warning\n ## TODO: make this abort writing anything to the dest, but continue\n ## processing, so as to deliver as many error messages as possible.\n unless ( \$original_digest eq \$reconstituted_digest ) {\n my \$reject_file_name = \$r->name ;\n \$reject_file_name =~ s{[^A-Za-z0-9 -.]+}{-}g ;\n \$reject_file_name =~ s{^-+}{}g ;\n my \$reject_file_path = File::Spec->catfile(\n File::Spec->tmpdir,\n\x09 \$reject_file_name\n ) ;\n\n link \$work_path, \$reject_file_path \n or die "vcp: digest check failed for ", \$r->as_string,\n\x09 "\\nvcp: failed to leave copy in '\$reject_file_path': \$!\\n" ;\n\n die "vcp: digest check failed for ", \$r->as_string,\n\x09 "\\nvcp: copy left in '\$reject_file_path'\\n",\n "got digest: \$reconstituted_digest\\n",\n "expected digest: \$original_digest\\n";\n }\n}\n\n\n## Having this and no sub rev_characters causes the parser to accumulate\n## content.\nsub end_rev {\n my VCP::Source::revml \$self = shift ;\n\n \$self->revs->add( \$self->{REV} ) unless \$self->{REV}->is_base_rev ;\n\n ## Release this rev.\n \$self->{REV} = undef ;\n}\n\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAACI #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <=2000-11-18 5:26:30" \n # All file revs newer than a date/time\n\n vcp cvs:module/... -r foo # all files in module and below labelled foo\n vcp cvs:module/... -r foo: # All revs of files labelled foo and newer,\n # including files not tagged with foo.\n vcp cvs:module/... -r 1.1:1.10 # revs 1.1..1.10\n vcp cvs:module/... -r 1.1: # revs 1.1 and up\n\n ## NOTE: Unlike cvs, vcp requires spaces after option letters.\n\n=head1 DESCRIPTION\n\nSource driver enabling L|vcp> to extract versions form a cvs\nrepository.\n\nThis doesn't deal with branches yet (at least not intentionally). That\nwill require a bit of Deep Thought.\n\nThe source specification for CVS looks like:\n\n cvs:cvsroot/filespec []\n\nwhere the C is passed to C with the C<-d> option if\nprovided (C is optional if the environment variable C\nis set) and the filespec and EoptionsE determine what revisions\nto extract.\n\nC may contain trailing wildcards, like C to extract\nan entire directory tree.\n\n=head1 OPTIONS\n\n=over\n\n=item -b, --bootstrap\n\n -b ...\n --bootstrap=...\n -b file1[,file2[, etc.]]\n --bootstrap=file1[,file2[, etc. ]]\n\n(the C<...> there is three periods, a\nL wildcard borrowed from C\npath syntax).\n\nForces bootstrap mode for an entire export (C<-b ...>) or for certain\nfiles. Filenames may contain wildcards, see L for\ndetails on what wildcards are accepted.\n\nControls how the first revision of a file is exported. A bootstrap\nexport contains the entire contents of the first revision in the\nrevision range. This should only be necessary when exporting for the\nfirst time.\n\nAn incremental export contains a digest of the revision preceding the\nfirst revision in the revision range, followed by a delta record between\nthat revision and the first revision in the range. This allows the\ndestination import function to make sure that the incremental export\nbegins where the last export left off.\n\nThe default is decided on a per-file basis: if the first revision in the\nrange is revision #.1, the full contents are exported. Otherwise an\nincremental export is done for that file.\n\nThis option is necessary when exporting only more recent revisions from\na repository.\n\n=item --cd\n\nUsed to set the CVS working directory. VCP::Source::cvs will cd to this\ndirectory before calling cvs, and won't initialize a CVS workspace of\nit's own (normally, VCP::Source::cvs does a "cvs checkout" in a\ntemporary directory).\n\nThis is an advanced option that allows you to use a CVS workspace you\nestablish instead of letting vcp create one in a temporary directory\nsomewhere. This is useful if you want to read from a CVS branch or if\nyou want to delete some files or subdirectories in the workspace.\n\nIf this option is a relative directory, then it is treated as relative\nto the current directory.\n\n=item -kb, -k b\n\nPass the -kb option to cvs, forces a binary checkout. This is\nuseful when you want a text file to be checked out with Unix linends,\nor if you know that some files in the repository are not flagged as\nbinary files and should be.\n\n=item --rev-root\n\nB.\n\nFalsifies the root of the source tree being extracted; files will\nappear to have been extracted from some place else in the hierarchy.\nThis can be useful when exporting RevML, the RevML file can be made\nto insert the files in to a different place in the eventual destination\nrepository than they existed in the source repository.\n\nThe default C is the file spec up to the first path segment\n(directory name) containing a wildcard, so\n\n cvs:/a/b/c...\n\nwould have a rev-root of C.\n\nIn direct repository-to-repository transfers, this option should not be\nnecessary, the destination filespec overrides it.\n\n=item -r\n\n -r v_0_001:v_0_002\n -r v_0_002:\n\nPassed to C as a C<-r> revision specification. This corresponds\nto the C<-r> option for the rlog command, not either of the C<-r>\noptions for the cvs command. Yes, it's confusing, but 'cvs log' calls\n'rlog' and passes the options through.\n\nIMPORTANT: When using tags to specify CVS file revisions, it would ordinarily\nbe the case that a number of unwanted revisions would be selected. This is\nbecause the behavior of the cvs log command dumps the entire log history for\nany files that do not contain the tag. VCP captures the histories of such files\nand ignores all revisions that are older or newer than any files that match the\ntags.\n\nBe cautious using HEAD as the end of a revision range, this seems to cause the\ndelete actions for files deleted in the last revision to not be noticed. Not\nsure why.\n\nOne of C<-r> or L|-d> must be specified.\n\n=item C<-d>\n\n -d "2000-11-18 5:26:30<="\n\nPassed to 'cvs log' as a C<-d> date specification. \n\nWARNING: if this string doesn't contain a '>' or '<', you're probably doing\nsomething wrong, since you're not specifying a range. vcp may warn about this\nin the future.\n\nOne of L|-r> or C<-d> must be specified.\n\n=back\n\n=head2 Files that aren't tagged\n\nCVS has one peculiarity that this driver works around.\n\nIf a file does not contain the tag(s) used to select the source files,\nC outputs the entire life history of that file. We don't want\nto capture the entire history of such files, so L goes\nignores any revisions before and after the oldest and newest tagged file\nin the range.\n\n=head1 LIMITATIONS\n\n "What we have here is a failure to communicate!"\n - The warden in Cool Hand Luke\n\nCVS does not try to protect itself from people checking in things that\nlook like snippets of CVS log file: they come out exactly like they\nwent in, confusing the log file parser.\n\nSo, if a repository contains messages in the log file that look like the \noutput from some other "cvs log" command, things will likely go awry.\n\nAt least one cvs repository out there has multiple revisions of a single file\nwith the same rev number. The second and later revisions with the same rev\nnumber are ignored with a warning like "Can't add same revision twice:...".\n\n=cut\n\n\$VERSION = 1.2 ;\n\n# Removed docs for -f, since I now think it's overcomplicating things...\n#Without a -f This will normally only replicate files which are tagged. This\n#means that files that have been added since, or which are missing the tag for\n#some reason, are ignored.\n#\n#Use the L option to force files that don't contain the tag to be\n#=item -f\n#\n#This option causes vcp to attempt to export files that don't contain a\n#particular tag but which occur in the date range spanned by the revisions\n#specified with -r. The typical use is to get all files from a certain\n#tag to now.\n#\n#It does this by exporting all revisions of files between the oldest and\n#newest files that the -r specified. Without C<-f>, these would\n#be ignored.\n#\n#It is an error to specify C<-f> without C<-r>.\n#\n#exported.\n\nuse strict ;\n\nuse Carp ;\nuse Getopt::Long ;\nuse Regexp::Shellish qw( :all ) ;\nuse VCP::Rev ;\nuse VCP::Debug ':debug' ;\nuse VCP::Source ;\nuse VCP::Utils::cvs ;\n\nuse base qw( VCP::Source VCP::Utils::cvs ) ;\nuse fields (\n 'CVS_CUR', ## The current change number being processed\n 'CVS_BOOTSTRAP', ## Forces bootstrap mode\n 'CVS_IS_INCREMENTAL', ## Hash of filenames, 0->bootstrap, 1->incremental\n 'CVS_INFO', ## Results of the 'cvs --version' command and CVSROOT\n 'CVS_LABEL_CACHE', ## ->{\$name}->{\$rev} is a list of labels for that rev\n 'CVS_LABELS', ## Array of labels from 'p4 labels'\n 'CVS_MAX', ## The last change number needed\n 'CVS_MIN', ## The first change number needed\n 'CVS_REV_SPEC', ## The revision spec to pass to `cvs log`\n 'CVS_DATE_SPEC', ## The date spec to pass to `cvs log`\n 'CVS_FORCE_MISSING', ## Set if -r was specified.\n\n 'CVS_K_OPTION', ## Which of the CVS/RCS "-k" options to use, if any\n\n 'CVS_LOG_CARRYOVER', ## The unparsed bit of the log file\n 'CVS_LOG_FILE_DATA', ## Data about all revs of a file from the log file\n 'CVS_LOG_STATE', ## Parser state machine state\n 'CVS_LOG_REV', ## The revision being parsed (a hash)\n\n 'CVS_NAME_REP_NAME', ## A mapping of repository names to names, used to\n ## figure out what files to ignore when a cvs log\n\x09\x09\x09 ## goes ahead and logs a file which doesn't match\n\x09\x09\x09 ## the revisions we asked for.\n\n 'CVS_NEEDS_BASE_REV', ## What base revisions are needed. Base revs are\n ## needed for incremental (ie non-bootstrap) updates,\n\x09\x09\x09 ## which is decided on a per-file basis by looking\n\x09\x09\x09 ## at VCP::Source::is_bootstrap_mode( \$file ) and\n\x09\x09\x09 ## the file's rev number (ie does it end in .1).\n 'CVS_SAW_EQUALS', ## Set when we see the ==== lines in log file [1]\n) ;\n\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Source::cvs \$self = \$class->SUPER::new( \@_ ) ;\n\n ## Parse the options\n my ( \$spec, \$options ) = \@_ ;\n\n \$self->parse_repo_spec( \$spec ) ;\n\n my \$work_dir ;\n my \$rev_root ;\n my \$rev_spec ;\n my \$date_spec ;\n # my \$force_missing ;\n\n GetOptions(\n "b|bootstrap:s" => sub {\n\x09 my ( \$name, \$val ) = \@_ ;\n\x09 \$self->{CVS_BOOTSTRAP} = \$val eq ""\n\x09 ? [ compile_shellish( "..." ) ]\n\x09 : [ map compile_shellish( \$_ ), split /,+/, \$val ] ;\n },\n "cd=s" => \\\$work_dir,\n "rev-root=s" => \\\$rev_root,\n "r=s" => \\\$rev_spec,\n "d=s" => \\\$date_spec,\n "k=s" => sub { warn \$self->{CVS_K_OPTION} = \$_[1] } ,\n "kb" => sub { warn \$self->{CVS_K_OPTION} = "b" } ,\n# "f+" => \\\$force_missing,\n ) or \$self->usage_and_exit ;\n\n unless ( defined \$rev_spec || defined \$date_spec ) {\n print STDERR "Revision (-r) or date (-d) specification missing\\n" ;\n \$self->usage_and_exit ;\n }\n\n# if ( \$force_missing && ! defined \$rev_spec ) {\n# print STDERR\n# "Force missing (-f) may not be used without a revision spec (-r)\\n" ;\n# \$self->usage_and_exit ;\n# }\n#\n my \$files = \$self->repo_filespec ;\n unless ( defined \$rev_root ) {\n \$self->deduce_rev_root( \$files ) ;\n }\n# else {\n# \$files = "\$rev_root/\$files" ;\n# }\n#\n### TODO: Figure out whether we should make rev_root merely set the rev_root\n### in the header. I think we probably should do it that way, as it's more\n### flexible and less confusing.\n\n my \$recurse = \$files =~ s{/\\.\\.\\.\$}{} ;\n\n ## Don't normalize the filespec.\n \$self->repo_filespec( \$files ) ;\n\n \$self->rev_spec( \$rev_spec ) ;\n \$self->date_spec( \$date_spec ) ;\n \$self->force_missing( defined \$rev_spec ) ;\n# \$self->force_missing( \$force_missing ) ;\n\n ## Make sure the cvs command is available\n \$self->command_stderr_filter(\n qr{^\n (?:cvs\\s\n (?:\n (?:server|add|remove):\\suse\\s'cvs\\scommit'\\sto.*\n |tag.*(?:waiting for.*lock|obtained_lock).*\n )\n )\\n\n }x\n ) ;\n\n ## Doing a CVS command or two here also forces cvs to be found in new(),\n ## or an exception will be thrown.\n \$self->cvs( ['--version' ], \\\$self->{CVS_INFO} ) ;\n\n ## This does a checkout, so we'll blow up quickly if there's a problem.\n unless ( defined \$work_dir ) {\n \$self->create_cvs_workspace ;\n }\n else {\n \$self->work_root( File::Spec->rel2abs( \$work_dir ) ) ; \n \$self->command_chdir( \$self->work_path ) ;\n }\n\n return \$self ;\n}\n\n\nsub is_incremental {\n my VCP::Source::cvs \$self= shift ;\n my ( \$file, \$first_rev ) = \@_ ;\n\n my \$bootstrap_mode = substr( \$first_rev, -2 ) eq ".1"\n || ( \$self->{CVS_BOOTSTRAP}\n && grep \$file =~ \$_, \@{\$self->{CVS_BOOTSTRAP}}\n ) ;\n\n return \$bootstrap_mode ? 0 : "incremental" ;\n}\n\n\nsub rev_spec {\n my VCP::Source::cvs \$self = shift ;\n \$self->{CVS_REV_SPEC} = shift if \@_ ;\n return \$self->{CVS_REV_SPEC} ;\n}\n\n\nsub rev_spec_cvs_option {\n my VCP::Source::cvs \$self = shift ;\n return defined \$self->rev_spec? "-r" . \$self->rev_spec : (),\n}\n\n\nsub date_spec {\n my VCP::Source::cvs \$self = shift ;\n \$self->{CVS_DATE_SPEC} = shift if \@_ ;\n return \$self->{CVS_DATE_SPEC} ;\n}\n\n\nsub date_spec_cvs_option {\n my VCP::Source::cvs \$self = shift ;\n return defined \$self->date_spec ? "-d" . \$self->date_spec : (),\n}\n\n\nsub force_missing {\n my VCP::Source::cvs \$self = shift ;\n \$self->{CVS_FORCE_MISSING} = shift if \@_ ;\n return \$self->{CVS_FORCE_MISSING} ;\n}\n\n\nsub denormalize_name {\n my VCP::Source::cvs \$self = shift ;\n return '/' . \$self->SUPER::denormalize_name( \@_ ) ;\n}\n\n\nsub handle_header {\n my VCP::Source::cvs \$self = shift ;\n my ( \$header ) = \@_ ;\n\n \$header->{rep_type} = 'cvs' ;\n \$header->{rep_desc} = \$self->{CVS_INFO} ;\n \$header->{rev_root} = \$self->rev_root ;\n\n \$self->dest->handle_header( \$header ) ;\n return ;\n}\n\n\nsub get_rev {\n my VCP::Source::cvs \$self = shift ;\n\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n my \$wp = \$self->work_path( "revs", \$r->name, \$r->rev_id ) ;\n \$r->work_path( \$wp ) ;\n \$self->mkpdir( \$wp ) ;\n\n \$self->cvs( [\n\x09 "checkout",\n\x09 "-r" . \$r->rev_id,\n\x09 "-p",\n\x09 \$r->source_name,\n ],\n '>', \$wp,\n ) ;\n}\n\n\nsub copy_revs {\n my VCP::Source::cvs \$self = shift ;\n\n \$self->{CVS_LOG_STATE} = '' ;\n \$self->{CVS_LOG_CARRYOVER} = '' ;\n \$self->revs( VCP::Revs->new ) ;\n\n ## We need to watch STDERR for messages like\n ## cvs log: warning: no revision `ch_3' in `/home/barries/src/revengine/tmp/cvsroot/foo/add/f4,v'\n ## Files that cause this warning need to have some revisions ignored because\n ## cvs log will emit the entire log for these files in addition to \n ## the warning, including revisions checked in before the first tag and\n ## after the last tag.\n my \$tmp_f = \$self->command_stderr_filter ;\n my %ignore_files ;\n my \$ignore_file = sub {\n exists \$ignore_files{\$self->{CVS_NAME_REP_NAME}->{\$_[0]}} ;\n } ;\n\n ## This regexp needs to gobble newlines.\n \$self->command_stderr_filter( sub {\n my ( \$err_text_ref ) = \@_ ;\n \$\$err_text_ref =~ s\@\n ^cvs(?:\\.exe)?\\slog:\\swarning:\\sno\\srevision\\s.*?\\sin\\s[`"'](.*)[`"']\\r?\\n\\r?\n \@\n \$ignore_files{\$1} = undef ;\n\x09 '' ;\n \@gxmei ;\n } ) ; ## `\n\n \$self->{CVS_LOG_FILE_DATA} = {} ;\n \$self->{CVS_LOG_REV} = {} ;\n \$self->{CVS_SAW_EQUALS} = 0 ;\n # The log command must be run in the directory above the work root,\n # since we pass in the name of the workroot dir as the first dir in\n # the filespec.\n my \$tmp_command_chdir = \$self->command_chdir ;\n \$self->command_chdir( \$self->tmp_dir( "co" ) ) ;\n \$self->cvs( [\n "log",\n\x09 \$self->rev_spec_cvs_option,\n\x09 \$self->date_spec_cvs_option,\n\x09 length \$self->repo_filespec ? \$self->repo_filespec : (),\n ],\n '>', sub { \$self->parse_log_file( \@_ ) },\n ) ;\n\n \$self->command_chdir( \$tmp_command_chdir ) ;\n \$self->command_stderr_filter( \$tmp_f ) ;\n\n my \$revs = \$self->revs ;\n\n ## Figure out the time stamp range for force_missing calcs.\n my ( \$min_rev_spec_time, \$max_rev_spec_time ) ;\n if ( \$self->force_missing ) {\n ## If the rev_spec is /:\$/ || /^:/, we tweak the range ends.\n my \$max_time = 0 ;\n \$max_rev_spec_time = 0 ;\n \$min_rev_spec_time = 0 if substr( \$self->rev_spec, 0, 1 ) eq ':' ;\n for my \$r ( \@{\$revs->as_array_ref} ) {\n next if \$r->is_base_rev ;\n my \$t = \$r->time ;\n \$max_time = \$t if \$t >= \$max_rev_spec_time ;\n\x09 next if \$ignore_file->( \$r->source_name ) ;\n \$min_rev_spec_time = \$t if \$t <= ( \$min_rev_spec_time || \$t ) ;\n \$max_rev_spec_time = \$t if \$t >= \$max_rev_spec_time ;\n }\n# \$max_rev_spec_time = \$max_time if substr( \$self->rev_spec, -1 ) eq ':' ;\n \$max_rev_spec_time = undef if substr( \$self->rev_spec, -1 ) eq ':' ;\n\n debug(\n\x09 "vcp: including files in ['",\n\x09 localtime( \$min_rev_spec_time ),\n\x09 "'..'",\n\x09 defined \$max_rev_spec_time\n\x09 ? localtime( \$max_rev_spec_time )\n\x09 : "",\n\x09 "']"\n ) if debugging \$self ;\n }\n\n ## Remove extra revs from queue by copying from \$revs to \$self->revs().\n ## TODO: Debug simultaneous use of -r and -d, since we probably are\n ## blowing away revs that -d included that -r didn't. I haven't\n ## checked to see if we do or don't blow said revs away.\n my %oldest_revs ;\n \$self->revs( VCP::Revs->new ) ;\n for my \$r ( \@{\$revs->as_array_ref} ) {\n if ( \$ignore_file->( \$r->source_name ) ) {\n\x09 if (\n\x09 (!defined \$min_rev_spec_time || \$r->time >= \$min_rev_spec_time)\n\x09 && (!defined \$max_rev_spec_time || \$r->time <= \$max_rev_spec_time)\n\x09 ) {\n\x09 debug(\n\x09 "vcp: including file ", \$r->as_string\n\x09 ) if debugging \$self ;\n\x09 }\n\x09 else {\n\x09 debug(\n\x09 "vcp: ignoring file ", \$r->as_string,\n\x09 ": no revisions match -r"\n\x09 ) if debugging \$self ;\n\x09 next ;\n\x09 }\n }\n ## Because of the order of the log file, the last rev set is always\n ## the first rev in the range.\n \$oldest_revs{\$r->source_name} = \$r ;\n \$self->revs->add( \$r ) ;\n }\n \$revs = \$self->revs ;\n\n ## Add in base revs\n for my \$fn ( keys %oldest_revs ) {\n my \$r = \$oldest_revs{\$fn} ;\n my \$rev_id = \$r->rev_id ;\n if ( \$self->is_incremental( \$fn, \$rev_id ) ) {\n\x09 \$rev_id =~ s{(\\d+)\$}{\$1-1}e ;\n \$revs->add(\n\x09 VCP::Rev->new(\n\x09 source_name => \$r->source_name,\n\x09 name => \$r->name,\n\x09 rev_id => \$rev_id,\n\x09 type => \$r->type,\n\x09 )\n\x09 )\n }\n }\n\n \$self->dest->sort_revs( \$self->revs ) ;\n\n my VCP::Rev \$r ;\n while ( \$r = \$self->revs->shift ) {\n \$self->get_rev( \$r ) ;\n \$self->dest->handle_rev( \$r ) ;\n }\n}\n\n\n# Here's a typical file log entry.\n#\n###############################################################################\n#\n#RCS file: /var/cvs/cvsroot/src/Eesh/Changes,v\n#Working file: src/Eesh/Changes\n#head: 1.3\n#branch:\n#locks: strict\n#access list:\n#symbolic names:\n# Eesh_003_000: 1.3\n# Eesh_002_000: 1.2\n# Eesh_000_002: 1.1\n#keyword substitution: kv\n#total revisions: 3; selected revisions: 3\n#description:\n#----------------------------\n#revision 1.3\n#date: 2000/04/22 05:35:27; author: barries; state: Exp; lines: +5 -0\n#*** empty log message ***\n#----------------------------\n#revision 1.2\n#date: 2000/04/21 17:32:14; author: barries; state: Exp; lines: +22 -0\n#Moved a bunch of code from eesh, then deleted most of it.\n#----------------------------\n#revision 1.1\n#date: 2000/03/24 14:54:10; author: barries; state: Exp;\n#*** empty log message ***\n#=============================================================================\n###############################################################################\n\nsub parse_log_file {\n my ( \$self, \$input ) = \@_ ;\n\n if ( defined \$input ) {\n \$self->{CVS_LOG_CARRYOVER} .= \$input ;\n }\n else {\n ## There can only be leftovers if they don't end in a "\\n". I've never\n ## seen that happen, but given large comments, I could be surprised...\n \$self->{CVS_LOG_CARRYOVER} .= "\\n" if length \$self->{CVS_LOG_CARRYOVER} ;\n }\n\n my \$store_rev = sub {\n# my ( \$is_oldest ) = \@_ ;\n return unless keys %{\$self->{CVS_LOG_REV}} ;\n\n \$self->{CVS_LOG_REV}->{MESSAGE} = ''\n if \$self->{CVS_LOG_REV}->{MESSAGE} eq '*** empty log message ***' ;\n\n \$self->{CVS_LOG_REV}->{MESSAGE} =~ s/\\r\\n|\\n\\r/\\n/g ;\n\n#debug map "\$_ => \$self->{CVS_LOG_FILE_DATA}->{\$_},", sort keys %{\$self->{CVS_LOG_FILE_DATA}} ;\n \$self->_add_rev( \$self->{CVS_LOG_FILE_DATA}, \$self->{CVS_LOG_REV} ) ;\n\n# if ( \$is_oldest ) {\n# if ( \n#\x09 \$self->is_incremental(\n#\x09 \$self->{CVS_LOG_FILE_DATA}->{WORKING},\n#\x09 \$self->{CVS_LOG_REV}->{REV}\n#\x09 )\n#\x09 ) {\n#\x09 \$self->{CVS_LOG_REV}->{REV} =~ s{(\\d+)\$}{\$1-1}e ;\n#\n#\x09 \$self->_add_rev(\n#\x09 \$self->{CVS_LOG_FILE_DATA},\n#\x09 \$self->{CVS_LOG_REV},\n#\x09 "is base rev"\n#\x09 );\n#\x09 }\n# }\n \$self->{CVS_LOG_REV} = {} ;\n } ;\n\n local \$_ ;\n\n ## DOS, Unix, Mac lineends spoken here.\n while ( \$self->{CVS_LOG_CARRYOVER} =~ s/^(.*(?:\\r\\n|\\n\\r|\\n))// ) {\n \$_ = \$1 ;\n\n ## [1] See bottom of file for a footnote explaining this delaying of \n ## clearing CVS_LOG_FILE_DATA and CVS_LOG_STATE until we see\n ## a ========= line followed by something other than a -----------\n ## line.\n ## TODO: Move to a state machine design, hoping that all versions\n ## of CVS emit similar enough output to not trip it up.\n\n ## TODO: BUG: Turns out that some CVS-philes like to put text\n ## snippets in their revision messages that mimic the equals lines\n ## and dash lines that CVS uses for delimiters!!\n\n PLEASE_TRY_AGAIN:\n if ( /^===========================================================*\$/ ) {\n \$store_rev->() ;# "is oldest" ) ;\n\x09 \$self->{CVS_SAW_EQUALS} = 1 ;\n\x09 next ;\n }\n\n if ( /^----------------------------*\$/ ) {\n \$store_rev->() unless \$self->{CVS_SAW_EQUALS} ;\n\x09 \$self->{CVS_SAW_EQUALS} = 0 ;\n\x09 \$self->{CVS_LOG_STATE} = 'rev' ;\n\x09 next ;\n }\n\n if ( \$self->{CVS_SAW_EQUALS} ) {\n\x09 \$self->{CVS_LOG_FILE_DATA} = {} ;\n\x09 \$self->{CVS_LOG_STATE} = '' ;\n\x09 \$self->{CVS_SAW_EQUALS} = 0 ;\n }\n\n unless ( \$self->{CVS_LOG_STATE} ) {\n\x09 if (\n\x09 /^(RCS file|Working file|head|branch|locks|access list|keyword substitution):\\s*(.*)/i\n\x09 ) {\n#warn uc( (split /\\s+/, \$1 )[0] ), "/", \$1, ": ", \$2, "\\n" ;\n\x09 \$self->{CVS_LOG_FILE_DATA}->{uc( (split /\\s+/, \$1 )[0] )} = \$2 ;\n#\$DB::single = 1 if /keyword/ && \$self->{CVS_LOG_FILE_DATA}->{WORKING} =~ /Makefile/ ;\n\x09 }\n\x09 elsif ( /^total revisions:\\s*([^;]*)/i ) {\n\x09 \$self->{CVS_LOG_FILE_DATA}->{TOTAL} = \$1 ;\n\x09 if ( /selected revisions:\\s*(.*)/i ) {\n\x09 \$self->{CVS_LOG_FILE_DATA}->{SELECTED} = \$1 ;\n\x09 }\n\x09 }\n\x09 elsif ( /^symbolic names:/i ) {\n\x09 \$self->{CVS_LOG_STATE} = 'tags' ;\n\x09 next ;\n\x09 }\n\x09 elsif ( /^description:/i ) {\n\x09 \$self->{CVS_LOG_STATE} = 'desc' ;\n\x09 next ;\n\x09 }\n\x09 else {\n\x09 carp "Unhandled CVS log line '\$_'" if /\\S/ ;\n\x09 }\n }\n elsif ( \$self->{CVS_LOG_STATE} eq 'tags' ) {\n\x09 if ( /^\\S/ ) {\n\x09 \$self->{CVS_LOG_STATE} = '' ;\n\x09 goto PLEASE_TRY_AGAIN ;\n\x09 }\n\x09 my ( \$tag, \$rev ) = m{(\\S+):\\s+(\\S+)} ;\n\x09 unless ( defined \$tag ) {\n\x09 carp "Can't parse tag from CVS log line '\$_'" ;\n\x09 \$self->{CVS_LOG_STATE} = '' ;\n\x09 next ;\n\x09 }\n\x09 \$self->{CVS_LOG_FILE_DATA}->{TAGS}->{\$tag} = \$rev ; \n\x09 push( \@{\$self->{CVS_LOG_FILE_DATA}->{RTAGS}->{\$rev}}, \$tag ) ; \n }\n elsif ( \$self->{CVS_LOG_STATE} eq 'rev' ) {\n\x09 ( \$self->{CVS_LOG_REV}->{REV} ) = m/([\\d.]+)/ ;\n\x09 \$self->{CVS_LOG_STATE} = 'rev_meta' ;\n\x09 next ;\n }\n elsif ( \$self->{CVS_LOG_STATE} eq 'rev_meta' ) {\n\x09 for ( split /;\\s*/ ) {\n\x09 my ( \$key, \$value ) = m/(\\S+):\\s+(.*?)\\s*\$/ ;\n\x09 \$self->{CVS_LOG_REV}->{uc(\$key)} = \$value ;\n\x09 }\n\x09 \$self->{CVS_LOG_STATE} = 'rev_message' ;\n\x09 next ;\n }\n elsif ( \$self->{CVS_LOG_STATE} eq 'rev_message' ) {\n\x09 \$self->{CVS_LOG_REV}->{MESSAGE} .= \$_ ;\n }\n }\n\n ## Never, ever forget the last rev. "Wait for me! Wait for me!"\n ## Most of the time, this should not be a problem: cvs log puts a\n ## line of "=" at the end. But just in case I don't know of a\n ## funcky condition where that might not happen...\n unless ( defined \$input ) {\n \$store_rev->() ; # "is oldest" ) ;\n \$self->{CVS_LOG_REV} = undef ;\n \$self->{CVS_LOG_FILE_DATA} = undef ;\n }\n}\n\n\n# Here's a (probably out-of-date by the time you read this) dump of the args\n# for _add_rev:\n#\n###############################################################################\n#\$file = {\n# 'WORKING' => 'src/Eesh/eg/synopsis',\n# 'SELECTED' => '2',\n# 'LOCKS' => 'strict',\n# 'TOTAL' => '2',\n# 'ACCESS' => '',\n# 'RCS' => '/var/cvs/cvsroot/src/Eesh/eg/synopsis,v',\n# 'KEYWORD' => 'kv',\n# 'RTAGS' => {\n# '1.1' => [\n# 'Eesh_003_000',\n# 'Eesh_002_000'\n# ]\n# },\n# 'HEAD' => '1.2',\n# 'TAGS' => {\n# 'Eesh_002_000' => '1.1',\n# 'Eesh_003_000' => '1.1'\n# },\n# 'BRANCH' => ''\n#};\n#\$rev = {\n# 'DATE' => '2000/04/21 17:32:16',\n# 'MESSAGE' => 'Moved a bunch of code from eesh, then deleted most of it.\n#',\n# 'STATE' => 'Exp',\n# 'AUTHOR' => 'barries',\n# 'REV' => '1.1'\n#};\n###############################################################################\n\nsub _add_rev {\n my VCP::Source::cvs \$self = shift ;\n my ( \$file_data, \$rev_data, \$is_base_rev ) = \@_ ;\n\n my \$norm_name = \$self->normalize_name( \$file_data->{WORKING} ) ;\n\n my \$action = \$rev_data->{STATE} eq "dead" ? "delete" : "edit" ;\n\n my \$type = \$file_data->{KEYWORD} =~ /[o|b]/ ? "binary" : "text" ;\n\n#debug map "\$_ => \$rev_data->{\$_}, ", sort keys %{\$rev_data} ;\n\n my VCP::Rev \$r = VCP::Rev->new(\n source_name => \$file_data->{WORKING},\n name => \$norm_name,\n rev_id => \$rev_data->{REV},\n type => \$type,\n# ! \$is_base_rev\n#\x09 ? (\n\x09 action => \$action,\n\x09 time => \$self->parse_time( \$rev_data->{DATE} ),\n\x09 user_id => \$rev_data->{AUTHOR},\n\x09 comment => \$rev_data->{MESSAGE},\n\x09 state => \$rev_data->{STATE},\n\x09 labels => \$file_data->{RTAGS}->{\$rev_data->{REV}},\n#\x09 )\n#\x09 : (),\n ) ;\n\n \$self->{CVS_NAME_REP_NAME}->{\$file_data->{WORKING}} = \$file_data->{RCS} ;\n eval {\n \$self->revs->add( \$r ) ;\n } ;\n if ( \$\@ ) {\n if ( \$\@ =~ /Can't add same revision twice/ ) {\n warn \$\@ ;\n }\n else {\n die \$\@ ;\n }\n }\n}\n\n## FOOTNOTES:\n# [1] :pserver:guest\@cvs.tigris.org:/cvs hass some goofiness like:\n#----------------------------\n#revision 1.12\n#date: 2000/09/05 22:37:42; author: thom; state: Exp; lines: +8 -4\n#\n#merge revision history for cvspatches/root/log_accum.in\n#----------------------------\n#revision 1.11\n#date: 2000/08/30 01:29:38; author: kfogel; state: Exp; lines: +8 -4\n#(derive_subject_from_changes_file): use \\t to represent tab\n#characters, not the incorrect \\i.\n#=============================================================================\n#----------------------------\n#revision 1.11\n#date: 2000/09/05 22:37:32; author: thom; state: Exp; lines: +3 -3\n#\n#merge revision history for cvspatches/root/log_accum.in\n#----------------------------\n#revision 1.10\n#date: 2000/07/29 01:44:06; author: kfogel; state: Exp; lines: +3 -3\n#Change all "Tigris" ==> "Helm" and "tigris" ==> helm", as per Daniel\n#Rall's email about how the tigris path is probably obsolete.\n#=============================================================================\n#----------------------------\n#revision 1.10\n#date: 2000/09/05 22:37:23; author: thom; state: Exp; lines: +22 -19\n#\n#merge revision history for cvspatches/root/log_accum.in\n#----------------------------\n#revision 1.9\n#date: 2000/07/29 01:12:26; author: kfogel; state: Exp; lines: +22 -19\n#tweak derive_subject_from_changes_file()\n#=============================================================================\n#----------------------------\n#revision 1.9\n#date: 2000/09/05 22:37:13; author: thom; state: Exp; lines: +33 -3\n#\n#merge revision history for cvspatches/root/log_accum.in\n#\n\n=head1 SEE ALSO\n\nL, L, L.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACJ #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <repo_server\n if defined \$self->repo_server;\n\n return \$self->run_safely( [ qw( cvs -Q -z9 ), \@\$args ], \@_ ) ;\n}\n\n=item create_cvs_workspace\n\nCreates a temp dir named "co" for C to work in, checks out the module\nthere, and sets the work root and cvs working dir to that directory.\n\n=cut\n\nsub create_cvs_workspace {\n my \$self = shift ;\n\n confess "Can't create_workspace twice" unless \$self->none_seen ;\n\n ## establish_workspace in a directory named "co" for "checkout". This is\n ## so that VCP::Source::cvs can use a different directory to contain\n ## the revs, since all the revs need to be kept around until the VCP::Dest\n ## is through with them.\n \$self->command_chdir( \$self->tmp_dir( "co" ) ) ;\n my ( \$module ) = \$self->rev_root =~ m{^/*(.*?)(/|\\Z)} ;\n die "Unable to parse cvs module name from '", \$self->rev_root, "'\\n"\n unless defined \$module and length \$module ;\n \$self->cvs( [ 'checkout', \$module ] ) ;\n \$self->work_root( \$self->tmp_dir( "co", \$self->rev_root ) ) ;\n \$self->command_chdir( \$self->tmp_dir( "co", \$self->rev_root ) ) ;\n}\n\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAACK #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <vss. Not sure if VSS\r\nsets the cp and workfold per machine or per user.\r\n\r\n=cut\r\n\r\nsub ss {\r\n my \$self = shift ;\r\n\r\n my \$args = shift ;\r\n\r\n my \$user = \$self->repo_user;\r\n my \@Y_arg;\r\n push \@Y_arg, "-Y\$user" if defined \$user and length \$user;\r\n\r\n local \$ENV{SSPWD} = \$self->repo_password if defined \$self->repo_password;\r\n\r\n my \@I_arg;\r\n\r\n push \@I_arg, "-I-" unless grep /^-I/, \@\$args;\r\n\r\n \$self->run_safely(\r\n [ qw( ss ), \@\$args, \@Y_arg, \@I_arg ], \@_\r\n ) ;\r\n\r\n return;\r\n}\r\n\r\n=item create_vss_workspace\r\n\r\nCreates a temporary directory.\r\n\r\n=cut\r\n\r\nsub create_vss_workspace {\r\n my \$self = shift ;\r\n\r\n confess "Can't create_workspace twice" unless \$self->none_seen ;\r\n\r\n ## establish_workspace in a directory named "co" for "checkout". This is\r\n ## so that VCP::Source::vss can use a different directory to contain\r\n ## the revs, since all the revs need to be kept around until the VCP::Dest\r\n ## is through with them.\r\n my \$workspace = \$self->tmp_dir;\r\n\r\n \$self->mkdir( \$workspace );\r\n}\r\n\r\n\r\n=item get_vss_file_list\r\n\r\nRetrieves a list of all files and directories under a particular\r\npath. We need this so we can tell what dirs and files need to be added.\r\n\r\n=cut\r\n\r\nsub _scan_for_files {\r\n my \$self = shift;\r\n my ( \$path, \$type, \$filelist ) = \@_;\r\n\r\n \$path = \$self->repo_filespec\r\n unless defined \$path;\r\n \$path =~ s{^\\\$[\\\\/]}{};\r\n\r\n my \$path_re = compile_shellish( \$path );\r\n\r\n debug "vcp: file scan re: \$path_re" if debugging \$self ;\r\n my \$cur_project;\r\n for ( \@\$filelist ) {\r\n if ( /^(|No items found.*|\\d+ item.*s.*)\$/i ) {\r\n undef \$cur_project;\r\n next;\r\n }\r\n\r\n if ( m{^\\\$/(.*):} ) {\r\n \$cur_project = \$1;\r\n ## Catch all project entries, because we may be importing\r\n ## to a non-existant project inside a project that exists.\r\n if ( length \$cur_project ) {\r\n ## Add a slash so a preexisting dest project is found.\r\n# if ( "\$cur_project/" =~ \$path_re ) {\r\n my \$p = \$cur_project;\r\n# ## Catch all parent projects. This prevents us from\r\n# ## creating more than need be.\r\n# do {\r\n \$self->{VSS_FILES}->{\$p} = "project";\r\n# } while \$p =~ s{/[^/]*}{} && length \$p;\r\n# }\r\n \$cur_project .= "/";\r\n }\r\n next;\r\n }\r\n\r\n if ( m{^\\\$(.*)} ) {\r\n confess "undefined \\\$cur_project" unless defined \$cur_project;\r\n ## A subproject. note here for the fun of it; it should also\r\n ## occur later in a \$/foo: section of it's own.\r\n my \$pjt = "\$cur_project\$1";\r\n \$self->{VSS_FILES}->{\$pjt} = "project"\r\n if \$pjt =~ \$path_re;\r\n next;\r\n }\r\n\r\n if ( "\$cur_project\$_" =~ \$path_re ) {\r\n if ( defined \$self->{VSS_FILES}->{"\$cur_project\$_"} ) {\r\n \$self->{VSS_FILES}->{"\$cur_project\$_"} .= ", \$type";\r\n }\r\n else {\r\n \$self->{VSS_FILES}->{"\$cur_project\$_"} = \$type;\r\n }\r\n next;\r\n }\r\n }\r\n\r\n}\r\n\r\nsub get_vss_file_list {\r\n my \$self = shift;\r\n my ( \$path ) = \@_;\r\n\r\n ## Sigh. I tried passing in \$path to the Dir -D command and\r\n ## ss.exe whines because \$path is rarely a deleted path RATHER\r\n ## THAN JUST GIVING ME ALL DELETED FILES UNDER \$path!!!\r\n ## So, we get all the output and filter it for \$path/... ourselves.\r\n ## This does have the advantage that we can use full wildcards in\r\n ## \$path.\r\n\r\n \$self->{VSS_FILES} = {};\r\n\r\n my \$ignored_stdout;\r\n \$self->ss( [ "cp", "\\\$/" ], \\\$ignored_stdout );\r\n\r\n \$self->_scan_for_files( \$path, "file",\r\n [ do {\r\n my \$filelist;\r\n \$self->ss( [qw( Dir -R )], ">", \\\$filelist );\r\n map { chomp; \$_ } split /^/m, \$filelist;\r\n } ]\r\n );\r\n\r\n \$self->_scan_for_files( \$path, "deleted file",\r\n [ do {\r\n my \$filelist;\r\n \$self->ss( [qw( Dir -R -D)], ">", \\\$filelist );\r\n map { chomp; \$_ } split /^/m, \$filelist;\r\n } ]\r\n );\r\n if ( debugging \$self ) {\r\n require Data::Dumper;\\\r\n debug Data::Dumper::Dumper( \$self->{VSS_FILES} );\r\n }\r\n}\r\n\r\n=item vss_files\r\n\r\n \@files = \$self->vss_files;\r\n\r\nreturns a list of all files (not projects) that get_vss_file_list()\r\nloaded.\r\n\r\n=cut\r\n\r\nsub vss_files {\r\n my \$self = shift;\r\n\r\n ## TODO: allow a pattern. This would let us handle filespecs like\r\n ## /a*/b*\r\n grep index( \$self->{VSS_FILES}->{\$_}, "project" ) < 0,\r\n keys %{\$self->{VSS_FILES}};\r\n}\r\n\r\n=item vss_file\r\n\r\n \$self->vss_file( \$path );\r\n \$self->vss_file( \$path, undef ); ## To mark as non-existant\r\n \$self->vss_file( \$path, 1 ); ## To mark as existant\r\n \$self->vss_file( \$path, "project" ); ## To mark as being a project\r\n\r\nAccepts an absolute path with or without the leading C<\$/> or C and\r\nreturns TRUE if it exists in CVS.\r\n\r\n=cut\r\n\r\nsub vss_file {\r\n my \$self = shift;\r\n my ( \$path, \$value ) = \@_;\r\n\r\n confess unless defined \$path;\r\n\r\n \$self->get_vss_file_list unless \$self->{VSS_FILES};\r\n\r\n for ( \$path ) {\r\n s{\\\\}{/}g;\r\n s{\\/+\$}{};\r\n s{\\\$+}{}g;\r\n s{^/+}{};\r\n }\r\n\r\n if ( \@_ > 1 ) {\r\n \$self->{VSS_FILES}->{\$path} = \$value;\r\n if ( \$value ) {\r\n my \$p = \$path;\r\n while () {\r\n \$p =~ s{(^|/)+[^/]+\$}{};\r\n last unless length \$p || \$self->{VSS_FILES}->{\$p};\r\n \$self->{VSS_FILES}->{\$p} = "project";\r\n }\r\n }\r\n }\r\n\r\n return exists \$self->{VSS_FILES}->{\$path} && \$self->{VSS_FILES}->{\$path};\r\n}\r\n\r\n=item vss_file_is_deleted\r\n\r\nReturns 1 if the file is a deleted file.\r\n\r\nNOTE: in VSS a file may be deleted and not deleted at the same time!\r\nThanks to Dave Foglesong for pointing this out.\r\n\r\n=cut\r\n\r\nsub vss_file_is_deleted {\r\n return 0 <= index shift->vss_file( \@_ ), "deleted";\r\n}\r\n\r\n=item vss_file_is_active\r\n\r\nReturns 1 if the file is an active (undeleted) file.\r\n\r\nNOTE: in VSS a file may be deleted and active at the same time!\r\nThanks to Dave Foglesong for pointing this out.\r\n\r\n=cut\r\n\r\nsub vss_file_is_active {\r\n return shift->vss_file( \@_ ) =~ /(^|, )file/;\r\n}\r\n\r\n=head1 COPYRIGHT\r\n\r\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\r\n\r\nThis module and the VCP package are licensed according to the terms given in\r\nthe file LICENSE accompanying this distribution, a copy of which is included in\r\nL.\r\n\r\n=cut\r\n\r\n1 ;\r\n\r\n1;\r\n END_OF_FILE_AAAAAAAAAACL #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <{P4_REPO_CLIENT} = shift if \@_ ;\n return \$self->{P4_REPO_CLIENT} ;\n}\n\n\n=item p4\n\nCalls the p4 command with the appropriate user, client, port, and password.\n\n=cut\n\nsub p4 {\n my \$self = shift ;\n\n local \$ENV{P4PASSWD} = \$self->repo_password if defined \$self->repo_password ;\n unshift \@{\$_[0]}, '-p', \$self->repo_server if defined \$self->repo_server ;\n unshift \@{\$_[0]}, '-c', \$self->repo_client if defined \$self->repo_client ;\n unshift \@{\$_[0]}, '-u', \$self->repo_user if defined \$self->repo_user ;\n\n ## TODO: Specify an empty \n\n ## localizing this was giving me some grief. Can't recall what.\n ## PWD must be cleared because, unlike all other Unix utilities I\n ## know of, p4 looks at it and bases it's path calculations on it.\n my \$tmp = \$ENV{PWD} ;\n delete \$ENV{PWD} ;\n\n my \$args = shift ;\n#if ( \$ENV{UHOH} && grep( /^client\$/, \@\$args ) && grep( /^-o\$/, \@\$args ) ) {\n# warn( ">>>>>>>>>>>>>p4.exe \@\$args > bah1" );\n# system( "p4.exe \@\$args > bah1" );\n# system( "p4.exe \@\$args > bah2" );\n#}\n\n \$self->run_safely( [ "p4", \@\$args ], \@_ ) ;\n \$ENV{PWD} = \$tmp if defined \$tmp ;\n}\n\n\n=item parse_p4_repo_spec\n\nCalls \$self->parse_repo_spec, the post-processes the repo_user in to a user\nname and a client view. If the user specified no client name, then a client\nname of "vcp_tmp_\$\$" is used by default.\n\nThis also initializes the client to have a mapping to a working directory\nunder /tmp, and arranges for the current client definition to be restored\nor deleted on exit.\n\n=cut\n\nsub parse_p4_repo_spec {\n my \$self = shift ;\n my ( \$spec ) = \@_ ;\n\n my \$parsed_spec = \$self->parse_repo_spec( \$spec ) ;\n\n my ( \$user, \$client ) ;\n ( \$user, \$client ) = \$self->repo_user =~ m/([^()]*)(?:\\((.*)\\))?/\n if defined \$self->repo_user ;\n \$client = "vcp_tmp_\$\$" unless defined \$client && length \$client ;\n\n \$self->repo_user( \$user ) ;\n \$self->repo_client( \$client ) ;\n\n if ( \$self->can( "min" ) ) {\n my \$filespec = \$self->repo_filespec ;\n\n ## If a change range was specified, we need to list the files in\n ## each change. p4 doesn't allow an \@ range in the filelog command,\n ## for wataver reason, so we must parse it ourselves and call lots\n ## of filelog commands. Even if it did, we need to chunk the list\n ## so that we don't consume too much memory or need a temporary file\n ## to contain one line per revision per file for an entire large\n ## repo.\n my ( \$name, \$min, \$comma, \$max ) ;\n ( \$name, \$min, \$comma, \$max ) =\n\x09 \$filespec =~ m/^([^\@]*)(?:\@(-?\\d+)(?:(\\D|\\.\\.)((?:\\d+|#head)))?)?\$/i\n\x09 or die "Unable to parse p4 filespec '\$filespec'\\n";\n\n die "'\$comma' should be ',' in revision range in '\$filespec'\\n"\n\x09 if defined \$comma && \$comma ne ',' ;\n\n if ( ! defined \$min ) {\n\x09 \$min = 1 ;\n\x09 \$max = '#head' ;\n }\n\n if ( ! defined \$max ) {\n\x09 \$max = \$min ;\n }\n elsif ( lc( \$max ) eq '#head' ) {\n\x09 \$self->p4( [qw( counter change )], \\\$max ) ;\n\x09 chomp \$max ;\n }\n\n if ( \$max == 0 ) {\n ## TODO: make this a "normal exit"\n die "Current change number is 0, no work to do\\n";\n }\n\n if ( \$min < 0 ) {\n\x09 \$min = \$max + \$min ;\n }\n\n \$self->repo_filespec( \$name ) ;\n \$self->min( \$min ) ;\n \$self->max( \$max ) ;\n }\n\n return \$parsed_spec ;\n}\n\n\n=item init_p4_view\n\n \$self->init_p4_view\n\nBorrows or creates a client with the right view. Only called from\nVCP::Dest::p4, since VCP::Source::p4 uses non-view oriented commands.\n\n=cut\n\nsub init_p4_view {\n my \$self = shift ;\n\n my \$client = \$self->repo_client ;\n\n \$self->repo_client( undef ) ;\n my \$client_exists = grep \$_ eq \$client, \$self->p4_clients ;\n debug "p4: client '\$client' exists" if \$client_exists && debugging \$self ;\n \$self->repo_client( \$client ) ;\n my \$client_spec = \$self->p4_get_client_spec ;\n## work around a wierd intermittant failure on Win32. The\n## Options: line *should* end in nomodtime normdir\n## instead it looks like:\n##\n## Options:\x09noallwrite noclobber nocompress unlocked nom\xd4+\n##\n## but only occasionally!\n\$client_spec = \$self->p4_get_client_spec\n if \$^O =~ /Win32/ && \$client_spec =~ /[\\x80-\\xFF]/;\n\n \$self->queue_p4_restore_client_spec( \$client_exists ? \$client_spec : undef );\n\n my \$p4_spec = \$self->repo_filespec ;\n \$p4_spec =~ s{(/(\\.\\.\\.)?)?\$}{/...} ;\n my \$work_dir = \$self->work_root ;\n\n \$client_spec =~ s{^Root.*}{Root:\\t\$work_dir}m ;\n \$client_spec =~ s{^View.*}{View:\\n\\t\$p4_spec\\t//\$client/...\\n}ms ;\n debug "p4: using client spec", \$client_spec if debugging \$self ;\n \$client_spec =~ s{^(Options:.*)}{\$1 nocrlf}m \n if \$^O =~ /Win32/ ;\n \$client_spec =~ s{^LineEnd.*}{LineEnd:\\tunix}mi ;\n\n debug "p4: using client spec", \$client_spec if debugging \$self ;\n\n \$self->p4_set_client_spec( \$client_spec ) ;\n\n}\n\n=item p4_clients\n\nReturns a list of known clients.\n\n=cut\n\nsub p4_clients {\n my \$self = shift ;\n\n my \$clients ;\n \$self->p4( [ "clients", ], ">", \\\$clients ) ;\n return map { /^Client (\\S*)/ ; \$1 } split /\\n/m, \$clients ;\n}\n\n=item p4_get_client_spec\n\nReturns the current client spec for the named client. The client may or may not\nexist first, grep the results from L to see if it already exists.\n\n=cut\n\nsub p4_get_client_spec {\n my \$self = shift ;\n my \$client_spec ;\n \$self->p4( [ "client", "-o" ], ">", \\\$client_spec ) ;\n return \$client_spec ;\n}\n\n\n=item queue_p4_restore_client_spec\n\n \$self->queue_p4_restore_client_spec( \$client_spec ) ;\n\nSaves a copy of the named p4 client and arranges for it's restoral on exit\n(assuming END blocks run). Used when altering a user-specified client that\nalready exists.\n\nIf \$client_spec is undefined, then the named client will be deleted on\nexit.\n\nNote that END blocks may be skipped in certain cases, like coredumps,\nkill -9, or a call to POSIX::exit(). None of these should happen except\nin debugging, but...\n\n=cut\n\nmy \@client_backups ;\n\nEND {\n my \$child_exit;\n {\n local \$?; ## Protect this; we're about to run a child process and\n ## we want to exit with the appropriate value.\n for ( \@client_backups ) {\n my ( \$object, \$name, \$spec ) = \@\$_ ;\n my \$tmp_name = \$object->repo_client ;\n \$object->repo_client( \$name ) ;\n if ( defined \$spec ) {\n \$object->p4_set_client_spec( \$spec ) ;\n }\n else {\n my \$out ;\n \$object->p4( [ "client", "-df", \$object->repo_client ], ">", \\\$out);\n warn "vcp: unexpected stdout from p4:\\np4: ", \$out\n unless \$out =~ /^Client\\s.*\\sdeleted./ ;\n \$child_exit = \$?;\n }\n \$object->repo_client( \$tmp_name ) ;\n \$_ = undef ;\n }\n \@client_backups = () ;\n }\n \$? = \$child_exit if \$child_exit && ! \$?;\n}\n\n\nsub queue_p4_restore_client_spec {\n my \$self = shift ;\n my ( \$client_spec ) = \@_ ;\n push \@client_backups, [ \$self, \$self->repo_client, \$client_spec ] ;\n}\n\n=item p4_set_client_spec\n\n \$self->p4_set_client_spec( \$client_spec ) ;\n\nWrites a client spec to the repository.\n\n=cut\n\n\nsub p4_set_client_spec {\n my \$self = shift ;\n my ( \$client_spec ) = \@_ ;\n\n ## Capture stdout so it doesn't leak.\n my \$out ;\n \$self->p4( [ "client", "-i" ], "<", \\\$client_spec, ">", \\\$out ) ;\n\n die "vcp: unexpected stdout from p4:\\np4: ", \$out\n unless \$out =~ /^Client\\s.*\\ssaved.\$/ ;\n}\n\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAACM #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < p4:user:password\@p4port:[]\n vcp p4:user(client):password\@p4port:[]\n vcp p4:[]\n\nThe spec is a perforce repository spec and must begin with // and a\ndepot name ("//depot"), not a local filesystem spec or a client spec. There\nshould be a trailing "/..." specified.\n\nIf no user name, password, or port are given, the underlying p4 command will\nlook at that standard environment variables. The password is passed using the\nenvironment variable P4PASSWD so it won't be logged in debugging or error\nmessages, the other options are passed on the command line.\n\nIf no client name is given, a temporary client name like "vcp_tmp_1234" will be\ncreated and used. The P4CLIENT environment variable will not be used. If an\nexisting client name is given, the named client spec will be saved off,\naltered, used, and restored. the client was created for this import, it will\nbe deleted when complete, regardless of whether the client was specified by the\nuser or was randomly generated. WARNING: If perl coredumps or is killed with a\nsignal that prevents cleanup--like a SIGKILL (9)--the the client deletion or\nrestoral will not occur. The client view is not saved on disk, either, so back\nit up manually if you care.\n\nTHE CLIENT SAVE/RESTORE FEATURE IS EXPERIMENTAL AND MAY CHANGE BASED ON USER\nFEEDBACK.\n\nVCP::Dest::p4 attempts change set aggregation by sorting incoming revisions.\nSee L for the order in which revisions are sorted. Once\nsorted, a change is submitted whenever the change number (if present) changes,\nthe comment (if present) changes, or a new rev of a file with the same name as\na revision that's pending. THIS IS EXPERIMENTAL, PLEASE DOUBLE CHECK\nEVERYTHING!\n\n=head1 DESCRIPTION\n\n=head1 METHODS\n\n=over\n\n=cut\n\n\$VERSION = 1 ;\n\nuse strict ;\nuse vars qw( \$debug ) ;\n\n\$debug = 0 ;\n\nuse Carp ;\nuse File::Basename ;\nuse File::Path ;\nuse Getopt::Long ;\nuse VCP::Debug ':debug' ;\nuse VCP::Dest ;\nuse VCP::Rev ;\n\nuse base qw( VCP::Dest VCP::Utils::p4 ) ;\nuse fields (\n# 'P4_SPEC', ## The root of the tree to update\n 'P4_PENDING', ## Revs pending the next submit\n 'P4_DELETES_PENDING', ## At least one 'delete' needs to be submitted.\n 'P4_WORK_DIR', ## Where to do the work.\n 'P4_REPO_CLIENT', ## See VCP::Utils::p4 for accessors and usage...\n\n ## members for change number divining:\n 'P4_PREV_CHANGE_ID', ## The change_id in the r sequence, if any\n 'P4_PREV_COMMENT', ## Used to detect change boundaries\n\n) ;\n\n=item new\n\nCreates a new instance of a VCP::Dest::p4. Contacts the p4d using the p4\ncommand and gets some initial information ('p4 info' and 'p4 labels').\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Dest::p4 \$self = \$class->SUPER::new( \@_ ) ;\n\n ## Parse the options\n my ( \$spec, \$options ) = \@_ ;\n\n \$self->parse_p4_repo_spec( \$spec ) ;\n\n my \$files = \$self->repo_filespec ;\n\n ## No need to deduce the rev_root, we let p4 do that by setting the\n ## client view to the destination path the user specified.\n\n GetOptions( "ArGhOpTioN" => \\"" ) or \$self->usage_and_exit ; # No options!\n\n \$self->init_p4_view ;\n\n return \$self ;\n}\n\n\nsub checkout_file {\n my VCP::Dest::p4 \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\nconfess unless defined \$self && defined \$self->header ;\n\n debug "vcp: retrieving '", \$r->as_string, "' from p4 dest repo"\n if debugging \$self ;\n\n ## The rev_root was put in the client view, p4 will "denormalize"\n ## the name for us.\n my \$work_path = \$self->work_path( \$r->name ) ;\n debug "vcp: work_path '\$work_path'" if debugging \$self ;\n\n my VCP::Rev \$saw = \$self->seen( \$r ) ;\n\n die "Can't backfill already seen file '", \$r->name, "'" if \$saw ;\n\n my ( undef, \$work_dir ) = fileparse( \$work_path ) ;\n \$self->mkpdir( \$work_path ) unless -d \$work_dir ;\n\n my \$tag = "r_" . \$r->rev_id ;\n \$tag =~ s/\\W+/_/g ;\n\n ## The -f forces p4 to sync even if it thinks it doesn't have to. It's\n ## not in there for any known reason, just being conservative.\n \$self->p4( ['sync', '-f', \$r->name . "\\\@\$tag" ] ) ;\n die "'\$work_path' not created in backfill" unless -e \$work_path ;\n\n return \$work_path ;\n}\n\n\nsub backfill {\n my VCP::Dest::p4 \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\nconfess unless defined \$self && defined \$self->header ;\n\n \$r->work_path( \$self->checkout_file( \$r ) ) ;\n\n return 1 ;\n}\n\n\nsub handle_header {\n my VCP::Dest::p4 \$self = shift ;\n \$self->{P4_PENDING} = [] ;\n \$self->{P4_PREV_COMMENT} = undef ;\n \$self->{P4_PREV_CHANGE_ID} = undef ;\n \$self->SUPER::handle_header( \@_ ) ;\n}\n\n\nsub handle_rev {\n my VCP::Dest::p4 \$self = shift ;\n\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\ndebug "vcp: handle_rev got \$r ", \$r->name if debugging \$self ;\n\n if ( \n ( \@{\$self->{P4_PENDING}} || \$self->{P4_DELETES_PENDING} )\n && (\n\x09 (\n\x09 defined \$r->change_id && defined \$self->{P4_PREV_CHANGE_ID}\n\x09 && \$r->change_id ne \$self->{P4_PREV_CHANGE_ID}\n\x09 && ( debugging( \$self ) ? debug "vcp: time to submit: change_id changed" : 1 )\n\x09 )\n\x09 || (\n\x09 defined \$r->comment && defined \$self->{P4_PREV_COMMENT}\n\x09 && \$r->comment ne \$self->{P4_PREV_COMMENT}\n\x09 && ( debugging( \$self ) ? debug "vcp: time to submit: comment changed [", \$r->comment, "] vs [", \$self->{P4_PREV_COMMENT}, "]" : 1 )\n\x09 )\n\x09 || (\n\x09 grep( \$r->name eq \$_->name, \@{\$self->{P4_PENDING}} )\n\x09 && ( debugging( \$self ) ? debug "vcp: time to submit: name repeated" : 1 )\n\x09 )\n )\n ) {\n \$self->submit ;\n }\n \n \$self->compare_base_revs( \$r )\n if \$r->is_base_rev && defined \$r->work_path ;\n\n my VCP::Rev \$saw = \$self->seen( \$r ) ;\n\n return if \$r->is_base_rev ;\n\n my \$fn = \$r->name ;\n debug "vcp: importing ", \$r->as_string if debugging \$self ;\n my \$work_path = \$self->work_path( \$fn ) ;\n debug "vcp: work_path '\$work_path'" if debugging \$self ;\n\n if ( \$r->action eq 'delete' ) {\n my \$already_deleted;\n if ( ! \$saw ) {\n \$self->p4( [ 'files', \$fn ], '>', \\my \$log );\n warn \$log;\n \$already_deleted = \$log =~ /- delete change \\d+/;\n \$self->p4( [ 'sync', '-f', \$fn ] )\n unless \$already_deleted;\n }\n\n if ( -e \$work_path ) {\n unlink \$work_path || die "\$! unlinking \$work_path" ;\n }\n\n unless ( \$already_deleted ) {\n \$self->p4( ['delete', \$fn] ) ;\n \$self->{P4_DELETES_PENDING} = 1 ;\n }\n \$self->delete_seen( \$r ) ;\n }\n else {\n ## TODO: Don't assume same filesystem or working link().\n {\n my \$filetype = defined \$r->p4_info && \$r->p4_info =~ /\\((\\S+)\\)\$/\n\x09 ? \$1\n\x09 : \$r->type ;\n\n my \$add_it ;\n\x09 if ( -e \$work_path ) {\n\x09 \$self->p4( ["edit", "-t", \$filetype, \$fn] ) ;\n\x09 unlink \$work_path or die "\$! unlinking \$work_path" ;\n\x09 }\n\x09 else {\n\x09 \$self->mkpdir( \$work_path ) ;\n\x09 \$add_it = 1 ;\n\x09 }\n\x09 debug "vcp: linking ", \$r->work_path, " to \$work_path" if debugging \$self ;\n\x09 link \$r->work_path, \$work_path\n\x09 or die "\$! linking ", \$r->work_path, " -> \$work_path" ;\n\n\x09 \$r->dest_work_path( \$work_path ) ;\n\n\x09 if ( defined \$r->mod_time ) {\n\x09 utime \$r->mod_time, \$r->mod_time, \$work_path\n\x09 or die "\$! changing times on \$work_path" ;\n\x09 }\n\x09 if ( \$add_it ) {\n\x09 \$self->p4( ["add", "-t", \$filetype, \$fn] ) ;\n\x09 }\n }\n\n unless ( \$saw ) {\n\x09 ## New file.\n }\n\n ## TODO: Provide command line options for user-defined tag prefixes\n my \$tag = "r_" . \$r->rev_id ;\n \$tag =~ s/\\W+/_/g ;\n \$r->add_label( \$tag ) ;\n if ( defined \$r->change_id ) {\n\x09 my \$tag = "ch_" . \$r->change_id ;\n\x09 \$tag =~ s/\\W+/_/g ;\n\x09 \$r->add_label( \$tag ) ;\n }\n\ndebug "vcp: saving off \$r ", \$r->name, " in PENDING" if debugging \$self ;\n push \@{\$self->{P4_PENDING}}, \$r ;\n }\n\n \$self->{P4_PREV_CHANGE_ID} = \$r->change_id ;\n \$self->{P4_PREV_COMMENT} = \$r->comment ;\n}\n\n\nsub handle_footer {\n my VCP::Dest::p4 \$self = shift ;\n\n \$self->submit\n if ( \$self->{P4_PENDING} && \@{\$self->{P4_PENDING}} )\n || \$self->{P4_DELETES_PENDING} ;\n \$self->SUPER::handle_footer ;\n}\n\n\nsub submit {\n my VCP::Dest::p4 \$self = shift ;\n\n my %pending_labels ;\n my %comments ;\n my \$max_time ;\n\n if ( \@{\$self->{P4_PENDING}} ) {\n for my \$r ( \@{\$self->{P4_PENDING}} ) {\n\x09 \$comments{\$r->comment} = \$r->name if defined \$r->comment ;\n\x09 \$max_time = \$r->time if ! defined \$max_time || \$r->time > \$max_time ;\n\x09 for my \$l ( \$r->labels ) {\n\x09 push \@{\$pending_labels{\$l}}, \$r->dest_work_path ;\n\x09 }\n }\n\n if ( defined \$max_time ) {\n\x09 my \@f = reverse( (localtime \$max_time)[0..5] ) ;\n\x09 \$f[0] += 1900 ;\n\x09 ++\$f[1] ; ## Day of month needs to be 1..12\n\x09 \$max_time = sprintf "%04d/%02d/%02d %02d:%02d:%02d", \@f ;\n }\n elsif ( debugging \$self ) {\n debug "No max_time found" ;\n }\n }\n\n my \$description = join( "\\n", keys %comments ) ;\n if ( length \$description ) {\n \$description =~ s/^/\\t/gm ;\n \$description .= "\\n" if substr \$description, -1 eq "\\n" ;\n }\n\n my \$change ;\n \$self->p4( [ 'change', '-o' ], \\\$change ) ;\n\n if ( defined \$max_time ) {\n \$change =~ s/^Date:.*\\r?\\n\\r/Date:\\t\$max_time\\n/m\n\x09 or \$change =~ s/(^Client:)/Date:\\t\$max_time\\n\\n\$1/m\n\x09 or die "vcp: Couldn't modify change date\\n\$change" ;\n }\n\n \$change =~ s/^Description:.*\\r?\\n\\r?.*/Description:\\n\$description/m\n or die "vcp: Couldn't modify change description\\n\$change" ;\n \$self->p4([ 'submit', '-i'], '<', \\\$change ) ;\n\n ## Create or add a label spec for each of the labels. The 'sort' is to\n ## make debugging output more legible.\n ## TODO: Modify RevML to allow label metadata (owner, desc, options)\n ## to be passed through. Same for user, client, jobs metadata etc.\n ## The assumption is made that most labels will apply to a single change\n ## number, so we do the labelling once per submit. I don't think that\n ## this will break if it doesn't, but TODO: add more labelling tests.\n for my \$l ( sort keys %pending_labels ) {\n my \$label_desc ;\n \$self->p4( [qw( label -o ), \$l], '>', \\\$label_desc ) ;\n \$self->p4( [qw( label -i ) ], '<', \\\$label_desc ) ;\n\n my \$pending_labels = join( "\\n", \@{\$pending_labels{\$l}} ) . "\\n" ;\n \$self->p4( [qw( -x - labelsync -a -l ), \$l ], "<", \\\$pending_labels ) ;\n }\n \@{\$self->{P4_PENDING}} = () ;\n \$self->{P4_DELETES_PENDING} = undef ;\n}\n\nsub tag {\n my VCP::Dest::p4 \$self = shift ;\n\n my \$tag = shift ;\n \$tag =~ s/\\W+/_/g ;\n \$self->p4( ['tag', \$tag, \@_] ) ;\n}\n\n\n## Prevent VCP::Plugin from rmtree-ing the workspace we're borrowing\nsub DESTROY {\n my VCP::Dest::p4 \$self = shift ;\n\n \$self->work_root( undef ) ;\n \$self->SUPER::DESTROY ;\n}\n\n\n=back\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACN #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <]\n revml:[] --dtd \n revml:[] --version \n revml:[] --sort-by=name,rev\n\n=head1 DESCRIPTION\n\nThe --dtd and --version options cause the output to be checked against\na particular version of revml. This does I cause output to be in \nthat version, but makes sure that output is compliant with that version.\n\n=head1 EXTERNAL METHODS\n\n=over\n\n=cut\n\nuse strict ;\n\nuse Carp ;\nuse Digest::MD5 ;\nuse Fcntl ;\nuse Getopt::Long ;\nuse MIME::Base64 ;\nuse RevML::Doctype ;\nuse RevML::Writer ;\nuse Symbol ;\nuse UNIVERSAL qw( isa ) ;\nuse VCP::Debug ':debug' ;\nuse VCP::Rev ;\nuse Text::Diff ;\n\nuse vars qw( \$VERSION ) ;\n\n\$VERSION = 0.1 ;\n\nuse base 'VCP::Dest' ;\n\nuse fields (\n 'OUT_NAME', ## The name of the output file, or '-' for stdout\n 'OUT_FH', ## The handle of the output file\n 'WRITER', ## The XML::AutoWriter instance write with\n) ;\n\n\n=item new\n\nCreates a new instance. The only parameter is '-dtd', which overrides\nthe default DTD found by searching for modules matching RevML::DTD:v*.pm.\n\nAttempts to create the output file if one is specified.\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Dest::revml \$self = \$class->SUPER::new( \@_ ) ;\n\n my \@errors ;\n\n my ( \$spec, \$options ) = \@_ ;\n\n my \$parsed_spec = \$self->parse_repo_spec( \$spec ) ;\n\n my \$file_name = \$parsed_spec->{FILES} ;\n \$self->{OUT_NAME} = defined \$file_name && length \$file_name\n ? \$file_name\n : '-' ;\n if ( \$self->{OUT_NAME} eq '-' ) {\n \$self->{OUT_FH} = \\*STDOUT ;\n ## TODO: Check OUT_FH for writability when it's set to STDOUT\n }\n else {\n require Symbol ;\n \$self->{OUT_FH} = Symbol::gensym ;\n ## TODO: Provide a '-f' force option\n open( \$self->{OUT_FH}, ">\$self->{OUT_NAME}" )\n or die "\$!: \$self->{OUT_NAME}" ;\n }\n ## BUG: Can't undo this AFAIK, so we're permanently altering STDOUT\n ## if OUT_NAME eq '-'.\n binmode \$self->{OUT_FH};\n\n my \$doctype ;\n my \@sort_spec ;\n {\n local *ARGV = \$options ;\n GetOptions(\n\x09 'dtd|version' => sub {\n\x09 \$doctype = RevML::Doctype->new( shift \@\$options ) ;\n\x09 },\n\x09 "k|sort-by=s" => \\\@sort_spec,\n ) or \$self->usage_and_exit ;\n }\n\n \$self->set_sort_spec( \@sort_spec ) if \@sort_spec ;\n\n \$doctype = RevML::Doctype->new\n unless \$doctype ;\n\n die join( '', \@errors ) if \@errors ;\n\n \$self->writer(\n RevML::Writer->new(\n\x09 DOCTYPE => \$doctype,\n\x09 OUTPUT => \$self->{OUT_FH},\n )\n );\n\n return \$self ;\n}\n\n\nsub _ISO8601(;\$) {\n my \@f = reverse( ( \@_ ? gmtime( shift ) : gmtime )[0..5] ) ;\n \$f[0] += 1900 ;\n \$f[1] ++ ; ## Month of year needs to be 1..12\n return sprintf( "%04d-%02d-%02d %02d:%02d:%02dZ", \@f ) ;\n}\n\nsub _emit_characters {\n my ( \$w, \$buf ) = \@_ ;\n\n \$w->setDataMode( 0 ) ;\n\n ## Note that we don't let XML munge \\r to be \\n!!\n while ( \$\$buf =~ m{\\G(?:\n ( [\\x00-\\x08\\x0b-\\x1f\\x7f-\\xff])\n | ([^\\x00-\\x08\\x0b-\\x1f\\x7f-\\xff]*)\n )}gx\n ) {\n if ( defined \$1 ) {\n\x09 \$w->char( "", code => sprintf( "0x%02x", ord \$1 ) ) ;\n }\n else {\n\x09 \$w->characters( \$2 ) ;\n }\n }\n\n}\n\n\nsub handle_rev {\n my VCP::Dest::revml \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n my \$w = \$self->writer ;\n\n if ( \$self->none_seen ) {\n \$w->setDataMode( 1 ) ;\n \$w->xmlDecl ;\n my \$h = \$self->header ;\n ## VCP::Source::revml passes through the original date. Other sources\n ## don't.\n \$w->time(\n defined \$h->{time}\n\x09 ? _ISO8601 \$h->{time}\n\x09 : _ISO8601\n ) ;\n \$w->rep_type( \$h->{rep_type} ) ;\n \$w->rep_desc( \$h->{rep_desc} ) ;\n \$w->rev_root( \$h->{rev_root} ) ;\n }\n\n my VCP::Rev \$saw = \$self->seen( \$r ) ;\n\n ## If there's no work path for the current file, keep the previous one.\n ## This is a cheat that allows us to diff against the last known version\n ## if a file is deleted and then re-added. Without this line, we would\n ## have to include the new version of the file.\n \$self->seen( \$saw ) if \$saw && ! defined \$r->work_path ;\n\n my \$fn = \$r->name ;\n\n my \$is_base_rev = \$r->is_base_rev ;\n die(\n "Saw '", \$saw->as_string,\n "', but found a later base rev '" . \$r->as_string, "'"\n ) if \$saw && \$is_base_rev ;\n\n ## type and rev_id are not provided for VSS deletes\n debug "vcp: emitting revml for ", \$r->as_string\n if debugging \$self;\n\n \$w->start_rev ;\n \$w->name( \$fn ) ;\n \$w->type( \$r->type ) if defined \$r->type ;\n \$w->p4_info( \$r->p4_info ) if defined \$r->p4_info ;\n \$w->cvs_info( \$r->cvs_info ) if defined \$r->cvs_info ;\n \$w->rev_id( \$r->rev_id ) if defined \$r->rev_id ;\n \$w->change_id( \$r->change_id ) if defined \$r->change_id ;\n \$w->time( _ISO8601 \$r->time ) if defined \$r->time ;\n \$w->mod_time( _ISO8601 \$r->mod_time ) if defined \$r->mod_time ;\n \$w->user_id( \$r->user_id ) if defined \$r->user_id;\n\n ## Sorted for readability & testability\n \$w->label( \$_ ) for sort \$r->labels ;\n\n if ( defined \$r->comment && length \$r->comment ) {\n \$w->start_comment ;\n my \$c = \$r->comment ;\n _emit_characters( \$w, \\\$c ) ;\n \$w->end_comment ;\n \$w->setDataMode( 1 ) ;\n }\n\n my \$convert_crs = \$^O =~ /Win32/ && ( \$r->type || "" ) eq "text" ;\n\n my \$digestion ;\n my \$close_it ;\n my \$cp = \$r->work_path ;\n if ( \$is_base_rev ) {\n sysopen( F, \$cp, O_RDONLY ) or die "\$!: \$cp\\n" ;\n binmode F ;\n \$digestion = 1 ;\n \$close_it = 1 ;\n }\n elsif ( \$r->action eq 'delete' ) {\n \$w->delete() ;\n \$self->delete_seen( \$r ) ;\n }\n else {\n sysopen( F, \$cp, O_RDONLY ) or die "\$!: \$cp\\n" ;\n ## need to binmode it so ^Z can pass through, need to do \\r and\n ## \\r\\n -> \\n conversion ourselves.\n binmode F ;\n \$close_it = 1 ;\n\n my \$buf ;\n my \$read ;\n my \$has_nul ;\n my \$total_char_count = 0 ;\n my \$bin_char_count = 0 ;\n while ( ! \$has_nul ) {\n\x09 \$read = sysread( F, \$buf, 100_000 ) ;\n\x09 die "\$! reading \$cp\\n" unless defined \$read ;\n\x09 last unless \$read ;\n\x09 \$has_nul = \$buf =~ tr/\\x00// ;\n\x09 \$bin_char_count += \$buf =~ tr/\\x00-\\x08\\x0b-\\x1f\\x7f-\\xff// ;\n\x09 \$total_char_count += length \$buf ;\n } ;\n\n sysseek( F, 0, 0 ) or die "\$! seeking on \$cp\\n" ;\n \n \$buf = '' unless \$read ;\n ## base64 generate 77 chars (including the newline) for every 57 chars\n ## of input. A '' element is 20 chars.\n my \$encoding = \$bin_char_count * 20 > \$total_char_count * 77/57\n\x09 ? "base64"\n\x09 : "none" ;\n\n if ( ! \$saw ## First rev, can't delta\n || ! defined \$saw->work_path ## No file, can't delta\n\x09 || \$has_nul ## patch would barf, can't delta\n\x09 || \$encoding ne "none" ## base64, can't delta\n ) {\n ## Full content, no delta.\n\x09 \$w->start_content( encoding => \$encoding ) ;\n\x09 my \$delete_nl ;\n\x09 while () {\n\x09 ## Odd chunk size is because base64 is most concise with\n\x09 ## chunk sizes a multiple of 57 bytes long.\n\x09 \$read = sysread( F, \$buf, 57_000 ) ;\n\x09 die "\$! reading \$cp\\n" unless defined \$read ;\n\x09 last unless \$read ;\n\x09 if ( \$convert_crs ) {\n\x09 substr( \$buf, 0, 1 ) = ""\n\x09\x09 if \$delete_nl && substr( \$buf, 0, 1 ) eq "\\n" ;\n \$delete_nl = substr( \$buf, -1 ) eq "\\n" ;\n\x09 \$buf =~ s/(\\r\\n|\\r)/\\n/g ; ## ouch, that's gotta hurt.\n\x09 }\n\x09 if ( \$encoding eq "none" ) {\n\x09 _emit_characters( \$w, \\\$buf ) ;\n\x09 }\n\x09 else {\n\x09 \$w->characters( encode_base64( \$buf ) ) ;\n\x09 }\n\x09 }\n\x09 \$w->end_content ;\n\x09 \$w->setDataMode( 1 ) ;\n }\n else {\n ## Delta from previous version\n\x09 \$w->base_name( \$saw->name )\n\x09 if \$saw->name ne \$r->name ;\n\x09 \$w->base_rev_id( \$saw->rev_id ) ;\n\n\x09 \$w->start_delta( type => 'diff-u', encoding => 'none' ) ;\n\n\x09 my \$old_cp = \$saw->work_path ;\n\n\x09 die "vcp: no old work path for '", \$saw->name, "'\\n"\n\x09 unless defined \$old_cp && length \$old_cp ;\n\n\x09 die "vcp: old work path '\$old_cp' not found for '", \$saw->name, "'\\n"\n\x09 unless -f \$old_cp ;\n\n ## TODO: Include entire contents if diff is larger than the contents.\n\n\x09 ## Accumulate a bunch of output so that characters can make a\n\x09 ## knowledgable CDATA vs <& escaping decision.\n\x09 my \@output ;\n\x09 my \$outlen = 0 ;\n\x09 my \$delete_nl ;\n\x09 ## TODO: Write a "minimal" diff output handler that doesn't\n\x09 ## emit any lines from \$old_cp, since they are redundant.\n\x09 diff \$old_cp, \$cp,\n\x09 {\n\x09 ## Not passing file names, so no filename header.\n STYLE => "VCP::DiffFormat",\n\x09 OUTPUT => sub {\n\x09\x09 push \@output, \$_[0] ;\n\x09\x09 ## Assume no lines split between \\r and \\n because\n\x09\x09 ## diff() splits based on lines, so we can just\n\x09\x09 ## do a simple conversion here.\n\x09\x09 \$output[-1] =~ s/\\r\\n|\\r/\\n/g if \$convert_crs ;\n\x09\x09 \$outlen += length \$_[0] ;\n\x09\x09 return unless \$outlen > 100_000 ;\n\x09\x09 _emit_characters( \$w, \\join "", splice \@output ) ;\n\x09 },\n\x09 } ;\n\x09 _emit_characters( \$w, \\join "", splice \@output ) if \$outlen ;\n\x09 \$w->end_delta ;\n\x09 \$w->setDataMode( 1 ) ;\n } ;\n \$digestion = 1 ;\n }\n\n if ( \$digestion ) {\n ## TODO: See if this should be seek or sysseek.\n sysseek F, 0, 0 or die "\$!: \$cp" ;\n my \$d= Digest::MD5->new ;\n ## gotta do this by hand, since it's in binmode and we want\n ## to handle ^Z and lone \\r's.\n my \$delete_nl ;\n my \$read ;\n my \$buf ;\n while () {\n\x09 \$read = sysread( F, \$buf, 10_000 ) ;\n\x09 die "\$! reading \$cp\\n" unless defined \$read ;\n\x09 last unless \$read ;\n\x09 if ( \$convert_crs ) {\n\x09 substr( \$buf, 0, 1 ) = ""\n\x09 if \$delete_nl && substr( \$buf, 0, 1 ) eq "\\n" ;\n\x09 \$delete_nl = substr( \$buf, -1 ) eq "\\n" ;\n\x09 \$buf =~ s/(\\r\\n|\\r)/\\n/g ; ## ouch, that's gotta hurt.\n\x09 }\n\x09 \$d->add( \$buf ) ;\n }\n \$d->addfile( \\*F ) ;\n \$w->digest( \$d->b64digest, type => 'MD5', encoding => 'base64' ) ;\n }\n if ( \$close_it ) {\n close F ;\n }\n\n \$w->end_rev ;\n\n# \$self->seen( \$r ) ;\n}\n\n\nsub handle_footer {\n my VCP::Dest::revml \$self = shift ;\n my ( \$footer ) = \@_ ;\n\n \$self->writer->endAllTags() unless \$self->none_seen ;\n\n return ;\n}\n\n\nsub writer {\n my VCP::Dest::revml \$self = shift ;\n \$self->{WRITER} = shift if \@_ ;\n return \$self->{WRITER} ;\n}\n\n\n=back\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACO #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < cvs:module\n vcp cvs:CVSROOT:module\n\nwhere module is a module or directory that already exists within CVS.\n\nThis destination driver will check out the indicated destination in\na temporary directory and use it to add, delete, and alter files.\n\n=head1 DESCRIPTION\n\nThis driver allows L to insert revisions in to a CVS\nrepository. There are no options at this time.\n\nTODO: Skip all directories named "CVS".\n\n=cut\n\n\$VERSION = 1 ;\n\nuse strict ;\nuse vars qw( \$debug ) ;\n\n\$debug = 0 ;\n\nuse Carp ;\nuse File::Basename ;\nuse File::Path ;\nuse Getopt::Long ;\nuse VCP::Debug ':debug' ;\nuse VCP::Rev ;\n\nuse base qw( VCP::Dest VCP::Utils::cvs ) ;\nuse fields (\n 'CVS_CHANGE_ID', ## The current change_id in the rev_meta sequence, if any\n 'CVS_LAST_MOD_TIME', ## A HASH keyed on working files of the mod_times of\n ## the previous revisions of those files. This is used\n\x09\x09 ## to make sure that new revision get a different mod_time\n\x09\x09 ## so that CVS never thinks that a new revision hasn't\n\x09\x09 ## changed just because the VCP::Source happened to create\n\x09\x09 ## two files with the same mod_time.\n 'CVS_PENDING_COMMAND', ## "add" or "edit"\n 'CVS_PENDING', ## Revs to be committed\n## These next fields are used to detect changes between revs that cause a\n## commit. Commits are batched for efficiency's sake.\n 'CVS_PREV_CHANGE_ID', ## Change ID of previous rev\n 'CVS_PREV_COMMENT', ## Revs to be committed\n) ;\n\n## Optimization note: The slowest thing is the call to "cvs commit" when\n## something's been added or altered. After all the changed files have\n## been checked in by CVS, there's a huge pause (at least with a CVSROOT\n## on the local filesystem). So, we issue "cvs add" whenever we need to,\n## but we queue up the files until a non-add is seem. Same for when\n## a file is edited. This preserves the order of the files, without causing\n## lots of commits. Note that we commit before each delete to make sure\n## that the order of adds/edits and deletes is maintained.\n\n#=item new\n#\n#Creates a new instance of a VCP::Dest::cvs. Contacts the cvsd using the cvs\n#command and gets some initial information ('cvs info' and 'cvs labels').\n#\n#=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Dest::cvs \$self = \$class->SUPER::new( \@_ ) ;\n\n ## Parse the options\n my ( \$spec, \$options ) = \@_ ;\n\n \$self->parse_repo_spec( \$spec ) ;\n \$self->deduce_rev_root( \$self->repo_filespec ) ;\n\n {\n local *ARGV = \$options ;\n GetOptions(\n "NoFreakinOptionsAllowed" => \\undef,\n )\n\x09 or \$self->usage_and_exit ;\n }\n\n \$self->command_stderr_filter(\n qr{^(?:cvs (?:server|add|remove): (re-adding|use 'cvs commit' to).*)\\n}\n ) ;\n\n return \$self ;\n}\n\n\nsub handle_header {\n my VCP::Dest::cvs \$self = shift ;\n\n debug "vcp: first rev" if debugging \$self ;\n \$self->rev_root( \$self->header->{rev_root} )\n unless defined \$self->rev_root ;\n\n \$self->create_cvs_workspace ;\n\n \$self->{CVS_PENDING_COMMAND} = "" ;\n \$self->{CVS_PENDING} = [] ;\n \$self->{CVS_PREV_COMMENT} = undef ;\n \$self->{CVS_PREV_CHANGE_ID} = undef ;\n\n \$self->SUPER::handle_header( \@_ ) ;\n}\n\n\nsub checkout_file {\n my VCP::Dest::cvs \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n debug "vcp: \$r checking out ", \$r->as_string, " from cvs dest repo"\n if debugging \$self ;\n\n my \$fn = \$r->name ;\n my \$work_path = \$self->work_path( \$fn ) ;\n debug "vcp: work_path '\$work_path'" if debugging \$self ;\n\n my \$saw = \$self->seen( \$r ) ;\n\n die "Can't backfill already seen file '", \$r->name, "'" if \$saw ;\n\n my ( undef, \$work_dir ) = fileparse( \$work_path ) ;\n \$self->mkpdir( \$work_path ) unless -d \$work_dir ;\n\n my \$tag = "r_" . \$r->rev_id ;\n \$tag =~ s/\\W+/_/g ;\n\n ## Ok, the tricky part: we need to use a tag, but we don't want it\n ## to be sticky, or we get an error the next time we commit this\n ## file, since the tag is not likely to be a branch revision.\n ## Apparently the way to do this is to print it to stdout on update\n ## (or checkout, but we used update so it works with a \$fn relative\n ## to the cwd, ie a \$fn with no module name first).\n## The -kb is a hack to get the tests to pass on Win32, where \\n\n## becomes \\r\\n on checkout otherwise. TODO: figure out what is\n## the best thing to do. We might try it without the -kb, then\n## if the digest check fails, try it again with -kb. Problem is\n## that said digest check occurs in VCP/Source/revml, not here,\n## so we need to add a "can retry" return result to the API and\n## modify the Sources to use it if a digest check fails.\n \$self->cvs(\n [ qw( update -d -kb -p ), -r => \$tag, \$fn ],\n '>', \$work_path\n ) ;\n die "'\$work_path' not created by cvs checkout" unless -e \$work_path ;\n\n return \$work_path ;\n}\n\n\nsub backfill {\n my VCP::Dest::cvs \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n \$r->work_path( \$self->checkout_file( \$r ) ) ;\n\n return 1 ;\n}\n\nmy \$old_r ;\nsub handle_rev {\n my VCP::Dest::cvs \$self = shift ;\n\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n if ( \n ( \@{\$self->{CVS_PENDING}} )#|| \$self->{CVS_DELETES_PENDING} )\n && (\n \@{\$self->{CVS_PENDING}} > 25 ## Limit command line length\n\x09 || (\n\x09 defined \$r->change_id && defined \$self->{CVS_PREV_CHANGE_ID}\n\x09 && \$r->change_id ne \$self->{CVS_PREV_CHANGE_ID}\n\x09 && ( debugging( \$self ) ? debug "vcp: change_id changed" : 1 )\n\x09 )\n\x09 || (\n\x09 defined \$r->comment && defined \$self->{CVS_PREV_COMMENT}\n\x09 && \$r->comment ne \$self->{CVS_PREV_COMMENT}\n\x09 && ( debugging( \$self ) ? debug "vcp: comment changed" : 1 )\n\x09 )\n\x09 || (\n\x09 grep( \$r->name eq \$_->name, \@{\$self->{CVS_PENDING}} )\n\x09 && ( debugging( \$self ) ? debug "vcp: name repeated" : 1 )\n\x09 )\n )\n ) {\n debug "vcp: committing on general principles" if debugging \$self ;\n \$self->commit ;\n }\n\n \$self->compare_base_revs( \$r )\n if \$r->is_base_rev && defined \$r->work_path ;\n\n ## Don't save the reference. This forces the DESTROY to happen here,\n ## if possible. TODO: Keep VCP::Rev from deleting files prematurely.\n my \$saw = ! ! \$self->seen( \$r ) ;\n\n return if \$r->is_base_rev ;\n\n my \$fn = \$r->name ;\n my \$work_path = \$self->work_path( \$fn ) ;\n\n if ( \$r->action eq 'delete' ) {\n \$self->commit ;\n unlink \$work_path || die "\$! unlinking \$work_path" ;\n \$self->cvs( ['remove', \$fn] ) ;\n ## Do this commit by hand since there are no CVS_PENDING revs, which\n ## means \$self->commit will not work. It's relatively fast, too.\n \$self->cvs( ['commit', '-m', \$r->comment || '', \$fn] ) ;\n \$self->delete_seen( \$r ) ;\n }\n else {\n ## TODO: Move this in to commit().\n {\n\x09 my ( \$vol, \$work_dir, undef ) = File::Spec->splitpath( \$work_path ) ;\n\x09 unless ( -d \$work_dir ) {\n\x09 my \@dirs = File::Spec->splitdir( \$work_dir ) ;\n\x09 my \$this_dir = shift \@dirs ;\n\x09 my \$base_dir = File::Spec->catpath( \$vol, \$this_dir, "" ) ;\n\x09 do {\n\x09 ## Warn: MacOS danger here: "" is like Unix's "..". Shouldn't\n\x09 ## ever be a problem, we hope.\n\x09 if ( length \$base_dir && ! -d \$base_dir ) {\n\x09 \$self->mkdir( \$base_dir ) ;\n\x09\x09 ## We dont' queue these to a PENDING because these\n\x09\x09 ## should be pretty rare after the first checkin. Could\n\x09\x09 ## have a modal CVS_PENDING with modes like "add", "remove",\n\x09\x09 ## etc. and commit whenever the mode's about to change,\n\x09\x09 ## I guess.\n\x09\x09 \$self->cvs( ["add", \$base_dir] ) ;\n\x09 }\n\x09 \$this_dir = shift \@dirs ;\n\x09 \$base_dir = File::Spec->catdir( \$base_dir, \$this_dir ) ;\n\x09 } while \@dirs ;\n\x09 }\n }\n if ( -e \$work_path ) {\n\x09 unlink \$work_path or die "\$! unlinking \$work_path" ;\n }\n\n debug "vcp: linking ", \$r->work_path, " to \$work_path"\n if debugging \$self ;\n\n ## TODO: Don't assume same filesystem or working link().\n link \$r->work_path, \$work_path\n\x09 or die "\$! linking '", \$r->work_path, "' -> \$work_path" ;\n\n if ( defined \$r->mod_time ) {\n\x09 utime \$r->mod_time, \$r->mod_time, \$work_path\n\x09 or die "\$! changing times on \$work_path" ;\n }\n\n my ( \$acc_time, \$mod_time ) = (stat( \$work_path ))[8,9] ;\n if ( ( \$self->{CVS_LAST_MOD_TIME}->{\$work_path} || 0 ) == \$mod_time ) {\n ++\$mod_time ;\n\x09 debug "vcp: tweaking mod_time on '\$work_path'" if debugging \$self ;\n\x09 utime \$acc_time, \$mod_time, \$work_path\n\x09 or die "\$! changing times on \$work_path" ;\n }\n \$self->{CVS_LAST_MOD_TIME}->{\$work_path} = \$mod_time ;\n\n \$r->dest_work_path( \$fn ) ;\n\n if ( ! \$saw ) {\n\x09 ## New file.\n\x09 my \@bin_opts = \$r->type ne "text" ? "-kb" : () ;\n\x09 \$self->commit if \$self->{CVS_PENDING_COMMAND} ne "add" ;\n\x09 \$self->cvs( [ "add", \@bin_opts, "-m", \$r->comment || '', \$fn ] ) ;\n\x09 \$self->{CVS_PENDING_COMMAND} = "add" ;\n }\n else {\n\x09 \$self->commit if \$self->{CVS_PENDING_COMMAND} ne "edit" ;\n\x09 \$self->{CVS_PENDING_COMMAND} = "edit" ;\n }\n\n# ## TODO: batch the commits when the comment changes or we see a\n# ## new rev for a file with a pending commit..\n# \$self->cvs( ['commit', '-m', \$r->comment || '', \$fn] ) ;\n#\ndebug "\$r pushing ", \$r->dest_work_path if debugging \$self ;\n push \@{\$self->{CVS_PENDING}}, \$r ;\n }\n\n \$self->{CVS_PREV_CHANGE_ID} = \$r->change_id ;\n \$self->{CVS_PREV_COMMENT} = \$r->comment ;\n}\n\n\nsub handle_footer {\n my VCP::Dest::cvs \$self = shift ;\n\n \$self->commit\n if \$self->{CVS_PENDING} && \@{\$self->{CVS_PENDING}} ;#|| \$self->{CVS_DELETES_PENDING} ;\n \$self->SUPER::handle_footer ;\n}\n\n\nsub commit {\n my VCP::Dest::cvs \$self = shift ;\n\n return unless \@{\$self->{CVS_PENDING}} ;\n\n ## All comments should be the same, since we alway commit when the \n ## comment changes.\n my \$comment = \$self->{CVS_PENDING}->[0]->comment || '' ;\n\n ## \@names was originally to try to convince cvs to commit things in the\n ## preferred order. No go: cvs chooses some order I can't fathom without\n ## reading it's source code. I'm leaving this in for now to keep cvs\n ## from having to scan the working dirs for changes, which may or may\n ## not be happening now (need to check at some point).\n my \@names = map \$_->dest_work_path, \@{\$self->{CVS_PENDING}} ;\n\n \$self->cvs( ['commit', '-m', \$comment, \@names ] ) ;\n\n for my \$r ( \@{\$self->{CVS_PENDING}} ) {\n ## TODO: Don't rtag it with r_ if it gets the same rev number from the\n ## commit.\n ## TODO: Batch files in to the rtag command, esp. for change number tags,\n ## for performance's sake.\n ## TODO: batch tags, too.\n my \@tags = map {\n s/^([^a-zA-Z])/tag_\$1/ ;\n\x09 s/\\W/_/g ;\n\x09 \$_ ;\n }(\n\x09 defined \$r->rev_id ? "r_" . \$r->rev_id : (),\n defined \$r->change_id ? "ch_" . \$r->change_id : (),\n\x09 \$r->labels,\n ) ;\n\n \$self->tag( \$_, \$r->dest_work_path ) for \@tags ;\n ## TODO: Provide command line options for user-defined tag prefixes\n }\n\n \@{\$self->{CVS_PENDING}} = () ;\n \$self->{CVS_PENDING_COMMAND} = "" ;\n}\n\n\nsub tag {\n my VCP::Dest::cvs \$self = shift ;\n\n my \$tag = shift ;\n \$tag =~ s/\\W+/_/g ;\n \$self->cvs( ['tag', \$tag, \@_] ) ;\n}\n\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACP #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < vss:module\n vcp vss:VSSROOT:module\n\nwhere module is a module or directory that already exists within VSS.\n\nThis destination driver will check out the indicated destination in\na temporary directory and use it to add, delete, and alter files.\n\n=head1 DESCRIPTION\n\nB. See L for details.\n\nThis driver allows L to insert revisions in to a VSS\nrepository. There are no options at this time.\n\n=cut\n\n\$VERSION = 1 ;\n\nuse strict ;\nuse vars qw( \$debug ) ;\n\n\$debug = 0 ;\n\nuse Carp ;\nuse File::Basename ;\nuse File::Path ;\nuse Getopt::Long ;\nuse VCP::Debug ':debug' ;\nuse VCP::Rev ;\nuse File::Spec ;\nuse File::Spec::Unix ;\n\nuse base qw( VCP::Dest VCP::Utils::vss ) ;\nuse fields (\n 'VSS_FILES', ## HASH of all VSS files, managed by VCP::Utils::vss\n 'VSS_CHECKED_OUT', ## HASH of whether or not a file has been checked out.\n) ;\n\n## Optimization note: The slowest thing is the call to "vss commit" when\n## something's been added or altered. After all the changed files have\n## been checked in by VSS, there's a huge pause (at least with a VSSROOT\n## on the local filesystem). So, we issue "vss add" whenever we need to,\n## but we queue up the files until a non-add is seem. Same for when\n## a file is edited. This preserves the order of the files, without causing\n## lots of commits. Note that we commit before each delete to make sure\n## that the order of adds/edits and deletes is maintained.\n\n#=item new\n#\n#Creates a new instance of a VCP::Dest::vss. Contacts the vssd using the vss\n#command and gets some initial information ('vss info' and 'vss labels').\n#\n#=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my VCP::Dest::vss \$self = \$class->SUPER::new( \@_ ) ;\n\n ## Parse the options\n my ( \$spec, \$options ) = \@_ ;\n\n \$self->parse_repo_spec( \$spec ) ;\n \$self->deduce_rev_root( \$self->repo_filespec ) ;\n\n {\n local *ARGV = \$options ;\n GetOptions(\n "NoFreakinOptionsAllowed" => \\undef,\n )\n\x09 or \$self->usage_and_exit ;\n }\n\n ## We need to know about the hierarchy under the target path.\n my \$dest_path = \$self->repo_filespec;\n \$dest_path =~ s{([\\\\/]|[\\\\/](\\.\\.\\.|\\*\\*))?\\z}{/...};\n \$self->get_vss_file_list( \$dest_path );\n\n return \$self ;\n}\n\n\nsub handle_header {\n my VCP::Dest::vss \$self = shift ;\n\n debug "vcp: first rev" if debugging \$self ;\n \$self->rev_root( \$self->header->{rev_root} )\n unless defined \$self->rev_root ;\n\n \$self->create_vss_workspace ;\n\n \$self->{VSS_CHECKED_OUT} = {};\n\n \$self->SUPER::handle_header( \@_ ) ;\n}\n\n\nsub checkout_file {\n my VCP::Dest::vss \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n debug "vcp: checking out ", \$r->as_string, " from vss dest repo"\n if debugging \$self ;\n\n my \$fn = \$self->denormalize_name( \$r->name ) ;\n my \$work_path = \$self->work_path( "co", \$fn ) ;\n debug "vcp: work_path '\$work_path'" if debugging \$self ;\n\n my \$saw = \$self->seen( \$r ) ;\n\n Carp::confess "Can't backfill already seen file '", \$r->name, "'" if \$saw ;\n die "Can't backfill already seen file '", \$r->name, "'" if \$saw ;\n\n my ( \$file, \$work_dir ) = fileparse( \$work_path ) ;\n \$self->mkpdir( \$work_path ) unless -d \$work_dir ;\n \$work_dir =~ s{[\\\\/]+\$}{}g;\n\n# my \$tag = "r_" . \$r->rev_id ;\n# \$tag =~ s/\\W+/_/g ;\n#\n my ( undef, \$dirs ) = fileparse( \$fn );\n\n \$self->ss( [ "cp", "\\\$/\$dirs" ] );\n ## This -GN is a hack; it's here because the test suite uses\n ## Unix lineends and the checksums require it.\n \$self->ss( [ "Get", \$file, "-GL\$work_dir", "-GN" ] );\n die "'\$work_path' not created by vss checkout" unless -e \$work_path ;\n\n return \$work_path ;\n}\n\n\nsub backfill {\n my VCP::Dest::vss \$self = shift ;\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n \$r->work_path( \$self->checkout_file( \$r ) ) ;\n\n return 1 ;\n}\n\n\nsub handle_rev {\n my VCP::Dest::vss \$self = shift ;\n\n my VCP::Rev \$r ;\n ( \$r ) = \@_ ;\n\n \$self->compare_base_revs( \$r )\n if \$r->is_base_rev && defined \$r->work_path ;\n\n ## Don't save the reference. This forces the DESTROY to happen here,\n ## if possible. TODO: Keep VCP::Rev from deleting files prematurely.\n my \$saw = ! ! \$self->seen( \$r );\n\n return if \$r->is_base_rev ;\n\n my \$fn = File::Spec->catfile(\n \$self->rev_root,\n \$r->name\n );\n my \$work_path = \$self->work_path( "co", \$fn ) ;\n\n my ( \$vol, \$work_dir, undef ) = File::Spec->splitpath( \$work_path ) ;\n \$work_dir = File::Spec->catpath( \$vol, \$work_dir, "" );\n \$self->mkdir( \$work_dir );\n \$work_dir =~ s{[\\\\/]+\$}{}; ## vss is picky about trailing slashes in -GLpath\n\n if ( -e \$work_path ) {\n unlink \$work_path or die "\$! unlinking \$work_path" ;\n }\n\n ## Add the directories we need to VSS as projects\n my ( \$file, \$dirs ) = fileparse( \$fn );\n \$dirs =~ s{\\\\}{/}g; ## Make debugging output pretty, ss is cool with /\n {\n my \@dirs = File::Spec::Unix->splitdir( \$dirs );\n shift \@dirs while \@dirs && ! length \$dirs[0];\n pop \@dirs while \@dirs && ! length \$dirs[-1];\n\n my \$cur_project = "";\n for ( \@dirs ) {\n \$cur_project .= "/" if length \$cur_project;\n \$cur_project .= \$_;\n\n unless ( \$self->vss_file( \$cur_project ) ) {\n \$self->ss( [ "Create", "\\\$/\$cur_project", "-C-" ] );\n \$self->vss_file( \$cur_project, "project" );\n }\n }\n }\n\n \$self->ss( [ "cp", "\\\$/\$dirs" ] );\n\n if ( \$r->action eq "delete" ) {\n my \$tmp_f = \$self->command_stderr_filter;\n \$self->command_stderr_filter(\n qr{^You have.*checked out.*Y[\\r\\n]*\$}s\n ) ;\n\n \$self->ss( [ 'Delete', \$file, "-I-y" ] );\n \$self->vss_file( \$fn, 0 );\n \$self->{VSS_CHECKED_OUT}->{\$fn} = 0;\n ## TODO: Restore the file instead of adding it if it comes back?\n \$self->command_stderr_filter( \$tmp_f );\n }\n else {\n debug "vcp: linking ", \$r->work_path, " to \$work_path"\n if debugging \$self ;\n\n link \$r->work_path, \$work_path\n or die "\$! linking '", \$r->work_path, "' -> \$work_path" ;\n\n if ( defined \$r->mod_time ) {\n utime \$r->mod_time, \$r->mod_time, \$work_path\n or die "\$! changing times on \$work_path" ;\n }\n\n my \$comment_flag = "-C-";\n if ( defined \$r->comment ) {\n my \$cfn = \$self->work_path( "comment.txt" ) ;\n open COMMENT, ">\$cfn" or die "\$!: \$cfn";\n print COMMENT \$r->comment or die "\$!: \$cfn";\n close COMMENT or die "\$!: \$cfn";\n \$comment_flag = "-C\\\@\$cfn";\n }\n\n my \$check_it_in = 1;\n\n if ( ! \$self->{VSS_CHECKED_OUT}->{\$fn} ) {\n my \$bin_flag = \$r->type ne "text" ? "-B" : "-B-";\n# my \$tmp_f = \$self->command_stderr_filter;\n# \$self->command_stderr_filter(\n# qr{^A deleted file of the same name already exists.*|[\\r\\n]*\$}s\n# ) ;\n\n my \$stderr = "";\n if ( ! \$self->vss_file_is_active( \$fn ) ) {\n ## If the file has been deleted before, -I-y causes ss to recover it\n ## instead of adding it anew.\n \$check_it_in = 0;\n \$self->ss(\n [ "Add", \$work_path, "-K", \$bin_flag, \$comment_flag, "-I-y" ],\n '2>', \\\$stderr,\n );\n }\n\n if ( \$stderr =~ /A deleted file of the same name already exists/ ) {\n \$check_it_in = 1;\n \$self->ss(\n [ "Checkout", \$file, "-G-" ]\n );\n\n }\n elsif ( length \$stderr ) {\n die "unexpected stderr from ss Add:\\n", \$stderr;\n }\n \$self->vss_file( \$fn, "file" );\n \$self->{VSS_CHECKED_OUT}->{\$fn}++;\n# \$self->command_stderr_filter( \$tmp_f );\n }\n\n if ( \$check_it_in ) {\n ## TODO: Don't assume same filesystem or working link().\n my \$tmp_f = \$self->command_stderr_filter;\n \$self->command_stderr_filter(\n qr{^.*was checked out from.*not from the current folder\\.\\r?\\nContinue.*\\r?\\n}\n ) ;\n\n \$self->ss(\n [ "Checkin", \$file, "-GL\$work_dir", "-K", "-I-y", \$comment_flag ]\n );\n \$self->command_stderr_filter( \$tmp_f );\n }\n\n my \@labels = map {\n s/^([^a-zA-Z])/tag_\$1/ ;\n\x09 s/\\W/_/g ;\n\x09 \$_ ;\n }(\n\x09 \$r->labels, ## Put the existing label (if any) first\n\x09 defined \$r->rev_id ? "r_" . \$r->rev_id : (),\n defined \$r->change_id ? "ch_" . \$r->change_id : (),\n ) ;\n\n my \$version;\n\$self->ss( [ "cp", "\\\$/\$dirs" ] );\n \$self->ss( [ "History", \$file, "-#1" ], ">", \\\$version );\n undef \$version unless\n \$version =~ s/.*^\\*+\\s+Version\\s+(\\d+)\\s+\\*+\$(.*)/\$1/ms;\n ## Don't replace existing labels; this can happen if the version\n ## was recovered from a previous delete, for instance.\n undef \$version if \$2 =~ /^Label:\\s+/m;\n\n for ( \@labels ) {\n \$self->ss( [\n "Label",\n \$file,\n "-L\$_",\n "-C-",\n "-I-y", ## Yes, please reuse the label\n defined \$version ? "-V\$version" : ()\n ]);\n\n undef \$version;\n }\n }\n}\n\n=head1 TODO\n\nThis module is here purely to support the VCP test suite, which must\nimport a bunch of files in to VSS before it can test the export. It works,\nbut is not field tested.\n\nWhile I'm sure there exist pressing reasons for importing files in to\nVSS from other repositories, I have never had such a request and do not\nwish to invest a lot of effort in advance of such a request.\n\nTherefore, this module does not batch checkins, cope with branches,\noptimize comment settings, etc.\n\nPatches or contracts welcome.\n\n=head1 NOTES\n\nVSS does not flag individual revisions as binary vs. text; the change is\nmade on a per-file basis. This module does not alter the filetype on\nC, however it does set binary (-B) vs. text (-B-) on C.\n\nVSS allows one label per file, and adding a label (by default) causes a\nnew versions of the file. This module adds the first label it receives\nfor a file (which is first may or may not be predictable depending on\nthe source repository) to the existing version unless the existing\nversion already has a label, then it just adds new versions as needed.\n\nThis leads to the backfilling issue: when backfilling, there are no labels\nto request, so backfilling always assumes that the most recent rev is the\nbase rev for incremental imports.\n\nThe C command does not allow a comment.\n\nFiles are recalled from deleted status when added again if they were\ndeleted.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nCopyright (c) 2000, 2001, 2002 Perforce Software, Inc.\nAll rights reserved.\n\nSee L (C) for the terms of use.\n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACQ #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAACR #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew <new ;\n\n ## To parse a .dtd file:\n \$rmldt = RevML::Doctype->new( 'revml.dtd' );\n \$rmldt = RevML::Doctype->new( DTD_FILE => 'revml.dtd' );\n\n ## To load a preparsed .pm file\n \$rmldt = RevML::Doctype->new( 1.1 ) ;\n \$rmldt = RevML::Doctype->new( VERSION => 1.1 ) ;\n\n\n=head1 DESCRIPTION\n\n=head1 METHODS\n\n=over\n\n=cut\n\nuse strict ;\n\nuse Carp ;\n\nuse XML::Doctype ;\n\nuse base 'XML::Doctype' ;\n\nuse fields (\n) ;\n\nuse vars qw( \$VERSION ) ;\n\n\$VERSION = 0.1 ;\n\n\n=item new\n\nCreates an instance.\n\n=cut\n\nsub _highest_doctype_pm_version {\n my \$ver = 0 ;\n for ( \@INC ) {\n for ( glob "\$_/RevML/Doctype/*.pm" ) {\n\x09 next unless s{.*/v([\\d_]+)\\.pm\$}{\$1} ;\n\x09 tr/_/./ ;\n\x09 \$ver = \$_ if \$_ > \$ver;\n } \n }\n return \$ver ;\n}\n\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my ( \$dtd_spec ) = \@_ ;\n\n \$dtd_spec = _highest_doctype_pm_version\n if ! defined \$dtd_spec || \$dtd_spec eq 'DEFAULT' ;\n\n die "No RevML::Doctype found, use -dtd option or install a RevML::DocType::vXXX module\\n"\n unless \$dtd_spec ;\n\n ## Try to load \$self from a file, or bless one ourself and parse a DTD.\n my RevML::Doctype \$self ;\n\n if ( \$dtd_spec =~ /^\\d+(?:\\.\\d+)*\$/ ) {\n ## TODO: Make the save format provide a new(), or be data-only.\n my \$doctype_pm = \$dtd_spec ;\n \$doctype_pm =~ tr/./_/ ;\n require "RevML/Doctype/v\$doctype_pm.pm" ;\n no strict 'refs' ;\n \$self = \${"RevML::Doctype::v\$doctype_pm\\::doctype"} ;\n die \$\@ if \$\@ ;\n }\n else {\n ## Read in the DTD from a file.\n {\n\x09 no strict 'refs' ;\n\x09 \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n ## Read in the file instead of referring to an external entitity to\n ## get more meaningful error messages. It's short.\n ## TODO: This is probably the result of a minor tail-chasing incident\n ## and we might be able to go back and read the file directly\n open( DTD, "<\$dtd_spec" ) or die "\$!: \$dtd_spec" ;\n my \$dtd = join( '', ) ;\n close DTD ;\n \$self = \$class->SUPER::new( 'revml', DTD_TEXT => \$dtd ) ;\n }\n\n die "Unable to load DTD", defined \$dtd_spec ? " '\$dtd_spec'" : '', "\\n"\n unless \$self ;\n\n die "No version attribute found"\n unless defined \$self->version ;\n\n return \$self ;\n}\n\n\n=item save_as_pm\n\n \$doctype->save_as_pm ;\n \$doctype->save_as_pm( \$out_spec ) ;\n\nOutspec is a module name. 'RevML::Doctype::vNNN' is assumed if\nno outspec is provided. Use '-' to emit to STDOUT.\n\nSaves the Doctype object in a perl module. Tries to save in\nlib/RevML/Doctype/ if that directory exists, then in ./ if not.\n\n=cut\n\nsub save_as_pm {\n my RevML::Doctype \$self = shift ;\n\n my ( \$out_spec ) = \@_ ;\n ## TODO: Try to prevent accidental overwrites by looking for\n ## the destination and diffing, then promping if a diff is\n ## found.\n \$out_spec = "RevML::Doctype::v" . \$self->version\n unless defined \$out_spec ;\n\n \$out_spec =~ s/\\./_/g ;\n\n if ( \$out_spec ne '-' ) {\n my \$out_file = \$out_spec ;\n \$out_file =~ s{::}{/}g ;\n \$out_file =~ s{^/+}{}g ;\n \$out_file .= '.pm' ;\n\n require File::Basename ;\n my \$out_dir = File::Basename::dirname( \$out_file ) ;\n\n if ( -d File::Spec->catdir( 'lib', \$out_dir ) ) {\n\x09 \$out_file = File::Spec->catfile( 'lib', \$out_file ) ;\n }\n elsif ( ! -d \$out_dir ) {\n\x09 \$out_file = File::Basename::fileparse( \$out_file ) ;\n }\n\n print "writing RevML v" . \$self->version . " to '\$out_file' as '\$out_spec'.\\n" ;\n open( F, ">\$out_file" ) || die "\$! \$out_file" ;\n print F \$self->as_pm( \$out_spec ) ;\n close F ;\n\n ## Test for compilability if we saved it.\n exec( 'perl', '-w', \$out_file ) if defined \$out_file ;\n }\n else {\n print \$self->as_pm( \$out_spec ) ;\n }\n\n return ;\n}\n\n\nsub version {\n my RevML::Doctype \$self = shift ;\n return \$self->element_decl( 'revml' )->attdef( 'version' )->default ;\n}\n\n\n=item import\n\n=item use\n\n ## To extablish a default RevML::Doctype for the current package:\n use RevML::Doctype 'DEFAULT' ;\n use RevML::Doctype DTD_FILE => 'revml.dtd' ;\n\n=cut\n\n## This inherits XML::Doctype::import, which passes through the args\n## to our constructor.\n\n\n=head1 SUBCLASSING\n\nThis class uses the fields pragma, so you'll need to use base and \npossibly fields in any subclasses.\n\n=head1 COPYRIGHT\n\nCopyright 2000, Perforce Software, Inc. All Rights Reserved.\n\nThis module and the VCP package are licensed according to the terms given in\nthe file LICENSE accompanying this distribution, a copy of which is included in\nL.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=cut\n\n1\n END_OF_FILE_AAAAAAAAAACS #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < 2,\n 'PUBID' => 4,\n 'ELTS' => 1,\n 'SYSID' => 3\n },\n {\n 'rev' => bless( [\n {\n 'NAMES' => 5,\n 'ATTDEFS' => 1,\n 'DECLARED' => 3,\n 'NAME' => 4,\n 'CONTENT' => 2,\n 'TODO' => 7,\n 'PATHS' => 6\n },\n undef,\n '^(?:|||)?(?:)?(?:)?).\n\n=cut\n\nsub defaultWriter(;\$) {\n my \$pkg = \@_ ? shift : caller ;\n \n croak "No default XML::ValidWriter created for package '\$pkg'"\n unless exists \$pkg_writers{\$pkg}\n && \$pkg_writers{\$pkg} ;\n \n}\n\n\n=item doctype\n\n # Using the writer's associated DTD:\n doctype ;\n\n # Ignoring the writer's associated DTD:\n doctype( \$type ) ;\n doctype( \$type, undef, \$system ) ;\n doctype( \$type, \$public, \$system ) ;\n\n \$writer->doctype ;\n ...etc\n\nSee L to emit the entire DTD in the document.\n\nThis checks to make sure that no doctype or elements have been emitted.\n\nA warning is emitted if standalone="yes" was specified in the \ndeclaration and a system id is specified. This is extremely likely to\nbe an error. If you need to silence the warning, write me (see below).\n\nPassing '' or '0' (zero) as a \$public_id or as a \$system_id also generates\na warning, as these are extremely likely to be errors.\n\n=cut\n\nsub doctype {\n my XML::ValidWriter \$self = &_self ;\n my ( \$type, \$public_id, \$system_id ) = \@_ ;\n\n croak " already emitted"\n if defined \$self->{EMITTED_DOCTYPE} ;\n\n croak " can't be emitted after elements"\n if defined \$self->{EMITTED_ROOT} ;\n\n croak "A PUBLIC_ID was specified, but no SYSTEM_ID"\n if \$public_id && ! \$system_id ;\n\n carp "'' passed for a PUBLIC_ID"\n if defined \$public_id && ! \$public_id ;\n\n carp "'' passed for a SYSTEM_ID"\n if defined \$system_id && ! \$system_id ;\n\n carp "SYSTEM_ID specified for a standalone document"\n if defined \$system_id && \$self->{IS_STANDALONE} ;\n\n \$self->rawCharacters(\n ""\n ) ;\n\n \$self->{EMITTED_DOCTYPE} = defined \$type ? \$type : "UNKNOWN" ;\n}\n\n=item emptyTag\n\n emptyTag( \$tag[, attr1 => \$val1... ] ) ;\n \$writer->emptyTag( \$tag[, attr1 => \$val1... ] ) ;\n\nEmits an empty tag like ''. The extra space is for compatibility\nwith XHTML.\n\n=cut\n\nsub emptyTag {\n my XML::ValidWriter \$self = shift ;\n\n ## Sneaky, sneaky...\n return \$self->startTag( \@_, '#EMPTY' ) ;\n}\n\n=item endTag\n\n endTag ;\n endTag( 'a' ) ;\n \$writer->endTag ;\n \$writer->endTag( 'a' ) ;\n\nPrints one or more end tags. The tag name is optional and defaults to the\nmost recently emitted start tag if not present.\n\nThis will emit as many close tags as necessary to close the supplied tag\nname, or will emit an error if the tag name specified is not open in the\noutput document.\n\n=cut\n\nsub endTag {\n my XML::ValidWriter \$self = &_self ;\n\n \$self->{CHECKED_XML_DECL} ||=\n ( carp( "No emitted." ), 1 ) ;\n\n my \$stack = \$self->{STACK} ;\n unless ( \@\$stack ) {\n my \$tag = \@_ ? shift : '' ;\n if ( \$self->{EMITTED_ROOT} ) {\n\x09 croak "Too many end tags emitted" .\n\x09 ( \$tag ? ", can't emit '\$tag'" : '' ) ;\n }\n\n croak "Can't endTag(", \$tag ? " '\$tag' " : '',\n ") when no tags have been emitted" ;\n }\n\n my \$se = pop \@\$stack ;\n my \$tag = \@_ ? shift : \$se->{NAME} ;\n croak "Unmatched , open tags are: ",\n join( '', map "<\$_->{NAME}>", \@\$stack, \$se )\n if \$tag ne \$se->{NAME} ;\n\n unless ( \$se->{ELT_DECL}->validate_content( \$se->{CONTENT} ) ) {\n if ( \@{\$se->{CONTENT}} ) {\n\x09 croak(\n\x09 "Invalid content for <\$tag>: " .\n\x09 join( '', map "<\$_>", \@{\$se->{CONTENT}} ) \n\x09 )\n }\n else {\n croak "Content required for <\$tag>" ;\n }\n }\n\n my \$prefix = '' ;\n if ( \$self->{DATA_MODE} && \$self->{WAS_END_TAG} ) {\n \$prefix = " " x ( 3 * \@\$stack ) ;\n }\n\n if ( \$self->{STRAGGLERS} eq '>' ) {\n ## Last thing emitted was a start tag.\n \$self->{STRAGGLERS} = '' ;\n \$self->rawCharacters(\n ' />',\n ! \@{\$stack} || \$self->getDataMode ? "\\n" : ()\n ) ;\n }\n else {\n \$self->rawCharacters(\n\x09 \$prefix, '',\n\x09 ! \@{\$stack} || \$self->getDataMode ? "\\n" : ()\n ) ;\n }\n\n \$self->{WAS_END_TAG} = 1 ;\n}\n\n\n=item end\n\n \$writer->end ; # Not a function!!\n\nEmits all necessary end tags to close the document. Available as a method\nonly, since 'end' is a little to generic to be exported as a function\nname, IMHO. See 'endAllTags' for the plain function equivalent function.\n\n=cut\n\nsub end {\n # Well, I lied, you could call it as a function.\n my XML::ValidWriter \$self = &_self ;\n\n \$self->endTag() while \@{\$self->{STACK}} ;\n\n croak "No root element emitted"\n unless defined \$self->{EMITTED_ROOT} ;\n}\n\n\n=item endAllTags\n\n endAllTags ;\n \$writer->endAllTags ;\n\nA plain function that emits all necessart end tags to close the document.\nCorresponds to the method C, but is exportable as a function/\n\n=cut\n\n{\n no strict 'refs' ;\n *{"endAllTags"} = \\&end ;\n}\n\n=item exportDTDTags\n\n \$writer->exportDTDTags() ;\n \$writer->exportDTDTags( \$to_pkg ) ;\n\nExports the tags found in the DTD to the caller's namespace.\n\n=cut\n\nsub exportDTDTags {\n my XML::ValidWriter \$self = &_self ;\n\n my \$pkg = ref \$self ;\n my \$callpkg = \@_ ? shift : caller ;\n\n my \$doctype = \$self->{DOCTYPE} ;\n\n croak "No DOCTYPE specified to export tags from"\n unless \$doctype ;\n\n ## Export tag() and end_tag(), tag_element(), and empty_tag() ;\n no strict 'refs' ;\n for my \$tag ( \$doctype->element_names ) {\n *{"\$callpkg\\::start_\$tag"} = sub {\n\x09 \$pkg_writers{\$callpkg}->startTag( \$tag, \@_ ) ;\n },\n\n *{"\$callpkg\\::end_\$tag"} = sub {\n\x09 \$pkg_writers{\$callpkg}->endTag( \$tag, \@_ ) ;\n },\n\n *{"\$callpkg\\::empty_\$tag"} = sub {\n\x09 \$pkg_writers{\$callpkg}->emptyTag( \$tag, \@_ ) ;\n },\n\n *{"\$callpkg\\::\$tag"} = sub {\n\x09 \$pkg_writers{\$callpkg}->dataElement( \$tag, \@_ ) ;\n },\n\n }\n\n}\n\n\n\n=item getDataMode\n\n \$m = getDataMode ;\n \$m = \$writer->getDataMode ;\n\nReturns TRUE if the writer is in DATA_MODE.\n\n=cut\n\nsub getDataMode {\n my XML::ValidWriter \$self = shift ;\n\n return \$self->{DATA_MODE} ;\n}\n\n\n=item getDoctype\n\n \$dtd = getDoctype ;\n \$dtd = \$writer->getDoctype ;\n\nThis is used to get the writer's XML::Doctype object.\n\n=cut\n\nsub getDoctype {\n my XML::ValidWriter \$self = &_self ;\n return \$self->{DOCTYPE} ;\n}\n\n=item getOutput\n\n \$fh = getOutput ;\n \$fh = \$writer->getOutput ;\n\nGets the filehandle an XML::ValidWriter sends output to.\n\n=cut\n\nsub getOutput {\n my XML::ValidWriter \$self = &_self ;\n return \$self->{OUTPUT} ;\n}\n\n\n=item rawCharacters\n\n rawCharacters( "", "& more text" ) ;\n \$writer->rawCharacters( "", "& more text" ) ;\n\nThis allows you to emit raw text without any escape processing. The text\nis not examined for tags, so you can invalidate your document and even\ncorrupt it's well-formedness.\n\n=cut\n\n## This is called everywhere to emit raw characters *except* characters(),\n## which must go direct because it uses STRAGGLERS and CDATA_END_PART\n## differently.\nsub rawCharacters {\n my XML::ValidWriter \$self = &_self ;\n\n my \$to= \$self->{OUTPUT} || select ;\n\n return unless grep length \$_, \@_ ;\n\n if ( ref \$to eq 'SCALAR' ) {\n \$\$to .= join(\n '',\n _esc_cdata_ends( \$self->{CDATA_END_PART} ),\n\x09 \$self->{STRAGGLERS},\n\x09 \@_\n ) ;\n \$self->{AT_BOL} = substr( \$\$to, -1, 1 ) eq "\\n" ;\n }\n else {\n no strict 'refs' ;\n\n for ( my \$i = \$#_ ; \$i >= 0 ; --\$i ) {\n next unless length \$_[\$i] ;\n\x09 \$self->{AT_BOL} = substr( \$_[\$i], -1, 1 ) eq "\\n" ;\n\x09 last ;\n }\n\n print \$to\n _esc_cdata_ends( \$self->{CDATA_END_PART} ),\n \$self->{STRAGGLERS},\n\x09 \@_ or croak \$!;\n }\n \$self->{CDATA_END_PART} = '' ;\n \$self->{STRAGGLERS} = '' ;\n}\n\n\n=item reset\n\n \$writer->reset ; # Not a function!\n\nResets a writer to be initialized, but not have emitted anything.\n\nThis is useful if you need to abort output, but want to reuse the\nXML::ValidWriter.\n\n=cut\n\nsub reset {\n my XML::ValidWriter \$self = shift ;\n \$self->{STACK} = [] ;\n\n # If we should warn, clear the flag that says we checked it & vice versa\n \$self->{CHECKED_XML_DECL} = ! \$self->{SHOULD_WARN} ;\n\n ## I'd use assignement to a slice here, but older perls...\n \$self->{IS_STANDALONE} = 0 ;\n \$self->{EMITTED_DOCTYPE} = undef ;\n \$self->{EMITTED_ROOT} = undef ;\n \$self->{EMITTED_XML} = undef ;\n\n \$self->{AT_BOL} = 1 ;\n \$self->{WAS_END_TAG} = 1 ;\n \$self->{STRAGGLERS} = '' ;\n \$self->{CDATA_END_PART} = '' ;\n\n if ( defined \$self->{FILE_NAME} ) {\n if ( defined \$self->{OUTPUT} ) {\n\x09 close \$self->{OUTPUT} or croak "\$! closing '\$self->{FILE_NAME}'." ;\n }\n else {\n\x09 require Symbol ;\n\x09 \$self->{OUTPUT} = Symbol::gensym() ;\n }\n eval "use Fcntl ; 1" or croak \$\@ ;\n open(\n\x09 \$self->{OUTPUT},\n\x09 ">\$self->{FILE_NAME}",\n ) \n\x09 or croak "\$!: \$self->{FILE_NAME}" ;\n }\n\n return ;\n}\n\n\n\n=item setDataMode\n\n setDataMode( 1 ) ;\n \$writer->setDataMode( 1 ) ;\n\nEnable or disable data mode.\n\n=cut\n\nsub setDataMode {\n my XML::ValidWriter \$self = &_self ;\n\n \$self->{DATA_MODE} = shift ;\n return ;\n}\n\n\n\n=item setDoctype\n\n setDoctype \$doctype ;\n \$writer->setDoctype( \$doctype ) ;\n\nThis is used to set the doctype object.\n\n=cut\n\nsub setDoctype {\n my XML::ValidWriter \$self = &_self ;\n \$self->{DOCTYPE} = shift if \@_ ;\n return ;\n}\n\n=item select_xml\n\n select_xml OUTHANDLE ; # Nnot a method!!\n\nSelects a filehandle to send the XML output to when not using the object\noriented interface. This is similar to perl's builtin select,\nbut only affects startTag and endTag functions, (not methods).\n\nThis is only needed if you want to interleave output to the selected \noutput files (usually STDOUT, see L and to an\nXML file on another filehandle.\n\nIf you want to redirect all output (yours and XML::Writer's) to the same\nfile, just use Perl's built-in select(), since startTag and endTag\nemit to the currently selected filehandle by default.\n\nLike select, this returns the old value.\n\n=cut\n\nsub select_xml(;*) {\n ## I cheat a little and this could be used as a method\n my XML::ValidWriter \$self = &_self ;\n\n my \$r = \$self->getOutput ;\n \$self->setOutput( shift ) if \@_ ;\n return \$r ;\n}\n\n=item setOutput\n\n setOutput( \\*FH ) ;\n \$writer->setOutput( \\*FH ) ;\n\nSets the filehandle an XML::ValidWriter sends output to.\n\n=cut\n\nsub setOutput {\n my XML::ValidWriter \$self = &_self ;\n \$self->{OUTPUT} = shift if \@_ ;\n return ;\n}\n\n\n=item startTag\n\n startTag( 'a', attr => val ) ; # use default XML::ValidWriter for\n # current package.\n \$writer->startTag( 'a', attr => val ) ;\n\nEmits a named start tag with optional attributes. If the named tag\ncannot be a child of the most recently started tag, then any tags\nthat need to be opened between that one and the named tag are opened.\n\nIf the named tag cannot be enclosed within the most recently opened\ntag, no matter how deep, then startTag() tries to end as few started tags\nas necessary to allow the named tag to be emitted within a tag already on the\nstack.\n\nThis warns (once) if no declaration has been emitted. It does not\ncheck to see if a has been emitted. It dies if an attempt\nis made to emit a second root element.\n\n=cut\n\nsub startTag {\n my XML::ValidWriter \$self = &_self ;\n my \$tag = shift ;\n croak "Must supply a tag name" unless defined \$tag ;\n\n \$self->{CHECKED_XML_DECL} ||=\n ( carp( "No emitted." ), 1 ) ;\n\n if ( ! \@{\$self->{STACK}} ) {\n if ( defined \$self->{EMITTED_ROOT} ) {\n\x09 croak\n\x09 "Root element '\$self->{EMITTED_ROOT}' ended, can't emit '\$tag'"\n }\n else {\n if ( \$tag ne \$self->{DOCTYPE}->name ) {\n\x09 croak\n\x09 "Root element '\$tag' does not match DOCTYPE '",\n\x09 \$self->getDTD->name,\n\x09 "'"\n\x09 }\n }\n \$self->{EMITTED_ROOT} = \$tag ;\n }\n\n my \$elt_decl = \$self->{DOCTYPE}->element_decl( \$tag ) ;\n\n my \@attrs ;\n my %attrs ;\n ## emptyTag sneaks an '#EMPTY' on the parms and calls us.\n my \$is_empty = \@_ && \$_[-1] eq '#EMPTY'\n ? pop\n : \$elt_decl->is_empty ;\n\n croak "Odd number of parameters passed to startTag( '\$tag' ): ",\n scalar( \@_ )\n if \@_ & 1 ;\n\n while ( \@_ ) {\n my ( \$attr, \$val ) = ( shift, shift ) ;\n\n croak "Undefined attribute name for <\$tag>" \n unless defined \$attr ;\n\n croak "Undefined attribute value for <\$tag>, attribute '\$attr'" \n unless defined \$val ;\n\n croak "Attribute '\$attr' already emitted"\n if \$attrs{\$attr} ;\n\n \$attrs{\$attr} = \$val ;\n\n push \@attrs, ( ' ', \$attr, '="', _attr_esc1( \$val ), '"' ) ;\n }\n\n if ( \$elt_decl ) {\n for my \$attdef ( \$elt_decl->attdefs ) {\n\x09 my \$name = \$attdef->name ;\n\x09 my \$quant = \$attdef->quant ;\n\n\x09 push \@attrs, (\n\x09 ' ',\n\x09 \$name,\n\x09 '="',\n\x09 \$attrs{\$name} = _attr_esc1( \$attdef->default_on_write ),\n\x09 '"'\n\x09 )\n\x09 if ! exists \$attrs{\$name} && defined \$attdef->default_on_write ;\n\n\x09 if ( \$quant eq '#FIXED' ) {\n\x09 if ( exists \$attrs{\$name} ) {\n\x09\x09croak "Attribute '\$name' is #FIXED to '" . \$attdef->default\n\x09\x09 . "' and cannot be emitted as '" . \$attrs{\$name} . "'"\n\x09\x09 if \$attdef->default ne \$attrs{\$name}\n\x09 }\n\x09 else {\n\x09 ## Output #FIXED attributes if they weren't passed\n\x09 push \@attrs, ( ' ', \$name, '="', _attr_esc1( \$attdef->default ), '"' ) ;\n\x09 }\n\x09 }\n\x09 elsif ( \$quant eq '#REQUIRED' ) {\n\x09 croak "Tag '\$tag', attribute '\$name' #REQUIRED, but not provided"\n\x09 unless exists \$attrs{\$name} && defined \$attrs{\$name} ;\n\x09 }\n }\n }\n\n ## TODO: A quick check to see if \$tag can be it's parent's child.\n ## TODO: Incremental data model checking.\n my \$stack = \$self->{STACK} ;\n\n my \$prefix = '' ;\n if ( \$self->{DATA_MODE} ) {\n \$prefix = ( \$self->{AT_BOL} ? "" : "\\n" ) . " " x ( 3 * \@\$stack ) ;\n }\n\n if ( \$is_empty ) {\n \$self->rawCharacters(\n \$prefix, '<', \$tag, \@attrs, ' />',\n ! \@\$stack || \$self->getDataMode ? "\\n" : ()\n ) ;\n }\n else {\n \$self->rawCharacters( \$prefix, '<', \$tag, \@attrs ) ;\n \$self->{STRAGGLERS} = '>' ;\n }\n\n \$stack->[-1]->add_content( \$tag )\n if \@{\$stack} ;\n push \@\$stack, XML::VWElement->new( \$elt_decl )\n unless \$is_empty ;\n\n \$self->{WAS_END_TAG} = \$is_empty ;\n\n return ;\n}\n\n\n=item xmlDecl([[\$encoding][, \$standalone])\n\n xmlDecl ;\n xmlDecl( "UTF-8" ) ;\n xmlDecl( "UTF-8", "yes" ) ;\n \$writer->xmlDecl( ... ) ;\n\nEmits an XML declaration. Must be called before any of the other\noutput routines.\n\nIf \$encoding is not defined, it is not output. This is slightly\ndifferent than XML::Writer, which outputs 'UTF-8' if you pass in\nundef, 0, or ''.\n\nIf \$encoding is '' or 0, then it is output as "" or "0"\nand a warning is generated.\n\nIf \$standalone is defined and is not 'no', 0, or '', it is output as 'yes'.\nIf it is 'no', then it is output as 'no'. If it's 0 or '' it is not\noutput.\n\n=cut\n\nsub xmlDecl {\n my XML::ValidWriter \$self = &_self ;\n\n croak " already emitted"\n if defined \$self->{EMITTED_XML} ;\n\n croak " not the first thing in the document"\n if defined \$self->{EMITTED_DOCTYPE} || defined \$self->{EMITTED_ROOT} ;\n\n my ( \$encoding, \$standalone ) = \@_ ;\n\n if ( defined \$encoding ) {\n carp "encoding '\$encoding' passed"\n if ! \$encoding ;\n }\n\n \$standalone = 'yes' if \$standalone && \$standalone ne 'no' ;\n\n \$self->rawCharacters(\n '\\n"\n ) ;\n\n \$self->{CHECKED_XML_DECL} = 1 ;\n \$self->{IS_STANDALONE} = \$standalone && \$standalone eq 'yes' ;\n # declare open season on tag emission\n \$self->{EMITTED_XML} = 1 ;\n}\n\n=item AUTOLOAD\n\nThis function is called whenever a function or method is not found\nin XML::ValidWriter.\n\nIf it was a method being called, and the desired method name is a start\nor end tag found in the DTD, then a method is cooked up on the fly.\n\nThese methods are slower than normal methods, but they are cached so\nthat they don't need to be recompiled. The speed penalty is probably\nnot significant since they do I/O and are thus usually orders of\nmagnitude slower than normal Perl methods.\n\n=cut\n\n## TODO: Perhaps change exportDTDTags to use AUTOLOAD\n## TODO: Allow caching of methods in package namespace as an option so\n## that specializations of XML::ValidWriter can avoid the AUTOLOAD speed\n## hit.\n\nuse vars qw( \$AUTOLOAD ) ;\n\nsub AUTOLOAD {\n croak "Function \$AUTOLOAD not AUTOLOADable (no functions are)"\n unless isa( \$_[0], __PACKAGE__ ) ;\n\n my XML::ValidWriter \$self = \$_[0] ;\n unless ( exists \$self->{METHODS}->{\$AUTOLOAD} ) {\n my ( \$class, \$ss, \$method ) =\n\x09 \$AUTOLOAD =~ /^(.*)::((?:start_|end_|empty_)?)(.*?)\$/ ;\n croak "Can't parse method name '\$AUTOLOAD'" unless defined \$class ;\n\n croak "Method \$AUTOLOAD does not refer to an element in the XML::Doctype"\n\x09 unless \$self->{DOCTYPE}->element_decl( \$method ) ;\n\n my \$sub = \$ss eq ''\n\x09 ? sub {\n\x09 my XML::ValidWriter \$self = shift ;\n\x09 \$self->dataElement( \$method, \@_ ) ;\n\x09 }\n\x09 : \$ss eq 'start_'\n\x09 ? sub {\n\x09 my XML::ValidWriter \$self = shift ;\n\x09 \$self->startTag( \$method, \@_ ) ;\n\x09 }\n\x09 : \$ss eq 'end_'\n\x09 ? sub {\n\x09 my XML::ValidWriter \$self = shift ;\n\x09 \$self->endTag( \$method, \@_ ) ;\n\x09 }\n\x09 : sub {\n\x09 my XML::ValidWriter \$self = shift ;\n\x09 \$self->emptyTag( \$method, \@_ ) ;\n\x09 }\n\x09 ;\n\n \$self->{METHODS}->{\$AUTOLOAD} = \$sub ;\n }\n \n goto &{\$self->{METHODS}->{\$AUTOLOAD}}\n}\n\n=item DESTROY\n\nDESTROY is called when an XML::ValidWriter is cleaned up. This is used\nto automatically close all tags that remain open. This will not work\nif you have closed the output filehandle that the ValidWriter was\nusing.\n\nThis method will also warn if anything was emitted bit no root node was\nemitted. This warning can be silenced by calling\n\n \$writer->reset() ;\n\nwhen you abandon output.\n\n=cut\n\n##TODO: Prevent \$self->end for errored objects.\n##TODO: Prevent further output to errored objects if they cannot ever\n## be valid. Perhaps prevent it to all errored objects?\n\nsub DESTROY {\n my XML::ValidWriter \$self = shift ;\n\n# if ( \@{\$self->{STACK}} ) {\n# \$self->end() ;\n# }\n\n if ( defined \$self->{FILE_NAME} ) {\n close \$self->{OUTPUT} or croak "\$! closing '\$self->{FILE_NAME}'." ;\n }\n\n if ( ! defined \$self->{EMITTED_ROOT}\n && ( defined \$self->{EMITTED_XML}\n\x09 || defined \$self->{EMITTED_DOCTYPE}\n )\n ) {\n ## TODO: Identify a document name here\n carp "No content emitted after preamble in ",\n ref \$self,\n\x09 " created at ",\n\x09 \$self->{CREATED_AT} ;\n ;\n }\n}\n\n##\n## A tiny helper class of instances ValidWriter places on the stack as\n## it opens new elements\n##\npackage XML::VWElement ;\n\nuse fields qw( NAME ELT_DECL CONTENT ) ;\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my XML::VWElement \$self ;\n {\n no strict 'refs' ;\n \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n my ( \$elt_decl ) = \@_ ;\n\n \$self->{NAME} = \$elt_decl->name ;\n \$self->{ELT_DECL} = \$elt_decl ;\n \$self->{CONTENT} = [] ;\n\n return \$self ;\n}\n\nsub add_content {\n my XML::VWElement \$self = shift ;\n\n for ( \@_ ) {\n if ( ! \@{\$self->{CONTENT}}\n || ! ( \$_ eq '#PCDATA' \n\x09 && \$self->{CONTENT}->[-1] eq '#PCDATA'\n\x09 )\n ) {\n push \@{\$self->{CONTENT}}, \$_ ;\n }\n }\n}\n\n=back\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nThis module is Copyright 2000, Barrie Slaymaker. All rights reserved.\n\nThis module is licensed under the GPL, version 2. Please contact me if this\ndoes not suit your needs.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAACZ #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < 'FooML', SYSTEM_ID => 'FooML.dtd' ;\n use XML::Doctype NAME => 'FooML', DTD_TEXT => \$dtd ;\n\n # Parsing at run-time\n \$doctype = XML::Doctype->new( 'FooML', SYSTEM_ID => 'FooML.dtd' ) ;\n\n # or\n \$doctype = XML::Doctype->new() ;\n \$doctype->parse( 'FooML', 'FooML.dtd' ) ;\n\n # Saving the parsed object\n open( PM, ">FooML/DTD/v1_000.pm" ) or die \$! ;\n print PM \$doctype->as_pm( 'FooML::DTD::v1_000' ) ;\n\n # Using a saved parsed DTD\n use FooML::DTD::v1_000 ;\n\n \$doctype = FooML::DTD::v1_000->new() ;\n\n\n=head1 DESCRIPTION\n\nThis module parses DTDs and allows them to be saved as .pm files and\nreloaded. The ability to save and reload is intended to aid in packaging\nparsed DTDs with XML tools so that XML::Parser need not be installed.\n\n=head1 STATUS\n\nThis module is alpha code. It's developed enough to support XML::ValidWriter,\nbut need a lot of work. Some big things that are lacking are:\n\n=over\n\n=item *\n\nmethods or objects to build / traverse the DTD\n\n=item *\n\nXML::Doctype::ELEMENT\n\n=item *\n\nXML::Doctype::ATTLIST\n\n=item *\n\nXML::Doctype::ENITITY\n\n=back\n\n=cut\n\nuse strict ;\nuse vars qw( \$VERSION %_default_dtds ) ;\nuse fields (\n 'ELTS', # A hash of declared & undeclared elements, keyed by name\n 'NAME', # The root node (the name from the DOCTYPE decl).\n 'SYSID',\n 'PUBID',\n) ;\n\nuse Carp ;\nuse XML::Doctype::ElementDecl ;\nuse XML::Doctype::AttDef ;\n\n\$VERSION = 0.11 ;\n\n=head1 METHODS\n\n=item new\n\n \$doctype = XML::Doctype->new() ;\n \$doctype = XML::Doctype->new( 'FooML', DTD_TEXT => \$doctype_text ) ;\n \$doctype = XML::Doctype->new( 'FooML', SYSTEM_ID => 'FooML.dtd' ) ;\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my XML::Doctype \$self ;\n {\n no strict 'refs' ;\n \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n return \$self unless \@_ ;\n\n my \$name = shift ;\n\n if ( \@_ == 1 ) {\n \$self->parse_dtd_file( \$name, shift ) ;\n }\n else {\n while ( \@_ ) {\n\x09 for ( shift ) {\n\x09 if ( /^SYSTEM(?:_ID)?\$/ ) {\n\x09 \$self->parse_dtd_file( \$name, shift ) ;\n\x09 }\n\x09 elsif ( \$_ eq 'DTD_TEXT' ) {\n\x09 \$self->parse_dtd( \$name, shift ) ;\n\x09 }\n\x09 else {\n\x09 croak "Unrecognized parameter '\$_'" ;\n\x09 }\n\x09 }\n }\n }\n\n ## Do this here so subclass author won't be suprised when eventually\n ## calling save_as_pm.\n no strict 'refs' ;\n croak "\\\$\$class\\::VERSION not defined" \n unless defined \${"\$class\\::VERSION"} ;\n\n return \$self ;\n}\n\n\n=item name\n\n \$name = \$doctype->name() ;\n\n Sets/gets the name.\n\n=cut\n\nsub name {\n my XML::Doctype \$self = shift ;\n \$self->{NAME} = shift if \@_ ;\n return \$self->{NAME}\n}\n\n\n##\n## Called to translate the XML::Parser::ContentModel passed by XML::Parser\n## in to a tree of XML::Doctype::ChildDecl instances.\nsub _import_ContentModel {\n}\n\n\nsub _do_parse {\n my XML::Doctype \$self = shift ;\n my ( \$fake_doc ) = \@_ ;\n\n my \$elts = \$self->{ELTS} = {} ;\n\n ## Should maybe use libwww to fetch URLs, but will do files for now\n ## We require this lazily to save load time and allow it to be\n ## not present if it's not needed.\n require XML::Parser ;\n my \$p = XML::Parser->new(\n ParseParamEnt => 1,\n Handlers => {\n Doctype => sub {\n my \$expat = shift ;\n\x09 my ( \$name, \$sysid, \$pubid, \$internal ) = \@_ ;\n\x09 \$self->{NAME} = \$name ;\n\x09 \$self->{SYSID} = \$sysid ;\n\x09 \$self->{PUBID} = \$pubid ;\n\x09 },\n\x09 \n\x09 Element => sub {\n\x09 my \$expat = shift ;\n\x09 my ( \$name, \$model ) = \@_ ;\n\n\x09 croak "ELEMENT '\$name' already defined"\n\x09 if exists \$elts->{\$name} && \$elts->{\$name}->is_declared ;\n\n my \$elt = XML::Doctype::ElementDecl->new( \$name, \$model ) ;\n\x09 \$elt->is_declared( 1 ) ;\n \$elts->{\$name} = \$elt ;\n\n\x09 for ( \$elt->child_names ) {\n\x09 \$elts->{\$_} = XML::Doctype::ElementDecl->new( \$_ )\n\x09 unless \$elts->{\$_} ;\n\x09 }\n\x09 },\n\n Attlist => sub {\n\x09 my \$expat = shift ;\n\x09 my ( \$elt_name, \$att_name, \$type, \$default, \$fixed ) = \@_ ;\n\n\x09 \$elts->{\$elt_name} = XML::Doctype::ElementDecl->new()\n\x09 unless exists \$elts->{\$elt_name} ;\n\n\x09 \$default =~ s/^'(.*)'\$/\$1/ || \$default =~ s/^"(.*)"\$/\$1/ ;\n\x09 \n\x09 \$elts->{\$elt_name}->add_attdef(\n\x09 XML::Doctype::AttDef->new( \n\x09\x09 \$att_name,\n\x09\x09 \$type,\n\x09\x09 \$fixed ? ( '#FIXED', \$default ) : ( \$default, undef ),\n\x09 )\n\x09 ) ;\n\x09 },\n },\n ) ;\n\n \$p->parse( \$fake_doc ) ;\n\n croak "Doctype",\n defined \$self->{SYSID} ? " SYSTEM_ID \$self->{SYSID}" : (),\n " did not declare root node <\$self->{NAME}>"\n unless exists \$self->{ELTS}->{\$self->{NAME}} ;\n\n# require Data::Dumper ; print Data::Dumper::Dumper( \$elts ) ;\n ## TODO: Check that all elements referred-to by name in the element tree\n ## rooted at \$self->{NAME} are actually declared.\n}\n\n\n=item parse_dtd\n\n \$doctype->parse_dtd( \$name, \$doctype_text ) ;\n \$doctype->parse_dtd( \$name, \$doctype_text, 'internal' ) ;\n\nParses the text of a DTD from a scalar. \$name is used to indicate the\nname of the DOCTYPE, and thus the root node.\n\nThe DTD is considered to be external unless the third parameter is\nTRUE.\n\n=cut\n\nsub parse_dtd {\n my XML::Doctype \$self = shift ;\n my ( \$name, \$text, \$internal ) = \@_ ;\n\n \$self->_do_parse( <\n\n<\$name>\nTOHERE\n}\n\n\n=item parse_dtd_file\n\n \$doctype->parse_dtd_file( \$name, \$system_id [, \$public_id] ) ;\n \$doctype->parse_dtd_file( \$name, \$system_id [, \$public_id], 'internal' ) ;\n\nParses a DTD from a file. Eventually will support full URL syntax.\n\n\$public_id is ignored for now, and \$system_id is used to locate\nthe DTD.\n\nThis routine requires XML::Parser. XML::Parser is not loaded at any\nother time and is not needed to use the resulting DTD object.\n\nThe DTD is considered to be external unless the fourth parameter is\nTRUE.\n\n \$doctype->parse_dtd_file( \$name, \$system_id, \$p_id, 'internal' ) ;\n \$doctype->parse_dtd_file( \$name, \$system_id, undef, 'internal' ) ;\n\n=cut\n\n\nsub parse_dtd_file {\n my XML::Doctype \$self = shift ;\n my ( \$name, \$system_id, undef, \$internal ) = \@_ ;\n\n \$self->_do_parse( <\n\n<\$name>\nTOHERE\n}\n\n\n=item system_id\n\n \$system_id = \$doctype->system_id() ;\n\n Sets/gets the system ID.\n\n=cut\n\nsub system_id {\n my XML::Doctype \$self = shift ;\n \$self->{SYSID} = shift if \@_ ;\n return \$self->{SYSID}\n}\n\n=item public_id\n\n \$public_id = \$doctype->public_id() ;\n\n Sets/gets the public_id.\n\n=cut\n\nsub public_id {\n my XML::Doctype \$self = shift ;\n \$self->{PUBID} = shift if \@_ ;\n return \$self->{PUBID}\n}\n\n=item element_decl\n\n \$elt_decl = \$doctype->element_decl( \$name ) ;\n\nReturns the XML::Doctype:Element object associated with \$name. These can\nbe defined by tags or undefined, which can happen if they\nwere just referred-to by or tags.\n\n=cut\n\nsub element_decl {\n my XML::Doctype \$self = shift ;\n my ( \$name ) = \@_ ;\n\n return \$self->{ELTS}->{\$name} if exists \$self->{ELTS}->{\$name} ;\n return ;\n}\n\n=item element_names\n\nReturns an unsorted list of element names. This list includes names that\nare declared and undeclared (but referred to in element declarations or\nattribute definitions).\n\n=cut\n\nsub element_names {\n my XML::Doctype \$self = shift ;\n my \$h = {} ;\n for ( keys %{\$self->{ELTS}} ) {\n \$h->{\$_} = 1 ;\n \$h->{\$_} = 1 for \$self->{ELTS}->{\$_}->child_names() ;\n }\n\n return keys %\$h ;\n}\n\n\n=item as_pm\n\n open( PM, "FooML/DTD/v1_001.pm" ) or die \$! ;\n print PM \$doctype->as_pm( 'FooML::DTD::v1_001' ) or die \$! ;\n close PM or die \$! ;\n\nThen, later:\n\n use FooML::DTD::v1_001 ; # Do *not* use () as a parameter list!\n\nReturns string containing the DTD as an independant module, allowing the\nDTD to be parsed in the development environment and shipped as Perl code,\nso that the target environment need not have XML::Parser installed.\n\nThis is useful for XML creation-only tools and as an\nefficiency tuning measure if you will be rereading the same set of DTDs over\nand over again.\n\n=cut\n\n## TODO: Save as pure, unblessed data structure that XML::Doctype can\n## convert to internal format, to increase inter-version compatibility.\n\nsub as_pm {\n my XML::Doctype \$self = shift ;\n my ( \$package ) = \@_ ;\n\n my \$date = localtime ;\n my \$class = ref \$self ;\n\n my \$version ;\n if ( \$class ne __PACKAGE__ ) {\n no strict 'refs' ;\n croak "\\\$\$class\\::VERSION not defined" \n unless defined \${"\$class\\::VERSION"} ;\n \$version = "\$class, v" . \${"\$class\\::VERSION"} . ", (" ;\n }\n\n \$version .= __PACKAGE__ . ", v\$VERSION" ;\n \$version .= ')'\n if \$class ne __PACKAGE__ ;\n\n require Data::Dumper ;\n my \$d = Data::Dumper->new( [\$self], ['\$doctype'], ) ;\n# \$d->Freezer( '_freeze' ) unless \$d->can( 'Dumpperl' ) ;\n \$d->Purity(1); ## We really do want to dump executable code.\n \$d->Indent(1); ## Used fixed indent depth. I find this more readable.\n\n return\n join( '', <can( 'Dumpperl' ) ? \$d->Dumpperl : \$d->Dump, "\\n 1 ;\\n" );\npackage \$package ;\n\n##\n## THIS FILE CREATED AUTOMATICALLY: YOU MAY LOSE ANY EDITS IF YOU MOFIFY IT.\n##\n## When: \$date\n## By: \$version\n##\n\nrequire XML::Doctype ;\n\nsub import {\n my \\\$pkg = shift ;\n my \\\$callpkg = caller ;\n \\\$XML::Doctype::_default_dtds{\\\$callpkg} = \\\$doctype ;\n}\n\nENDPREAMBLE\n}\n\n\nsub _freeze {\n my \$self = shift ;\n \$_->_freeze for values %{\$self->{ELTS}} ;\n return \$self ;\n}\n\n\n=item import\n\n=item use\n\n use XML::Doctype NAME => 'FooML', SYSTEM_ID => 'dtds/FooML.dtd' ;\n\nimport() constructs a default DTD object for the calling package\nso that XML::ValidWriter's functional interface can use it.\n\nIf XML::Doctype is subclassed, the subclasses' constructor is called with\nall parameters.\n\n=cut\n\nsub import {\n my \$class = shift ;\n my \$callpkg = caller ;\n\n my \@others ;\n my \@dtd_args ;\n while ( \@_ ) {\n for ( shift ) {\n\x09 if ( \$_ eq 'NAME' ) {\n\x09 push \@dtd_args, shift ;\n\x09 }\n\x09 elsif ( /^[A-Z][A-Z_0-9]*\$/ ) {\n\x09 push \@dtd_args, \$_, shift ;\n\x09 }\n\x09 else {\n\x09 push \@others, \$_ ;\n\x09 }\n }\n }\n \$_default_dtds{\$callpkg} = \$class->new( \@dtd_args ) \n if \@dtd_args ;\n\n croak join( ', ', \@others ), " not exported by \$class" if \@others ; \n}\n\n\n=head1 SUBCLASSING\n\nThis object uses the fields pragma, so you should use base and fields for\nany subclasses.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nThis module is Copyright 2000, Barrie Slaymaker. All rights reserved.\n\nThis module is licensed under the GPL, version 2. Please contact me if this\ndoes not suit your needs.\n\n=cut\n\n\n\n1 ;\n END_OF_FILE_AAAAAAAAAADA #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < tag\n\n=head1 SYNOPSIS\n\n \$attr = \$elt->attribute( \$name ) ;\n \$attr->name ;\n\n=head1 DESCRIPTION\n\nThis module is used to represent tags in an XML::Doctype object.\nIt contains tags as well.\n\n=head1 STATUS\n\nThis module is alpha code. It's developed enough to support XML::ValidWriter,\nbut need a lot of work. Some big things that are lacking are:\n\n=over\n\n=cut\n\nuse strict ;\nuse vars qw( \$VERSION %_default_dtds ) ;\nuse fields (\n 'DEFAULT', # The default value if QUANT is '#FIXED' or '', undef otherwise\n 'NAME',\n 'OUT_DEFAULT', # Used to set a universal output default value\n 'QUANT', # '#REQUIRED', '#IMPLIED', '#FIXED', undef\n 'TYPE', # 'CDATA', 'ID', ...\n) ;\n\nuse Carp ;\n\n\$VERSION = 0.1 ;\n\n=head1 METHODS\n\n=item new\n\n \$dtd = XML::Doctype::AttDef->new( \$name, \$type, \$default ) ;\n\n=cut\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my XML::Doctype::AttDef \$self ;\n {\n no strict 'refs' ;\n \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n ( \$self->{NAME}, \$self->{TYPE} ) = \@_[0,1] ;\n if ( \$_[0] =! /^#/ ) {\n ( \$self->{QUANT}, \$self->{DEFAULT} ) = \@_[2,3] ;\n }\n else {\n \$self->{DEFAULT} = \$_[2] ;\n }\n\n return \$self ;\n}\n\n\n=item default\n\n ( \$spec, \$value ) = \$attr->default ;\n \$attr->default( '#REQUIRED' ) ;\n \$attr->default( '#IMPLIED' ) ;\n \$attr->default( '', 'foo' ) ;\n \$attr->default( '#FIXED', 'foo' ) ;\n\nSets/gets the default value. This is a \n\n=cut\n\nsub default {\n my XML::Doctype::AttDef \$self = shift ;\n\n if ( \@_ ) {\n my ( \$default ) = \@_ ;\n my \$quant = \$self->quant ;\n if ( defined \$default ) {\n if ( defined \$quant && \$quant =~ /^#(REQUIRED|IMPLIED)/ ) {\n\x09 carp\n\x09 "Attribute '", \$self->name, "' \$quant default set to '\$default'" ;\n\x09 }\n }\n else {\n if ( ! defined \$quant ) {\n\x09 carp "Attribute '", \$self->name, "' default set to undef" ;\n }\n elsif ( \$quant eq '#FIXED' ) {\n\x09 carp "Attribute '", \$self->name, "' #FIXED default set to undef" ;\n\x09 }\n }\n \$self->{DEFAULT} = \$default ;\n }\n\n return \$self->{DEFAULT} ;\n}\n\n\n=item quant\n\n \$attdef->quant( \$q ) ;\n \$q = \$attdef->quant ;\n\nSets/gets the attribute quantifier: '#REQUIRED', '#FIXED', '#IMPLIED', or ''.\n\n=cut\n\nsub quant {\n my XML::Doctype::AttDef \$self = shift ;\n\n \$self->{QUANT} = shift if \@_ ;\n return \$self->{QUANT} ;\n}\n\n\n=item name\n\n \$attdef->name( \$name ) ;\n \$name = \$attdef->name ;\n\nSets/gets this attribute name. Don't change the name while an attribute\nis in an element's attlist, since it will then be filed under the wrong\nname.\n\n=cut\n\nsub name {\n my XML::Doctype::AttDef \$self = shift ;\n\n \$self->{NAME} = shift if \@_ ;\n return \$self->{NAME} ;\n}\n\n\n=item default_on_write\n\n \$attdef->default_on_write( \$value ) ;\n \$value = \$attdef->default_on_write ;\n\n \$attdef->default_on_write( \$attdef->default ) ;\n\nSets/gets the value which is automatically output for this attribute\nif none is supplied to \$writer->startTag. This is typically used\nto set a document-wide default for #REQUIRED attributes (and perhaps\nplain attributes) so that the attribute is treated like a #FIXED tag\nand emitted with a fixed value.\n\nThe default_on_write does not need to be the same as the default unless\nthe quantifier is #FIXED.\n\n=cut\n\nsub default_on_write {\n my XML::Doctype::AttDef \$self = shift ;\n\n \$self->{OUT_DEFAULT} = shift if \@_ ;\n return \$self->{OUT_DEFAULT} ;\n}\n\n\n=head1 SUBCLASSING\n\nThis object uses the fields pragma, so you should use base and fields for\nany subclasses.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nThis module is Copyright 2000, Barrie Slaymaker. All rights reserved.\n\nThis module is licensed under the GPL, version 2. Please contact me if this\ndoes not suit your needs.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAADB #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < tag\n\n=head1 SYNOPSIS\n\n \$elt = \$dtd->element( 'foo' ) ;\n \$elt->name() ;\n \$elt->attr( 'foo' ) ;\n\n=head1 DESCRIPTION\n\nThis module is used to represent tags in an XML::Doctype object.\nIt contains tags as well.\n\n=head1 STATUS\n\nThis module is alpha code. It's developed enough to support XML::ValidWriter,\nbut need a lot of work. Some big things that are lacking are:\n\n=over\n\n=cut\n\nuse strict ;\nuse vars qw( \$VERSION %_default_dtds ) ;\nuse fields (\n 'ATTDEFS',\n 'CONTENT', # 'EMPTY', 'ANY' or a regexp. undef if ! is_declared().\n 'DECLARED',\n 'NAME',\n 'NAMES',\n 'PATHS', # A hash which XML::ValidWriter uses to cache the paths\n # it finds from this element name to possible child elements.\n 'TODO', # A list of children that XML::ValidWriter has not yet\n # explored for possible inclusion in PATHS.\n) ;\n\nuse Carp ;\nuse UNIVERSAL qw( isa ) ;\n\n\$VERSION = 0.1 ;\n\n=head1 METHODS\n\n=item new\n\n # Undefined element constructors:\n \$dtd = XML::Doctype::ElementDecl->new( \$name ) ;\n \$dtd = XML::Doctype::ElementDecl->new( \$name, undef, \\\@attdefs ) ;\n\n # Defined element constructors\n \$dtd = XML::Doctype::ElementDecl->new( \$name, \\\@kids, \\\@attdef ) ;\n \$dtd = XML::Doctype::ElementDecl->new( \$name, [], \\\@attdefs ) ;\n\n=cut\n\nsub _assemble_re {\n ## Convert the tree of XML::Parser::ContentModel instances to a\n ## regular expression and accumulate a HASH of element names in\n ## NAMES. This hash is later converted to an ARRAY.\n my XML::Doctype::ElementDecl \$self = shift ;\n my ( \$cp ) = \@_ ;\n\n if ( \$cp->isname ) {\n return '(?:#PCDATA)*' if \$cp->name eq '#PCDATA' ;\n \${\$self->{NAMES}->{\$cp->name}} = 1 ;\n return join( '', '<', quotemeta \$cp->name, '>' ) unless \$cp->quant ;\n }\n \n return join( '', map \$self->_assemble_re( \$_ ), \$cp->children )\n if \$cp->isseq && ! \$cp->quant ;\n\n return join( '',\n '(?:',\n \$cp->isname\n ? ( '<', quotemeta( \$cp->name ), '>' )\n : \$cp->isseq\n ? join( '', map \$self->_assemble_re( \$_ ), \$cp->children )\n : \$cp->ischoice\n ? join( '|', map \$self->_assemble_re( \$_ ), \$cp->children )\n : \$cp->ismixed\n ? join(\n\x09 '|',\n\x09 '(?:#PCDATA)?',\n\x09 map(\n\x09 defined \$_ ? \$self->_assemble_re( \$_ ) : (),\n\x09 \$cp->children\n\x09 )\n\x09 )\n : (),\n ')',\n \$cp->quant || ()\n ) ;\n\n}\n\nsub new {\n my \$class = shift ;\n \$class = ref \$class || \$class ;\n\n my XML::Doctype::ElementDecl \$self ;\n {\n no strict 'refs' ;\n \$self = bless [ \\%{"\$class\\::FIELDS"} ], \$class ;\n }\n\n my \$cm ; # The XML::Expat::ContentModel object for this DECL.\n ( \$self->{NAME}, \$cm, \$self->{ATTDEFS} ) = \@_ ;\n\n if ( \$cm ) {\n if ( \$cm->isany ) {\n\x09 \$self->{CONTENT} = 'ANY' ;\n\x09 \$self->{NAMES} = [] ;\n }\n elsif ( \$cm->isempty ) {\n\x09 \$self->{CONTENT} = 'EMPTY' ;\n\x09 \$self->{NAMES} = [] ;\n }\n elsif ( \$cm->ismixed || \$cm->isseq || \$cm->ischoice ) {\n\x09 \$self->{NAMES} = {} ;\n\x09 my \$re = \$self->_assemble_re( \$cm ) ;\n\x09 \$self->{CONTENT} = "^\$re\\\$" ; # qr/^\$re\$/ ;\n\x09 \$self->{NAMES} = [ \$self->{NAMES} ? keys %{\$self->{NAMES}} : () ] ;\n }\n else {\n\x09 croak "'\$cm' passed for a content model" ;\n }\n }\n else {\n \$self->{NAMES} = [] ;\n }\n\n return \$self ;\n}\n\n\nsub _freeze {\n my \$self = shift ;\n if ( defined \$self->{CONTENT} && ref \$self->{CONTENT} eq 'Regexp' ) {\n ## need two assigns to really, really divorce the SV from the\n ## quircky-half-object RegExp type.\n \$self->{CONTENT} = '' ;\n \$self->{CONTENT} = "\$self->{CONTENT}" ;\n }\n}\n\n\n=item add_attdef\n\n \$elt_decl->add_attdef( \$att_def ) ;\n\n=cut\n\nsub add_attdef {\n my XML::Doctype::ElementDecl \$self = shift ;\n my ( \$attdef ) = \@_ ;\n \$self->{ATTDEFS}->{\$attdef->name} = \$attdef ;\n}\n \n\n=item attdef\n\n \$attr = \$elt->attdef( \$name ) ;\n\nReturns the XML::Doctype::AttDef named by \$name or undef if there is no\nsuch attribute.\n\n=cut\n\nsub attdef {\n my XML::Doctype::ElementDecl \$self = shift ;\n my ( \$name ) = \@_ ;\n\n return \$self->{ATTDEFS}->{\$name} if exists \$self->{ATTDEFS}->{\$name} ;\n return ;\n}\n\n\n=item attdefs\n\n \$attdefs = \$elt->attdefs( \$name ) ;\n\nReturns the list of XML::Doctype::AttDef instances associated with this\nelement.\n\n=cut\n\nsub attdefs {\n my XML::Doctype::ElementDecl \$self = shift ;\n my ( \$name ) = \@_ ;\n\n return \$self->{ATTDEFS} ? values %{\$self->{ATTDEFS}} : () ;\n}\n\n\n=item attribute_names\n\nReturns a list of the attdefs' names.\n\n=cut\n\nsub attribute_names {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \$self->{ATTDEFS} ? keys %{\$self->{ATTDEFS}} : () ;\n}\n\n\n=item child_names\n\n \@names = \$elt->child_names ;\n\nReturns a list of names of elements in this element decl's content model.\n\n=cut\n\nsub child_names {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \@{\$self->{NAMES}} ;\n}\n\n\n=item is_declared\n\n if ( \$elt_decl->is_declared ) ...\n \$elt_decl->is_declared( 1 ) ;\n\nReturns TRUE if there is any data defined in the element other than name and\nattributes or if is_declared has been set by calling is_declared( 1 ) or\npassing DECLARED => 1 to new().\n\n=cut\n\nsub is_declared {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n \$self->{DECLARED} = shift if \@_ ;\n\n return \$self->{DECLARED} || defined \$self->{CONTENT} ;\n}\n\n\n=item is_empty\n\n=cut\n\nsub is_empty {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \$self->{CONTENT} && \$self->{CONTENT} eq 'EMPTY' ;\n}\n\n\n=item is_any\n\n=cut\n\nsub is_any {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \$self->{CONTENT} && \$self->{CONTENT} eq 'ANY' ;\n}\n\n\n=item is_mixed\n\n=cut\n\nsub is_mixed {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \$self->{CONTENT} && \$self->{CONTENT} =~ /#PCDATA/ ;\n}\n\nsub can_contain_pcdata {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \$self->{CONTENT}\n && (\n\x09 \$self->{CONTENT} eq 'ANY'\n\x09 || return \$self->{CONTENT} =~ /#PCDATA/\n ) ;\n}\n\n=item name\n\n \$n = \$elt_decl->name ;\n\nGets the name of the element.\n\n=cut\n\nsub name {\n my XML::Doctype::ElementDecl \$self = shift ;\n\n return \$self->{NAME} ;\n}\n\n\n=item validate_content\n\n \$v = \$elt_decl->validate_content( \\\@seq ) ;\n\nTakes an ARRAY ref of tag names (or '#PCDATA') and checks to see if\nit would be valid content for elements of this type.\n\nRight now, this must be called only when an element's end tag is\nemitted. It can be broadened to be incremental if need be.\n\n=cut\n\nsub validate_content {\n my XML::Doctype::ElementDecl \$self = shift ;\n my ( \$c ) = \@_ ;\n\n return 1 if ! defined \$self->{CONTENT} || \$self->{CONTENT} eq 'ANY' ;\n return ! \@\$c if \$self->{CONTENT} eq 'EMPTY' ;\n\n ## Must be mixed. If this elt can have no kids, the test\n ## is quick. Otherwise we need to validate agains the content\n ## model tree.\n my \$content_desc = join(\n '',\n map \$_ eq '#PCDATA' ? \$_ : "<\$_>",\n \@\$c\n ) ;\n\n# print STDERR "\$content_desc\\n\$self->{CONTENT}\\n" ;\n\n#print \$self->{CONTENT}, "\\n" ;\n\n return \$content_desc =~ \$self->{CONTENT} ;\n}\n\n\n=head1 SUBCLASSING\n\nThis object uses the fields pragma, so you should use base and fields for\nany subclasses.\n\n=head1 AUTHOR\n\nBarrie Slaymaker \n\n=head1 COPYRIGHT\n\nThis module is Copyright 2000, Barrie Slaymaker. All rights reserved.\n\nThis module is licensed under the GPL, version 2. Please contact me if this\ndoes not suit your needs.\n\n=cut\n\n1 ;\n END_OF_FILE_AAAAAAAAAADC #=#----%<--------%<--------%<--------%<--------%<--------%<--------%<------#=# BEGIN { _spew < diffs for large files.\n my \$diff = diff "file1.txt", "file2.txt", { STYLE => "Context" };\n my \$diff = diff \\\$string1, \\\$string2, \\%options;\n my \$diff = diff \\*FH1, \\*FH2;\n my \$diff = diff \\&reader1, \\&reader2;\n my \$diff = diff \\\@records1, \\\@records2;\n\n ## May also mix input types:\n my \$diff = diff \\\@records1, "file_B.txt";\n\n=head1 DESCRIPTION\n\nC provides a basic set of services akin to the GNU C utility. It\nis not anywhere near as feature complete as GNU C, but it is better\nintegrated with Perl and available on all platforms. It is often faster than\nshelling out to a system's C executable for small files, and generally\nslower on larger files.\n\nRelies on L for, well, the algorithm. This may not produce\nthe same exact diff as a system's local C executable, but it will be a\nvalid diff and comprehensible by C. We haven't seen any differences\nbetween Algorithm::Diff's logic and GNU diff's, but we have not examined them\nto make sure they are indeed identical.\n\nB: If you don't want to import the C function, do one of the\nfollowing:\n\n use Text::Diff ();\n\n require Text::Diff;\n\nThat's a pretty rare occurence, so C is exported by default.\n\n=cut\n\nuse Exporter;\n\@ISA = qw( Exporter );\n\@EXPORT = qw( diff );\n\nuse strict;\nuse Carp;\nuse Algorithm::Diff qw( traverse_sequences );\n\n## Hunks are made of ops. An op is the starting index for each\n## sequence and the opcode:\nuse constant A => 0; # Array index before match/discard\nuse constant B => 1;\nuse constant OPCODE => 2; # "-", " ", "+"\nuse constant FLAG => 3; # What to display if not OPCODE "!"\n\n\n=head1 OPTIONS\n\ndiff() takes two parameters from which to draw input and a set of\noptions to control it's output. The options are:\n\n=over\n\n=item FILENAME_A, MTIME_A, FILENAME_B, MTIME_B\n\nThe name of the file and the modification time "files"\n\nThese are filled in automatically for each file when diff() is passed a\nfilename, unless a defined value is passed in.\n\nIf a filename is not passed in and FILENAME_A and FILENAME_B are not provided\nor C, the header will not be printed.\n\nUnused on C diffs.\n\n=item OFFSET_A, OFFSET_B\n\nThe index of the first line / element. These default to 1 for all\nparameter types except ARRAY references, for which the default is 0. This\nis because ARRAY references are presumed to be data structures, while the\nothers are line oriented text.\n\n=item STYLE\n\n"Unified", "Context", "OldStyle", or an object or class reference for a class\nproviding C, C, C, C and\nC methods. The two footer() methods are provided for\noverloading only; none of the formats provide them.\n\nDefaults to "Unified" (unlike standard C, but Unified is what's most\noften used in submitting patches and is the most human readable of the three.\n\nIf the package indicated by the STYLE has no hunk() method, c will\nload it automatically (lazy loading). Since all such packages should inherit\nfrom Text::Diff::Base, this should be marvy.\n\nStyles may be specified as class names (C