[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: little scripts to generate and apply diff series for Subversion

From: Liu Yubao <yubao.liu_at_gmail.com>
Date: 2007-01-09 05:42:40 CET

very sorry, I forgot the attachments.

Liu Yubao wrote:
> Hi,
>
> I wrote two little scripts to generate and apply diff series for Subversion,
> you need Subversion command line client and patch utility program to run it.
> Windows users can download the latter from http://gnuwin32.sourceforge.net or
> http://unxutils.sourceforge.net. I hope they are useful for you.
>
> I know SVK can help decentralized development, but it hasn't pretty GUI frontend
> now(no TortoiseSVK) and seems can't synchronzie between two Subversion repositories
> easily.
>
> Another similar tool I found is svnpush, but it seems not be maintained long time and
> I guess it's a pain to install Perl modules in windows.
>
> svn-patches.pl doesn't commit automatically because it's not easy to erase
> your mistake in Subversion. It makes use of GNU patch, no 3-way merge is done,
> so maybe it's not so convenient as you hope.
>
> Below is a short scenario to show its usage.
>
> ----------------------------------------------------
> reposA --- public repository
> reposB --- private repository
> svn export reposA/branches/something@2000
> svn import reposB/branches/something@100
> svn co reposB/branches/something
> ...work...commit...now it reaches revision 120
>
> mkdir diffs;
> perl svn-diffs.pl URL_to_reposB/branches/something 101 120
>
> cd working_copy_for_reposA;
> make sure the working copy is clean and updated;
> svn lock reposA/branches/something -m "applying diff series, please wait..."
>
> perl svn-patches.pl ..\path_to\diffs
> ...check...commit...run svn-patches.pl again until all patches are applied.
>
> svn unlock reposA/branches/something
> -----------------------------------------------------
>
> regards,
>
> Liu Yubao
>

#!/usr/bin/perl -w
#
# svn-diffs.pl:
# generate a diff series for svn, they can be applied by another
# little Perl script svn-patches.pl.
#
# set environment variables "SVNUSER" and "SVNPASSWD" to provide
# user name and password if needed.
#
# note:
# svn properties are ignored.
#
# 2007-01-09 Liu Yubao <yubao.liu@gmail.com>
#
use strict;
use warnings;

my $URL = shift;
my $fromREV = shift;
my $toREV = shift;

die "Usage: perl $0 URL [fromREV] [toREV]\n" if !defined $URL;
$fromREV ||= 2;

my $LANG=$ENV{LANG};
$ENV{LANG} = "en_US.UTF-8"; # make parsing easy
my $svncmd = "svn";
if (exists $ENV{SVNUSER} && exists $ENV{SVNPASSWD}) {
    $svncmd .= " --username $ENV{SVNUSER} --password $ENV{SVNPASSWD} ";
}

######################################################
# check whether svn has been installed
#
if (! qx/svn --version/) {
    die "[svn-diffs] Can't execute svn: $!\n";
}

######################################################
# check whether we are in an empty directory, avoid file overwrite by mistake
#
{
    my @dirs = <*>;
    die "[svn-diffs] Please run me in an empty directory.\n" if @dirs > 0;
}

######################################################
# check URL
#
my ($line, @lines);
open F, "$svncmd info $URL |" || die "[svn-diffs] Can't execute 'svn info': $!\n";
@lines = <F>;
close F;

($line) = grep /^Last Changed Rev:/, @lines;
die "[svn-diffs] Error happened when executed 'svn info': $!\n" if !defined $line;
($line) = $line =~ /(\d+)/;

$toREV = $line if (!defined $toREV) || ($toREV > $line);
print "[svn-diffs] generate diffs for $URL [$fromREV, $toREV]\n";


######################################################
# get log and generate diff
#
open F, "$svncmd log -r $fromREV:$toREV $URL |" || die "[svn-diffs] Can't execute 'svn log': $!\n";
$line = <F>;
die "[svn-diffs] Error happened when executed 'svn log': $!\n" if $line !~ /-{72}/;

my ($rev, $author, $date, $n);
my ($count, $file) = (0, "");
while ($line = <F>) {
    print "-----------------------------------------\n";
    ######################### parse log header #################################
    ++$count;
    ($rev, $author, $date, $n) = split / \| /, $line;
    $rev = substr $rev, 1;
    ($n) = $n =~ /(\d+)/;
    ++$n; # include blank line after log header

    $file = "$fromREV-$toREV-$count";

    ######################### generate log for one revision ####################
    print "write log for revision $rev as '$file.log'...\n";
    open LOG, ">$file.log" || die "[svn-diffs] Can't write $file.log: $!\n";
    print LOG $line;
    do {
        $line = <F>;
        print LOG $line;
    } while (--$n > 0);
    close LOG;
    $line = <F>; # skip '----------------' log separator

    ######################### generate patch and binary files for one revision #
    print "write diff for revision $rev as '$file.diff'...\n";
    open F2, "$svncmd diff -r " . ($rev - 1) . ":$rev $URL |" || die "[svn-diffs] Can't execute 'svn diff': $!\n";
    open DIFF, ">$file.diff" || die "[svn-diffs] Can't write $file.diff: $!\n";
    my $index;
    my @binary_files = ();
    while ($line = <F2>) {
        print DIFF $line;

        if ($line =~ /^Index: (.*)/) {
            $index = $1;
        } elsif ($line =~ /^Cannot display: file marked as a binary type.$/) {
            push @binary_files, $index;
            print "write binary file '$index' for revision $rev as '$file-$#binary_files.bin'...\n";
            system("$svncmd cat -r $rev $URL/$index > $file-$#binary_files.bin") &&
                    die "[svn-diff] Can't execute 'svn cat': $!\n";
        }
    }
    close DIFF;
    close F2;

    ######################### generate list of binary files ####################
    if (@binary_files > 0) {
        print "write list of binary files for revision $rev as '$file.idx'...\n";
        open IDX, ">$file.idx" || die "[svn-diffs] Can't write $file.idx: $!\n";
        foreach my $f (@binary_files) {
            print IDX "$f\n";
        }
        close IDX;
    }
}
close F;

#!/usr/bin/perl -w
#
# svn-patches.pl:
# apply a diff series generated by another little Perl script svn-diffs.pl.
#
# set environment variables "SVNUSER" and "SVNPASSWD" to provide
# user name and password if needed.
#
# note:
# svn properties are ignored, merge is done with GNU patch, no 3-way
# merge.
#
# 2007-01-09 Liu Yubao <yubao.liu@gmail.com>
#
use strict;
use warnings;
use File::Copy;
use File::Spec;

my $diffsDIR = shift;

die "Usage: perl $0 diffsDIR\n" if !defined $diffsDIR;

my $LANG=$ENV{LANG};
$ENV{LANG} = "en_US.UTF-8"; # make parsing easy
my $svncmd = "svn";
if (exists $ENV{SVNUSER} && exists $ENV{SVNPASSWD}) {
    $svncmd .= " --username $ENV{SVNUSER} --password $ENV{SVNPASSWD} ";
}

######################################################
# check whether svn has been installed
#
if (! qx/svn --version/) {
    die "[svn-patches] Can't execute 'svn': $!\n";
}

######################################################
# check whether patch has been installer
#
if (! qx/patch --version/) {
    die "[svn-patches] Can't execute 'patch': $!\n";
}

######################################################
# check whether we are in a clean working copy
#
die "[svn-patches] Not a clean working copy.\n" if `svn status`;

######################################################
# retrieve last applied diff
#
my $svnadminDIR = ".svn";
$svnadminDIR = "_svn" if exists $ENV{SVN_ASP_DOT_NET_HACK};

my $prevDIFF;
my $prevDIFF_file = File::Spec->catfile($svnadminDIR, "_prevDIFF_");
if (-e $prevDIFF_file) {
    open F, "<$prevDIFF_file" || die "[svn-patches] Can't open $prevDIFF_file: $!\n";
    $prevDIFF = <F>;
    chomp $prevDIFF;
    close F;
}

######################################################
# apply diffs as many as possible or only one
#
my @diffs = glob(File::Spec->catfile($diffsDIR, "*.diff"));
die "[svn-patches] No .diff found in $diffsDIR.\n" if @diffs < 1;

@diffs = sort {
    my ($a1) = $a =~ /\d+-\d+-(\d+)\.diff$/;
    my ($b1) = $b =~ /\d+-\d+-(\d+)\.diff$/;
    $a1 <=> $b1;
} @diffs;

my $nextDIFF; # diff being applied
if (!defined $prevDIFF) {
    $nextDIFF = 0;
} else {
    for ($nextDIFF = 0; $nextDIFF < @diffs; ++$nextDIFF) {
        last if $diffs[$nextDIFF] eq $prevDIFF;
    }
    if ($nextDIFF == @diffs) {
        print "[svn-patches] Can't find previous diff '$prevDIFF', can't go on patching\n";
        exit -1;
    } elsif ($nextDIFF == @diffs - 1) {
        print "[svn-patches] All patches have been applied, great!\n";
        unlink $prevDIFF_file;
        exit 0;
    } else {
        ++$nextDIFF;
    }
}

my $ret = 0;
while ($nextDIFF < @diffs) {
    print "------------------------------------------------------\n";
    print "[svn-patches] Applying '$diffs[$nextDIFF]'...\n";
    $ret = system "patch -N -p0 <" . $diffs[$nextDIFF];
    if ($ret < 0) {
        print "[svn-patches] Error happened when executed 'patch -p0': $!\n";
        print "[svn-patches] run me again after you have fixed the problem.\n";
        last;
    } elsif ($ret > 0) { # conflict
        $prevDIFF = $diffs[$nextDIFF];
        copy_binary_files();
        save_state();
        print "[svn-patches] run me again after you have resolved conflicts.\n";
        last;
    } else {
        $prevDIFF = $diffs[$nextDIFF];
        copy_binary_files();
        save_state();
        ++$nextDIFF;
        print "[svn-patches] Successfully applied, great!\n";
        # print "[svn-patches] Try to commit it...\n";
            print "[svn-patches] run me again after you have checked and committed these changes.\n";
            last;
    }
}

######################################################
#
sub save_state {
    print "[svn-patches] Save applying state to $prevDIFF_file.\n";
    open F, ">$prevDIFF_file" || die "[svn-patches] Can't write $prevDIFF_file: $!\n";
    print F "$prevDIFF\n";
    close F;
}

######################################################
#
sub copy_binary_files {
    my $idx = $prevDIFF;
    $idx =~ s/\.diff$/\.idx/;

    if (-e $idx) {
        open F, "<$idx" || die "[svn-patches] Can't read $idx: $!\n";
        my (@files, $prefix, $f);
        @files = <F>;
        chomp @files;
        $prefix = $idx;
        $prefix =~ s/\.idx$//;
        for (my $i = 0; $i < @files; ++$i) {
            $f = "$prefix-$i.bin";
            print "[svn-patches] Copy $f to $files[$i].\n";
            copy $f, $files[$i] || die "[svn-patches] Can't copy '$f' to '$files[$i]': $!\n";
        }
        close F;
    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@subversion.tigris.org
For additional commands, e-mail: users-help@subversion.tigris.org
Received on Tue Jan 9 05:44:51 2007

This is an archived mail posted to the Subversion Users mailing list.