* Path for renames during restore and renames during share (thanks to Bryan Aldrich...
[vss2svn.git] / script / Vss2Svn / ActionHandler.pm
blob02d273503565260fcb8230acd405260e99ee27af
1 package Vss2Svn::ActionHandler;
3 use warnings;
4 use strict;
6 our %gHandlers =
8 ADD => \&_add_handler,
9 COMMIT => \&_commit_handler,
10 RENAME => \&_rename_handler,
11 SHARE => \&_share_handler,
12 BRANCH => \&_branch_handler,
13 MOVE => \&_move_handler,
14 RESTORE => \&_restore_handler,
15 RESTOREDPROJECT => \&_restore_handler,
16 DELETE => \&_delete_handler,
17 RECOVER => \&_recover_handler,
18 PIN => \&_pin_handler,
19 LABEL => \&_label_handler,
22 our(%gPhysInfo);
23 our(%gOrphanedInfo);
25 ###############################################################################
26 # new
27 ###############################################################################
28 sub new {
29 my($class, $row) = @_;
31 my $self =
33 row => $row,
34 action => undef,
35 info => undef,
36 version => undef,
37 errmsg => '',
38 itempaths => undef,
39 recursed => 0,
40 physname_seen => '',
41 verbose => 0,
42 trunkdir => '',
45 return bless($self, $class);
46 } # End new
48 ###############################################################################
49 # handle
50 ###############################################################################
51 sub handle {
52 my($self, $action) = @_;
54 $self->{action} = $action;
55 my $handler = $gHandlers{$action};
57 if (!defined($handler)) {
58 $self->{errmsg} .= "Unknown action '$action'";
59 return 0;
62 if ($self->{verbose}) {
63 my $physprint = (defined $self->{row}->{physname})?
64 $self->{row}->{physname} : '!UNDEF';
65 my $parentprint = (defined $self->{row}->{parentphys})?
66 $self->{row}->{parentphys} : '!UNDEF';
67 print "$action: $physprint, $parentprint \@ $self->{row}->{timestamp}\n";
70 my $rv = $self->$handler;
72 $self->{errmsg} =~ s/\n$//;
73 return $rv;
75 } # End handle
77 ###############################################################################
78 # physinfo
79 ###############################################################################
80 sub physinfo {
81 my($self) = @_;
83 return $gPhysInfo{ $self->{row}->{physname} };
84 } # End physinfo
86 ###############################################################################
87 # _add_handler
88 ###############################################################################
89 sub _add_handler {
90 my($self) = @_;
91 my $row = $self->{row};
93 # For each physical item, we store its "real" physical parent in the
94 # 'parentphys' property, then keep a list of additional shared parents in
95 # the 'sharedphys' array.
97 my $parentphys = $row->{parentphys};
98 my ($orphaned);
100 if (!defined $parentphys) {
101 # '_' is used as a magic marker for orphaned files
102 $row->{parentphys} = '_' . $row->{physname};
103 # $row->{itemname} = $row->{physname} . '_' . $row->{itemname};
104 $orphaned = 1;
107 # the version number could have been changed by the share handler
108 # or in the branch handler, this is the version we branch.
109 my $version = defined $row->{version} ? $row->{version}
110 : $self->{version};
112 # if the item to be added was destroyed, then we don't have a version
113 # number here. So we don't need to add the item anyway.
114 if (!defined $version ) {
115 $self->{errmsg} .= "Attempt to add entry '$row->{physname}' with "
116 . "unknown version number (probably destroyed) parent: $row->{parentphys} itemtype: $row->{itemtype}\n";
118 $gOrphanedInfo {$row->{physname} } = 1;
119 return 0;
122 $gPhysInfo{ $row->{physname} } =
124 type => $row->{itemtype},
125 name => $row->{itemname},
126 # parentphys => $row->{parentphys},
127 # sharedphys => [],
128 parents => {},
129 first_version => $version,
130 last_version => $version,
131 orphaned => $orphaned,
132 was_binary => $row->{is_binary},
135 $self->_add_parent ($row->{physname}, $row->{parentphys});
136 $self->_track_item_paths ($version);
138 # File was just created so no need to look for shares
139 $self->{itempaths} = [$self->_get_current_item_path()];
141 # don't convert orphaned items
142 # return $orphaned ? 0 : 1;
143 return 1;
144 } # End _add_handler
146 ###############################################################################
147 # _commit_handler
148 ###############################################################################
149 sub _commit_handler {
150 my($self) = @_;
151 my $row = $self->{row};
153 my $physname = $row->{physname};
154 my $physinfo = $gPhysInfo{$physname};
156 if (!defined $physinfo) {
157 $self->{errmsg} .= "Attempt to commit unknown item '$physname':\n"
158 . "$self->{physname_seen}\n";
160 return 0;
163 $physinfo->{was_binary} = $row->{is_binary};
165 # We need to track at least the version number, even if there is no
166 # active parent. This is necessary, if we later share this item, we need
167 # to share from the latest seen version.
169 # remember the last version, in which the file was modified
170 $physinfo->{last_version} = $row->{version};
172 # and track all itempaths for the new version
173 $self->_track_item_paths ($row->{version});
175 my $itempaths = $self->_get_active_item_paths();
176 if (!defined $itempaths && defined $physinfo->{orphaned}) {
177 $self->{errmsg} .= "No more active itempath to commit to orphaned item '$physname':\n"
178 . "$self->{physname_seen}\n";
180 return 0;
183 $self->{itempaths} = $itempaths;
185 if (!defined $self->{itempaths}) {
186 $self->{errmsg} .= "No more active itempath to commit to '$physname':\n"
187 . "$self->{physname_seen}\n";
189 return 0;
192 return 1;
193 } # End _commit_handler
195 ###############################################################################
196 # _rename_handler
197 ###############################################################################
198 sub _rename_handler {
199 my($self) = @_;
200 my $row = $self->{row};
202 my $physname = $row->{physname};
203 my $physinfo = $gPhysInfo{$physname};
205 if (!defined $physinfo) {
206 # only report an error, if the file wasn't detected as orphaned.
207 if (!defined $gOrphanedInfo {$physname}) {
208 $self->{errmsg} .= "Attempt to rename unknown item '$physname':\n"
209 . "$self->{physname_seen}\n";
212 return 0;
215 # Get the existing paths before the rename; info will contain the new name
216 my $itempaths = $self->_get_vivid_item_paths();
218 # Renames on shares may show up once for each share, which we don't want
219 # since one rename takes care of all locations. If the "new" name is
220 # already the same as the old, just ignore it.
221 if ($physinfo->{name} eq $row->{info}) {
222 return 0;
225 # A rename of an item renames it in all its shares
226 $physinfo->{name} = $row->{info};
228 # no need to track the itempathes, since a rename doesn't create a new
229 # item version
231 $self->{itempaths} = $itempaths;
232 $self->{info} = $row->{info};
234 return 1;
235 } # End _rename_handler
237 ###############################################################################
238 # _share_handler
239 ###############################################################################
240 sub _share_handler {
241 my($self) = @_;
242 my $row = $self->{row};
244 my $physname = $row->{physname};
245 my $physinfo = $gPhysInfo{$physname};
247 if (!defined $physinfo) {
248 $self->{errmsg} .= "Attempt to share unknown item '$physname':\n"
249 . "$self->{physname_seen}\n";
251 return 0;
254 my $version = $row->{version};
255 $version = $physinfo->{last_version} if (!defined $version);
257 $row->{is_binary} = $physinfo->{was_binary};
259 # 'itempath' is the path for this new location (the share target);
260 # note: since we can share from a orphaned item, we use the itemname that
261 # is provided in the row information for the share target and not the
262 # current name of the item. The orphaned name is mangeled to make it unique
263 my $parentpath = $self->_get_current_parent_path ();
264 my $itempath = $parentpath . $row->{itemname};
266 # a SHARE *can* rename a file if the parent is no longer present.
267 $row->{info} = $row->{itemname};
268 $self->_rename_handler();
270 # 'sourceinfo' contains the source path
271 my $sourceinfo = $self->_get_valid_path ($physname, $row->{parentphys}, $version);
273 if (!defined($sourceinfo)) {
274 # We can't figure out the path for the parent that this share came from,
275 # so it was either destroyed or corrupted. That means that this isn't
276 # a share anymore; it's a new add.
278 $self->{action} = 'ADD';
281 # track the addition of the new parent
282 $self->_add_parent ($physname, $row->{parentphys});
284 # if this is a share+pin action, then remember the pin version
285 if (defined $row->{version}) {
286 $physinfo->{parents}->{$row->{parentphys}}->{pinned} = $row->{version};
289 $self->{itempaths} = [$itempath];
290 $self->{info} = $sourceinfo;
291 $self->{version} = $version;
293 # the share target is now also a valid "copy from" itempath
294 $self->_track_item_path ($physname, $row->{parentphys}, $version, $itempath);
296 return 1;
297 } # End _share_handler
299 ###############################################################################
300 # _branch_handler
301 ###############################################################################
302 sub _branch_handler {
303 my($self) = @_;
304 my $row = $self->{row};
306 # Branching a file is actually a null action in SVN; it simply means we
307 # stop duplicating checkins. Return the existing path, but internally
308 # we'll remove this parent from the list of shared physical parents from
309 # the old location, then create a new one with the pertinent info. The row's
310 # 'physname' is that of the new file; 'info' is the formerly shared file.
312 my $physname = $row->{physname};
313 my $oldphysname = $row->{info};
315 my $oldphysinfo = $gPhysInfo{$oldphysname};
317 if (!defined $oldphysinfo) {
318 $self->{errmsg} .= "Attempt to branch unknown item '$oldphysname':\n"
319 . "$self->{physname_seen}\n";
321 return 0;
324 my $version = defined $row->{version} ? $row->{version}
325 : $self->{version};
327 # if we branch into a destroyed object, delete is the logical choice
328 if (!defined $version ) {
329 $self->{errmsg} .= "Attempt to branch '$oldphysname' into "
330 . "'$physname' at an unknown version number "
331 . "('$physname' probably destroyed)\n";
332 $gOrphanedInfo{$physname} = 1;
333 $self->{action} = 'DELETE';
334 $row->{physname} = $oldphysname;
335 $row->{info} = undef;
336 return $self->_delete_handler();
339 # treat the old path as deleted
340 # we can't branch an item, that doesn't have a parent. This happens when the
341 # parent was destroyed.
342 if (defined $row->{parentphys}) {
343 $oldphysinfo->{parents}->{$row->{parentphys}}->{deleted} = 1;
345 else {
346 # since we have the "orphaned" handling, we can map this action to an
347 # addition, so that this item will show up in the orphaned cache.
348 # TODO: To keep the history of the item we can try to ShareBranch
349 # from original item if it is also somewhere accessible.
350 # something like:
351 # my $copypath = $self->_get_valid_path ($oldphysinfo, $row->{parentphys}, $row->{version});
353 $self->{action} = 'ADD';
356 # Now treat the new entry as a new addition
357 my $result = $self->_add_handler();
359 # remember the ancestor of this item, we need it, when we later whant to refer to versions prior
360 # to the branch, e.g in PIN situations.
361 if ($result) {
362 $gPhysInfo{ $row->{physname} }->{ancestor} = $oldphysname;
364 $self->{info} = $oldphysname;
366 return $result;
368 } # End _branch_handler
370 ###############################################################################
371 # _move_handler
372 ###############################################################################
373 sub _move_handler {
374 my($self, $oldName) = @_;
375 my $row = $self->{row};
377 my $physname = $row->{physname};
378 my $physinfo = $gPhysInfo{$physname};
380 if (!defined $physinfo) {
381 $self->{errmsg} .= "Attempt to move unknown item '$physname':\n"
382 . "$self->{physname_seen}\n";
384 return 0;
387 # row->{info} contains the source parent
388 # row->{parentphys} contains the target parent
390 # check the source path
391 if (!defined $row->{info}) {
392 # Check if this is an orphaned item
393 if (defined $physinfo->{orphaned}) {
394 $row->{info} = '_' . $physname;
395 undef $physinfo->{orphaned};
396 } else {
397 # Don't know from where to move. Share it there instead
398 $self->{action} = 'SHARE';
399 return $self->_share_handler();
403 # check the target path
404 if (!defined ($row->{parentphys})) {
405 # the target directory was destroyed, so there is no apropriate move
406 # target information. Fall back to a move to the orphaned cache
407 $physinfo->{orphaned} = 1;
408 $row->{parentphys} = '_' . $row->{physname};
411 # '$sourceinfo' is the path for the old location (the move source);
412 my $sourceparent = $self->_get_parent_path ($row->{info});
413 my $sourceinfo;
414 if (defined $oldName)
416 $sourceinfo = $sourceparent . $oldName;
418 else
420 $sourceinfo = $sourceparent . $row->{itemname};
423 # '$itempath' contains the move target path
424 my $parentpath = $self->_get_current_parent_path ();
425 my $itempath = $parentpath . $physinfo->{name}; # $row->{itemname};
428 if (!defined($sourceparent)) {
429 # We can't figure out the path for the parent that this move came from,
430 # so it was either destroyed or corrupted. That means that this isn't
431 # a move anymore; it's a new add.
433 $self->{action} = 'ADD';
434 undef $sourceinfo;
436 else {
437 # set the old parent inactive
438 $physinfo->{parents}->{$row->{info}}->{deleted} = 1;
441 # if the item mysteriously changed name during the move
442 $physinfo->{name} = $row->{itemname};
444 # track the addition of the new parent
445 $self->_add_parent ($physname, $row->{parentphys});
447 $self->{itempaths} = [$itempath];
448 $self->{info} = $sourceinfo;
450 # the move target is now also a valid "copy from" itempath
451 $self->_track_item_path ($physname, $row->{parentphys}, $physinfo->{last_version}, $itempath);
453 return 1;
454 } # End _move_handler
456 ###############################################################################
457 # _restore_handler
458 ###############################################################################
459 sub _restore_handler {
460 my($self) = @_;
461 my $row = $self->{row};
463 $self->{action} = 'MOVE';
464 $row->{actiontype} = 'MOVE';
465 # $row->{info} = $row->{parentphys};
466 # $row->{parentphys} = '_' . $row->{physname};
468 $gPhysInfo{ $row->{physname} } =
470 type => $row->{itemtype},
471 name => $row->{itemname},
472 parents => {},
473 first_version => 1,
474 last_version => 1,
475 orphaned => 1,
476 was_binary => $row->{is_binary},
479 my $newName = $row->{info};
481 undef $row->{info};
483 return $self->_move_handler ($newName);
486 ###############################################################################
487 # _delete_handler
488 ###############################################################################
489 sub _delete_handler {
490 my($self) = @_;
491 my $row = $self->{row};
493 # For a delete operation we return the path of the item to be deleted
495 my $physname = $row->{physname};
496 my $physinfo = $gPhysInfo{$physname};
498 if (!defined $physinfo) {
499 # only report an error, if the file wasn't detected as orphaned.
500 if (!defined $gOrphanedInfo {$physname}) {
501 $self->{errmsg} .= "Attempt to delete unknown item '$physname':\n"
502 . "$self->{physname_seen}\n";
504 return 0;
507 my $parentpath = $self->_get_current_parent_path ();
508 my $itempaths = [$parentpath . $physinfo->{name}];
510 # protect for delete/purge cycles: if the parentphys isn't in the shares
511 # anymore, the file was already deleted from the parent and is now purged
512 if (defined $physinfo->{parents}->{$row->{parentphys}}->{deleted}) {
513 return 0;
516 # set the parent inactive
517 $physinfo->{parents}->{$row->{parentphys}}->{deleted} = 1;
519 $self->{itempaths} = $itempaths;
521 return 1;
523 } # End _delete_handler
525 ###############################################################################
526 # _recover_handler
527 ###############################################################################
528 sub _recover_handler {
529 my($self) = @_;
530 my $row = $self->{row};
532 my $physname = $row->{physname};
533 my $physinfo = $gPhysInfo{$physname};
535 if (!defined $physinfo) {
536 # only report an error, if the file wasn't detected as orphaned.
537 if (!defined $gOrphanedInfo {$physname}) {
538 $self->{errmsg} .= "Attempt to recover unknown item '$physname':\n"
539 . "$self->{physname_seen}\n";
542 return 0;
545 # recover this item within the current parent
546 my $parentinfo = $physinfo->{parents}->{$row->{parentphys}};
547 if (!defined $parentinfo->{deleted}) {
548 $self->{errmsg} .= "Attempt to recover an active item '$physname':\n"
549 . "$self->{physname_seen}\n";
551 return 0;
553 undef $parentinfo->{deleted};
555 # We only recover the path explicitly set in this row, so build the path
556 # ourself by taking the path of this parent and appending the name
557 my $parentpath = $self->_get_current_parent_path();
558 my $itempath = $parentpath . $physinfo->{name};
560 # Since the item could be modified between the delete and the recovery,
561 # we need to find a valid source for the recover
562 $self->{info} =
563 $self->_get_valid_path ($physname, $row->{parentphys}, $row->{version});
564 $self->{itempaths} = [$itempath];
566 # We only set the version number, if this item is a file item. If it is a
567 # project item, we must recover from the last known revision, which is
568 # determined in the dumpfile handler
569 if ($row->{itemtype} == 2) {
570 $self->{version} = $physinfo->{last_version};
573 return 1;
574 } # End _recover_handler
576 ###############################################################################
577 # _pin_handler
578 ###############################################################################
579 sub _pin_handler {
580 my($self) = @_;
581 my $row = $self->{row};
583 my $physname = $row->{physname};
584 my $physinfo = $gPhysInfo{$physname};
586 if (!defined $physinfo) {
587 $self->{errmsg} .= "Attempt to pin unknown item '$physname':\n"
588 . "$self->{physname_seen}\n";
590 return 0;
593 my $parentpath = $self->_get_current_parent_path();
594 my $itempath = $parentpath . $physinfo->{name};
596 my $parentinfo = \%{$physinfo->{parents}->{$row->{parentphys}}};
598 # depending on the version number of the PIN/UNPIN action, we don't have
599 # to convert this action into a real commit. In this case we only have to
600 # track, the state.
601 my $change_action = 1;
603 my $version = $row->{version};
604 if (!defined $row->{version}) {
605 # this is the unpin handler
607 # is this the unpin version and the last version identically?
608 $change_action = 0 if (defined $parentinfo->{pinned}
609 && $parentinfo->{pinned} == $physinfo->{last_version} );
611 undef $parentinfo->{pinned};
612 $version = $physinfo->{last_version};
614 else {
615 # is this the pin version and the last version identically?
616 # since the UNPIN/PIN merge, it is possible, that the item can still be
617 # in a pinned state:
618 $change_action = 0 if ($row->{version} == $physinfo->{last_version}
619 && !defined $parentinfo->{pinned});
621 $parentinfo->{pinned} = $row->{version};
624 $self->{itempaths} = [$itempath];
625 $self->{info} =
626 $self->_get_valid_path ($physname, $row->{parentphys}, $row->{version});
627 $self->{version} = $version;
629 # the unpinned target is now also a valid "copy from" itempath
630 $self->_track_item_path ($physname, $row->{parentphys}, $version, $itempath);
632 return $change_action;
633 } # End _pin_handler
635 ###############################################################################
636 # _label_handler
637 ###############################################################################
638 sub _label_handler {
639 # currently the handler only tracks labels that where assigned to files
640 # we need this for the item name tracking
641 my($self) = @_;
642 my $row = $self->{row};
644 my $itempaths = $self->_get_active_item_paths();
646 $self->_track_item_paths ($row->{version});
648 $self->{itempaths} = $itempaths;
649 $self->{info} = $row->{label};
651 return 1;
652 } # End _label_handler
654 ###############################################################################
655 # _get_current_parent_path
656 ###############################################################################
657 sub _get_current_parent_path {
658 my($self) = @_;
660 return $self->_get_parent_path($self->{row}->{parentphys});
661 } # End _get_current_parent_path
664 ###############################################################################
665 # _get_parent_path
666 ###############################################################################
667 sub _get_parent_path {
668 my($self, $physname) = @_;
670 # Uses recursion to determine the current full paths for an item based on
671 # the name of its physical file. We can't cache this information because
672 # a rename in a parent folder would not immediately trigger a rename in
673 # all of the child items.
675 # By default, we return an anonymous array of all paths in which the item
676 # is shared, unless $mainonly is true. Luckily, only files can be shared,
677 # not projects, so once we start recursing we can set $mainonly to true.
679 if ($self->{verbose}) {
680 my $physprint = (defined $physname)? $physname : '!UNDEF';
681 my $space = ($self->{recursed})? ' ' : '';
682 print "${space}_get_parent_path($physprint)\n";
685 if (++($self->{recursed}) >= 1000) {
686 $self->{errmsg} .= "Infinite recursion detected while looking up "
687 . "parent for '$physname':\n$self->{physname_seen}\n";
689 return undef;
692 if (!defined($physname)) {
693 return undef;
696 if ($physname eq '') {
697 return '';
700 if ($physname eq 'AAAAAAAA') {
701 # End of recursion; all items must go back to 'AAAAAAAA', which was so
702 # named because that's what most VSS users yell after using it much. :-)
703 return $self->{trunkdir} . '/';
706 if ($physname =~ m/^_.*/) {
707 # End of recursion; this is the orphaned node
708 # return the name of the orphaned directory + the name of the orphaned
709 # file in order to make the path unique
710 return '/orphaned/' . $physname . '/';
713 my $physinfo = $gPhysInfo{$physname};
715 if (!defined $physinfo) {
716 $self->{errmsg} .= "Could not determine real path for '$physname':\n"
717 . "$self->{physname_seen}\n";
719 return undef;
722 $self->{physname_seen} .= "$physname, ";
724 # In a move szenario, we can have one deleted and one active parent. We
725 # are only interested in the active ones here.
726 my @pathstoget = $self->_get_active_parents ($physname);
728 # TODO: For projects there should be only one active parent
729 my $parent = $pathstoget[0];
731 # if we don't have any active parents, the item path itself is deleted
732 if (!defined ($parent)) {
733 return undef;
736 my $result;
738 $result = $self->_get_parent_path($pathstoget[0], 1);
740 if(!defined($result)) {
741 return undef;
744 return $result . $physinfo->{name};
746 } # End _get_parent_path
748 ###############################################################################
749 # _get_current_item_paths
750 ###############################################################################
751 sub _get_current_item_paths {
752 my($self, $mainonly) = @_;
754 my @parents = $self->_get_parents ($self->{row}->{physname});
755 return $self->_get_item_paths($self->{row}->{physname}, @parents);
756 } # End _get_current_item_paths
758 ###############################################################################
759 # _get_vivid_item_paths
760 ###############################################################################
761 sub _get_vivid_item_paths {
762 my($self, $mainonly) = @_;
764 my @parents = $self->_get_vivid_parents ($self->{row}->{physname});
765 return $self->_get_item_paths($self->{row}->{physname}, @parents);
766 } # End _get_vivid_item_paths
768 ###############################################################################
769 # _get_active_item_paths
770 ###############################################################################
771 sub _get_active_item_paths {
772 my($self, $mainonly) = @_;
774 my @parents = $self->_get_active_parents ($self->{row}->{physname});
775 return $self->_get_item_paths($self->{row}->{physname}, @parents);
776 } # End _get_active_item_paths
778 ###############################################################################
779 # _get_current_item_path
780 ###############################################################################
781 sub _get_current_item_path {
782 my($self) = @_;
784 my @parents = $self->_get_parents ($self->{row}->{physname});
786 if (scalar @parents == 0) {
787 return undef;
790 my $physname = $self->{row}->{physname};
791 my $paths = $self->_get_item_paths($physname, $parents[0]);
793 if (!defined $paths) {
794 $self->{errmsg} .= "Could not retrieve item path for '$physname': "
795 . "(probably bogous timestamp in parent and child action)\n";
796 return undef;
799 return $paths->[0];
800 } # End _get_current_item_path
802 ###############################################################################
803 # _get_item_paths
804 ###############################################################################
805 sub _get_item_paths {
806 my($self, $physname, @parents) = @_;
808 # Uses recursion to determine the current full paths for an item based on
809 # the name of its physical file. We can't cache this information because
810 # a rename in a parent folder would not immediately trigger a rename in
811 # all of the child items.
813 # By default, we return an anonymous array of all paths in which the item
814 # is shared, unless $mainonly is true. Luckily, only files can be shared,
815 # not projects, so once we start recursing we can set $mainonly to true.
817 if ($self->{verbose}) {
818 my $physprint = (defined $physname)? $physname : '!UNDEF';
819 my $space = ($self->{recursed})? ' ' : '';
820 print "${space}_get_item_paths($physprint)\n";
824 if (!defined($physname)) {
825 return undef;
828 if ($physname eq 'AAAAAAAA') {
829 # End of recursion; all items must go back to 'AAAAAAAA', which was so
830 # named because that's what most VSS users yell after using it much. :-)
831 return [$self->{trunkdir} . '/'];
834 if ($physname =~ m/^_.*/) {
835 # End of recursion; this is the orphaned node
836 # return the name of the orphaned directory + the name of the orphaned
837 # file in order to make the path unique
838 return '/orphaned/' . $physname . '/';
841 my $physinfo = $gPhysInfo{$physname};
843 if (!defined $physinfo) {
844 $self->{errmsg} .= "Could not determine real path for '$physname':\n"
845 . "$self->{physname_seen}\n";
847 return undef;
850 $self->{physname_seen} .= "$physname, ";
852 my @pathstoget = @parents;
854 my $paths;
855 my $result;
857 PARENT:
858 foreach my $parent (@pathstoget) {
859 if (!defined $parent) {
860 next PARENT;
862 $result = $self->_get_parent_path($parent);
864 if(!defined($result)) {
865 next PARENT;
868 push @$paths, $result . $physinfo->{name};
871 return $paths;
873 } # End _get_item_paths
877 ###############################################################################
878 # _track_item_paths:
879 # This function maintains a map that records the itempath that was valid for
880 # each version of the physical file in the context of the different parents.
881 # This map is needed, e.g. during pinning when a file is pinned to a previous
882 # version. Since the file, could have renamed in between, we need to know the
883 # previous itempath that was valid in the previous version.
885 # This map does not replace the recursive lookup of the itempath in teh function
886 # _get_item_paths. The itempathes stored here are "historic" item pathes.
887 # A rename e.g. is not reflectected in the version history of the physical file
888 # and therefor does not have a distinct version as in subversion.
889 ###############################################################################
890 sub _track_item_paths {
891 my($self, $version) = @_;
893 my $row = $self->{row};
895 # we only need to track the path for actions that deal with a specific
896 # version
897 if (defined $version) {
899 my $physinfo = $gPhysInfo{ $row->{physname} };
901 my @parents = $self->_get_active_parents ($row->{physname});
902 my $result;
904 PARENT:
905 foreach my $parent (@parents) {
907 my $parentpath = $self->_get_parent_path ($parent);
908 if (!defined $parentpath) {
909 next PARENT;
911 $result = $parentpath . $physinfo->{name};
913 $self->_track_item_path ($row->{physname}, $parent, $row->{version}, $result);
917 } # End _track_item_paths
920 ###############################################################################
921 # _track_item_path:
922 ###############################################################################
923 sub _track_item_path {
924 my($self, $physname, $parent, $version, $itempath) = @_;
926 if (defined $version && defined $itempath) {
928 my $physinfo = $gPhysInfo{ $physname };
930 my $versions = \@{$physinfo->{parents}->{$parent}->{versions}};
932 # in the case of pinning and sharing with pinning, the version number
933 # denotes a version in the past. So if there is already an entry for
934 # this version number skip this parent.
935 if (exists $versions->[$version]) {
936 return;
939 $versions->[$version] = $itempath;
941 } # End _track_item_path
944 ###############################################################################
945 # _get_vivid_parents
946 # This function returns all parents where the physical file is not deleted,
947 # r all active projects. If a file is deleted, the file
948 # does nor take place in any further rename activity, so it is
949 # inactive.
950 ###############################################################################
951 sub _get_vivid_parents {
952 my($self, $physname) = @_;
954 my $physinfo = $gPhysInfo{$physname};
956 my @parents;
957 if (defined $physinfo) {
959 PARENT:
960 foreach my $parentphys (@{$physinfo->{order}}) {
962 # skip orphaned parents
963 # if ($parentphys eq '99999999' ) {
964 # next PARENT;
967 my $parent = $physinfo->{parents}->{$parentphys};
968 if (!defined $parent)
970 next PARENT;
973 # skip deleted parents, since these parents do not
974 # participate in specific vss action
975 if (defined $parent->{deleted} ) {
976 next PARENT;
979 push @parents, $parentphys;
983 return @parents
984 } # End _get_vivid_parents
986 ###############################################################################
987 # _get_active_parents
988 # This function returns all parents where the physical file is not deleted
989 # or pinned, or all active projects. If a file is pinned or deleted, the file
990 # does nor take place in any further checkin or rename activity, so it is
991 # inactive.
992 ###############################################################################
993 sub _get_active_parents {
994 my($self, $physname) = @_;
996 my $physinfo = $gPhysInfo{$physname};
998 my @parents;
999 if (defined $physinfo) {
1001 PARENT:
1002 foreach my $parentphys (@{$physinfo->{order}}) {
1004 # skip orphaned parents
1005 # if ($parentphys eq '99999999' ) {
1006 # next PARENT;
1009 my $parent = $physinfo->{parents}->{$parentphys};
1010 if (!defined $parent)
1012 next PARENT;
1015 # skip deleted or pinned parents, since these parents do not
1016 # participate in any vss action
1017 if (defined $parent->{deleted} || defined $parent->{pinned} ) {
1018 next PARENT;
1021 push @parents, $parentphys;
1025 return @parents
1026 } # End _get_active_parents
1028 ###############################################################################
1029 # _get_parents
1030 # This function returns all parents for the physical file
1031 ###############################################################################
1032 sub _get_parents {
1033 my($self, $physname) = @_;
1035 my $physinfo = $gPhysInfo{$physname};
1037 my @parents;
1038 if (defined $physinfo) {
1040 PARENT:
1041 foreach my $parentphys (@{$physinfo->{order}}) {
1043 # skip orphaned parents
1044 # if ($parentphys eq '99999999' ) {
1045 # next PARENT;
1048 my $parent = $physinfo->{parents}->{$parentphys};
1049 if (!defined $parent)
1051 next PARENT;
1054 push @parents, $parentphys;
1058 return @parents
1059 } # End _get_active_parents
1061 ###############################################################################
1062 # _get_valid_path
1063 # This function returns an itempath for the physical file, that was valid in
1064 # the previous version. Since all activities that create new versions of a file
1065 # must be done on an active path, there should be at least one valid item path
1066 # for the version.
1067 # If we can't find any valid itempath, we can not perform a "copy from" revision
1068 # In this case, we need to recheckin the current content of the item
1069 ###############################################################################
1070 sub _get_valid_path {
1071 my($self, $physname, $parentphys, $version) = @_;
1073 my $physinfo = $gPhysInfo{$physname};
1074 if (!defined $physinfo) {
1075 return undef;
1078 if (!defined $version) {
1079 $version = $physinfo->{last_version};
1082 # 0.) If the version we are looking for is prior to the first version of this
1083 # item (e.g in a branch / Pin situation), we need to check the ancestor
1084 if (defined $physinfo &&
1085 $version < $physinfo->{first_version}) {
1086 return $self->_get_valid_path ($physinfo->{ancestor}, $parentphys, $version);
1089 # 1.) We first check for non-deleted contexts, even though these item pathes
1090 # are valid. A deleted context means, that an item existed in this context
1091 # in a specific version, but was later deleted from that context. So it is
1092 # normally not possible to perform any further action on this item. Therefore
1093 # we prefer non-deleted contexts.
1094 my $path = $self->_get_valid_path2 ($physname, $parentphys, $version, 0);
1095 return $path if defined $path;
1097 # 2.) now we also check for deleted contexts
1098 $path = $self->_get_valid_path2 ($physname, $parentphys, $version, 1);
1099 return $path;
1100 } # End _get_valid_path
1102 ###############################################################################
1103 # _get_valid_path2
1104 # This function is an internal helper: It will check for active and inactive,
1105 # but valid item pathes, depending on the $deleted flag, see also _get_valid_path
1106 ###############################################################################
1107 sub _get_valid_path2 {
1108 my($self, $physname, $parentphys, $version, $deleted) = @_;
1110 my $physinfo = $gPhysInfo{$physname};
1112 # 1. check the parent requested, if there was an item name for this version
1113 # we can use this item name, since it was valid in that time
1114 my $parent = $physinfo->{parents}->{$parentphys};
1115 if (defined $parent &&
1116 (!defined $parent->{deleted} || $deleted == 1) &&
1117 $parent->{versions}->[$version]) {
1118 return $parent->{versions}->[$version];
1121 # 2. check all other parents in the order, the where added
1122 my @parents;
1124 PARENT:
1125 foreach $parentphys (@{$physinfo->{order}}) {
1127 $parent = $physinfo->{parents}->{$parentphys};
1128 if (defined $parent &&
1129 (!defined $parent->{deleted} || $deleted == 1) &&
1130 $parent->{versions}->[$version]) {
1131 return $parent->{versions}->[$version];
1135 return undef;
1136 } # End _get_valid_path2
1138 ###############################################################################
1139 # _add_parent
1140 # Track the addition of a new parent to this itempath. This will also track the
1141 # order, in which the parents where added to the physical file. The valid
1142 # itempath lookup will search for valid pathes in the order the parents where
1143 # added to the project.
1144 ###############################################################################
1145 sub _add_parent {
1146 my($self, $physname, $parentphys) = @_;
1148 my $physinfo = $gPhysInfo{$physname};
1149 if (defined $physinfo) {
1150 # check wether this parent was previously deleted
1151 if (defined $physinfo->{parents}->{$parentphys} &&
1152 defined $physinfo->{parents}->{$parentphys}->{deleted}) {
1153 undef $physinfo->{parents}->{$parentphys}->{deleted};
1155 else {
1156 $physinfo->{parents}->{$parentphys} = {};
1157 push @{ $physinfo->{order} }, $parentphys;
1160 } # End _add_parent