package SVN::Simple::Edit;
@ISA = qw(SVN::Delta::Editor);
$VERSION = '0.27';
use strict;
use SVN::Core;
use SVN::Delta;
=head1 NAME
SVN::Simple::Edit - A simple interface for driving svn delta editors
=head1 SYNOPSIS
my $edit = SVN::Simple::Edit->new
(_editor => [SVN::Repos::get_commit_editor($repos, "file://$repospath",
'/', 'root', 'FOO', \&committed)],
);
$edit->open_root($fs->youngest_rev);
$edit->add_directory ('trunk');
$edit->add_file ('trunk/filea');
$edit->modify_file ("trunk/fileb", "content", $checksum);
$edit->delete_entry ("trunk/filec");
$edit->close_edit ();
...
$edit->copy_directory ('branches/a, trunk, 0);
=head1 DESCRIPTION
SVN::Simple::Edit wraps the subversion delta editor with a perl
friendly interface and then you could easily drive it for describing
changes to a tree. A common usage is to wrap the commit editor, so
you could make commits to a subversion repository easily.
This also means you can not supply the C<$edit> object as an
delta_editor to other API, and that's why this module is named
B<::Edit> instead of B<::Editor>.
See L<SVN::Simple::Editor> for simple interface implementing a delta editor.
=head1 PARAMETERS
=head2 for constructor
=over
=item _editor
The editor that will receive delta editor calls.
=item missing_handler
Called when parent directory are not opened yet, could be:
=over
=item \&SVN::Simple::Edit::build_missing
Always build parents if you don't open them explicitly.
=item \&SVN::Simple::Edit::open_missing
Always open the parents if you don't create them explicitly.
=item SVN::Simple::Edit::check_missing ([$root])
Check if the path exists on $root. Open it if so, otherwise create it.
=back
=item root
The default root to use by SVN::Simple::Edit::check_missing.
=item base_path
The base path the edit object is created to send delta editor calls.
=item noclose
Do not close files or directories. This might make non-sorted
operations on directories/files work.
=back
=head1 METHODS
Note: Don't expect all editors will work with operations not sorted in
DFS order.
=over
=item open_root ($base_rev)
=item add_directory ($path)
=item open_directory ($path)
=item copy_directory ($path, $from, $fromrev)
=item add_file ($path)
=item open_file ($path)
=item copy_file ($path, $from, $fromrev)
=item delete_entry ($path)
=item change_dir_prop ($path, $propname, $propvalue)
=item change_file_prop ($path, $propname, $propvalue)
=item close_edit ()
=back
=cut
require File::Spec::Unix;
sub splitpath { File::Spec::Unix->splitpath(@_) };
sub canonpath { File::Spec::Unix->canonpath(@_) };
sub build_missing {
my ($self, $path) = @_;
$self->add_directory ($path);
}
sub open_missing {
my ($self, $path) = @_;
$self->open_directory ($path);
}
sub check_missing {
my ($root) = @_;
return sub {
my ($self, $path) = @_;
$root ||= $self->{root};
$root->check_path (($self->{base_path} || '')."/$path") == $SVN::Node::none ?
$self->add_directory ($path) : $self->open_directory($path);
}
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{BATON} = {};
$self->{missing_handler} ||= \&build_missing;
return $self;
}
sub set_target_revision {
my ($self, $target_revision) = @_;
$self->SUPER::set_target_revision ($target_revision);
}
sub _rev_from_root {
my ($self, $path) = @_;
$path = "/$path" if $path;
$path ||= '';
return $self->{root}->node_created_rev($self->{base_path}.$path);
}
sub open_root {
my ($self, $base_revision) = @_;
$base_revision ||= $self->_rev_from_root () if $self->{root};
$self->{BASE} = $base_revision;
$self->{BATON}{''} = $self->SUPER::open_root
($base_revision, ${$self->{pool}});
}
sub find_pbaton {
my ($self, $path, $missing_handler) = @_;
use Carp;
return $self->{BATON}{''} unless $path;
my (undef, $dir, undef) = splitpath($path);
$dir = canonpath ($dir);
return $self->{BATON}{$dir} if exists $self->{BATON}{$dir};
$missing_handler ||= $self->{missing_handler};
die "unable to get baton for directory $dir"
unless $missing_handler;
my $pbaton = &$missing_handler ($self, $dir);
return $pbaton;
}
sub close_other_baton {
my ($self, $path) = @_;
return if $self->{noclose};
my (undef, $dir, undef) = splitpath($path);
$dir = canonpath ($dir);
for (reverse sort grep { !$dir || substr ($_, 0, length ($dir)+1) eq "$dir/"}
keys %{$self->{BATON}}) {
next unless $path;
my $baton = $self->{BATON}{$path};
if ($self->{FILES}{$path}) {
$self->SUPER::close_file ($baton, undef, $self->{pool});
}
else {
$self->SUPER::close_directory ($baton, $self->{pool});
}
delete $self->{FILES}{$path};
delete $self->{BATON}{$path};
}
}
sub open_directory {
my ($self, $path, $pbaton) = @_;
$path =~ s|^/||;
$self->close_other_baton ($path);
$pbaton ||= $self->find_pbaton ($path);
my $base_revision = $self->_rev_from_root ($path) if $self->{root};
$base_revision ||= $self->{BASE};
$self->{BATON}{$path} = $self->SUPER::open_directory ($path, $pbaton,
$base_revision,
$self->{pool});
}
sub add_directory {
my ($self, $path, $pbaton) = @_;
$path =~ s|^/||;
$self->close_other_baton ($path);
$pbaton ||= $self->find_pbaton ($path);
$self->{BATON}{$path} = $self->SUPER::add_directory ($path, $pbaton, undef,
-1, $self->{pool});
}
sub copy_directory {
my ($self, $path, $from, $fromrev, $pbaton) = @_;
$path =~ s|^/||;
$pbaton ||= $self->find_pbaton ($path);
$self->{BATON}{$path} = $self->SUPER::add_directory ($path, $pbaton, $from,
$fromrev,
$self->{pool});
}
sub open_file {
my ($self, $path, $pbaton) = @_;
$path =~ s|^/||;
$self->close_other_baton ($path);
$pbaton ||= $self->find_pbaton ($path);
my $base_revision = $self->_rev_from_root ($path) if $self->{root};
$base_revision ||= $self->{BASE};
$self->{FILES}{$path} = 1;
$self->{BATON}{$path} = $self->SUPER::open_file ($path, $pbaton,
$base_revision,
$self->{pool});
}
sub add_file {
my ($self, $path, $pbaton) = @_;
$path =~ s|^/||;
$self->close_other_baton ($path);
$pbaton ||= $self->find_pbaton ($path);
$self->{FILES}{$path} = 1;
$self->{BATON}{$path} = $self->SUPER::add_file ($path, $pbaton, undef, -1,
$self->{pool});
}
sub copy_file {
my ($self, $path, $from, $fromrev, $pbaton) = @_;
$path =~ s|^/||;
$pbaton ||= $self->find_pbaton ($path);
$self->{BATON}{$path} = $self->SUPER::add_file ($path, $pbaton, $from,
$fromrev, $self->{pool});
}
sub modify_file {
my ($self, $path, $content, $targetchecksum) = @_;
$path =~ s|^/|| unless ref($path);
my $baton = ref($path) ? $path :
($self->{BATON}{$path} || $self->open_file ($path));
my $ret = $self->apply_textdelta ($baton, undef, $self->{pool});
return unless $ret && $ret->[0];
if (ref($content) && $content->isa ('GLOB')) {
my $md5 = SVN::TxDelta::send_stream ($content,
@$ret,
$self->{pool});
die "checksum mistach ($md5) vs ($targetchecksum)" if $targetchecksum
&& $targetchecksum ne $md5;
}
else {
SVN::_Delta::svn_txdelta_send_string ($content, @$ret, $self->{pool});
}
}
sub delete_entry {
my ($self, $path, $pbaton) = @_;
my $base_revision;
$path =~ s|^/||;
$pbaton ||= $self->find_pbaton ($path, \&open_missing);
$base_revision = $self->_rev_from_root ($path) if $self->{root};
$base_revision ||= $self->{BASE};
$self->SUPER::delete_entry ($path, $base_revision, $pbaton, $self->{pool});
}
sub change_file_prop {
my ($self, $path, $key, $value) = @_;
$path =~ s|^/|| unless ref($path);
my $baton = ref($path) ? $path :
($self->{BATON}{$path} || $self->open_file ($path));
$self->SUPER::change_file_prop ($baton, $key, $value, $self->{pool});
}
sub change_dir_prop {
my ($self, $path, $key, $value) = @_;
$path =~ s|^/|| unless ref($path);
my $baton = ref($path) ? $path :
($self->{BATON}{$path} || $self->open_directory ($path));
$self->SUPER::change_dir_prop ($baton, $key, $value, $self->{pool});
}
sub close_file {
my ($self, $path, $checksum) = @_;
my $baton = $self->{BATON}{$path} or die "not opened";
delete $self->{BATON}{$path};
$self->SUPER::close_file ($baton, $checksum, $self->{pool});
}
sub close_directory {
my ($self, $path) = @_;
my $baton = $self->{BATON}{$path} or die "not opened";
delete $self->{BATON}{$path};
$self->SUPER::close_directory ($baton, $self->{pool});
}
sub close_edit {
my ($self) = @_;
$self->close_other_baton ('');
$self->SUPER::close_edit ($self->{pool});
}
sub abort_edit {
my ($self) = @_;
$self->SUPER::abort_edit ($self->{pool});
}
=head1 AUTHORS
Chia-liang Kao E<lt>clkao@clkao.orgE<gt>
=head1 COPYRIGHT
Copyright 2003-2004 by Chia-liang Kao E<lt>clkao@clkao.orgE<gt>.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
See L<http://www.perl.com/perl/misc/Artistic.html>
=cut
1;
syntax highlighted by Code2HTML, v. 0.9.1