
package SVN2GitEditor;
use strict;

require SVN::Delta;
our @ISA = qw(SVN::Delta::Editor);

use SVK::I18N;
use SVK::XD;
use autouse 'SVK::Util' => qw( slurp_fh tmpfile mimetype_is_text catfile );

sub set_target_revision {
    my ($self, $revision) = @_;
}

sub open_root {
    my ($self, $baserev) = @_;
    return '';
}

sub add_file {
    my ($self, $path, $pdir, $from_path, $from_rev, $pool) = @_;
    $self->{info}{$path}{fpool} = $pool;
    if (defined $from_path) {
	$self->{info}{$path}{from_path} = $from_path;
	$self->{info}{$path}{copied} = 1;
    } else {
	$self->{info}{$path}{added} = 1;
    }
    return $path;
}

sub open_file {
    my ($self, $path, $pdir, $rev, $pool) = @_;
    $self->{info}{$path}{from_path} = $path;
    $self->{info}{$path}{fpool} = $pool;

    return $path;
}

sub apply_textdelta {
    my ($self, $path, $checksum, $pool) = @_;
    return unless $path;
    my $info = $self->{info}{$path};
    $info->{base} = $self->{cb_basecontent}($info->{from_path}, $info->{fpool})
	unless $info->{added};

    unless ($self->{external}) {
	my $newtype = $info->{prop} && $info->{prop}{'svn:mime-type'};
	my $is_text = !$newtype || mimetype_is_text ($newtype);
	if ($is_text && !$info->{added}) {
	    my $basetype = $self->{cb_baseprop}->($info->{from_path}, 'svn:mime-type', $pool);
	    $is_text = !$basetype || mimetype_is_text ($basetype);
	}
	unless ($is_text) {
            confess("Cannot display: file marked as a binary type.\n");
	}
    }
    my $new;
    if ($self->{external}) {
	my $tmp = tmpfile ('diff');
	slurp_fh ($info->{base}, $tmp)
	    if $info->{base};
	seek $tmp, 0, 0;
	$info->{base} = $tmp;
	$info->{new} = $new = tmpfile ('diff');
    }
    else {
	$info->{new} = '';
	open $new, '>', \$info->{new};
    }

    return [SVN::TxDelta::apply ($info->{base}, $new,
				 undef, undef, $pool)];
}

sub close_file {
    my ($self, $path, $checksum, $pool) = @_;
    return unless $path;
    my $info = $self->{info}{$path};

    if (not exists $info->{new}) {
	$info->{new} = '';
	$info->{nodiff} = 1;
    }

    if (exists $info->{new}) {
	no warnings 'uninitialized';
	my $rpath = $self->{report} ? catfile($self->{report}, $path) : $path;
	my @label = map { $self->{$_} || $self->{"cb_$_"}->($path) } qw/llabel rlabel/;
	my $showpath = ($self->{lpath} ne $self->{rpath});
	my @showpath = map { $showpath ? $self->{$_} : undef } qw/lpath rpath/;
	if ($self->{external}) {
	    # XXX: the 2nd file could be - and save some disk IO
	    my @content = map { ($info->{$_}->filename) } qw/base new/;
	    @content = reverse @content if $self->{reverse};
	    (system (split (/ /, $self->{external}),
		    '-L', _full_label ($rpath, $showpath[0], $label[0]),
		    $content[0],
		    '-L', _full_label ($rpath, $showpath[1], $label[1]),
		    $content[1]) >= 0) or die loc("Could not run %1: %2", $self->{external}, $?);
	}
	else {
	    $info->{base} = '';
	    $info->{base} = $self->{cb_basecontent}($info->{from_path}, $info->{fpool})
		unless $info->{added};
	    my @content = ($info->{base}, \$self->{info}{$path}{new});
	    @content = reverse @content if $self->{reverse};
	    $self->output_diff ($rpath, @label, @showpath, @content);
	}
    }

#    $self->output_prop_diff ($path, $pool);
    delete $self->{info}{$path};
}

sub oldfilemode
{
	my ($self, $path) = @_;
	my $pool = $self->{info}{$path}{fpool};
	my $name = "svn:executable";

	return undef if $self->{info}{$path}{added};

	my $prop = $self->{cb_baseprop}->($path, $name, $pool);

	return "100644" unless defined($prop);

	return "100755" if (length($prop) > 0);

	return "100644";
}

sub newfilemode
{
	my ($self, $path, $oldmode) = @_;
	my $name = "svn:executable";

	return undef if $self->{info}{$path}{deleted};

	my $prop = $self->{info}{$path}{prop}{$name};

	my $changed = 1;
	$changed = 0 unless defined($prop);
	$changed = 1 if $self->{info}{$path}{added};

	return $oldmode unless $changed;

	return "100755" if (length($prop) > 0);

	return "100644";
}

