Remapping using label_mapper would cause replacements not changing the label director...
[vss2svn.git] / script / Vss2Svn / Dumpfile.pm
blob6a6d5e6a8d809b82341e2ba4bfa82dff0bdd1c02
1 package Vss2Svn::Dumpfile;
3 use Vss2Svn::Dumpfile::Node;
4 use Vss2Svn::Dumpfile::SanityChecker;
5 use Vss2Svn::Dumpfile::AutoProps;
6 use Vss2Svn::Dumpfile::LabelMapper;
8 require Time::Local;
10 use warnings;
11 use strict;
13 use File::Copy;
14 use Digest::MD5;
15 use Data::UUID;
17 our %gHandlers =
19 ADD => \&_add_handler,
20 COMMIT => \&_commit_handler,
21 RENAME => \&_rename_handler,
22 SHARE => \&_share_handler,
23 BRANCH => \&_branch_handler,
24 MOVE => \&_move_handler,
25 DELETE => \&_delete_handler,
26 RECOVER => \&_recover_handler,
27 PIN => \&_pin_handler,
28 LABEL => \&_label_handler,
31 # Keep track of when paths were modified or deleted, for subsequent copies
32 # or recovers.
34 #our %gModified = ();
35 our %gDeleted = ();
36 our %gVersion = ();
37 our $gTmpDir;
39 ###############################################################################
40 # SetTempDir
41 ###############################################################################
42 sub SetTempDir {
43 my($class, $dir) = @_;
45 $gTmpDir = $dir;
46 } # End SetTempDir
48 ###############################################################################
49 # new
50 ###############################################################################
51 sub new {
52 my($class, $fh, $autoprops, $md5, $labelmapper) = @_;
54 my $self =
56 fh => $fh,
57 revision => 0,
58 errors => [],
59 deleted_cache => {},
60 version_cache => [],
61 repository => Vss2Svn::Dumpfile::SanityChecker->new(),
62 auto_props => $autoprops,
63 do_md5 => $md5,
64 label_mapper => $labelmapper,
67 # prevent perl from doing line-ending conversions
68 binmode($fh);
70 my $old = select($fh);
71 $| = 1;
72 select($old);
74 print $fh "SVN-fs-dump-format-version: 2\n\n";
76 my $ug = new Data::UUID;
77 my $uuid = $ug->to_string( $ug->create() );
79 print $fh "UUID: $uuid\n\n";
81 $self = bless($self, $class);
82 return $self;
84 } # End new
86 ###############################################################################
87 # finish
88 ###############################################################################
89 sub finish {
90 my($self) = @_;
92 my $fh = $self->{fh};
94 print $fh "\n\n";
96 } # End finish
98 ###############################################################################
99 # begin_revision
100 ###############################################################################
101 sub begin_revision {
102 my($self, $data) = @_;
103 my($revision, $author, $timestamp, $comment) =
104 @{ $data }{qw(revision_id author timestamp comment)};
106 my $props = undef;
107 my $fh = $self->{fh};
109 print $fh "\nRevision-number: $revision\n";
111 $comment = '' if !defined($comment);
112 $author = '' if !defined($author);
114 if ($revision > 0) {
115 $props = { 'svn:log' => $comment,
116 'svn:author' => $author,
120 $props->{'svn:date'} = $self->svn_timestamp($timestamp);
122 $self->output_content($props);
123 $self->{revision} = $revision;
125 } # End begin_revision
127 ###############################################################################
128 # do_action
129 ###############################################################################
130 sub do_action {
131 my($self, $data, $expdir) = @_;
133 my $action = $data->{action};
135 my $nodes = [];
137 # Temporary hack to prevent shared files from stepping on the "modified"
138 # flag for other than the first commit. Ideally, we should keep all paths
139 # for a given physical file's last modified flags, and use the best match
140 # if we need to copy or recover one.
142 $self->{is_primary} = 1;
143 $self->{deleted_cache} = {};
144 $self->{version_cache} = [];
146 my($handler, $this_action);
148 foreach my $itempath (split "\t", $data->{itempaths}) {
149 $this_action = $action;
151 # $this_action = $self->sanity_checker->check ($data, $itempath, $nodes);
152 # if (!defined ($this_action)) {
153 # return 0;
156 $handler = $gHandlers{$this_action};
158 my $thisnodes = [];
159 $self->$handler($itempath, $thisnodes, $data, $expdir);
161 # we need to apply all local changes to our repository directly: if we
162 # have an action that operates on multiple items, e.g labeling, the
163 # necessary missing directories are created for the first item
164 foreach my $node (@$thisnodes) {
165 $self->{repository}->load($node);
166 push @$nodes, $node;
169 $self->{is_primary} = 0;
172 foreach my $node (@$nodes) {
173 $self->output_node($node);
176 my($physname, $cache);
178 my ($parentphys, $physnames);
179 while(($parentphys, $physnames) = each %{ $self->{deleted_cache} }) {
180 while(($physname, $cache) = each %{ $physnames }) {
181 $gDeleted{$parentphys}->{$physname} = $cache;
185 # track the version -> revision mapping for the file
186 foreach my $record (@{$self->{version_cache}}) {
187 my $version = \%{$gVersion{$record->{physname}}->[$record->{version}]};
188 $version->{$record->{itempath}} = $record->{revision};
191 } # End do_action
194 ###############################################################################
195 # _add_handler
196 ###############################################################################
197 sub _add_handler {
198 my($self, $itempath, $nodes, $data, $expdir) = @_;
200 if ($self->{repository}->exists ($itempath)) {
201 if ($data->{itemtype} == 2) {
202 $self->add_error("Attempt to re-add file '$itempath' at "
203 . "revision $data->{revision_id}, changing to modify; possibly "
204 . "missing delete");
205 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
207 else {
208 $self->add_error("Attempt to re-add directory '$itempath' at "
209 . "revision $data->{revision_id}, skipping action: possibly "
210 . "missing delete");
211 return 0;
215 my $success = $self->{repository}->exists_parent ($itempath);
216 if(!defined($success)) {
217 $self->add_error("Path consistency failure while trying to add "
218 . "item '$itempath' at revision $data->{revision_id}; skipping");
219 return 0;
221 elsif ($success == 0) {
222 $self->add_error("Parent path missing while trying to add "
223 . "item '$itempath' at revision $data->{revision_id}: adding missing "
224 . "parents");
225 $self->_create_svn_path ($nodes, $itempath);
228 my $node = Vss2Svn::Dumpfile::Node->new();
229 $node->set_initial_props($itempath, $data);
230 if ($data->{is_binary}) {
231 $node->add_prop('svn:mime-type', 'application/octet-stream');
233 if (defined $self->{auto_props}) {
234 $node->add_props ($self->{auto_props}->get_props ($itempath));
237 $node->{action} = 'add';
239 if ($data->{itemtype} == 2) {
240 $self->get_export_file($node, $data, $expdir);
243 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
244 $self->track_version ($data->{physname}, $data->{version}, $itempath);
246 push @$nodes, $node;
248 } # End _add_handler
250 ###############################################################################
251 # _commit_handler
252 ###############################################################################
253 sub _commit_handler {
254 my($self, $itempath, $nodes, $data, $expdir) = @_;
256 if (!$self->{repository}->exists ($itempath)) {
257 $self->add_error("Attempt to commit to non-existant file '$itempath' at "
258 . "revision $data->{revision_id}, changing to add; possibly "
259 . "missing recover");
260 return $self->_add_handler ($itempath, $nodes, $data, $expdir);
263 my $node = Vss2Svn::Dumpfile::Node->new();
264 $node->set_initial_props($itempath, $data);
265 $node->{action} = 'change';
267 if ($data->{itemtype} == 2) {
268 $self->get_export_file($node, $data, $expdir);
271 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
272 $self->track_version ($data->{physname}, $data->{version}, $itempath);
274 push @$nodes, $node;
276 } # End _commit_handler
278 ###############################################################################
279 # _rename_handler
280 ###############################################################################
281 sub _rename_handler {
282 my($self, $itempath, $nodes, $data, $expdir) = @_;
284 # to rename a file in SVN, we must add "with history" then delete the orig.
286 my $newname = $data->{info};
287 my $newpath = $itempath;
289 if ($data->{itemtype} == 1) {
290 $newpath =~ s:(.*/)?.+$:$1$newname:;
291 } else {
292 $newpath =~ s:(.*/)?.*:$1$newname:;
295 if ($self->{repository}->exists ($newpath)) {
296 $self->add_error("Attempt to rename item '$itempath' to '$newpath' at "
297 . "revision $data->{revision_id}, but destination already exists: possibly "
298 . "missing delete; skipping");
299 return 0;
302 if (!$self->{repository}->exists ($itempath)) {
303 $self->add_error("Attempt to rename item '$itempath' to '$newpath' at "
304 . "revision $data->{revision_id}, but source doesn't exists: possibly "
305 . "missing recover; skipping");
306 return 0;
309 my $node = Vss2Svn::Dumpfile::Node->new();
310 $node->set_initial_props($newpath, $data);
311 # change the properties according to the new name
312 if (defined $self->{auto_props}) {
313 $node->add_props ($self->{auto_props}->get_props ($newpath));
315 $node->{action} = 'add';
317 my($copyrev, $copypath);
319 # ideally, we should be finding the last time the file was modified and
320 # copy it from there, but that becomes difficult to track...
321 $copyrev = $data->{revision_id} - 1;
322 $copypath = $itempath;
324 $node->{copyrev} = $copyrev;
325 $node->{copypath} = $copypath;
327 push @$nodes, $node;
329 # $self->track_modified($data->{physname}, $data->{revision_id}, $newpath);
330 # $self->track_version ($data->{physname}, $data->{version}, $newpath);
332 $node = Vss2Svn::Dumpfile::Node->new();
333 $node->set_initial_props($itempath, $data);
334 $node->{action} = 'delete';
335 $node->{hideprops} = 1;
337 push @$nodes, $node;
339 # We don't add this to %gDeleted since VSS doesn't treat a rename as an
340 # add/delete and therefore we wouldn't recover from this point
342 } # End _rename_handler
344 ###############################################################################
345 # _share_handler
346 ###############################################################################
347 sub _share_handler {
348 my($self, $itempath, $nodes, $data, $expdir) = @_;
350 if ($self->{repository}->exists ($itempath)) {
351 $self->add_error("Attempt to share item '$data->{info}' to '$itempath' at "
352 . "revision $data->{revision_id}, but destination already exists: possibly "
353 . "missing delete; skipping");
354 return 0;
357 # It could be possible that we share from a historically renamed item, so we don't check the source
358 # if ($self->{repository}->exists ($data->{info})) {
359 # $self->add_error("Attempt to share item '$itempath' to '$newpath' at "
360 # . "revision $data->{revision_id}, but destination already exists: possibly "
361 # . "missing delete; skipping");
362 # return 0;
365 my $node = Vss2Svn::Dumpfile::Node->new();
366 $node->set_initial_props($itempath, $data);
367 $node->{action} = 'add';
369 # @{ $node }{ qw(copyrev copypath) }
370 # = $self->last_modified_rev_path($data->{physname});
371 $node->{copyrev} =
372 $self->get_revision ($data->{physname}, $data->{version}, $data->{info});
373 $node->{copypath} = $data->{info};
375 if (!defined $node->{copyrev} || !defined $node->{copypath}) {
376 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
379 $self->track_version ($data->{physname}, $data->{version}, $itempath);
381 push @$nodes, $node;
383 } # End _share_handler
385 ###############################################################################
386 # _branch_handler
387 ###############################################################################
388 sub _branch_handler {
389 my($self, $itempath, $nodes, $data, $expdir) = @_;
391 # branching is a no-op in SVN
393 # since it is possible, that we refer to version prior to the branch later, we
394 # need to copy all internal information about the ancestor to the child.
395 if (defined $data->{info}) {
396 # only copy versions, that are common between the branch source and the branch.
397 my $copy_version=$data->{version};
398 while(--$copy_version > 0) {
399 if (defined $gVersion{$data->{info}}->[$copy_version]) {
400 $gVersion{$data->{physname}}->[$copy_version] =
401 $gVersion{$data->{info}}->[$copy_version];
406 # # if the file is copied later, we need to track, the revision of this branch
407 # # see the shareBranchShareModify Test
408 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
409 $self->track_version ($data->{physname}, $data->{version}, $itempath);
411 } # End _branch_handler
413 ###############################################################################
414 # _move_handler
415 ###############################################################################
416 sub _move_handler {
417 my($self, $itempath, $nodes, $data, $expdir) = @_;
419 # moving in SVN is the same as renaming; add the new and delete the old
421 my $oldpath = $data->{info};
423 if ($self->{repository}->exists ($itempath)) {
424 $self->add_error("Attempt to move item '$oldpath' to '$itempath' at "
425 . "revision $data->{revision_id}, but destination already exists: possibly "
426 . "missing delete; skipping");
427 return 0;
430 if (!$self->{repository}->exists ($oldpath)) {
431 $self->add_error("Attempt to move item '$oldpath' to '$itempath' at "
432 . "revision $data->{revision_id}, but source doesn't exists: possibly "
433 . "missing recover; skipping");
434 return 0;
437 my $success = $self->{repository}->exists_parent($itempath);
438 if(!defined($success)) {
439 $self->add_error("Attempt to move item '$oldpath' to '$itempath' at "
440 . "revision $data->{revision_id}, but path consistency failure at dest");
441 return 0;
443 elsif ($success == 0) {
444 $self->add_error("Parent path missing while trying to move "
445 . "item '$oldpath' to '$itempath' at "
446 . "revision $data->{revision_id}: adding missing parents");
447 $self->_create_svn_path ($nodes, $itempath);
450 my $node = Vss2Svn::Dumpfile::Node->new();
451 $node->set_initial_props($itempath, $data);
452 $node->{action} = 'add';
454 my($copyrev, $copypath);
456 $copyrev = $data->{revision_id} - 1;
457 $copypath = $oldpath;
459 $node->{copyrev} = $copyrev;
460 $node->{copypath} = $copypath;
462 push @$nodes, $node;
464 # the new move target is a valid path.
465 $self->track_version ($data->{physname}, $data->{version}, $itempath);
467 $node = Vss2Svn::Dumpfile::Node->new();
468 $node->set_initial_props($oldpath, $data);
469 $node->{action} = 'delete';
470 $node->{hideprops} = 1;
472 # Deleted tracking is only necessary to be able to recover the item. But a move
473 # does not set a recover point, so we don't need to track the delete here. Additionally
474 # we do not have enough information for this operation.
475 # $self->track_deleted($data->{oldparentphys}, $data->{physname},
476 # $data->{revision_id}, $oldpath);
478 push @$nodes, $node;
480 } # End _move_handler
482 ###############################################################################
483 # _delete_handler
484 ###############################################################################
485 sub _delete_handler {
486 my($self, $itempath, $nodes, $data, $expdir) = @_;
488 if (!$self->{repository}->exists ($itempath)) {
489 $self->add_error("Attempt to delete non-existent item '$itempath' at "
490 . "revision $data->{revision_id}: possibly "
491 . "missing recover/add/share; skipping");
492 return 0;
495 my $node = Vss2Svn::Dumpfile::Node->new();
496 $node->set_initial_props($itempath, $data);
497 $node->{action} = 'delete';
498 $node->{hideprops} = 1;
500 push @$nodes, $node;
502 $self->track_deleted($data->{parentphys}, $data->{physname},
503 $data->{revision_id}, $itempath);
505 } # End _delete_handler
507 ###############################################################################
508 # _recover_handler
509 ###############################################################################
510 sub _recover_handler {
511 my($self, $itempath, $nodes, $data, $expdir) = @_;
513 if ($self->{repository}->exists ($itempath)) {
514 $self->add_error("Attempt to recover existing item '$itempath' at "
515 . "revision $data->{revision_id}: possibly "
516 . "missing delete; change to commit");
517 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
520 my $node = Vss2Svn::Dumpfile::Node->new();
521 $node->set_initial_props($itempath, $data);
522 $node->{action} = 'add';
524 # for projects we want to go back to the revision just one before the deleted
525 # revision. For files, we need to go back to the specified revision, since
526 # the file could have been modified via a share.
527 my($copyrev, $copypath);
528 if (!defined ($data->{version})) {
529 ($copyrev, $copypath)= $self->last_deleted_rev_path($data->{parentphys},
530 $data->{physname});
531 $copyrev -= 1;
533 else {
534 $copyrev =
535 $self->get_revision ($data->{physname}, $data->{version}, $data->{info});
536 $copypath = $data->{info};
539 if (!defined $copyrev || !defined $copypath) {
540 $self->add_error(
541 "Could not recover path $itempath at revision $data->{revision_id};"
542 . " unable to determine deleted revision or path");
543 return 0;
546 $node->{copyrev} = $copyrev;
547 $node->{copypath} = $copypath;
549 if (defined ($data->{version})) {
550 $self->track_version ($data->{physname}, $data->{version}, $itempath);
553 push @$nodes, $node;
555 } # End _recover_handler
557 ###############################################################################
558 # _pin_handler
559 ###############################################################################
560 sub _pin_handler {
561 my($self, $itempath, $nodes, $data, $expdir) = @_;
563 if (!$self->{repository}->exists ($itempath)) {
564 $self->add_error("Attempt to pin non-existing item '$itempath' at "
565 . "revision $data->{revision_id}: possibly "
566 . "missing recover; skipping");
567 return 0;
570 my $copyrev =
571 $self->get_revision ($data->{physname}, $data->{version}, $data->{info});
572 my $copypath = $data->{info};
574 # if one of the necessary copy from attributes are unavailable we fall back
575 # to a complete checkin
576 if (defined $copyrev && defined $copypath) {
577 $data->{comment} = "ported from $copypath r$copyrev";
579 # if (!defined $copyrev || !defined $copypath) {
580 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
583 my $node = Vss2Svn::Dumpfile::Node->new();
584 $node->set_initial_props($itempath, $data);
585 $node->{action} = 'add';
587 $node->{copyrev} = $copyrev;
588 $node->{copypath} = $copypath;
590 $self->track_version ($data->{physname}, $data->{version}, $itempath);
592 push @$nodes, $node;
594 } # End _pin_handler
596 ###############################################################################
597 # _label_handler
598 ###############################################################################
599 sub _label_handler {
600 my($self, $itempath, $nodes, $data, $expdir) = @_;
602 if (!$self->{repository}->exists ($itempath)) {
603 $self->add_error("Attempt to label non-existing item '$itempath' at "
604 . "revision $data->{revision_id}: possibly "
605 . "missing recover; skipping");
606 return 0;
609 my $label = $data->{info};
611 # It is possible that the label was deleted later, so we see here a label
612 # action, but no label was assigned. In this case, we only need to track
613 # the version->revision mapping, since the version could have been used
614 # as a valid share source.
615 if (defined ($label)) {
616 my $labeldir = $main::gCfg{labeldir};
618 if (defined $self->{label_mapper}) {
619 my $mapping = $self->{label_mapper}->remap ($main::gCfg{labeldir}, $label);
620 $labeldir = $mapping->{replacement} if $mapping->{is_labeldir};
621 $label = $mapping->{replacement} unless $mapping->{is_labeldir};
623 $labeldir =~ s:\\:/:g;
624 $labeldir =~ s:/$::;
626 $label =~ s![\\/:*?"<>|]!_!g;
628 my $vssitempath = $itempath;
629 $vssitempath =~ s/^$main::gCfg{trunkdir}//;
630 my $labelpath = "$labeldir/$label$vssitempath";
632 $self->_create_svn_path ($nodes, $labelpath);
634 my $node = Vss2Svn::Dumpfile::Node->new();
635 $node->set_initial_props($labelpath, $data);
636 $node->{action} = 'add';
638 my $copyrev = $data->{revision_id} - 1;
639 my $copypath = $itempath;
641 $node->{copyrev} = $copyrev;
642 $node->{copypath} = $copypath;
644 push @$nodes, $node;
648 $self->track_version ($data->{physname}, $data->{version}, $itempath);
649 } # End _label_handler
651 ###############################################################################
652 # _add_svn_dir
653 ###############################################################################
654 sub _add_svn_dir {
655 my($self, $nodes, $dir) = @_;
657 my $node = Vss2Svn::Dumpfile::Node->new();
658 my $data = { itemtype => 1, is_binary => 0 };
660 $node->set_initial_props($dir, $data);
661 $node->{action} = 'add';
663 push @$nodes, $node;
664 } # End _add_svn_dir
667 ###############################################################################
668 # _create_svn_path
669 ###############################################################################
670 sub _create_svn_path {
671 my($self, $nodes, $itempath) = @_;
673 my $missing_dirs = $self->{repository}->get_missing_dirs($itempath);
675 foreach my $dir (@$missing_dirs) {
676 $self->_add_svn_dir($nodes, $dir);
678 } # End _create_svn_path
680 ###############################################################################
681 # track_version
682 ###############################################################################
683 sub track_version {
684 my($self, $physname, $version, $itempath) = @_;
686 my $record =
688 physname => $physname,
689 version => $version,
690 revision => $self->{revision},
691 itempath => $itempath,
693 push @{$self->{version_cache}}, $record;
695 } # End track_version
698 ###############################################################################
699 # get_revision
700 ###############################################################################
701 sub get_revision {
702 my($self, $physname, $version, $itempath) = @_;
704 if (!defined($gVersion{$physname})) {
705 return (undef);
708 if (!exists($gVersion{$physname}->[$version])) {
709 return (undef);
712 return $gVersion{$physname}->[$version]->{$itempath};
714 } # End get_revision
716 ###############################################################################
717 # track_deleted
718 ###############################################################################
719 sub track_deleted {
720 my($self, $parentphys, $physname, $revision, $path) = @_;
722 $self->{deleted_cache}->{$parentphys}->{$physname} =
724 revision => $revision,
725 path => $path,
728 } # End track_deleted
730 ###############################################################################
731 # last_deleted_rev_path
732 ###############################################################################
733 sub last_deleted_rev_path {
734 my($self, $parentphys, $physname) = @_;
736 if (!defined($gDeleted{$parentphys})) {
737 return (undef, undef);
740 if (!defined($gDeleted{$parentphys}->{$physname})) {
741 return (undef, undef);
744 return @{ $gDeleted{$parentphys}->{$physname} }{ qw(revision path) };
745 } # End last_deleted_rev_path
747 ###############################################################################
748 # get_export_file
749 ###############################################################################
750 sub get_export_file {
751 my($self, $node, $data, $expdir) = @_;
753 if (!defined($expdir)) {
754 return 0;
755 } elsif (!defined($data->{version})) {
756 $self->add_error(
757 "Attempt to retrieve file contents with unknown version number");
758 return 0;
761 $node->{file} = "$expdir/$data->{physname}.$data->{version}";
762 return 1;
764 } # End get_export_file
766 ###############################################################################
767 # output_node
768 ###############################################################################
769 sub output_node {
770 my($self, $node) = @_;
771 my $fh = $self->{fh};
773 # only in an add or rename action the propery array is set. So we have
774 # to lookup the eol-style flag again. The best thing is to query the
775 # property always temporarirly
776 my %tmpProps = ();
777 if (defined $self->{auto_props}) {
778 %tmpProps = $self->{auto_props}->get_props ($node->{path});
780 my $eolStyle = $tmpProps{'svn:eol-style'};
781 my $isNative = (defined $eolStyle && $eolStyle eq 'native') ? 1 : 0;
783 my $string = $node->get_headers();
784 print $fh $string;
785 $self->output_content($node->{hideprops}? undef : $node->{props},
786 $node->{text}, $node->{file}, $isNative);
787 } # End output_node
789 ###############################################################################
790 # output_content
791 ###############################################################################
792 sub output_content {
793 my($self, $props, $text, $file, $isNative) = @_;
795 my $fh = $self->{fh};
797 $text = '' unless defined $text || defined $file;
799 my $proplen = 0;
800 my $textlen = 0;
801 my($propout, $textout) = ('') x 2;
803 if (defined($props)) {
804 foreach my $key (keys %$props) {
805 my $value = $props->{$key};
806 $propout .= 'K ' . length($key) . "\n$key\n";
807 if (defined $value) {
808 $propout .= 'V ' . length($value) . "\n$value\n";
810 else {
811 $propout .= "V 0\n\n";
815 $propout .= "PROPS-END\n";
816 $proplen = length($propout);
819 my $md5;
820 $md5 = Digest::MD5->new if $self->{do_md5};
822 # prevent errors due to non existing files
823 if(!defined $text && defined $file && !-e $file) {
824 $text = "";
827 # convert CRLF -> LF before calculating the size and compute the md5
828 if(!defined $text && defined $file) {
830 my ($input, $output);
831 if (defined $isNative && $isNative) {
832 open ($input, "<:crlf", $file);
833 my $tmpFile = "$gTmpDir/crlf_to_lf.tmp.txt";
834 open ($output, ">", $tmpFile);
835 binmode ($output);
837 while(<$input>) {
838 $md5->add($_) if $self->{do_md5};
839 print $output $_;
842 close $input;
843 close $output;
844 $file = $tmpFile;
846 else {
847 open ($input, "<", $file);
848 binmode ($input);
849 $md5->addfile($input) if $self->{do_md5};
850 close $input;
852 } else {
853 $md5->add($text) if $self->{do_md5};
856 my $digest = $md5->hexdigest if $self->{do_md5};
857 # print "digest: $digest\n";
859 if(!defined $text && defined $file) {
860 $textlen = -s $file;
861 } else {
862 $textlen = length($text);
864 return if ($textlen + $proplen == 0);
866 if ($proplen > 0) {
867 print $fh "Prop-content-length: $proplen\n";
870 if ($textlen > 0) {
871 print $fh "Text-content-length: $textlen\n";
872 print $fh "Text-content-md5: $digest\n" if $self->{do_md5};
875 print $fh "Content-length: " . ($proplen + $textlen)
876 . "\n\n$propout";
878 if(!defined $text && defined $file) {
879 copy($file, $fh);
880 print $fh "\n";
881 } else {
882 print $fh "$text\n";
885 } # End output_content
887 ###############################################################################
888 # svn_timestamp
889 ###############################################################################
890 sub svn_timestamp {
891 my($self, $vss_timestamp) = @_;
893 return &SvnTimestamp($vss_timestamp);
895 } # End svn_timestamp
897 ###############################################################################
898 # SvnTimestamp
899 ###############################################################################
900 sub SvnTimestamp {
901 my($vss_timestamp) = @_;
903 # set the correct time: VSS stores the local time as the timestamp, but subversion
904 # needs a gmtime. So we need to reverse adjust the timestamp in order to turn back
905 # the clock.
906 my($sec, $min, $hour, $day, $mon, $year) = gmtime($vss_timestamp);
907 my($faketime) = Time::Local::timelocal ($sec, $min, $hour, $day, $mon, $year);
908 ($sec, $min, $hour, $day, $mon, $year) = gmtime($faketime);
910 $year += 1900;
911 $mon += 1;
913 return sprintf("%4.4i-%2.2i-%2.2iT%2.2i:%2.2i:%2.2i.%6.6iZ",
914 $year, $mon, $day, $hour, $min, $sec, 0);
916 } # End SvnTimestamp
918 ###############################################################################
919 # add_error
920 ###############################################################################
921 sub add_error {
922 my($self, $msg) = @_;
924 push @{ $self->{errors} }, $msg;
925 } # End add_error