#!/usr/bin/perl -w

# cherry-pick merge multiple changes from one subversion directory tree to
# another inside same repository
#
# 2005-01-01 Dobrica Pavlinusic <dpavlin@rot13.org> original shell version
# 2005-04-10 rewritten in perl, become really cherry-pick like

use strict;
use XML::Simple;
use Data::Dumper;

my ($from, $to, $rev) = @ARGV;

die "usage: $0 from_svn_path to_svn_path start_rev\n" unless ($from and $to and $rev);

###
### subs
###

sub cmd($) {
	my $cmd = shift;
	print "## $cmd\n";
	open(my $fh, '-|', $cmd . ' 2>&1') or die "failed to execute $cmd: $?";
	my $out;
	while(<$fh>) {
		$out .= $_;
	}
	close($fh) || die "can't close $cmd: $!";
	return $out;
}


sub cmd_regex($$) {
	my ($cmd, $regex) = @_;

	my $out = cmd($cmd);
	return unless ($out);
	return $out =~ $regex;
}

sub svn_update($) {
	my $path = shift;

	my $rev = cmd_regex("cd $path && svn update", qr/revision\s+(\d+)/);
	return $rev;
}

my %rev2i;

sub svn_log {
	my $path = shift;

	my $cmd = "svn log -v ".join(" ",@_)." $path";
	print "## $cmd\n";

	open(my $fh, '-|', $cmd) || die "svn log failed: $!";
	my @log_arr;
	my $log;
	while(<$fh>) {
		if (/^-+$/) {
			if ($log) {
				$rev2i{$1} = @log_arr if ($log =~ m/^r(\d+)\s/);
				push @log_arr, $log;
			}
			undef $log;
		} else {
			$log .= $_;
		}
	}
	close($fh) || die "can't close log: $!";
	return @log_arr;
}

sub readln {
	my $msg = shift;
	my $default = shift;
	print "${msg}: " if ($msg);
	my $tmp = <STDIN>;
	chomp($tmp);
	$tmp ||= $default;
	return $tmp;
}

###
###
###

#my $from_rev = svn_update($from);
#my $to_rev = svn_update($to);
#die "src/dest revisions not same: ${from_rev}:${to_rev}\n" unless ($from_rev == $to_rev);

my @src_log = svn_log($from, '-r '.$rev.':HEAD');
my $editor = $ENV{'EDITOR'} || 'vi';

# move away old log file
rename 'log', 'log.old' if -e ('log');

# position in changelog
my $i = 0;

my $l = 'foo';

sub inc_i { if ($i < (@src_log-1) ) { $i++; } else { print "!! end of changelog\n"; } }
sub dec_i { if ($i > 0) { $i--; } else { print "!! allready on first revision!\n"; } }

my %applied;

while ($l && $l !~ m/^q/) {

	# commit message
	my $log = $src_log[$i] || die "no log message $i";
	# revision
	my $r = $1 if ($log =~ m/^r(\d+)\s/);
	print "-" x 76, "\n$log\n";

	my $un = '';
	$un = 'un' if ($r && $applied{$r});

	my $l = readln("# ".(join(",",sort keys %applied) || 'none')." [${un}apply/diff/commit/msg/list/revert/+-]",'');
	print "\n";

	if ($l =~ m/^a/i) {
		print cmd("svn merge -r ".($r-1).":${r} $from $to");

		open(LOG, '>>', 'log') || die "can't open log: $!";
		print LOG $log;
		close(LOG);

		$applied{$r} = $i;

		inc_i();
	} elsif ($l =~ m/^u/i) {
		print cmd("svn merge -r ${r}:".($r-1)." $from $to");
		delete($applied{$r});
		inc_i();
	} elsif ($l =~ m/^d/i) {
		system "cd $to && svn status && svn diff | $editor -R -";
	} elsif ($l =~ m/^m/i) {
		system "$editor log" or warn "editor: $?";
	} elsif ($l =~ m/^c/i) {
		open(LOG, '>>', 'log') || die "can't append changed files";
		print LOG "--This line, and those below, will be ignored--\n\n";
		system "svn status -q $to >> log && $editor log && svn commit -F log $to && mv log log.commited";
		%applied = ();
	} elsif ($l =~ m/^[\+>]/i) {
		inc_i();
	} elsif ($l =~ m/^[\-<]/i) {
		dec_i();
	} elsif ($l =~ m/^l/i) {
		my $sep = "-" x 76;
		$sep .= "\n";
		my $log = join($sep, @src_log);
		if (open(my $vim, "|-", $editor." -R -")) {
			print $vim $log;
			close($vim);
		} else {
			print $log;
		}
	} elsif ($l =~ m/^r*(\d+)$/) {
		if (defined($rev2i{$1})) {
			$i = $rev2i{$1};
		} else {
			print "!! can't find revision $1\n";
		}
	} elsif ($l =~ m/^R/) {
		%applied = ();
		system "svn status -q $to | cut -c7- | xargs -i svn revert {}";
	}
}

