=== lib/SVK/Editor/Merge.pm ================================================================== --- lib/SVK/Editor/Merge.pm (revision 5583) +++ lib/SVK/Editor/Merge.pm (local) @@ -301,27 +301,31 @@ } } +sub _retrieve_base +{ + my ($self, $path, $pool) = @_; + my @base = tmpfile('base-'); + $path = "$self->{base_anchor}/$path" if $self->{base_anchor}; + slurp_fh ($self->{base_root}->file_contents ($path, $pool), $base[FH]); + seek $base[FH], 0, 0; + return @base; +} + sub apply_textdelta { my ($self, $path, $checksum, $pool) = @_; return unless $path; my $info = $self->{info}{$path}; my $fh = $info->{fh} = {}; - my ($base); if (($pool = $info->{fpool}) && ($fh->{local} = $self->{cb_localmod}->($path, $checksum || '', $pool))) { # retrieve base unless ($info->{addmerge}) { - $fh->{base} = [tmpfile('base-')]; - $path = "$self->{base_anchor}/$path" if $self->{base_anchor}; - slurp_fh ($self->{base_root}->file_contents ($path, $pool), - $fh->{base}[FH]); - $base = $fh->{base}[FH]; - seek $base, 0, 0; + $fh->{base} = [$self->_retrieve_base($path, $pool)]; } # get new $fh->{new} = [tmpfile('new-')]; - return [SVN::TxDelta::apply ($base, $fh->{new}[FH], undef, undef, $pool)]; + return [SVN::TxDelta::apply ($fh->{base}[FH], $fh->{new}[FH], undef, undef, $pool)]; } $self->{notify}->node_status ($path, 'U') unless $self->{notify}->node_status ($path); @@ -366,6 +370,24 @@ return ($conflict, $mfh); } +sub _overwrite_local_file { + my ($self, $fh, $path, $nfh, $pool) = @_; + my $handle = $self->{storage}-> + apply_textdelta ($self->{storage_baton}{$path}, $fh->{local}[CHECKSUM], + $pool); + + if ($handle && $#{$handle} >= 0) { + if ($self->{send_fulltext}) { + SVN::TxDelta::send_stream ($nfh, @$handle, $pool); + } + else { + seek $fh->{local}[FH], 0, 0; + my $txstream = SVN::TxDelta::new($fh->{local}[FH], $nfh, $pool); + SVN::TxDelta::send_txstream ($txstream, @$handle, $pool); + } + } +} + sub close_file { my ($self, $path, $checksum, $pool) = @_; return unless $path; @@ -395,23 +417,8 @@ $self->{notify}->node_status ($path, $conflict ? 'C' : 'G'); $iod = IO::Digest->new ($mfh, 'MD5'); - my $handle = $self->{storage}-> - apply_textdelta ($self->{storage_baton}{$path}, $fh->{local}[CHECKSUM], - $pool); + $self->_overwrite_local_file ($fh, $path, $mfh, $pool); - if ($handle && $#{$handle} >= 0) { - if ($self->{send_fulltext}) { - SVN::TxDelta::send_stream ($mfh, @$handle, $pool) - if $handle && $#{$handle} >= 0; - } - else { - seek $fh->{local}[FH], 0, 0; - my $txstream = SVN::TxDelta::new - ($fh->{local}[FH], $mfh, $pool); - SVN::TxDelta::send_txstream ($txstream, @$handle, $pool); - } - } - undef $fh->{base}[FILENAME] if $info->{addmerge}; $self->cleanup_fh ($fh); @@ -489,16 +496,52 @@ unless $path eq ''; } +sub _merge_file_delete { + my ($self, $path, $rpath, $pdir, $pool) = @_; + return undef unless $self->{cb_localmod}->( + $path, + $self->{base_root}->file_md5_checksum ($rpath, $pool), + $pool); + return {} unless $self->{resolve}; + + my $fh = $self->{info}{$path}->{fh} || {}; + $fh->{base} ||= [$self->_retrieve_base($path, $pool)]; + $fh->{new} = [tmpfile('new-')]; + $fh->{local} = [tmpfile('local-')]; + my ($tmp) = $self->{cb_localmod}->($path, '', $pool); + slurp_fh ( $tmp->[FH], $fh->{local}[FH]); + seek $fh->{local}[FH], 0, 0; + $fh->{local}[CHECKSUM] = $tmp->[CHECKSUM]; + + my ($conflict, $mfh) = $self->_merge_text_change( $fh, $path, $pool); + if( $conflict ) { + $self->clean_up($fh); + return {}; + } elsif( !(stat($mfh))[7] ) { + #delete file if merged size is 0 + $self->clean_up($fh); + return undef; + } + seek $mfh, 0, 0; + my $iod = IO::Digest->new ($mfh, 'MD5'); + + $self->{info}{$path}{open} = [$pdir, -1]; + $self->{info}{$path}{fpool} = $pool; + $self->ensure_open ($path); + $self->_overwrite_local_file ($fh, $path, $mfh, $pool); + ++$self->{changes}; + $self->ensure_close ($path, $iod->hexdigest, $pool); + + return 1; +} # returns undef for deleting this, a hash for partial delete. # returns 1 for merged delete # Note that empty hash means don't delete. sub _check_delete_conflict { my ($self, $path, $rpath, $kind, $pdir, $pool) = @_; - return $self->{cb_localmod}-> - ($path, $self->{base_root}->file_md5_checksum ($rpath, $pool), $pool) - ? {} : undef - if $kind == $SVN::Node::file; + return $self->_merge_file_delete($path, $rpath, $pdir, $pool) if $kind == $SVN::Node::file; + my $dirmodified = $self->{cb_dirdelta}->($path, $self->{base_root}, $rpath); my $entries = $self->{base_root}->dir_entries ($rpath); my ($torm, $modified, $merged); @@ -578,11 +621,12 @@ my $torm = $self->_check_delete_conflict ($path, $rpath, $self->{base_root}->check_path ($rpath), $pdir, @arg); - if ($torm) { + if (ref($torm)) { $self->node_conflict ($path); $self->_partial_delete ($torm, $path, $self->{storage_baton}{$pdir}, @arg); - } - else { + } elsif( $torm && $torm == 1) { + $self->{notify}->node_status ($path, 'G'); + } else { $self->{storage}->delete_entry ($path, $self->{cb_rev}->($path), $self->{storage_baton}{$pdir}, @arg); $self->{notify}->node_status ($path, 'D'); === t/07smerge-external.t ================================================================== --- t/07smerge-external.t (revision 5583) +++ t/07smerge-external.t (local) @@ -1,12 +1,12 @@ #!/usr/bin/perl -w use strict; -use Test::More tests => 16; +use Test::More tests => 24; BEGIN { require 't/tree.pl' }; our ($answer, $output); my ($xd, $svk) = build_test(); -my ($copath, $corpath) = get_copath ('smerge'); +my ($copath, $corpath) = get_copath ('smerge-external'); $answer = 's'; # skip @@ -153,3 +153,68 @@ $svk->up($copath); is_file_content ("$copath/test.pl", "merged\n"); +# merge deleted files interactive +$svk->switch ('//trunk', $copath); +$svk->up($copath); +overwrite_file ("$copath/foo", "trunk\n"); +$svk->add ("$copath/foo"); +$svk->commit ('-m', 'foo', $copath); +$svk->sm ('-m', 'merge from trunk to local', '//trunk', '//local'); +$svk->delete ('-m', 'delete foo in trunk', '//trunk/foo'); +$svk->switch ('//local', $copath); +$svk->up($copath); +overwrite_file ("$copath/foo", "local\n"); +$svk->commit ('-m', 'change on local', $copath); + +$ENV{SVKRESOLVE} = 't'; # thiers +is_output_like ($svk, 'sm', ['-C', '//trunk', '//local'], + qr|1 conflict found.|); +is_output_like ($svk, 'sm', ['-m', 'merge to local again', '//trunk', '//local'], + qr|D foo|); + +$svk->switch ('//trunk', $copath); +$svk->up($copath); +overwrite_file ("$copath/foo", "trunk\n"); +$svk->add ("$copath/foo"); +$svk->commit ('-m', 'foo', $copath); +$svk->sm ('-m', 'merge from trunk to local', '//trunk', '//local'); +$svk->delete ('-m', 'delete foo in trunk', '//trunk/foo'); +$svk->switch ('//local', $copath); +$svk->up($copath); +overwrite_file ("$copath/foo", "local\n"); +$svk->commit ('-m', 'change on local', $copath); + +$ENV{SVKRESOLVE} = 'y'; # thiers +is_output_like ($svk, 'sm', ['-C', '//trunk', '//local'], + qr|1 conflict found.|); +is_output_like ($svk, 'sm', ['-m', 'merge to local again', '//trunk', '//local'], + qr|G foo|); +is_file_content ("$copath/foo", "local\n"); +$svk->delete ('-m', 'delete foo in local(cleanup)', '//local/foo'); + +$svk->switch ('//trunk', $copath); +$svk->up($copath); +overwrite_file ("$copath/foo", "trunk\n"); +$svk->add ("$copath/foo"); +$svk->commit ('-m', 'foo', $copath); +$svk->sm ('-m', 'merge from trunk to local', '//trunk', '//local'); +$svk->delete ('-m', 'delete foo in trunk', '//trunk/foo'); +$svk->switch ('//local', $copath); +$svk->up($copath); +overwrite_file ("$copath/foo", "local\n"); +$svk->commit ('-m', 'change on local', $copath); + +$ENV{SVKRESOLVE} = 'e'; # thiers +$answer = 'a'; +set_editor(<< "TMP"); +\$_ = shift; +open _, ">\$_" or die $!; +print _ "merged\\n"; +TMP +is_output_like ($svk, 'sm', ['-C', '//trunk', '//local'], + qr|1 conflict found.|); +is_output_like ($svk, 'sm', ['-m', 'merge to local again', '//trunk', '//local'], + qr|G foo|); +$svk->up($copath); +is_file_content ("$copath/foo", "merged\n"); +$svk->delete ('-m', 'delete foo in local(cleanup)', '//local/foo');