1# BEGIN BPS TAGGED BLOCK {{{ 2# COPYRIGHT: 3# 4# This software is Copyright (c) 2003-2008 Best Practical Solutions, LLC 5# <clkao@bestpractical.com> 6# 7# (Except where explicitly superseded by other copyright notices) 8# 9# 10# LICENSE: 11# 12# 13# This program is free software; you can redistribute it and/or 14# modify it under the terms of either: 15# 16# a) Version 2 of the GNU General Public License. You should have 17# received a copy of the GNU General Public License along with this 18# program. If not, write to the Free Software Foundation, Inc., 51 19# Franklin Street, Fifth Floor, Boston, MA 02110-1301 or visit 20# their web page on the internet at 21# http://www.gnu.org/copyleft/gpl.html. 22# 23# b) Version 1 of Perl's "Artistic License". You should have received 24# a copy of the Artistic License with this package, in the file 25# named "ARTISTIC". The license is also available at 26# http://opensource.org/licenses/artistic-license.php. 27# 28# This work is distributed in the hope that it will be useful, but 29# WITHOUT ANY WARRANTY; without even the implied warranty of 30# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 31# General Public License for more details. 32# 33# CONTRIBUTION SUBMISSION POLICY: 34# 35# (The following paragraph is not intended to limit the rights granted 36# to you to modify and distribute this software under the terms of the 37# GNU General Public License and is only of importance to you if you 38# choose to contribute your changes and enhancements to the community 39# by submitting them to Best Practical Solutions, LLC.) 40# 41# By intentionally submitting any modifications, corrections or 42# derivatives to this work, or any other work intended for use with SVK, 43# to Best Practical Solutions, LLC, you confirm that you are the 44# copyright holder for those contributions and you grant Best Practical 45# Solutions, LLC a nonexclusive, worldwide, irrevocable, royalty-free, 46# perpetual, license to use, copy, create derivative works based on 47# those contributions, and sublicense and distribute those contributions 48# and any derivatives thereof. 49# 50# END BPS TAGGED BLOCK }}} 51package SVK::Command::Copy; 52use strict; 53use SVK::Version; our $VERSION = $SVK::VERSION; 54use base qw( SVK::Command::Mkdir ); 55use SVK::Util qw( get_anchor abs2rel splitdir is_uri make_path is_path_inside); 56use SVK::I18N; 57use SVK::Logger; 58 59sub options { 60 ($_[0]->SUPER::options, 61 'q|quiet' => 'quiet', 62 'r|revision=s' => 'rev'); 63} 64 65sub parse_arg { 66 my ($self, @arg) = @_; 67 return if @arg < 1; 68 69 push @arg, '' if @arg == 1; 70 71 my $dst = pop(@arg); 72 die loc ("Copy destination can't be URI.\n") 73 if is_uri ($dst); 74 75 die loc ("More than one URI found.\n") 76 if (grep {is_uri($_)} @arg) > 1; 77 my @src; 78 79 if ( my $target = eval { $self->{xd}->target_from_copath_maybe($dst) }) { 80 $dst = $target; 81 # don't allow new uri in source when target is copath 82 @src = (map {$self->arg_co_maybe 83 ($_, $dst->isa('SVK::Path::Checkout') 84 ? loc ("path '%1' is already a checkout", $dst->report) 85 : undef)} @arg); 86 } 87 else { 88 @src = (map {$self->arg_co_maybe ($_)} @arg); 89 # Asking the user for copy destination. 90 # In this case, first magically promote ourselves to "cp -p". 91 # (otherwise it hurts when user types //deep/directory/name) 92 $self->{parent} = 1; 93 94 # -- make a sane default here for mirroring -- 95 my $default = undef; 96 if (@src == 1 and $src[0]->path =~ m{/mirror/([^/]+)$}) { 97 $default = "/" . $src[0]->depotname . "/$1"; 98 } 99 100 my $path = $self->prompt_depotpath("copy", $default); 101 102 if ($dst eq '.') { 103 $self->{_checkout_path} = (splitdir($path))[-1]; 104 } 105 else { 106 $self->{_checkout_path} = $dst; 107 } 108 109 $dst = $self->arg_depotpath("$path/"); 110 } 111 112 return (@src, $dst); 113} 114 115sub lock { 116 my $self = shift; 117 $self->lock_coroot($_[-1]); 118} 119 120sub handle_co_item { 121 my ($self, $src, $dst) = @_; 122 $src = $src->as_depotpath; 123 die loc ("Path %1 does not exist.\n", $src->path_anchor) 124 if $src->root->check_path ($src->path_anchor) == $SVN::Node::none; 125 my ($copath, $report) = ($dst->copath, $dst->report); 126 die loc ("Path %1 already exists.\n", $copath) 127 if -e $copath; 128 my ($entry, $schedule) = $self->{xd}->get_entry($copath, 1); 129 $src->normalize; $src->anchorify; 130 $self->ensure_parent($dst); 131 $dst->anchorify; 132 133 my $notify = $self->{quiet} ? SVK::Notify->new(quiet => 1) : undef; 134 # if SVK::Merge could take src being copath to do checkout_delta 135 # then we have 'svk cp copath... copath' for free. 136 # XXX: use editor::file when svkup branch is merged 137 my ($editor, $inspector, %cb) = $dst->get_editor 138 ( ignore_checksum => 1, quiet => 1, 139 check_only => $self->{check_only}, 140 update => 1, ignore_keywords => 1, 141 ); 142 SVK::Merge->new (%$self, repos => $dst->repos, nodelay => 1, 143 report => $report, notify => $notify, 144 base => $src->new (path => '/', revision => 0), 145 src => $src, dst => $dst) 146 ->run 147 ($editor, %cb, inspector => $inspector); 148 149 $self->{xd}{checkout}->store 150 ($copath, { revision => undef }); 151 # XXX: can the schedule be something other than delete ? 152 $self->{xd}{checkout}->store ($copath, {'.schedule' => $schedule ? 'replace' : 'add', 153 scheduleanchor => $copath, 154 '.copyfrom' => $src->path, 155 '.copyfrom_rev' => $src->revision}); 156} 157 158sub handle_direct_item { 159 my ($self, $editor, $anchor, $m, $src, $dst, $other_call) = @_; 160 $src->normalize; 161 # if we have targets, ->{path} must exist 162 if (!$self->{parent} && $dst->{targets} && !$dst->root->check_path ($dst->path_anchor)) { 163 die loc ("Parent directory %1 doesn't exist, use -p.\n", $dst->report); 164 } 165 my ($path, $rev) = ($src->path, $src->revision); 166 my $baton = $editor->add_directory (abs2rel ($dst->path, $anchor => undef, '/'), 0, $path, $rev); 167 $other_call->($baton) if $other_call; 168 $editor->close_directory($baton); 169 $editor->adjust_last_anchor; 170} 171 172sub _unmodified { 173 my ($self, $target) = @_; 174 my (@modified, @unknown); 175 $target = $self->{xd}->target_condensed($target); # anchor 176 $self->{xd}->checkout_delta 177 ( $target->for_checkout_delta, 178 xdroot => $target->create_xd_root, 179 editor => SVK::Editor::Status->new 180 ( notify => SVK::Notify->new 181 ( cb_flush => sub { push @modified, $_[0] })), 182 cb_unknown => sub { push @unknown, $_[1] } ); 183 184 if (@modified || @unknown) { 185 my @reports = sort map { loc ("%1 is modified.\n", $target->report_copath ($_)) } @modified; 186 push @reports, sort map { loc ("%1 is unknown.\n", $target->report_copath ($_)) } @unknown; 187 die join("", @reports); 188 } 189} 190 191sub check_src { 192 my ($self, @src) = @_; 193 for my $src (@src) { 194 $src->revision($self->resolve_revision($src, $self->{rev})) if defined $self->{rev}; 195 $self->apply_revision($src); 196 next unless $src->isa('SVK::Path::Checkout'); 197 $self->_unmodified ($src->new); 198 } 199} 200 201sub run { 202 my ($self, @src) = @_; 203 my $dst = pop @src; 204 205 return loc("Different depots.\n") unless $dst->same_repos(@src); 206 my $m = $self->under_mirror($dst); 207 if ( $m && !$dst->same_source(@src) ) { 208 $logger->error(loc("You are trying to copy across different mirrors.")); 209 die loc( "Create an empty directory %1, and run smerge --baseless %2 %3.\n", 210 $dst->report, $src[0]->report, $dst->report ) 211 if $#src == 0 && $dst->isa('SVK::Path'); 212 return 1; 213 } 214 $self->check_src (@src); 215 # XXX: check dst to see if the copy is obstructured or missing parent 216 my $fs = $dst->repos->fs; 217 if ($dst->isa('SVK::Path::Checkout')) { 218 return loc("%1 is not a directory.\n", $dst->report) 219 if $#src > 0 && !-d $dst->copath; 220 return loc("%1 is not a versioned directory.\n", $dst->report) 221 if -d $dst->copath && $dst->root->check_path($dst->path) != $SVN::Node::dir; 222 223 my @cpdst; 224 for (@src) { 225 my $cpdst = $dst->new; 226 # implicit target for "cp x y z dir/" 227 if (-d $cpdst->copath) { 228 if ($_->path_anchor eq $cpdst->path_anchor) { 229 $logger->warn(loc("Ignoring %1 as source.", $_->report)); 230 next; 231 } 232 if ( is_path_inside($cpdst->path_anchor, $_->path_anchor) ) { 233 die loc("Invalid argument: copying directory %1 into itself.\n", $_->report); 234 } 235 $cpdst->descend ($_->path_anchor =~ m|/([^/]+)/?$|) 236 } 237 die loc ("Path %1 already exists.\n", $cpdst->report) 238 if -e $cpdst->copath; 239 push @cpdst, [$_, $cpdst]; 240 } 241 $self->handle_co_item(@$_) for @cpdst; 242 } 243 else { 244 if ($dst->root->check_path($dst->path_anchor) != $SVN::Node::dir) { 245 die loc ("Copying more than one source requires %1 to be directory.\n", $dst->report) 246 if $#src > 0; 247 $dst->anchorify; 248 } 249 $self->get_commit_message (); 250 my ($anchor, $editor) = $self->get_dynamic_editor ($dst); 251 for (@src) { 252 eval { 253 $self->handle_direct_item ($editor, $anchor, $m, $_, 254 $dst->{targets} ? $dst : 255 $dst->new (targets => [$_->path_anchor =~ m|/([^/]+)/?$|])); 256 }; 257 if ($@) { 258 my $err = $@; # make sure not to lose it 259 # Clean up transaction. 260 $editor->abort_edit; 261 die $err; 262 } 263 } 264 $self->finalize_dynamic_editor ($editor); 265 } 266 267 if (defined( my $copath = $self->{_checkout_path} )) { 268 my $checkout = $self->command ('checkout'); 269 $checkout->getopt ([]); 270 my @arg = $checkout->parse_arg ('/'.$dst->depotname.$dst->path, $copath); 271 $checkout->lock (@arg); 272 $checkout->run (@arg); 273 } 274 275 return; 276} 277 2781; 279 280__DATA__ 281 282=head1 NAME 283 284SVK::Command::Copy - Make a versioned copy 285 286=head1 SYNOPSIS 287 288 copy DEPOTPATH1 DEPOTPATH2 289 copy DEPOTPATH [PATH] 290 291=head1 OPTIONS 292 293 -r [--revision] REV : act on revision REV instead of the head revision 294 -p [--parent] : create intermediate directories as required 295 -q [--quiet] : print as little as possible 296 -m [--message] MESSAGE : specify commit message MESSAGE 297 -F [--file] FILENAME : read commit message from FILENAME 298 --template : use the specified message as the template to edit 299 --encoding ENC : treat -m/-F value as being in charset encoding ENC 300 -P [--patch] NAME : instead of commit, save this change as a patch 301 -S [--sign] : sign this change 302 -C [--check-only] : try operation but make no changes 303 --direct : commit directly even if the path is mirrored 304 305