sub output_diff {
    my ($self, $path, $llabel, $rlabel, $lpath, $rpath) = splice(@_, 0, 6);
    my $fh = $self->_output_fh;

    my $ofile = $self->{info}{$path}{added} ? "/dev/null": "a/$path";
    my $nfile = $self->{info}{$path}{deleted} ? "/dev/null": "b/$path";

    my $osha1 = $self->{info}{$path}{added} ? "0000000": "1234567";
    my $nsha1 = $self->{info}{$path}{deleted} ? "0000000": "7654321";

    my $omode = $self->oldfilemode($path);
    my $nmode = $self->newfilemode($path, $omode);

    my $mode = "";

    if (defined($omode) and defined($nmode)) {
	if ($omode ne $nmode) {
	    $mode = "old mode $omode\nnew mode $nmode\n";
	}
    } elsif (defined($omode)) {
	$mode = "deleted file mode $omode\n";
    } elsif (defined($nmode)) {
	$mode = "new file mode $nmode\n";
    }

    my $index = "";
    $index = "index $osha1..$nsha1\n" unless $self->{info}{$path}{nodiff};

    print $fh (
	"diff --git $ofile $nfile\n",
	$mode,
	$index
    );

    return if $self->{info}{$path}{nodiff};

    unshift @_, $self->_output_fh;
    push @_, $ofile, $nfile;

    goto &{$self->can('_output_diff_content')};
}

# _output_diff_content($fh, $ltext, $rtext, $llabel, $rlabel)
sub _output_diff_content {
    my ($fh, $ltext, $rtext, $llabel, $rlabel) = @_;

    my ($lfh, $lfn) = tmpfile ('diff');
    my ($rfh, $rfn) = tmpfile ('diff');

    slurp_fh ($ltext => $lfh); close ($lfh);
    slurp_fh ($rtext => $rfh); close ($rfh);

    my $diff = SVN::Core::diff_file_diff( $lfn, $rfn );

    SVN::Core::diff_file_output_unified(
        $fh, $diff, $lfn, $rfn, $llabel, $rlabel
    );

    unlink ($lfn, $rfn);
}

sub output_prop_diff {
    my ($self, $path, $pool) = @_;
    if ($self->{info}{$path}{prop}) {
	my $rpath = $self->{report} ? catfile($self->{report}, $path) : $path;
	$self->_print("\n", loc("Property changes on: %1\n", $rpath), ('_' x 67), "\n");
	for (sort keys %{$self->{info}{$path}{prop}}) {
	    $self->_print(loc("Name: %1\n", $_));
	    my $baseprop;
	    $baseprop = $self->{cb_baseprop}->($path, $_, $pool)
		unless $self->{info}{$path}{added};
            my @args =
                map \$_,
                map { (length || /\n$/) ? "$_\n" : $_ }
                    ($baseprop||''), ($self->{info}{$path}{prop}{$_}||'');
            @args = reverse @args if $self->{reverse};

            my $diff = '';
            open my $fh, '>', \$diff;
            _output_diff_content($fh, @args, '', '');
            $diff =~ s/.*\n.*\n//;
            $diff =~ s/^\@.*\n//mg;
            $diff =~ s/^/ /mg;
            $self->_print($diff);
	}
	$self->_print("\n");
    }
}

sub add_directory {
    my ($self, $path, $pdir, @arg) = @_;
#    $self->{info}{$path}{added} = 1;
    return $path;
}

sub open_directory {
    my ($self, $path, $pdir, $rev, @arg) = @_;
    return $path;
}

sub close_directory {
    my ($self, $path, $pool) = @_;
#    $self->output_prop_diff ($path, $pool);
    delete $self->{info}{$path};
}

sub delete_entry {
    my ($self, $path, $revision, $pdir, $pool) = @_;

    my $fullpath = "$self->{oldbranch}/$path";

    if ($self->{oldroot}->is_file($fullpath)) {
	$self->{info}{$path}{from_path} = $path;
	$self->{info}{$path}{deleted} = 1;
	$self->{info}{$path}{fpool} = $pool;
	$self->{info}{$path}{new} = '';
	$self->close_file($path, undef, $pool);
	return;
    }

    my $entries = $self->{oldroot}->dir_entries($fullpath);
    foreach my $c (keys %{$entries}) {
	$self->delete_entry("$path/$c", $revision, $path, $pool);
    }
}

sub change_file_prop {
    my ($self, $path, $name, $value) = @_;
    $self->{info}{$path}{prop}{$name} = $value;
}

sub change_dir_prop {
    my ($self, $path, $name, $value) = @_;
}

sub close_edit {
    my ($self, @arg) = @_;
}

sub _print {
    my $self = shift;
    $self->{output} or return print @_;
    ${ $self->{output} } .= $_ for @_;
}

sub _output_fh {
    my $self = shift;

    no strict 'refs';
    $self->{output} or return \*{select()};

    open my $fh, '>>', $self->{output};
    return $fh;
}

=head1 AUTHORS

Chia-liang Kao E<lt>clkao@clkao.orgE<gt>

=head1 COPYRIGHT

Copyright 2003-2005 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;
