1 package Vss2Svn
::Dumpfile
;
3 use Vss2Svn
::Dumpfile
::Node
;
4 use Encode
qw(from_to);
11 ADD
=> \
&_add_handler
,
12 COMMIT
=> \
&_commit_handler
,
13 RENAME
=> \
&_rename_handler
,
14 SHARE
=> \
&_share_handler
,
15 BRANCH
=> \
&_branch_handler
,
16 MOVE
=> \
&_move_handler
,
17 DELETE
=> \
&_delete_handler
,
18 RECOVER
=> \
&_recover_handler
,
21 # Keep track of when paths were modified or deleted, for subsequent copies
27 ###############################################################################
29 ###############################################################################
43 need_missing_dirs
=> [],
46 # prevent perl from doing line-ending conversions
49 my $old = select($fh);
53 print $fh "SVN-fs-dump-format-version: 2\n\n";
55 $self = bless($self, $class);
60 ###############################################################################
62 ###############################################################################
72 ###############################################################################
74 ###############################################################################
76 my($self, $data) = @_;
77 my($revision, $author, $timestamp, $comment) =
78 @
{ $data }{qw(revision_id author timestamp comment)};
83 print $fh "\nRevision-number: $revision\n";
85 $comment = '' if !defined($comment);
86 $author = '' if !defined($author);
89 from_to
($comment, "windows-1252", "utf8");
90 from_to
($author, "windows-1252", "utf8");
93 push @
$props, ['svn:log', $comment];
94 push @
$props, ['svn:author', $author];
97 push @
$props, ['svn:date', $self->svn_timestamp($timestamp)];
99 $self->output_content($props);
100 $self->{revision
} = $revision;
102 } # End begin_revision
104 ###############################################################################
106 ###############################################################################
108 my($self, $data, $expdir) = @_;
110 my $action = $data->{action
};
114 # Temporary hack to prevent shared files from stepping on the "modified"
115 # flag for other than the first commit. Ideally, we should keep all paths
116 # for a given physical file's last modified flags, and use the best match
117 # if we need to copy or recover one.
119 $self->{is_primary
} = 1;
120 $self->{modified_cache
} = {};
121 $self->{deleted_cache
} = {};
123 my($handler, $this_action);
125 foreach my $itempath (split "\t", $data->{itempaths
}) {
126 $this_action = $action;
128 if(defined($itempath)) {
129 ($this_action, $itempath) =
130 $self->_action_path_sanity_check($this_action, $itempath, $data);
132 return 0 unless defined($itempath);
135 # if the item's path isn't defined, its real name was corrupted in
136 # vss, so we'll check it in to the junk drawer as an add
137 if (defined $main::gCfg
{junkdir
}) {
138 $itempath = $self->_get_junk_itempath($main::gCfg
{junkdir
},
139 join('.', @
$data{ qw(physname version revision_id) }));
141 $self->add_error("Using filename '$itempath' for item with "
142 . "unrecoverable name at revision $data->{revision_id}");
144 $this_action = 'ADD';
150 # if need_junkdir = 1, the first item is just about to be added to the
151 # junk drawer, so create the dumpfile node to add this directory
152 if ($self->{need_junkdir
} == 1) {
153 $self->_add_svn_dir($nodes, $main::gCfg
{junkdir
});
154 $self->{need_junkdir
} = -1;
157 foreach my $dir (@
{ $self->{need_missing_dirs
} }) {
158 $self->_add_svn_dir($nodes, $dir);
159 $self->add_error("Creating missing directory '$dir' for item "
160 . "'$itempath' at revision $data->{revision_id}");
163 $handler = $gHandlers{$this_action};
165 $self->$handler($itempath, $nodes, $data, $expdir);
166 $self->{is_primary
} = 0;
169 foreach my $node (@
$nodes) {
170 $self->output_node($node);
173 my($physname, $cache);
174 while(($physname, $cache) = each %{ $self->{modified_cache
} }) {
175 $gModified{$physname} = $cache;
178 while(($physname, $cache) = each %{ $self->{deleted_cache
} }) {
179 $gDeleted{$physname} = $cache;
184 ###############################################################################
186 ###############################################################################
187 sub _get_junk_itempath
{
188 my($self, $dir, $base) = @_;
191 my $itempath = "$dir/$base";
194 if($self->{need_junkdir
} == 0) {
195 $self->{need_junkdir
} = 1;
198 if(!defined($self->{junk_itempaths
}->{$itempath})) {
199 $self->{junk_itempaths
}->{$itempath} = 1;
205 if($base =~ m/^(.*)\.(.*)/) {
206 ($file, $ext) = ($1, ".$2");
208 ($file, $ext) = ($base, '');
211 while(defined($self->{junk_itempaths
}->{$itempath})) {
212 $itempath = "$dir/$file.$count$ext";
217 } # End _get_junk_itempath
219 ###############################################################################
220 # _action_path_sanity_check
221 ###############################################################################
222 sub _action_path_sanity_check
{
223 my($self, $action, $itempath, $data) = @_;
225 my($itemtype, $revision_id) = @
{ $data }{qw(itemtype revision_id)};
227 return($action, $itempath) if ($itempath eq '' || $itempath eq '/');
229 my($newaction, $newpath) = ($action, $itempath);
232 $self->{need_missing_dirs
} = [];
234 if($action eq 'ADD' || $action eq 'SHARE' || $action eq 'RECOVER') {
235 $success = $self->_add_svn_struct_item($itempath, $itemtype);
237 if(!defined($success)) {
239 $self->add_error("Path consistency failure while trying to add "
240 . "item '$itempath' at revision $revision_id; skipping");
242 } elsif($success == 0) {
243 # trying to re-add existing item; if file, change it to a commit
244 if ($itemtype == 1) {
247 $self->add_error("Attempt to re-add directory '$itempath' at "
248 . "revision $revision_id; possibly missing delete");
252 $newaction = 'COMMIT';
253 $self->add_error("Attempt to re-add file '$itempath' at "
254 . "revision $revision_id, changing to modify; possibly "
260 } elsif ($action eq 'DELETE') {
261 $success = $self->_delete_svn_struct_item($itempath, $itemtype);
265 $self->add_error("Attempt to delete non-existent item '$itempath' "
266 . "at revision $revision_id; skipping...");
269 } elsif ($action eq 'RENAME') {
270 $success = $self->_rename_svn_struct_item($itempath, $itemtype,
275 $self->add_error("Attempt to rename non-existent item '$itempath' "
276 . "at revision $revision_id; skipping...");
278 } elsif ($action eq 'MOVE') {
279 my ($ref, $item) = $self->_get_svn_struct_ref_for_move($itempath);
283 $self->add_error("Attempt to move non-existent directory '$itempath' "
284 . "at revision $revision_id; skipping...");
287 $success = $self->_add_svn_struct_item($data->{info
}, 1, $ref->{$item});
291 $self->add_error("Error while attempting to move directory '$itempath' "
292 . "at revision $revision_id; skipping...");
295 delete $ref->{$item};
298 return($newaction, $newpath);
300 } # End _action_path_sanity_check
302 ###############################################################################
303 # _add_svn_struct_item
304 ###############################################################################
305 sub _add_svn_struct_item
{
306 my($self, $itempath, $itemtype, $newref) = @_;
309 my @subdirs = split '/', $itempath;
311 my $item = pop(@subdirs);
312 my $ref = $self->{svn_items
};
316 foreach my $subdir (@subdirs) {
317 $thispath .= "$subdir/";
319 if(ref($ref) ne 'HASH') {
322 if(!defined($ref->{$subdir})) {
323 # parent directory doesn't exist; add it to list of missing dirs
325 push @
{ $self->{need_missing_dirs
} }, $thispath;
327 $ref->{$subdir} = {};
330 $ref = $ref->{$subdir};
333 if(ref($ref) ne 'HASH') {
334 # parent "directory" is actually a file
338 if(defined($ref->{$item})) {
339 # item already exists; can't add it
343 if(defined($newref)) {
344 $ref->{$item} = $newref;
346 $ref->{$item} = ($itemtype == 1)?
{} : 1;
351 } # End _add_svn_struct_item
353 ###############################################################################
354 # _delete_svn_struct_item
355 ###############################################################################
356 sub _delete_svn_struct_item
{
357 my($self, $itempath, $itemtype) = @_;
359 return $self->_delete_rename_svn_struct_item($itempath, $itemtype);
360 } # End _delete_svn_struct_item
362 ###############################################################################
363 # _rename_svn_struct_item
364 ###############################################################################
365 sub _rename_svn_struct_item
{
366 my($self, $itempath, $itemtype, $newname) = @_;
368 return $self->_delete_rename_svn_struct_item($itempath, $itemtype, $newname);
369 } # End _rename_svn_struct_item
371 ###############################################################################
372 # _delete_rename_svn_struct_item
373 ###############################################################################
374 sub _delete_rename_svn_struct_item
{
375 my($self, $itempath, $itemtype, $newname, $movedref) = @_;
378 $newname =~ s
:/$:: if defined($newname);
379 my @subdirs = split '/', $itempath;
381 my $item = pop(@subdirs);
382 my $ref = $self->{svn_items
};
384 foreach my $subdir (@subdirs) {
385 if(!(ref($ref) eq 'HASH') || !defined($ref->{$subdir})) {
386 # can't get to item because a parent directory doesn't exist; give up
390 $ref = $ref->{$subdir};
393 if((ref($ref) ne 'HASH') || !defined($ref->{$item})) {
394 # item doesn't exist; can't delete/rename it
398 if(defined $newname) {
399 $ref->{$newname} = $ref->{$item};
402 delete $ref->{$item};
406 } # End _delete_rename_svn_struct_item
408 ###############################################################################
409 # _get_svn_struct_ref_for_move
410 ###############################################################################
411 sub _get_svn_struct_ref_for_move
{
412 my($self, $itempath) = @_;
415 my @subdirs = split '/', $itempath;
417 my $item = pop(@subdirs);
418 my $ref = $self->{svn_items
};
422 foreach my $subdir (@subdirs) {
423 $thispath .= "$subdir/";
425 if(ref($ref) ne 'HASH') {
428 if(!defined($ref->{$subdir})) {
432 $ref = $ref->{$subdir};
435 if((ref($ref) ne 'HASH') || !defined($ref->{$item}) ||
436 (ref($ref->{$item} ne 'HASH'))) {
440 return ($ref, $item);
442 } # End _get_svn_struct_ref_for_move
444 ###############################################################################
446 ###############################################################################
448 my($self, $nodes, $dir) = @_;
450 my $node = Vss2Svn
::Dumpfile
::Node
->new();
451 my $data = { itemtype
=> 1, is_binary
=> 0 };
453 $node->set_initial_props($dir, $data);
454 $node->{action
} = 'add';
457 $self->_add_svn_struct_item($dir, 1);
461 ###############################################################################
463 ###############################################################################
465 my($self, $itempath, $nodes, $data, $expdir) = @_;
467 my $node = Vss2Svn
::Dumpfile
::Node
->new();
468 $node->set_initial_props($itempath, $data);
469 $node->{action
} = 'add';
471 if ($data->{itemtype
} == 2) {
472 $self->get_export_contents($node, $data, $expdir);
475 $self->track_modified($data->{physname
}, $data->{revision_id
}, $itempath);
481 ###############################################################################
483 ###############################################################################
484 sub _commit_handler
{
485 my($self, $itempath, $nodes, $data, $expdir) = @_;
487 my $node = Vss2Svn
::Dumpfile
::Node
->new();
488 $node->set_initial_props($itempath, $data);
489 $node->{action
} = 'change';
491 if ($data->{itemtype
} == 2) {
492 $self->get_export_contents($node, $data, $expdir);
495 $self->track_modified($data->{physname
}, $data->{revision_id
}, $itempath);
499 } # End _commit_handler
501 ###############################################################################
503 ###############################################################################
504 sub _rename_handler
{
505 my($self, $itempath, $nodes, $data, $expdir) = @_;
507 # to rename a file in SVN, we must add "with history" then delete the orig.
509 my $newname = $data->{info
};
511 my $newpath = $itempath;
513 $newpath =~ s
:(.*/)?
.*:$1$newname:;
515 my $node = Vss2Svn
::Dumpfile
::Node
->new();
516 $node->set_initial_props($newpath, $data);
517 $node->{action
} = 'add';
519 my($copyrev, $copypath);
521 # ideally, we should be finding the last time the file was modified and
522 # copy it from there, but that becomes difficult to track...
523 $copyrev = $data->{revision_id
} - 1;
524 $copypath = $itempath;
526 $node->{copyrev
} = $copyrev;
527 $node->{copypath
} = $copypath;
531 $self->track_modified($data->{physname
}, $data->{revision_id
}, $newpath);
533 $node = Vss2Svn
::Dumpfile
::Node
->new();
534 $node->{path
} = $itempath;
535 $node->{action
} = 'delete';
536 $node->{hideprops
} = 1;
540 # We don't add this to %gDeleted since VSS doesn't treat a rename as an
541 # add/delete and therefore we wouldn't recover from this point
543 } # End _rename_handler
545 ###############################################################################
547 ###############################################################################
549 my($self, $itempath, $nodes, $data, $expdir) = @_;
551 my $node = Vss2Svn
::Dumpfile
::Node
->new();
552 $node->set_initial_props($itempath, $data);
553 $node->{action
} = 'add';
555 @
{ $node }{ qw(copyrev copypath) }
556 = $self->last_modified_rev_path($data->{physname
});
558 return unless defined($node->{copyrev
});
562 } # End _share_handler
564 ###############################################################################
566 ###############################################################################
567 sub _branch_handler
{
568 my($self, $itempath, $nodes, $data, $expdir) = @_;
570 # branching is a no-op in SVN
572 # if the file is copied later, we need to track, the revision of this branch
573 # see the shareBranchShareModify Test
574 $self->track_modified($data->{physname
}, $data->{revision_id
}, $itempath);
576 } # End _branch_handler
578 ###############################################################################
580 ###############################################################################
582 my($self, $itempath, $nodes, $data, $expdir) = @_;
584 # moving in SVN is the same as renaming; add the new and delete the old
586 my $newpath = $data->{info
};
588 my $node = Vss2Svn
::Dumpfile
::Node
->new();
589 $node->set_initial_props($newpath, $data);
590 $node->{action
} = 'add';
592 my($copyrev, $copypath);
594 $copyrev = $data->{revision_id
} - 1;
595 $copypath = $itempath;
597 $node->{copyrev
} = $copyrev;
598 $node->{copypath
} = $copypath;
602 $self->track_modified($data->{physname
}, $data->{revision_id
}, $newpath);
604 $node = Vss2Svn
::Dumpfile
::Node
->new();
605 $node->{path
} = $itempath;
606 $node->{action
} = 'delete';
607 $node->{hideprops
} = 1;
611 } # End _move_handler
613 ###############################################################################
615 ###############################################################################
616 sub _delete_handler
{
617 my($self, $itempath, $nodes, $data, $expdir) = @_;
619 my $node = Vss2Svn
::Dumpfile
::Node
->new();
620 $node->{path
} = $itempath;
621 $node->{action
} = 'delete';
622 $node->{hideprops
} = 1;
626 $self->track_deleted($data->{physname
}, $data->{revision_id
},
629 } # End _delete_handler
631 ###############################################################################
633 ###############################################################################
634 sub _recover_handler
{
635 my($self, $itempath, $nodes, $data, $expdir) = @_;
637 my $node = Vss2Svn
::Dumpfile
::Node
->new();
638 $node->set_initial_props($itempath, $data);
639 $node->{action
} = 'add';
641 my($copyrev, $copypath) = $self->last_deleted_rev_path($data->{physname
});
643 if (!defined $copyrev) {
645 "Could not recover path $itempath at revision $data->{revision_id};"
646 . " unable to determine deleted revision");
650 $node->{copyrev
} = $copyrev - 1;
651 $node->{copypath
} = $copypath;
655 } # End _recover_handler
657 ###############################################################################
659 ###############################################################################
661 my($self, $physname, $revision, $path) = @_;
663 return unless $self->{is_primary
};
665 $self->{modified_cache
}->{$physname} =
667 revision
=> $revision,
671 } # End track_modified
673 ###############################################################################
675 ###############################################################################
677 my($self, $physname, $revision, $path) = @_;
679 $self->{deleted_cache
}->{$physname} =
681 revision
=> $revision,
685 } # End track_deleted
687 ###############################################################################
688 # last_modified_rev_path
689 ###############################################################################
690 sub last_modified_rev_path
{
691 my($self, $physname) = @_;
693 if (!defined($gModified{$physname})) {
694 return (undef, undef);
697 return @
{ $gModified{$physname} }{ qw(revision path) };
698 } # End last_modified_rev_path
700 ###############################################################################
701 # last_deleted_rev_path
702 ###############################################################################
703 sub last_deleted_rev_path
{
704 my($self, $physname) = @_;
706 if (!defined($gDeleted{$physname})) {
707 return (undef, undef);
710 return @
{ $gDeleted{$physname} }{ qw(revision path) };
711 } # End last_deleted_rev_path
713 ###############################################################################
714 # get_export_contents
715 ###############################################################################
716 sub get_export_contents
{
717 my($self, $node, $data, $expdir) = @_;
719 if (!defined($expdir)) {
721 } elsif (!defined($data->{version
})) {
723 "Attempt to retrieve file contents with unknown version number");
727 my $file = "$expdir\\$data->{physname}.$data->{version}";
729 if (!open EXP
, "$file") {
730 $self->add_error("Could not open export file '$file'");
736 # $node->{text} = join('', <EXP>);
737 $node->{text
} = do { local( $/ ) ; <EXP
> } ;
743 } # End get_export_contents
745 ###############################################################################
747 ###############################################################################
749 my($self, $node) = @_;
750 my $fh = $self->{fh
};
752 my $string = $node->get_headers();
753 from_to
($string, "windows-1252", "utf8");
755 $self->output_content($node->{hideprops
}?
undef : $node->{props
},
759 ###############################################################################
761 ###############################################################################
763 my($self, $props, $text) = @_;
765 my $fh = $self->{fh
};
767 $text = '' unless defined $text;
771 my($propout, $textout) = ('') x
2;
775 if (defined($props)) {
776 foreach my $prop (@
$props) {
777 ($key, $value) = @
$prop;
778 $propout .= 'K ' . length($key) . "\n$key\nV " . length($value)
782 $propout .= "PROPS-END\n";
783 $proplen = length($propout);
786 $textlen = length($text);
787 return if ($textlen + $proplen == 0);
790 print $fh "Prop-content-length: $proplen\n";
794 print $fh "Text-content-length: $textlen\n";
797 print $fh "Content-length: " . ($proplen + $textlen)
798 . "\n\n$propout$text\n";
800 } # End output_content
802 ###############################################################################
804 ###############################################################################
806 my($self, $vss_timestamp) = @_;
808 return &SvnTimestamp
($vss_timestamp);
810 } # End svn_timestamp
812 ###############################################################################
814 ###############################################################################
816 my($vss_timestamp) = @_;
818 my($sec, $min, $hour, $day, $mon, $year) = gmtime($vss_timestamp);
823 return sprintf("%4.4i-%2.2i-%2.2iT%2.2i:%2.2i:%2.2i.%6.6iZ",
824 $year, $mon, $day, $hour, $min, $sec, 0);
828 ###############################################################################
830 ###############################################################################
832 my($self, $msg) = @_;
834 push @
{ $self->{errors
} }, $msg;