vss2svn.pl: Refactor to put VSS physical handlers in separate class
[vss2svn.git] / script / Vss2Svn / ActionHandler.pm
blob507d8d7e666cbdc1798931ad02796529c34733a2
1 package Vss2Svn::ActionHandler;
3 use warnings;
4 use strict;
6 our %handlers =
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 DELETE => \&_delete_handler,
15 RECOVER => \&_recover_handler,
18 our(%gPhysInfo);
20 ###############################################################################
21 # new
22 ###############################################################################
23 sub new {
24 my($class, $row) = @_;
26 my $self =
28 row => $row,
29 info => undef,
30 errmsg => undef,
31 itempaths => undef,
32 recursed => 0,
33 physname_seen => '',
36 return bless($self, $class);
37 } # End new
39 ###############################################################################
40 # handle
41 ###############################################################################
42 sub handle {
43 my($self, $action) = @_;
45 my $handler = $handlers{$action};
47 if (!defined($handler)) {
48 $self->{errmsg} = "Unknown action '$action'";
49 return 0;
52 return $self->$handler;
54 } # End handle
56 ###############################################################################
57 # physinfo
58 ###############################################################################
59 sub physinfo {
60 my($self) = @_;
62 return $gPhysInfo{ $self->{row}->{physname} };
63 } # End physinfo
65 ###############################################################################
66 # _add_handler
67 ###############################################################################
68 sub _add_handler {
69 my($self) = @_;
70 my $row = $self->{row};
72 # For each physical item, we store its "real" physical parent in the
73 # 'parentphys' property, then keep a list of additional shared parents in
74 # the 'sharedphys' array.
76 $gPhysInfo{ $row->{physname} } =
78 type => $row->{itemtype},
79 name => $row->{itemname},
80 parentphys => $row->{parentphys},
81 sharedphys => [],
84 # File was just created so no need to look for shares
85 $self->{itempaths} = $self->_get_current_item_paths(1);
87 return 1;
89 } # End _add_handler
91 ###############################################################################
92 # _commit_handler
93 ###############################################################################
94 sub _commit_handler {
95 my($self) = @_;
96 my $row = $self->{row};
98 $self->{itempaths} = $self->_get_current_item_paths();
100 } # End _commit_handler
102 ###############################################################################
103 # _rename_handler
104 ###############################################################################
105 sub _rename_handler {
106 my($self) = @_;
107 my $row = $self->{row};
109 # Get the existing paths before the rename; info will contain the new name
110 my $physname = $row->{physname};
111 my $itempaths = $self->_get_current_item_paths();
113 my $physinfo = $gPhysInfo{$physname};
115 if (!defined $physinfo) {
116 $self->{errmsg} = "Attempt to rename unknown item '$physname':\n"
117 . $self->{nameResolveSeen};
119 return 0;
122 # A rename of an item renames it in all its shares
123 $physinfo->{name} = $row->{info};
125 $self->{itempaths} = $itempaths;
126 $self->{info} = $self->_get_current_item_name();
128 return 1;
129 } # End _rename_handler
131 ###############################################################################
132 # _share_handler
133 ###############################################################################
134 sub _share_handler {
135 my($self) = @_;
136 my $row = $self->{row};
138 my $physname = $row->{physname};
139 my $physinfo = $gPhysInfo{$physname};
141 if (!defined $physinfo) {
142 $self->{errmsg} = "Attempt to share unknown item '$physname':\n"
143 . $self->{physname_seen};
145 return 0;
148 push @{ $physinfo->{sharedphys} }, $row->{parentphys};
150 # 'itempaths' is the path for this new location (the share target);
151 # 'info' contains the source path
152 my $parentpaths = $self->_get_item_paths($row->{parentphys}, 1);
154 $self->{itempaths} = [$parentpaths->[0] . $physinfo->{name}];
155 $self->{info} = $self->_get_current_item_paths(1)->[0];
157 return 1;
159 } # End _share_handler
161 ###############################################################################
162 # _branch_handler
163 ###############################################################################
164 sub _branch_handler {
165 my($self) = @_;
166 my $row = $self->{row};
168 # Branching a file is actually a null action in SVN; it simply means we
169 # stop duplicating checkins. Return the existing path, but internally
170 # we'll remove this parent from the list of shared physical parents from
171 # the old location, then create a new one with the pertinent info. The row's
172 # 'physname' is that of the new file; 'info' is the formerly shared file.
174 my $physname = $row->{physname};
175 my $oldphysname = $row->{info};
177 my $oldphysinfo = $gPhysInfo{$oldphysname};
179 # First delete this parentphys from the old shared object; see
180 # _delete_handler for details
181 if ($oldphysinfo->{parentphys} eq $row->{parentphys}) {
182 $oldphysinfo->{parentphys} = shift( @{ $oldphysinfo->{sharedphys} } );
183 } else {
184 my $sharedphys = [];
186 foreach my $oldparent (@{ $oldphysinfo->{sharedphys} }) {
187 push @$sharedphys, $oldparent
188 unless $oldparent eq $row->{parentphys};
191 $oldphysinfo->{sharedphys} = $sharedphys;
194 # Now create a new entry for this branched item
195 $gPhysInfo{$physname} =
197 type => $row->{itemtype},
198 name => $row->{itemname},
199 parentphys => $row->{parentphys},
200 sharedphys => [],
203 $self->{itempaths} = $self->_get_current_item_paths(1);
205 return 1;
207 } # End _branch_handler
209 ###############################################################################
210 # _move_handler
211 ###############################################################################
212 sub _move_handler {
213 my($self) = @_;
214 my $row = $self->{row};
216 # Get the existing paths before the move; parent sub will get the new
217 # name
218 my $physname = $row->{physname};
219 my $itempaths = $self->_get_current_item_paths();
221 my $physinfo = $gPhysInfo{$physname};
223 if (!defined $physinfo) {
224 $self->{errmsg} = "Attempt to rename unknown item '$physname':\n"
225 . $self->{physname_seen};
227 return 0;
230 # Only projects can have true "moves", and projects don't have shares, so
231 # we don't need to worry about any shared paths
232 $physinfo->{parentphys} = $row->{parentphys};
234 # 'itempaths' has the original path; 'info' has the new
235 $self->{itempaths} = $itempaths;
236 $self->{info} = $self->_get_current_item_paths(1)->[0];
238 return 1;
240 } # End _move_handler
242 ###############################################################################
243 # _delete_handler
244 ###############################################################################
245 sub _delete_handler {
246 my($self) = @_;
247 my $row = $self->{row};
249 # For a delete operation we return only the "main" path, since any deletion
250 # of shared paths will have their own entry
252 my $physname = $row->{physname};
254 my $itempaths = $self->_get_current_item_paths(1);
256 my $physinfo = $gPhysInfo{$physname};
258 if (!defined $physinfo) {
259 $self->{errmsg} = "Attempt to delete unknown item '$physname':\n"
260 . $self->{physname_seen};
261 return 0;
264 if ($physinfo->{parentphys} eq $row->{parentphys}) {
265 # Deleting from the "main" parent; find a new one by shifting off the
266 # first shared path, if any; if none exists this will leave a null
267 # parent entry. We could probably just delete the whole node at this
268 # point.
270 $physinfo->{parentphys} = shift( @{ $physinfo->{sharedphys} } );
272 } else {
273 my $sharedphys = [];
275 foreach my $parent (@{ $physinfo->{sharedphys} }) {
276 push @$sharedphys, $parent
277 unless $parent eq $row->{parentphys};
280 $physinfo->{sharedphys} = $sharedphys;
283 $self->{itempaths} = $itempaths;
285 return 1;
287 } # End _delete_handler
289 ###############################################################################
290 # _recover_handler
291 ###############################################################################
292 sub _recover_handler {
293 my($self) = @_;
294 my $row = $self->{row};
296 my $physname = $row->{physname};
298 my $physinfo = $gPhysInfo{$physname};
300 if (!defined $physinfo) {
301 $self->{errmsg} = "Attempt to recover unknown item '$physname':\n"
302 . $self->{physname_seen};
304 return 0;
307 if (defined $physinfo->{parentphys}) {
308 # Item still has other shares, so recover it by pushing this parent
309 # onto its shared list
311 push( @{ $physinfo->{sharedphys} }, $row->{parentphys} );
313 } else {
314 # Recovering its only location; set the main parent back to this
315 $physinfo->{parentphys} = $row->{parentphys};
318 # We only recover the path explicitly set in this row, so build the path
319 # ourself by taking the path of this parent and appending the name
320 my $parentpaths = $self->_get_item_paths($row->{parentphys}, 1);
321 $self->{itempaths} = [$parentpaths->[0] . $physinfo->{name}];
323 return 1;
325 } # End _recover_handler
327 ###############################################################################
328 # _get_current_item_paths
329 ###############################################################################
330 sub _get_current_item_paths {
331 my($self, $mainonly) = @_;
333 return $self->_get_item_paths($self->{row}->{physname}, $mainonly);
334 } # End _get_current_item_paths
336 ###############################################################################
337 # _get_item_paths
338 ###############################################################################
339 sub _get_item_paths {
340 my($self, $physname, $mainonly) = @_;
342 # Uses recursion to determine the current full paths for an item based on
343 # the name of its physical file. We can't cache this information because
344 # a rename in a parent folder would not immediately trigger a rename in
345 # all of the child items.
347 # By default, we return an anonymous array of all paths in which the item
348 # is shared, unless $mainonly is true. Luckily, only files can be shared,
349 # not projects, so once we start recursing we can set $mainonly to true.
351 if (++($self->{recursed}) >= 1000) {
352 $self->{errmsg} = "Infinite recursion detected while looking up "
353 . "parent for '$physname':\n$self->{physname_seen}";
355 return 0;
358 if ($physname eq 'AAAAAAAA') {
359 # End of recursion; all items must go back to 'AAAAAAAA', which was so
360 # named because that's what most VSS users yell after using it much. :-)
361 return ['/'];
364 my $physinfo = $gPhysInfo{$physname};
366 if (!defined $physinfo) {
367 $self->{errmsg} = "Could not determine real path for '$physname':\n"
368 . $self->{physname_seen};
370 return undef;
373 $self->{physname_seen} .= "$physname, ";
375 my @pathstoget = $mainonly? ($physinfo->{parentphys}) :
376 ($physinfo->{parentphys}, @{ $physinfo->{sharedphys} } );
378 my $paths = [];
379 my $result;
381 foreach my $parent (@pathstoget) {
382 if (!defined $parent) {
385 $result = $self->_get_item_paths($parent, 1, 1);
387 if(!defined $result) {
388 return undef;
391 push @$paths, $result->[0] . $physinfo->{name};
394 return $paths;
396 } # End _get_item_paths
398 ###############################################################################
399 # _get_current_item_name
400 ###############################################################################
401 sub _get_current_item_name {
402 my($self) = @_;
404 my $physname = $self->{row}->{physname};
405 my $physinfo = $gPhysInfo{$physname};
407 if (!defined $physinfo) {
408 $self->{errmsg} = "Could not determine real name for '$physname':\n"
409 . $self->{physname_seen};
410 return undef;
413 return $physinfo->{name};
414 } # End _get_current_item_name