1 package Vss2Svn
::Dumpfile
;
3 use Vss2Svn
::Dumpfile
::Node
;
4 use Vss2Svn
::Dumpfile
::SanityChecker
;
5 use Encode
qw(from_to);
12 ADD
=> \
&_add_handler
,
13 COMMIT
=> \
&_commit_handler
,
14 RENAME
=> \
&_rename_handler
,
15 SHARE
=> \
&_share_handler
,
16 BRANCH
=> \
&_branch_handler
,
17 MOVE
=> \
&_move_handler
,
18 DELETE
=> \
&_delete_handler
,
19 RECOVER
=> \
&_recover_handler
,
20 PIN
=> \
&_pin_handler
,
21 LABEL
=> \
&_label_handler
,
24 # Keep track of when paths were modified or deleted, for subsequent copies
31 ###############################################################################
33 ###############################################################################
44 repository
=> Vss2Svn
::Dumpfile
::SanityChecker
->new(),
47 # prevent perl from doing line-ending conversions
50 my $old = select($fh);
54 print $fh "SVN-fs-dump-format-version: 2\n\n";
56 $self = bless($self, $class);
61 ###############################################################################
63 ###############################################################################
73 ###############################################################################
75 ###############################################################################
77 my($self, $data) = @_;
78 my($revision, $author, $timestamp, $comment) =
79 @
{ $data }{qw(revision_id author timestamp comment)};
84 print $fh "\nRevision-number: $revision\n";
86 $comment = '' if !defined($comment);
87 $author = '' if !defined($author);
90 from_to
($comment, "windows-1252", "utf8");
91 from_to
($author, "windows-1252", "utf8");
94 push @
$props, ['svn:log', $comment];
95 push @
$props, ['svn:author', $author];
98 push @
$props, ['svn:date', $self->svn_timestamp($timestamp)];
100 $self->output_content($props);
101 $self->{revision
} = $revision;
103 } # End begin_revision
105 ###############################################################################
107 ###############################################################################
109 my($self, $data, $expdir) = @_;
111 my $action = $data->{action
};
115 # Temporary hack to prevent shared files from stepping on the "modified"
116 # flag for other than the first commit. Ideally, we should keep all paths
117 # for a given physical file's last modified flags, and use the best match
118 # if we need to copy or recover one.
120 $self->{is_primary
} = 1;
121 $self->{deleted_cache
} = {};
122 $self->{version_cache
} = [];
124 my($handler, $this_action);
126 foreach my $itempath (split "\t", $data->{itempaths
}) {
127 $this_action = $action;
129 # $this_action = $self->sanity_checker->check ($data, $itempath, $nodes);
130 # if (!defined ($this_action)) {
134 $handler = $gHandlers{$this_action};
137 $self->$handler($itempath, $thisnodes, $data, $expdir);
139 # we need to apply all local changes to our repository directly: if we
140 # have an action that operates on multiple items, e.g labeling, the
141 # necessary missing directories are created for the first item
142 foreach my $node (@
$thisnodes) {
143 $self->{repository
}->load($node);
147 $self->{is_primary
} = 0;
150 foreach my $node (@
$nodes) {
151 $self->output_node($node);
154 my($physname, $cache);
156 my ($parentphys, $physnames);
157 while(($parentphys, $physnames) = each %{ $self->{deleted_cache
} }) {
158 while(($physname, $cache) = each %{ $physnames }) {
159 $gDeleted{$parentphys}->{$physname} = $cache;
163 # track the version -> revision mapping for the file
164 foreach my $record (@
{$self->{version_cache
}}) {
165 my $version = \
%{$gVersion{$record->{physname
}}->[$record->{version
}]};
166 $version->{$record->{itempath
}} = $record->{revision
};
172 ###############################################################################
174 ###############################################################################
176 my($self, $itempath, $nodes, $data, $expdir) = @_;
178 if ($self->{repository
}->exists ($itempath)) {
179 if ($data->{itemtype
} == 2) {
180 $self->add_error("Attempt to re-add file '$itempath' at "
181 . "revision $data->{revision_id}, changing to modify; possibly "
183 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
186 $self->add_error("Attempt to re-add directory '$itempath' at "
187 . "revision $data->{revision_id}, skipping action: possibly "
193 my $success = $self->{repository
}->exists_parent ($itempath);
194 if(!defined($success)) {
195 $self->add_error("Path consistency failure while trying to add "
196 . "item '$itempath' at revision $data->{revision_id}; skipping");
199 elsif ($success == 0) {
200 $self->add_error("Parent path missing while trying to add "
201 . "item '$itempath' at revision $data->{revision_id}: adding missing "
203 $self->_create_svn_path ($nodes, $itempath);
206 my $node = Vss2Svn
::Dumpfile
::Node
->new();
207 $node->set_initial_props($itempath, $data);
208 $node->{action
} = 'add';
210 if ($data->{itemtype
} == 2) {
211 $self->get_export_contents($node, $data, $expdir);
214 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
215 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
221 ###############################################################################
223 ###############################################################################
224 sub _commit_handler
{
225 my($self, $itempath, $nodes, $data, $expdir) = @_;
227 if (!$self->{repository
}->exists ($itempath)) {
228 $self->add_error("Attempt to commit to non-existant file '$itempath' at "
229 . "revision $data->{revision_id}, changing to add; possibly "
230 . "missing recover");
231 return $self->_add_handler ($itempath, $nodes, $data, $expdir);
234 my $node = Vss2Svn
::Dumpfile
::Node
->new();
235 $node->set_initial_props($itempath, $data);
236 $node->{action
} = 'change';
238 if ($data->{itemtype
} == 2) {
239 $self->get_export_contents($node, $data, $expdir);
242 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
243 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
247 } # End _commit_handler
249 ###############################################################################
251 ###############################################################################
252 sub _rename_handler
{
253 my($self, $itempath, $nodes, $data, $expdir) = @_;
255 # to rename a file in SVN, we must add "with history" then delete the orig.
257 my $newname = $data->{info
};
258 my $newpath = $itempath;
260 if ($data->{itemtype
} == 1) {
261 $newpath =~ s
:(.*/)?
.+$:$1$newname:;
263 $newpath =~ s
:(.*/)?
.*:$1$newname:;
266 if ($self->{repository
}->exists ($newpath)) {
267 $self->add_error("Attempt to rename item '$itempath' to '$newpath' at "
268 . "revision $data->{revision_id}, but destination already exists: possibly "
269 . "missing delete; skipping");
273 if (!$self->{repository
}->exists ($itempath)) {
274 $self->add_error("Attempt to rename item '$itempath' to '$newpath' at "
275 . "revision $data->{revision_id}, but source doesn't exists: possibly "
276 . "missing recover; skipping");
280 my $node = Vss2Svn
::Dumpfile
::Node
->new();
281 $node->set_initial_props($newpath, $data);
282 $node->{action
} = 'add';
284 my($copyrev, $copypath);
286 # ideally, we should be finding the last time the file was modified and
287 # copy it from there, but that becomes difficult to track...
288 $copyrev = $data->{revision_id
} - 1;
289 $copypath = $itempath;
291 $node->{copyrev
} = $copyrev;
292 $node->{copypath
} = $copypath;
296 # $self->track_modified($data->{physname}, $data->{revision_id}, $newpath);
297 # $self->track_version ($data->{physname}, $data->{version}, $newpath);
299 $node = Vss2Svn
::Dumpfile
::Node
->new();
300 $node->set_initial_props($itempath, $data);
301 $node->{action
} = 'delete';
302 $node->{hideprops
} = 1;
306 # We don't add this to %gDeleted since VSS doesn't treat a rename as an
307 # add/delete and therefore we wouldn't recover from this point
309 } # End _rename_handler
311 ###############################################################################
313 ###############################################################################
315 my($self, $itempath, $nodes, $data, $expdir) = @_;
317 if ($self->{repository
}->exists ($itempath)) {
318 $self->add_error("Attempt to share item '$data->{info}' to '$itempath' at "
319 . "revision $data->{revision_id}, but destination already exists: possibly "
320 . "missing delete; skipping");
324 # It could be possible that we share from a historically renamed item, so we don't check the source
325 # if ($self->{repository}->exists ($data->{info})) {
326 # $self->add_error("Attempt to share item '$itempath' to '$newpath' at "
327 # . "revision $data->{revision_id}, but destination already exists: possibly "
328 # . "missing delete; skipping");
332 my $node = Vss2Svn
::Dumpfile
::Node
->new();
333 $node->set_initial_props($itempath, $data);
334 $node->{action
} = 'add';
336 # @{ $node }{ qw(copyrev copypath) }
337 # = $self->last_modified_rev_path($data->{physname});
339 $self->get_revision ($data->{physname
}, $data->{version
}, $data->{info
});
340 $node->{copypath
} = $data->{info
};
342 if (!defined $node->{copyrev
} || !defined $node->{copypath
}) {
343 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
346 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
350 } # End _share_handler
352 ###############################################################################
354 ###############################################################################
355 sub _branch_handler
{
356 my($self, $itempath, $nodes, $data, $expdir) = @_;
358 # branching is a no-op in SVN
360 # # if the file is copied later, we need to track, the revision of this branch
361 # # see the shareBranchShareModify Test
362 # $self->track_modified($data->{physname}, $data->{revision_id}, $itempath);
363 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
365 } # End _branch_handler
367 ###############################################################################
369 ###############################################################################
371 my($self, $itempath, $nodes, $data, $expdir) = @_;
373 # moving in SVN is the same as renaming; add the new and delete the old
375 my $newpath = $data->{info
};
377 if ($self->{repository
}->exists ($newpath)) {
378 $self->add_error("Attempt to move item '$itempath' to '$newpath' at "
379 . "revision $data->{revision_id}, but destination already exists: possibly "
380 . "missing delete; skipping");
384 if (!$self->{repository
}->exists ($itempath)) {
385 $self->add_error("Attempt to move item '$itempath' to '$newpath' at "
386 . "revision $data->{revision_id}, but source doesn't exists: possibly "
387 . "missing recover; skipping");
391 my $node = Vss2Svn
::Dumpfile
::Node
->new();
392 $node->set_initial_props($newpath, $data);
393 $node->{action
} = 'add';
395 my($copyrev, $copypath);
397 $copyrev = $data->{revision_id
} - 1;
398 $copypath = $itempath;
400 $node->{copyrev
} = $copyrev;
401 $node->{copypath
} = $copypath;
405 # $self->track_modified($data->{physname}, $data->{revision_id}, $newpath);
406 # $self->track_version ($data->{physname}, $data->{version}, $newpath);
408 $node = Vss2Svn
::Dumpfile
::Node
->new();
409 $node->set_initial_props($itempath, $data);
410 $node->{action
} = 'delete';
411 $node->{hideprops
} = 1;
415 } # End _move_handler
417 ###############################################################################
419 ###############################################################################
420 sub _delete_handler
{
421 my($self, $itempath, $nodes, $data, $expdir) = @_;
423 if (!$self->{repository
}->exists ($itempath)) {
424 $self->add_error("Attempt to delete non-existent item '$itempath' at "
425 . "revision $data->{revision_id}: possibly "
426 . "missing recover/add/share; skipping");
430 my $node = Vss2Svn
::Dumpfile
::Node
->new();
431 $node->set_initial_props($itempath, $data);
432 $node->{action
} = 'delete';
433 $node->{hideprops
} = 1;
437 $self->track_deleted($data->{parentphys
}, $data->{physname
},
438 $data->{revision_id
}, $itempath);
440 } # End _delete_handler
442 ###############################################################################
444 ###############################################################################
445 sub _recover_handler
{
446 my($self, $itempath, $nodes, $data, $expdir) = @_;
448 if ($self->{repository
}->exists ($itempath)) {
449 $self->add_error("Attempt to recover existing item '$itempath' at "
450 . "revision $data->{revision_id}: possibly "
451 . "missing delete; change to commit");
452 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
455 my $node = Vss2Svn
::Dumpfile
::Node
->new();
456 $node->set_initial_props($itempath, $data);
457 $node->{action
} = 'add';
459 # for projects we want to go back to the revision just one before the deleted
460 # revision. For files, we need to go back to the specified revision, since
461 # the file could have been modified via a share.
462 my($copyrev, $copypath);
463 if (!defined ($data->{version
})) {
464 ($copyrev, $copypath)= $self->last_deleted_rev_path($data->{parentphys
},
470 $self->get_revision ($data->{physname
}, $data->{version
}, $data->{info
});
471 $copypath = $data->{info
};
474 if (!defined $copyrev || !defined $copypath) {
476 "Could not recover path $itempath at revision $data->{revision_id};"
477 . " unable to determine deleted revision or path");
481 $node->{copyrev
} = $copyrev;
482 $node->{copypath
} = $copypath;
484 if (defined ($data->{version
})) {
485 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
490 } # End _recover_handler
492 ###############################################################################
494 ###############################################################################
496 my($self, $itempath, $nodes, $data, $expdir) = @_;
498 if (!$self->{repository
}->exists ($itempath)) {
499 $self->add_error("Attempt to pin non-existing item '$itempath' at "
500 . "revision $data->{revision_id}: possibly "
501 . "missing recover; skipping");
506 $self->get_revision ($data->{physname
}, $data->{version
}, $data->{info
});
507 my $copypath = $data->{info
};
509 # if one of the necessary copy from attributes are unavailable we fall back
510 # to a complete checkin
511 if (!defined $copyrev || !defined $copypath) {
512 return $self->_commit_handler ($itempath, $nodes, $data, $expdir);
515 my $node = Vss2Svn
::Dumpfile
::Node
->new();
516 $node->set_initial_props($itempath, $data);
517 $node->{action
} = 'add';
519 $node->{copyrev
} = $copyrev;
520 $node->{copypath
} = $copypath;
522 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
528 ###############################################################################
530 ###############################################################################
532 my($self, $itempath, $nodes, $data, $expdir) = @_;
534 if (!$self->{repository
}->exists ($itempath)) {
535 $self->add_error("Attempt to label non-existing item '$itempath' at "
536 . "revision $data->{revision_id}: possibly "
537 . "missing recover; skipping");
541 my $label = $data->{info
};
543 # It is possible that the label was deleted later, so we see here a label
544 # action, but no label was assigned. In this case, we only need to track
545 # the version->revision mapping, since the version could have been used
546 # as a valid share source.
547 if (defined ($label)) {
548 my $uniquepath = join('.', @
$data{ qw(physname version) });
549 my $labelpath = "$main::gCfg{labeldir}/$data->{info}$itempath";
551 $self->_create_svn_path ($nodes, $labelpath);
553 my $node = Vss2Svn
::Dumpfile
::Node
->new();
554 $node->set_initial_props($labelpath, $data);
555 $node->{action
} = 'add';
557 my $copyrev = $data->{revision_id
} - 1;
558 my $copypath = $itempath;
560 $node->{copyrev
} = $copyrev;
561 $node->{copypath
} = $copypath;
567 $self->track_version ($data->{physname
}, $data->{version
}, $itempath);
568 } # End _label_handler
570 ###############################################################################
572 ###############################################################################
574 my($self, $nodes, $dir) = @_;
576 my $node = Vss2Svn
::Dumpfile
::Node
->new();
577 my $data = { itemtype
=> 1, is_binary
=> 0 };
579 $node->set_initial_props($dir, $data);
580 $node->{action
} = 'add';
586 ###############################################################################
588 ###############################################################################
589 sub _create_svn_path
{
590 my($self, $nodes, $itempath) = @_;
592 my $missing_dirs = $self->{repository
}->get_missing_dirs($itempath);
594 foreach my $dir (@
$missing_dirs) {
595 $self->_add_svn_dir($nodes, $dir);
597 } # End _create_svn_path
599 ###############################################################################
601 ###############################################################################
603 my($self, $physname, $version, $itempath) = @_;
607 physname
=> $physname,
609 revision
=> $self->{revision
},
610 itempath
=> $itempath,
612 push @
{$self->{version_cache
}}, $record;
614 } # End track_version
617 ###############################################################################
619 ###############################################################################
621 my($self, $physname, $version, $itempath) = @_;
623 if (!defined($gVersion{$physname})) {
627 if (!exists($gVersion{$physname}->[$version])) {
631 return $gVersion{$physname}->[$version]->{$itempath};
635 ###############################################################################
637 ###############################################################################
639 my($self, $parentphys, $physname, $revision, $path) = @_;
641 $self->{deleted_cache
}->{$parentphys}->{$physname} =
643 revision
=> $revision,
647 } # End track_deleted
649 ###############################################################################
650 # last_deleted_rev_path
651 ###############################################################################
652 sub last_deleted_rev_path
{
653 my($self, $parentphys, $physname) = @_;
655 if (!defined($gDeleted{$parentphys})) {
656 return (undef, undef);
659 if (!defined($gDeleted{$parentphys}->{$physname})) {
660 return (undef, undef);
663 return @
{ $gDeleted{$parentphys}->{$physname} }{ qw(revision path) };
664 } # End last_deleted_rev_path
666 ###############################################################################
667 # get_export_contents
668 ###############################################################################
669 sub get_export_contents
{
670 my($self, $node, $data, $expdir) = @_;
672 if (!defined($expdir)) {
674 } elsif (!defined($data->{version
})) {
676 "Attempt to retrieve file contents with unknown version number");
680 my $file = "$expdir/$data->{physname}.$data->{version}";
682 if (!open EXP
, "$file") {
683 $self->add_error("Could not open export file '$file'");
689 # $node->{text} = join('', <EXP>);
690 $node->{text
} = do { local( $/ ) ; <EXP
> } ;
696 } # End get_export_contents
698 ###############################################################################
700 ###############################################################################
702 my($self, $node) = @_;
703 my $fh = $self->{fh
};
705 my $string = $node->get_headers();
706 from_to
($string, "windows-1252", "utf8");
708 $self->output_content($node->{hideprops
}?
undef : $node->{props
},
712 ###############################################################################
714 ###############################################################################
716 my($self, $props, $text) = @_;
718 my $fh = $self->{fh
};
720 $text = '' unless defined $text;
724 my($propout, $textout) = ('') x
2;
728 if (defined($props)) {
729 foreach my $prop (@
$props) {
730 ($key, $value) = @
$prop;
731 $propout .= 'K ' . length($key) . "\n$key\nV " . length($value)
735 $propout .= "PROPS-END\n";
736 $proplen = length($propout);
739 $textlen = length($text);
740 return if ($textlen + $proplen == 0);
743 print $fh "Prop-content-length: $proplen\n";
747 print $fh "Text-content-length: $textlen\n";
750 print $fh "Content-length: " . ($proplen + $textlen)
751 . "\n\n$propout$text\n";
753 } # End output_content
755 ###############################################################################
757 ###############################################################################
759 my($self, $vss_timestamp) = @_;
761 return &SvnTimestamp
($vss_timestamp);
763 } # End svn_timestamp
765 ###############################################################################
767 ###############################################################################
769 my($vss_timestamp) = @_;
771 my($sec, $min, $hour, $day, $mon, $year) = gmtime($vss_timestamp);
776 return sprintf("%4.4i-%2.2i-%2.2iT%2.2i:%2.2i:%2.2i.%6.6iZ",
777 $year, $mon, $day, $hour, $min, $sec, 0);
781 ###############################################################################
783 ###############################################################################
785 my($self, $msg) = @_;
787 push @
{ $self->{errors
} }, $msg;