Index: lib/SVN/S4/ViewSpec.pm
===================================================================
--- lib/SVN/S4/ViewSpec.pm (revision 33618)
+++ lib/SVN/S4/ViewSpec.pm (working copy)
@@ -21,7 +21,16 @@
#
# Top of tree is: $self->{viewspec_tree}
#
-# Each node is a hashref with a few common keys:
+# Each node is a hashref that looks like:
+# $node = {
+# url => "http://bla/path",
+# rev => 1234,
+# dirs => {
+# 'subdir1' => $node_subdir1,
+# 'subdir2' => $node_subdir2,
+# },
+# in_repo => 0, # 1=in repository, 0=not in repository
+# };
#
package SVN::S4::ViewSpec;
@@ -163,7 +172,7 @@
sub add_to_vsmap {
my $self = shift;
my %params = (#item=>,
- #override=>,
+ #can_override=>,
@_);
# This item replaces any previous item that matches it, e.g.
# 1. map a/b/c to url1
@@ -177,8 +186,8 @@
my $actdir = $vsmap[$i]->{dir};
$self->dbg ("checking $cmd on $actdir");
if ($actdir =~ /^$params{item}->{dir}/) {
- if ($params{override} || $cmd eq 'unmap') {
- # If override is set, then you are allowed to override.
+ if ($params{can_override} || $cmd eq 'unmap') {
+ # If can_override is set, then you are allowed to override.
# Anybody is allowed to override an unmap command.
$self->dbg ("deleting action=$cmd on dir=$actdir");
push @delete_indices, $i;
@@ -191,9 +200,60 @@
splice (@vsmap, $i, 1);
}
push @vsmap, $params{item};
- #### add to viewspec tree
}
+sub add_to_vstree {
+ my $self = shift;
+ my %params = (#path=>, # path relative the top of tree, e.g. "A/D/G"
+ #node=>,
+ #can_override=>,
+ @_);
+ # make sure there are no extra slashes at beginning or end
+ die "malformed path $params{path}" if $params{path} =~ m%^/% || $params{path} =~ m%/$%;
+ my $node = $self->{viewspec_tree};
+ # Travel down the tree, creating empty nodes as necessary until $node points
+ # to the node corresponding to $params{path}. It is important to distinguish
+ # empty nodes from nodes with a URL of void. An empty node means that the viewspec
+ # did not specify what goes there, so the node may be filled from the repository.
+ # A void node means that the viewspec asked for that node to be switched to void,
+ # so it will be an empty directory no matter what the repository says would normally
+ # be there.
+ foreach my $dirpart (split ('/', $params{path})) {
+ my $next = $node->{dirs}->{$dirpart};
+ if (!defined $next) {
+ $node->{dirs}->{$dirpart} = {};
+ $next = $node->{dirs}->{$dirpart};
+ }
+ $node = $next;
+ }
+ if (!$params{can_override} && (scalar (keys %{$node}) > 0)) {
+ print Dumper($node);
+ $self->error("view line for dir '$params{path}' collides with previous line");
+ }
+ # copy everything from the parameter data structure to $node
+ foreach my $key (keys %{$params{node}}) {
+ $node->{$key} = $params{node}->{$key};
+ }
+ #$self->dbg("after add_to_vstree $params{path}, viewspec tree is now:\n", Dumper($self->{viewspec_tree}));
+}
+
+# call with wcpath like "A/B" and it will return the node entry for A/B, if any.
+sub vstree_get_node {
+ my $self = shift;
+ my %params = (#wcpath=>, # path relative the top of tree, e.g. "A/D/G"
+ @_);
+ # make sure there are no extra slashes at beginning or end
+ die "malformed path $params{wcpath}" if $params{wcpath} =~ m%^/% || $params{wcpath} =~ m%/$%;
+ my $node = $self->{viewspec_tree};
+ # travel down the tree. if any node does not exist, return undef.
+ foreach my $dirpart (split ('/', $params{wcpath})) {
+ my $next = $node->{dirs}->{$dirpart};
+ return if !defined $next;
+ $node = $next;
+ }
+ return $node;
+}
+
sub viewspec_cmd_view {
my $self = shift;
my ($url, $dir, $revtype, $rev) = @_;
@@ -220,7 +280,9 @@
}
$self->ensure_valid_rev_string($rev);
my $item = { cmd=>'map', url=>$url, dir=>$dir, rev=>$rev };
- $self->add_to_vsmap(item=>$item, override=>0);
+ $self->add_to_vsmap(item=>$item, can_override=>0);
+ my $node = {url=>$url, rev=>$rev, dirs=>{}, in_repo=>undef};
+ $self->add_to_vstree(path=>$dir, node=>$node, can_override=>0);
}
sub viewspec_cmd_unview {
@@ -229,7 +291,9 @@
$self->dbg ("viewspec_cmd_unview: dir=$dir");
my $ndel = 0;
my $item = { cmd=>'unmap', dir=>$dir };
- $self->add_to_vsmap (item=>$item, override=>1);
+ $self->add_to_vsmap (item=>$item, can_override=>1);
+ my $node = {url=>undef, rev=>undef, dirs=>{}, in_repo=>undef};
+ $self->add_to_vstree(path=>$dir, node=>$node, can_override=>1);
}
sub viewspec_cmd_include {
@@ -268,45 +332,6 @@
return 1; # all revs were the same, return true
}
-sub lookup_mapping {
- my $self = shift;
- my %params = (#wcpath=>,
- @_);
- my $longest_match = -1;
- my $match;
- my $url;
- my $rev;
- foreach my $item (@vsmap) {
- print "Comparing $params{wcpath} with map item: ", Dumper($item), "\n" if $self->debug>1;
- if ($params{wcpath} =~ /^$item->{dir}/) {
- if (length $item->{dir} > $longest_match) {
- $longest_match = length $item->{dir};
- $match = $item;
- if ($item->{cmd} eq 'map') {
- # it's a match. is it the longest match?
- my $nonmatching = substr ($params{wcpath}, $longest_match);
- $nonmatching =~ s/^\/*//; # remove leading slash
- $self->dbg("nonmatching part is $nonmatching");
- $url = $item->{url};
- $url .= "/" . $nonmatching if length $nonmatching > 0;
- $rev = $item->{rev};
- } elsif ($item->{cmd} eq 'unmap') {
- undef $url;
- undef $rev;
- } else {
- $self->error ("unknown cmd in vsmap '$item->{cmd}'");
- }
- }
- }
- }
- if (!defined $url || !defined $rev) {
- return;
- #$self->error("wcpath '$params{wcpath}': did not match anything");
- }
- $self->dbg("wcpath '$params{wcpath}' matched '", Dumper($match), "'. returning url=$url and rev=$rev\n");
- return ($url, $rev);
-}
-
sub has_submappings {
my $self = shift;
my %params = (#wcpath=>,
@@ -362,21 +387,12 @@
sub list_switchpoints_at {
my $self = shift;
- my %params = (#wcpath=>,
+ my %params = (#node=>,
@_);
- # Find switchpoints that branch directly off of wcpath.
- # E.g. list of switchpoints = A, A/B, A/B/C, and A/B/D/F.
- # The switchpoints at A/B are C and D.
- my %switchpoints; # use hash to avoid the dups
- foreach my $item (@vsmap) {
- my $dir = $item->{dir};
- $self->dbg("checking if '$dir' matches $params{wcpath}");
- if ($dir =~ s/^$params{wcpath}//) {
- $self->dbg("found match '$dir' under $params{wcpath}");
- $dir =~ s%^/%%g; # remove leading slashes
- $dir =~ s%/.*%%g; # remove slash and anything after it
- $switchpoints{$dir}=1 if length $dir > 0;
- }
+ my %switchpoints;
+ foreach my $childname (keys %{$params{node}->{dirs}}) {
+ my $child = $params{node}->{dirs}->{$childname};
+ $switchpoints{$childname}=1 if !$child->{in_repo};
}
return keys %switchpoints;
}
@@ -390,8 +406,7 @@
sub fix_urls_recurse {
my $self = shift;
- my %params = (#wctop=>,
- #wcpath=>,
+ my %params = (#wcpath=>,
#basepath=>,
#node=>,
@_);
@@ -400,33 +415,25 @@
$path .= "/" if length $path > 0 && length $params{wcpath} > 0;
$path .= $params{wcpath} if length $params{wcpath} > 0;
my $current_url = $self->file_url (filename=>$path, assert_exists=>0);
- my ($desired_url,$desired_rev) = $self->lookup_mapping (wcpath=>$params{wcpath});
- if (!defined $desired_url) {
- $self->dbg("desired_url is null for $path");
+ my $node = $params{node};
+ die "node is undefined for wcpath=$params{wcpath}" if !defined $node;
+ die "node at $params{wcpath} has undefined url" if !defined $node->{url};
+ if ($node->{url} eq 'void') {
+ $node->{url} = $self->void_url;
}
- my $inrepo = $desired_url && $self->is_file_in_repo(url=>$desired_url);
+ $node->{in_repo} = $self->is_file_in_repo(url=>$node->{url});
my $has_submappings = $self->has_submappings(wcpath=>$params{wcpath});
- my $disappear = !defined $desired_url && !$has_submappings && !$inrepo && !(-e $path);
- if (!$inrepo) {
- $desired_url = $self->void_url;
- }
- $self->dbg ("for $params{wcpath}, inrepo=$inrepo, has_submap=$has_submappings, disappear=$disappear");
- $desired_rev ||= $self->{revision}; # needed for unviews
- $params{node}->{url} = $desired_url;
- $params{node}->{rev} = $desired_rev;
- if ($disappear) {
- $self->dbg("making path $params{wcpath} disappear");
- $params{node}->{disappear} = 1;
- } elsif ($current_url && $current_url eq $desired_url) {
+ $self->dbg ("for $params{wcpath}, inrepo=$node->{in_repo}, has_submap=$has_submappings");
+ if ($current_url && $current_url eq $node->{url}) {
$self->dbg("url is right for '$params{wcpath}'");
} else {
- my @cmd = ('switch', $desired_url, $path, '--revision', $desired_rev);
- if ($desired_url eq $self->void_url) {
+ my @cmd = ('switch', $node->{url}, $path, '--revision', $node->{rev});
+ if ($node->{url} eq $self->void_url) {
print "s4: Creating empty directory $params{wcpath}\n";
push @cmd, "--quiet" unless $self->debug;
$params{node}->{url} = 'void';
} else {
- print "s4: Switching $params{wcpath} to $desired_url\n";
+ print "s4: Switching $params{wcpath} to $node->{url}\n";
$params{node}->{has_submappings} = $has_submappings;
if ($params{node}->{has_submappings}) {
push @cmd, "--non-recursive";
@@ -435,24 +442,40 @@
$self->create_switchpoint_hierarchical($params{basepath}, $params{wcpath});
$self->run_svn(@cmd);
}
- if (!$disappear) {
- my @disk = $self->list_subdirs_on_disk (path=>$path);
- my @repo = $self->list_subdirs_in_repo (path=>$path, revision=>$self->{revision});
- my @switches = $self->list_switchpoints_at (wcpath=>$params{wcpath});
- print "disk=", Dumper(\@disk) if $self->debug > 1;
- print "repo=", Dumper(\@repo) if $self->debug > 1;
- print "switches=", Dumper(\@switches) if $self->debug > 1;
- my @all = remove_duplicates_from_list (@disk, @repo, @switches);
- foreach my $subdir (@all) {
- $self->dbg("Recurse into $subdir");
- my $newdir = $params{wcpath};
- $newdir .= "/" unless $newdir eq "";
- $newdir .= $subdir;
- $params{node}->{dirs}->{$subdir} ||= {}; # set to empty hash, if not defined
- $self->fix_urls_recurse(wctop=>$params{wctop}, wcpath=>$newdir, basepath=>$params{basepath},
- node=>$params{node}->{dirs}->{$subdir});
+ my @disk = $self->list_subdirs_on_disk (path=>$path);
+ my @repo = $self->list_subdirs_in_repo (path=>$path, revision=>$self->{revision});
+ foreach my $subdir (@repo) {
+ $self->dbg("check if there is a node for $subdir");
+ my $childurl = $node->{url} . "/$subdir";
+ my $childrev = $self->{revision};
+ my $undef_node = !defined ($node->{dirs}->{$subdir});
+ my $empty_node = !$undef_node && !defined ($node->{dirs}->{$subdir}->{url});
+ if ($undef_node) {
+ # if there is no node there yet, make one from scratch.
+ $self->dbg("making in_repo=1 node for $subdir");
+ $node->{dirs}->{$subdir} = { url=>$childurl, rev=>$childrev, in_repo=>1 };
}
+ if ($empty_node || $undef_node) {
+ $node->{dirs}->{$subdir}->{url} = $childurl;
+ $node->{dirs}->{$subdir}->{rev} = $childrev;
+ }
}
+ # It is still possible that empty nodes remain, for example if you may "A/D" to some URL
+ # and A isn't in the repository.
+ my @switches = $self->list_switchpoints_at (node=>$node);
+ print "disk=", Dumper(\@disk) if $self->debug > 1;
+ print "repo=", Dumper(\@repo) if $self->debug > 1;
+ print "switches=", Dumper(\@switches) if $self->debug > 1;
+ my @all = remove_duplicates_from_list (@repo, @switches);
+ foreach my $subdir (@all) {
+ $self->dbg("Recurse into $subdir");
+ my $newdir = $params{wcpath};
+ $newdir .= "/" unless $newdir eq "";
+ $newdir .= $subdir;
+ $params{node}->{dirs}->{$subdir} ||= {}; # set to empty hash, if not defined
+ $self->fix_urls_recurse(wcpath=>$newdir, basepath=>$params{basepath},
+ node=>$params{node}->{dirs}->{$subdir});
+ }
$self->dbg("RETURN FROM fix_urls_recurse wcpath=$params{wcpath} basepath=$params{basepath}");
}
@@ -477,15 +500,19 @@
# Undo those switch points, if possible.
$self->remove_unused_switchpoints (basepath=>$params{path});
# add one more map item, which defines the url mapping of the top level.
- my $base_url = $self->file_url (filename=>$params{path});
- my $item = { cmd=>'map', url=>$base_url, dir=>'', rev=>$self->{revision} };
- push @vsmap, $item;
+ my $base_url = $self->file_url (filename=>$params{path}); #FIXME remove
+ my $item = { cmd=>'map', url=>$base_url, dir=>'', rev=>$self->{revision} }; #FIXME remove
+ push @vsmap, $item; #FIXME remove
+ # add to viewspec_tree
+ $self->{viewspec_tree}->{url} = $base_url;
+ $self->{viewspec_tree}->{rev} = $self->{revision};
+ $self->{viewspec_tree}->{in_repo} = 1;
my $base_uuid;
# compute voids url once
$self->void_url(url => $self->file_url(filename=>$params{path}));
- $self->{mytree} = {};
- $self->fix_urls_recurse (wctop=>$params{path}, basepath=>$params{path}, wcpath=>'', node=>$self->{mytree});
- $self->dbg("mytree = ", Dumper($self->{mytree}));
+ $self->dbg ("viewspec_tree = ", Dumper($self->{viewspec_tree}));
+ $self->fix_urls_recurse (basepath=>$params{path}, wcpath=>'', node=>$self->{viewspec_tree});
+ $self->dbg("viewspec_tree = ", Dumper($self->{viewspec_tree}));
$self->dbg("done with apply_viewspec_new");
# now do an update?
$self->minimal_update_tree (path=>$params{path});
@@ -510,7 +537,7 @@
# that are needed.
my $needed = $self->minimal_update_tree_recurse (
path=>$params{path},
- node=>$self->{mytree},
+ node=>$self->{viewspec_tree},
);
$self->dbg ("minimal_update_tree needed = ", Dumper($needed));
# do the commands
@@ -525,7 +552,7 @@
}
# Call minimal_update_tree_recurse with a node and a path. The
-# node is a reference to a piece of $self->{mytree} and path is
+# node is a reference to a piece of $self->{viewspec_tree} and path is
# the path that leads to it, e.g. "A/B/C". This method examines
# its rev and the revs of its children, and returns a hashref
# with entries that describe how to update the node and its children.