* Path for renames during restore and renames during share (thanks to Bryan Aldrich...
[vss2svn.git] / script / Vss2Svn / Dumpfile.pm
blob1536d7c2d180fddb6817897ec90080003079b1c4
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 #creating a new VSS database can cause a "ADD" for a "/" item which will fail.
209 if (!($itempath eq "/")) {
210 $self->add_error("Attempt to re-add directory '$itempath' at "
211 . "revision $data->{revision_id}, skipping action: possibly "
212 . "missing delete");
215 return 0;
219 my $success = $self->{repository}->exists_parent ($itempath);
220 if(!defined($success)) {
221 $self->add_error("Path consistency failure while trying to add "
222 . "item '$itempath' at revision $data->{revision_id}; skipping");
223 return 0;
225 elsif ($success == 0) {
226 if (!($itempath =~ m/^\/orphaned\/_.*/))
228 $self->add_error("Parent path missing while trying to add "
229 . "item '$itempath' at revision $data->{revision_id}: adding missing "
230 . "parents");
232 $self->_create_svn_path ($nodes, $itempath);
235 my $node = Vss2Svn::Dumpfile::Node->new();
236 $node->set_initial_props($itempath, $data);
237 if ($data->{is_binary}) {
238 $node->add_prop('svn:mime-type', 'application/octet-stream');
240 if (defined $self->{auto_props}) {
241 $node->add_props ($self->{auto_props}->get_props ($itempath));
244 $node->{action} = 'add';
246 if ($data->{itemtype} == 2) {
247 $self->get_export_file($node, $data, $expdir);
250 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
251 $self->track_version ($data->{physname}, $data->{version}, $itempath);
253 push @$nodes, $node;
255 } # End _add_handler
257 ###############################################################################
258 # _commit_handler
259 ###############################################################################
260 sub _commit_handler {
261 my($self, $itempath, $nodes, $data, $expdir) = @_;
263 if (!$self->{repository}->exists ($itempath)) {
264 $self->add_error("Attempt to commit to non-existant file '$itempath' at "
265 . "revision $data->{revision_id}, changing to add; possibly "
266 . "missing recover");
267 return $self->_add_handler ($itempath, $nodes, $data, $expdir);
270 my $node = Vss2Svn::Dumpfile::Node->new();
271 $node->set_initial_props($itempath, $data);
272 $node->{action} = 'change';
274 if ($data->{itemtype} == 2) {
275 $self->get_export_file($node, $data, $expdir);
278 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
279 $self->track_version ($data->{physname}, $data->{version}, $itempath);
281 push @$nodes, $node;
283 } # End _commit_handler
285 ###############################################################################
286 # _rename_handler
287 ###############################################################################
288 sub _rename_handler {
289 my($self, $itempath, $nodes, $data, $expdir) = @_;
291 # to rename a file in SVN, we must add "with history" then delete the orig.
293 my $newname = $data->{info};
294 my $newpath = $itempath;
296 if ($data->{itemtype} == 1) {
297 $newpath =~ s:(.*/)?.+$:$1$newname:;
298 } else {
299 $newpath =~ s:(.*/)?.*:$1$newname:;
302 if ($self->{repository}->exists ($newpath)) {
303 $self->add_error("Attempt to rename item '$itempath' to '$newpath' at "
304 . "revision $data->{revision_id}, but destination already exists: possibly "
305 . "missing delete; skipping");
306 return 0;
309 if (!$self->{repository}->exists ($itempath)) {
310 $self->add_error("Attempt to rename item '$itempath' to '$newpath' at "
311 . "revision $data->{revision_id}, but source doesn't exists: possibly "
312 . "missing recover; skipping");
313 return 0;
316 my $node = Vss2Svn::Dumpfile::Node->new();
317 $node->set_initial_props($newpath, $data);
318 # change the properties according to the new name
319 if (defined $self->{auto_props}) {
320 $node->add_props ($self->{auto_props}->get_props ($newpath));
322 $node->{action} = 'add';
324 my($copyrev, $copypath);
326 # ideally, we should be finding the last time the file was modified and
327 # copy it from there, but that becomes difficult to track...
328 $copyrev = $data->{revision_id} - 1;
329 $copypath = $itempath;
331 $node->{copyrev} = $copyrev;
332 $node->{copypath} = $copypath;
334 push @$nodes, $node;
336 # $self->track_modified($data->{physname}, $data->{revision_id}, $newpath);
337 # $self->track_version ($data->{physname}, $data->{version}, $newpath);
339 $node = Vss2Svn::Dumpfile::Node->new();
340 $node->set_initial_props($itempath, $data);
341 $node->{action} = 'delete';
342 $node->{hideprops} = 1;
344 push @$nodes, $node;
346 # We don't add this to %gDeleted since VSS doesn't treat a rename as an
347 # add/delete and therefore we wouldn't recover from this point
349 } # End _rename_handler
351 ###############################################################################
352 # _share_handler
353 ###############################################################################
354 sub _share_handler {
355 my($self, $itempath, $nodes, $data, $expdir) = @_;
357 if ($self->{repository}->exists ($itempath)) {
358 $self->add_error("Attempt to share item '$data->{info}' to '$itempath' at "
359 . "revision $data->{revision_id}, but destination already exists: possibly "
360 . "missing delete; skipping");
361 return 0;
364 # It could be possible that we share from a historically renamed item, so we don't check the source
365 # if ($self->{repository}->exists ($data->{info})) {
366 # $self->add_error("Attempt to share item '$itempath' to '$newpath' at "
367 # . "revision $data->{revision_id}, but destination already exists: possibly "
368 # . "missing delete; skipping");
369 # return 0;
372 my $node = Vss2Svn::Dumpfile::Node->new();
373 $node->set_initial_props($itempath, $data);
374 $node->{action} = 'add';
376 # @{ $node }{ qw(copyrev copypath) }
377 # = $self->last_modified_rev_path($data->{physname});
378 $node->{copyrev} =
379 $self->get_revision ($data->{physname}, $data->{version}, $data->{info});
380 $node->{copypath} = $data->{info};
382 if (!defined $node->{copyrev} || !defined $node->{copypath}) {
383 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
386 $self->track_version ($data->{physname}, $data->{version}, $itempath);
388 push @$nodes, $node;
390 } # End _share_handler
392 ###############################################################################
393 # _branch_handler
394 ###############################################################################
395 sub _branch_handler {
396 my($self, $itempath, $nodes, $data, $expdir) = @_;
398 # branching is a no-op in SVN
400 # since it is possible, that we refer to version prior to the branch later, we
401 # need to copy all internal information about the ancestor to the child.
402 if (defined $data->{info}) {
403 # only copy versions, that are common between the branch source and the branch.
404 my $copy_version=$data->{version};
405 while(--$copy_version > 0) {
406 if (defined $gVersion{$data->{info}}->[$copy_version]) {
407 $gVersion{$data->{physname}}->[$copy_version] =
408 $gVersion{$data->{info}}->[$copy_version];
413 # # if the file is copied later, we need to track, the revision of this branch
414 # # see the shareBranchShareModify Test
415 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
416 $self->track_version ($data->{physname}, $data->{version}, $itempath);
418 } # End _branch_handler
420 ###############################################################################
421 # _move_handler
422 ###############################################################################
423 sub _move_handler {
424 my($self, $itempath, $nodes, $data, $expdir) = @_;
426 # moving in SVN is the same as renaming; add the new and delete the old
428 my $oldpath = $data->{info};
430 if ($self->{repository}->exists ($itempath)) {
431 $self->add_error("Attempt to move item '$oldpath' to '$itempath' at "
432 . "revision $data->{revision_id}, but destination already exists: possibly "
433 . "missing delete; skipping");
434 return 0;
437 if (!$self->{repository}->exists ($oldpath)) {
438 $self->add_error("Attempt to move item '$oldpath' to '$itempath' at "
439 . "revision $data->{revision_id}, but source doesn't exists: possibly "
440 . "missing recover; skipping");
441 return 0;
444 my $success = $self->{repository}->exists_parent($itempath);
445 if(!defined($success)) {
446 $self->add_error("Attempt to move item '$oldpath' to '$itempath' at "
447 . "revision $data->{revision_id}, but path consistency failure at dest");
448 return 0;
450 elsif ($success == 0) {
451 $self->add_error("Parent path missing while trying to move "
452 . "item '$oldpath' to '$itempath' at "
453 . "revision $data->{revision_id}: adding missing parents");
454 $self->_create_svn_path ($nodes, $itempath);
457 my $node = Vss2Svn::Dumpfile::Node->new();
458 $node->set_initial_props($itempath, $data);
459 $node->{action} = 'add';
461 my($copyrev, $copypath);
463 $copyrev = $data->{revision_id} - 1;
464 $copypath = $oldpath;
466 $node->{copyrev} = $copyrev;
467 $node->{copypath} = $copypath;
469 push @$nodes, $node;
471 # the new move target is a valid path.
472 $self->track_version ($data->{physname}, $data->{version}, $itempath);
474 $node = Vss2Svn::Dumpfile::Node->new();
475 $node->set_initial_props($oldpath, $data);
476 $node->{action} = 'delete';
477 $node->{hideprops} = 1;
479 # Deleted tracking is only necessary to be able to recover the item. But a move
480 # does not set a recover point, so we don't need to track the delete here. Additionally
481 # we do not have enough information for this operation.
482 # $self->track_deleted($data->{oldparentphys}, $data->{physname},
483 # $data->{revision_id}, $oldpath);
485 push @$nodes, $node;
487 } # End _move_handler
489 ###############################################################################
490 # _delete_handler
491 ###############################################################################
492 sub _delete_handler {
493 my($self, $itempath, $nodes, $data, $expdir) = @_;
495 if (!$self->{repository}->exists ($itempath)) {
496 $self->add_error("Attempt to delete non-existent item '$itempath' at "
497 . "revision $data->{revision_id}: possibly "
498 . "missing recover/add/share; skipping");
499 return 0;
502 my $node = Vss2Svn::Dumpfile::Node->new();
503 $node->set_initial_props($itempath, $data);
504 $node->{action} = 'delete';
505 $node->{hideprops} = 1;
507 push @$nodes, $node;
509 $self->track_deleted($data->{parentphys}, $data->{physname},
510 $data->{revision_id}, $itempath);
512 } # End _delete_handler
514 ###############################################################################
515 # _recover_handler
516 ###############################################################################
517 sub _recover_handler {
518 my($self, $itempath, $nodes, $data, $expdir) = @_;
520 if ($self->{repository}->exists ($itempath)) {
521 $self->add_error("Attempt to recover existing item '$itempath' at "
522 . "revision $data->{revision_id}: possibly "
523 . "missing delete; change to commit");
524 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
527 my $node = Vss2Svn::Dumpfile::Node->new();
528 $node->set_initial_props($itempath, $data);
529 $node->{action} = 'add';
531 # for projects we want to go back to the revision just one before the deleted
532 # revision. For files, we need to go back to the specified revision, since
533 # the file could have been modified via a share.
534 my($copyrev, $copypath);
535 if (!defined ($data->{version})) {
536 ($copyrev, $copypath)= $self->last_deleted_rev_path($data->{parentphys},
537 $data->{physname});
538 $copyrev -= 1;
540 else {
541 $copyrev =
542 $self->get_revision ($data->{physname}, $data->{version}, $data->{info});
543 $copypath = $data->{info};
546 if (!defined $copyrev || !defined $copypath) {
547 $self->add_error(
548 "Could not recover path $itempath at revision $data->{revision_id};"
549 . " unable to determine deleted revision or path");
550 return 0;
553 $node->{copyrev} = $copyrev;
554 $node->{copypath} = $copypath;
556 if (defined ($data->{version})) {
557 $self->track_version ($data->{physname}, $data->{version}, $itempath);
560 push @$nodes, $node;
562 } # End _recover_handler
564 ###############################################################################
565 # _pin_handler
566 ###############################################################################
567 sub _pin_handler {
568 my($self, $itempath, $nodes, $data, $expdir) = @_;
570 if (!$self->{repository}->exists ($itempath)) {
571 $self->add_error("Attempt to pin non-existing item '$itempath' at "
572 . "revision $data->{revision_id}: possibly "
573 . "missing recover; skipping");
574 return 0;
577 my $copyrev =
578 $self->get_revision ($data->{physname}, $data->{version}, $data->{info});
579 my $copypath = $data->{info};
581 # if one of the necessary copy from attributes are unavailable we fall back
582 # to a complete checkin
583 if (defined $copyrev && defined $copypath) {
584 $data->{comment} = "ported from $copypath r$copyrev";
586 # if (!defined $copyrev || !defined $copypath) {
587 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
590 my $node = Vss2Svn::Dumpfile::Node->new();
591 $node->set_initial_props($itempath, $data);
592 $node->{action} = 'add';
594 $node->{copyrev} = $copyrev;
595 $node->{copypath} = $copypath;
597 $self->track_version ($data->{physname}, $data->{version}, $itempath);
599 push @$nodes, $node;
601 } # End _pin_handler
603 ###############################################################################
604 # _label_handler
605 ###############################################################################
606 sub _label_handler {
607 my($self, $itempath, $nodes, $data, $expdir) = @_;
609 if (!$self->{repository}->exists ($itempath)) {
610 $self->add_error("Attempt to label non-existing item '$itempath' at "
611 . "revision $data->{revision_id}: possibly "
612 . "missing recover; skipping");
613 return 0;
616 my $label = $data->{info};
618 # It is possible that the label was deleted later, so we see here a label
619 # action, but no label was assigned. In this case, we only need to track
620 # the version->revision mapping, since the version could have been used
621 # as a valid share source.
622 if (defined ($label)) {
623 my $labeldir = $main::gCfg{labeldir};
625 if (defined $self->{label_mapper}) {
626 $labeldir = $self->{label_mapper}->remap ($main::gCfg{labeldir}, $label);
628 $labeldir =~ s:\\:/:g;
629 $labeldir =~ s:/$::;
631 $label =~ s![\\/:*?"<>|]!_!g;
633 my $vssitempath = $itempath;
634 $vssitempath =~ s/^$main::gCfg{trunkdir}//;
635 my $labelpath = "$labeldir/$label$vssitempath";
637 $self->_create_svn_path ($nodes, $labelpath);
639 my $node = Vss2Svn::Dumpfile::Node->new();
640 $node->set_initial_props($labelpath, $data);
641 $node->{action} = 'add';
643 my $copyrev = $data->{revision_id} - 1;
644 my $copypath = $itempath;
646 $node->{copyrev} = $copyrev;
647 $node->{copypath} = $copypath;
649 push @$nodes, $node;
653 $self->track_version ($data->{physname}, $data->{version}, $itempath);
654 } # End _label_handler
656 ###############################################################################
657 # _add_svn_dir
658 ###############################################################################
659 sub _add_svn_dir {
660 my($self, $nodes, $dir) = @_;
662 my $node = Vss2Svn::Dumpfile::Node->new();
663 my $data = { itemtype => 1, is_binary => 0 };
665 $node->set_initial_props($dir, $data);
666 $node->{action} = 'add';
668 push @$nodes, $node;
669 } # End _add_svn_dir
672 ###############################################################################
673 # _create_svn_path
674 ###############################################################################
675 sub _create_svn_path {
676 my($self, $nodes, $itempath) = @_;
678 my $missing_dirs = $self->{repository}->get_missing_dirs($itempath);
680 foreach my $dir (@$missing_dirs) {
681 $self->_add_svn_dir($nodes, $dir);
683 } # End _create_svn_path
685 ###############################################################################
686 # track_version
687 ###############################################################################
688 sub track_version {
689 my($self, $physname, $version, $itempath) = @_;
691 my $record =
693 physname => $physname,
694 version => $version,
695 revision => $self->{revision},
696 itempath => $itempath,
698 push @{$self->{version_cache}}, $record;
700 } # End track_version
703 ###############################################################################
704 # get_revision
705 ###############################################################################
706 sub get_revision {
707 my($self, $physname, $version, $itempath) = @_;
709 if (!defined($gVersion{$physname})) {
710 return (undef);
713 if (!exists($gVersion{$physname}->[$version])) {
714 return (undef);
717 return $gVersion{$physname}->[$version]->{$itempath};
719 } # End get_revision
721 ###############################################################################
722 # track_deleted
723 ###############################################################################
724 sub track_deleted {
725 my($self, $parentphys, $physname, $revision, $path) = @_;
727 $self->{deleted_cache}->{$parentphys}->{$physname} =
729 revision => $revision,
730 path => $path,
733 } # End track_deleted
735 ###############################################################################
736 # last_deleted_rev_path
737 ###############################################################################
738 sub last_deleted_rev_path {
739 my($self, $parentphys, $physname) = @_;
741 if (!defined($gDeleted{$parentphys})) {
742 return (undef, undef);
745 if (!defined($gDeleted{$parentphys}->{$physname})) {
746 return (undef, undef);
749 return @{ $gDeleted{$parentphys}->{$physname} }{ qw(revision path) };
750 } # End last_deleted_rev_path
752 ###############################################################################
753 # get_export_file
754 ###############################################################################
755 sub get_export_file {
756 my($self, $node, $data, $expdir) = @_;
758 if (!defined($expdir)) {
759 return 0;
760 } elsif (!defined($data->{version})) {
761 $self->add_error(
762 "Attempt to retrieve file contents with unknown version number");
763 return 0;
766 $node->{file} = "$expdir/$data->{physname}.$data->{version}";
767 return 1;
769 } # End get_export_file
771 ###############################################################################
772 # output_node
773 ###############################################################################
774 sub output_node {
775 my($self, $node) = @_;
776 my $fh = $self->{fh};
778 # only in an add or rename action the propery array is set. So we have
779 # to lookup the eol-style flag again. The best thing is to query the
780 # property always temporarirly
781 my %tmpProps = ();
782 if (defined $self->{auto_props}) {
783 %tmpProps = $self->{auto_props}->get_props ($node->{path});
785 my $eolStyle = $tmpProps{'svn:eol-style'};
786 my $isNative = (defined $eolStyle && $eolStyle eq 'native') ? 1 : 0;
788 my $string = $node->get_headers();
789 print $fh $string;
790 $self->output_content($node->{hideprops}? undef : $node->{props},
791 $node->{text}, $node->{file}, $isNative);
792 } # End output_node
794 ###############################################################################
795 # output_content
796 ###############################################################################
797 sub output_content {
798 my($self, $props, $text, $file, $isNative) = @_;
800 my $fh = $self->{fh};
802 $text = '' unless defined $text || defined $file;
804 my $proplen = 0;
805 my $textlen = 0;
806 my($propout, $textout) = ('') x 2;
808 if (defined($props)) {
809 foreach my $key (keys %$props) {
810 my $value = $props->{$key};
811 $propout .= 'K ' . length($key) . "\n$key\n";
812 if (defined $value) {
813 $propout .= 'V ' . length($value) . "\n$value\n";
815 else {
816 $propout .= "V 0\n\n";
820 $propout .= "PROPS-END\n";
821 $proplen = length($propout);
824 my $md5;
825 $md5 = Digest::MD5->new if $self->{do_md5};
827 # prevent errors due to non existing files
828 if(!defined $text && defined $file && !-e $file) {
829 $text = "";
832 # convert CRLF -> LF before calculating the size and compute the md5
833 if(!defined $text && defined $file) {
835 my ($input, $output);
836 if (defined $isNative && $isNative) {
837 open ($input, "<:crlf", $file);
838 my $tmpFile = "$gTmpDir/crlf_to_lf.tmp.txt";
839 open ($output, ">", $tmpFile);
840 binmode ($output);
842 while(<$input>) {
843 $md5->add($_) if $self->{do_md5};
844 print $output $_;
847 close $input;
848 close $output;
849 $file = $tmpFile;
851 else {
852 open ($input, "<", $file);
853 binmode ($input);
854 $md5->addfile($input) if $self->{do_md5};
855 close $input;
857 } else {
858 $md5->add($text) if $self->{do_md5};
861 my $digest = $md5->hexdigest if $self->{do_md5};
862 # print "digest: $digest\n";
864 if(!defined $text && defined $file) {
865 $textlen = -s $file;
866 } else {
867 $textlen = length($text);
869 return if ($textlen + $proplen == 0 && !defined $file);
871 if ($proplen > 0) {
872 print $fh "Prop-content-length: $proplen\n";
875 if (defined $file || $textlen > 0) {
876 print $fh "Text-content-length: $textlen\n";
877 print $fh "Text-content-md5: $digest\n" if $self->{do_md5};
880 print $fh "Content-length: " . ($proplen + $textlen)
881 . "\n\n$propout";
883 if(!defined $text && defined $file) {
884 copy($file, $fh);
885 print $fh "\n";
886 } else {
887 print $fh "$text\n";
890 } # End output_content
892 ###############################################################################
893 # svn_timestamp
894 ###############################################################################
895 sub svn_timestamp {
896 my($self, $vss_timestamp) = @_;
898 return &SvnTimestamp($vss_timestamp);
900 } # End svn_timestamp
902 ###############################################################################
903 # SvnTimestamp
904 ###############################################################################
905 sub SvnTimestamp {
906 my($vss_timestamp) = @_;
908 # set the correct time: VSS stores the local time as the timestamp, but subversion
909 # needs a gmtime. So we need to reverse adjust the timestamp in order to turn back
910 # the clock.
911 my($sec, $min, $hour, $day, $mon, $year) = gmtime($vss_timestamp);
912 my($faketime) = Time::Local::timelocal ($sec, $min, $hour, $day, $mon, $year);
913 ($sec, $min, $hour, $day, $mon, $year) = gmtime($faketime);
915 $year += 1900;
916 $mon += 1;
918 return sprintf("%4.4i-%2.2i-%2.2iT%2.2i:%2.2i:%2.2i.%6.6iZ",
919 $year, $mon, $day, $hour, $min, $sec, 0);
921 } # End SvnTimestamp
923 ###############################################################################
924 # add_error
925 ###############################################################################
926 sub add_error {
927 my($self, $msg) = @_;
929 push @{ $self->{errors} }, $msg;
930 } # End add_error