blob: 9d84e7f611180060c0f6538ab8c1d9345ea18f25 [file] [log] [blame]
#!/usr/bin/perl -w
# ====================================================================
#
# svn_update.pl
#
# Put this in your path somewhere and make sure it has exec perms.
# Do your thing w/subversion but when you would use 'svn update'
# call this script instead.
#
# ====================================================================
# Copyright (c) 2000-2004 CollabNet. All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
# This software consists of voluntary contributions made by many
# individuals. For exact contribution history, see the revision
# history and logs, available at http://subversion.tigris.org/.
# ====================================================================
# WHY THE NEED FOR THIS SCRIPT?
#
# Currently, the subversion server will attempt to stream all file
# data to the client at once for _each_ merge candidate. For cases
# that have >1 file and/or the complexity of the merge for any
# given file(s) that would require >n minutes, where n is the
# server's magic timeout (5 min.??), the server will timeout. This
# leaves the client/user in an unswell state. See issue #2048 for
# details http://subversion.tigris.org/issues/show_bug.cgi?id=2048.
#
# One solution is to wrap the 'svn update' command in a script that
# will perform the update one file at a time. The problem with
# this solution is that it defeats the beneficial atomic nature of
# subversion for this type of action. If commits are still coming
# in to the repository, the value of "HEAD" might change between each
# of these update operations.
#
# Another solution, the one that this script utilizes, passes the
# --diff3-cmd directive to 'svn update' using a command which forces
# a failed contextual merge ('/bin/false', for example). These faux
# merge failures cause subversion to leave all of the accounting files
# involved in a merge behind and puts them into the 'conflict'
# state. Since all the data required for all the merges took place
# at that exact moment atomicity is preserved and life is swell.
#######################################################################
# This is required for copy() command. I believe it's a standard
# module. If there's a doubt run this from a shell:
# perl -e 'use File::Copy;'
# If you don't get any complaint from perl you're good. Otherwise
# comment this line out and change the $backup_mine_files to 0.
use File::Copy;
# This forces backing up of the .mine files for reference even
# after the resolved command. The backups will be stored as
# <filename.username>
$backup_mine_files=1;
# Choose your favorite graphical diff app. If it's not here, just add
# the full path to it and the style of the options to the
# %DIFF3CMD_hash below.
$DIFF3CMD="xcleardiff";
# Override the diff3-cmd in the config file here.
# If this is an empty string it'll use the config file's
# setting.
#$d3cmdoverride="";
$d3cmdoverride="/bin/false";
# Add more diff programs here.
# For the internal, discovered, file parameters:
# +A+ ==> mine
# +B+ ==> older
# +C+ ==> latest
# +D+ ==> Destination (The output of your merged code would go here.
# This would, generally, be whatever
# $(basename <+A+> .mine) would evaluate to.
# But you can feel free to do something like these:
# "+B+ +C+ +A+ -out +D+.bob"
# "+B+ +A+ +C+ -out /tmp/bob"
# Just note that the '+' (plus) are to limit the false positives in the
# search and replace sub.
#
# HAVING THE CORRECT PATH, AND ARGS FOR YOUR DIFF PROGRAM, IS CRITICAL!!
%DIFF3CMD_hash=(# Ahh...hallowed be thy name.
"/opt/atria/bin/xcleardiff" => "+A+ +B+ +C+ -out +D+",
# This one is slow.
"/usr/bin/kdiff3" => "+A+ +B+ +C+ -o +D+",
# This one's slow and it sucks!(BUGGY)
"/usr/bin/xxdiff" => "-M +D+ +A+ +B+ +C+",
# This one's even worse (no output filename).
"/usr/bin/meld" => "+A+ +B+ +C+",
);
sub exec_cmd
{
my @args=@_;
my $CMD=$args[0];
my @retData;
my $i=0;
open (FH,$CMD) || die("Can't '$CMD': $!\n");
while($_=<FH>)
{
chomp($_);
$retData[$i]=$_;
$i++;
}
close(FH);
return \@retData;
} # exec_cmd
sub diff_it
{
my @args=@_;
my $A=$args[0]; # mine
my $B=$args[1]; # older
my $C=$args[2]; # latest
my $D=$args[3]; # output of merge (Destination)
my @rdat;
# What's is the diff of choice?
if( $CHOSENDIFF eq "" )
{
# Glean our choice.
diff_of_choice();
}
# $CHOSENDIFF has data. We deal with the args.
($diffcmd,$diff_format)=(split /:/,$CHOSENDIFF);
# This works.
$diff_format=~s/\+A\+/$A/g;
$diff_format=~s/\+B\+/$B/g;
$diff_format=~s/\+C\+/$C/g;
$diff_format=~s/\+D\+/$D/g;
@rdat=@{exec_cmd("$diffcmd $diff_format 2>/dev/null |")};
return @rdat;
} #diff_it
sub diff_of_choice
{
foreach $diff_app (sort(keys(%DIFF3CMD_hash)))
{
if( ${diff_app}=~m/${DIFF3CMD}/o )
{
$CHOSENDIFF="$diff_app:$DIFF3CMD_hash{$diff_app}";
}
}
if( $CHOSENDIFF eq "" )
{
# Big problem. Some kind of disconnect w/the choice and the hash.
# Most likely a typo.
print "It would seem that the '${DIFF3CMD}' was not found in\n";
print "the hash of diff applications I know about. Please\n";
print "investigate and correct.\n";
exit(1);
}
} #diff_of_choice
sub svn_update_info
{
my @data;
my @file_array;
my $j;
# Check to see if the d3cmdoverride is set so
# we don't fail here if it wasn't.
if( ${d3cmdoverride} eq "" )
{
$d3cmdoverride_final="";
}
else
{
$d3cmdoverride_final="--diff3-cmd=${d3cmdoverride}";
}
@data=@{exec_cmd("svn update ${d3cmdoverride_final} | grep ^C | awk '{print \$2}' |")};
for($j=0;$j<(scalar(@data));$j++)
{
push( @file_array, exec_cmd("svn info $data[$j] |") );
}
return @file_array;
} # svn_update_info
sub parse_it
{
my @file_array=@_;
my @file;
my $fname;
my $fpath_tmp;
my $fpath;
my $file_ref;
my $sbox_repo_base;
my $sbox_repo_changed;
my $sbox_repo_latest;
my @cleanup_array;
my @commit_array;
my $rdat;
my $retline;
my $i;
foreach $file_ref (@file_array)
{
@file=@{$file_ref};
for($i=0; $i < (scalar(@file)-1);$i++)
{
if( $file[$i]=~m/Name:/o )
{
# Key off of "Name:" and then back up one to get "Path:".
# This way the calculations will be correct when chopping off
# the name portion on the path bit.
$fname=(split /Name: /,$file[$i])[1];
$fpath_tmp=(split /Path: /,$file[$i-1])[1];
$fpath=(substr($fpath_tmp,0,(length($fpath_tmp)-(length($fname)+1))));
}
elsif( $file[$i]=~m/Conflict Previous Base File:/o )
{
$sbox_repo_base=(split /Conflict Previous Base File: /,$file[$i])[1];
}
elsif( $file[$i]=~m/Conflict Previous Working File:/o )
{
$sbox_repo_changed=(split /Conflict Previous Working File: /,
$file[$i])[1];
}
elsif( $file[$i]=~m/Conflict Current Base File:/o )
{
$sbox_repo_latest=(split /Conflict Current Base File: /,$file[$i])[1];
}
}
# print "\n----------------------------------------------\n";
# print " \$fpath: '$fpath'\n";
# print " \$fname: '$fname'\n";
# print "[A](mine) \$sbox_repo_changed: '$sbox_repo_changed'\n";
# print "[B](older) \$sbox_repo_base: '$sbox_repo_base'\n";
# print "[C](latest)\$sbox_repo_latest: '$sbox_repo_latest'\n";
# print "\n----------------------------------------------\n";
# Send them in standard, ABCD, order.
@rdat=diff_it("${fpath}/${sbox_repo_changed}",
"${fpath}/${sbox_repo_base}",
"${fpath}/${sbox_repo_latest}",
"${fpath}/${fname}");
# Print out any return data. Shouldn't be much of anything due to
# the redirection to /dev/null in the invocation of whatever diff
# command is used.
print "\nOutput from diff3 command.\n";
print "-----------------------\n";
foreach $retline (@rdat)
{
print "$retline\n";
}
print "---------END-----------\n";
# Add the files we may wish to remove to an array. Keep *.mine
# files in case something bad happened.
push(@cleanup_array,
"${fpath}/${sbox_repo_base}",
"${fpath}/${sbox_repo_latest}",
"${fpath}/${fname}.orig");
# Make copies of *.mine for ctya purposes.
if ( ${backup_mine_files} > 0 )
{
copy("${fpath}/${sbox_repo_changed}",
"${fpath}/${fname}.${ENV{USERNAME}}");
}
# Return an array that contains all the files we might wish to
# commit.
push(@commit_array,"${fpath}/${fname}");
}
return \@cleanup_array, \@commit_array;
} #parse_it
sub clean_up
{
my @args=@_;
my @rdat;
foreach $file (@{$args[0]})
{
unlink($file);
}
# Need to tell subversion we have resolved the conflicts.
@rdat=@{exec_cmd("svn -R resolved . |")};
print "\nOutput from subversion 'resolved' command.\n";
print "-----------------------\n";
foreach $line (@rdat)
{
print "$line\n";
}
print "---------END-----------\n";
} #clean_up
sub main
{
my @args=@_;
my $cleanup_aref;
my $commit_aref;
my $file;
($cleanup_aref,$commit_aref)=parse_it(svn_update_info());
clean_up($cleanup_aref);
print "\nDon't forget to commit these files:\n";
print "-----------------------\n";
foreach $file (@{$commit_aref})
{
print "$file\n";
}
print "---------END-----------\n";
} #main
# Special global.
$CHOSENDIFF="";
# Get the ball rolling.
main(@ARGV);