ElaborateDiff
Jump to navigation
Jump to search
SVNDIFF taken from http://svn.sunbase.org/repos/svnutils/trunk/src/svndiff
Put this in your ./bin/ directory. Make it executable. And you are set.
svndiff --help will give you the usage syntax.
#!/usr/bin/perl -w #======================================================================= # $Id$ # Uses a specified diff program for viewing differences in a Subversion # versioned directory tree. # # Character set: UTF-8 # License: GNU General Public License, see end of file for legal stuff. # ©opyleft 2004– Øyvind A. Holm <sunny@sunbase.org> # This file is part of the svnutils project — http://svnutils.tigris.org #======================================================================= use strict; $| = 1; use Getopt::Long; our ($opt_conflict, $opt_create_rc, $opt_diffcmd, $opt_svncmd, $opt_help) = ( 0, 0, "", "", 0); our ($opt_diffargs, $opt_revision) = ( "", ""); my $Debug = 0; # Default value, can be overridden in ~/.svndiffrc my $Cmd = "vimdiff -o"; # Change this if the svn executable is non-standard and you don’t want # to use the -e option all the time: my $CMD_SVN = "svn"; my $ST_CONFLICT = 'C'; my $ST_MODIFIED = 'M'; my $valid_rev = '\d+|HEAD|{\d+[^}]*?[Z\d]}'; # Used in regexps our $progname = $0; $progname =~ s#^(.*)/(.+?)$#$2#; my %rev_diff = (); my $rc_file = defined($ENV{SVNDIFFRC}) ? $ENV{SVNDIFFRC} : ""; unless (length($rc_file)) { if (defined($ENV{HOME})) { $rc_file = "$ENV{HOME}/.svndiffrc"; } else { warn("Both SVNDIFFRC and HOME environment variables not defined, " . "unable to read rc file.\n" . "Using default values. To override, " . "define the SVNDIFFRC variable.\n" ); } } length($rc_file) && (-e $rc_file) && read_rcfile($rc_file); Getopt::Long::Configure("bundling"); GetOptions( "conflict|C" => \$opt_conflict, "create-rc" => \$opt_create_rc, "diffcmd|c=s" => \$opt_diffcmd, "svncmd|e=s" => \$opt_svncmd, "help|h" => \$opt_help, "diffargs|p=s" => \$opt_diffargs, "revision|r=s" => \$opt_revision ) || die("$progname: Option error. Use -h for help.\n"); $opt_help && usage(0); if ($opt_create_rc) { print(<<END); <svndiffrc> <diffprog>vimdiff</diffprog> <svnclient>svn</svnclient> <reversediffs> <program> <name>vimdiff</name> <reverse>1</reverse> </program> <program> <name>meld</name> <reverse>1</reverse> </program> <program> <name>kompare</name> <reverse>1</reverse> </program> <program> <name>xxdiff</name> <reverse>1</reverse> </program> </reversediffs> </svndiffrc> END exit(0); } length($opt_diffcmd) && ($Cmd = $opt_diffcmd); length($opt_diffargs) && ($Cmd .= " $opt_diffargs"); length($opt_svncmd) && ($CMD_SVN = $opt_svncmd); my $stat_chars = "$ST_CONFLICT$ST_MODIFIED"; $opt_conflict && ($stat_chars = "$ST_CONFLICT"); my @mod_array = (); if (scalar(@ARGV)) { # Filename(s) specified on command line. {{{ @mod_array = @ARGV; for my $Curr (@mod_array) { D("ARG = \"$Curr\"\n"); if ((-f $Curr && !-l $Curr) || is_url($Curr)) { D("$Curr is a file or URL."); my $has_conflict; if (!is_url($Curr)) { D("Before PipeFP 1: CMD_SVN = \"$CMD_SVN\""); if (open(PipeFP, "$CMD_SVN stat $Curr -q |")) { $has_conflict = (<PipeFP> =~ /^$ST_CONFLICT/) ? 1 : 0; } else { warn("$progname: Error opening " . "\"$CMD_SVN $Curr stat -q\" pipe: $!"); } } else { if (!length($opt_revision)) { die("$progname: Need to specify the --revision option " . "when diffing an URL\n"); } $has_conflict = 0; } diff_file($Curr, $has_conflict, $opt_revision); } else { D("$Curr is NOT a file."); warn("$progname: \"$Curr\" is not a file or doesn't exist\n"); } } # }}} } else { # {{{ length($opt_revision) && die("Need to specify one or more " . "files when using the -r option\n"); D("Before PipeFP 2: CMD_SVN = \"$CMD_SVN\""); if (open(PipeFP, "$CMD_SVN stat -q |")) { my %has_conflict = (); while (<PipeFP>) { chomp(); D("<PipeFP> = \"$_\"\n"); if (/^([$stat_chars])......(.*)/) { my ($Stat, $File) = ( $1, $2); D("\$Stat = \"$Stat\", \$File = \"$File\"\n"); push(@mod_array, $File); $has_conflict{$File} = ($Stat =~ /^$ST_CONFLICT/) ? 1 : 0; D("\$has_conflict{$File} = \"$has_conflict{$File}\"\n"); } } close(PipeFP); for (sort @mod_array) { my $File = $_; (-f $File && !-l $File) && diff_file($File, $has_conflict{$File}); } } else { warn("$progname: Error opening \"$CMD_SVN stat -q\" pipe: $!"); } # }}} } sub diff_file { # {{{ my ($File1, $has_conflict, $Revs) = @_; my $Path = ""; my $File = $File1; defined($Revs) || ($Revs = ""); D("diff_file(\"$File1\", \"$has_conflict\", \"$Revs\");\n"); if ($File =~ m#^(.*/)(.+?)$#) { $Path = $1; $File = $2; } my $File2 = ""; my @rm_files = (); D("opt_revision = \"$opt_revision\""); if (length($opt_revision)) { my ($Rev1, $Rev2); my ($tmp1, $tmp2); if ($opt_revision =~ /^($valid_rev)$/) { $Rev1 = $1; $Rev2 = ""; $tmp1 = "$File1.r$Rev1.tmp"; } elsif ($opt_revision =~ /^($valid_rev):($valid_rev)$/) { $Rev1 = $1; $Rev2 = $2; $tmp1 = "$File1.r$Rev1.tmp"; $tmp2 = "$File1.r$Rev2.tmp"; } else { die("$progname: Revision format error in --revision argument, " . "use -h for help\n"); } if (is_url($File1)) { $tmp1 =~ s#^(\S+/)(\S+?)$#$2#; length($Rev2) || ($Rev2 = "HEAD"); $tmp2 = "$File1.r$Rev2.tmp"; $tmp2 =~ s#^(\S+/)(\S+?)$#$2#; } else { $tmp2 = "$File1.r$Rev2.tmp"; } D("Rev1 = \"$Rev1\", Rev2 = \"$Rev2\"\n"); (-e $tmp1) && (die("$progname: $tmp1: Temporary file already exists\n")); (length($Rev2) && -e $tmp2) && (die("$progname: $tmp2: Temporary file already exists\n")); D("tmp1 = \"$tmp1\""); D("tmp2 = \"$tmp2\""); if ($tmp1 eq $tmp2) { warn("$progname: $File1: Start and end revisions are the same\n"); return; } mysyst("$CMD_SVN cat -r$Rev1 $File1 >$tmp1"); mysyst("$CMD_SVN cat -r$Rev2 $File1 >$tmp2") if (length($Rev2)); if (length($Rev2)) { $File2 = "$tmp2"; $File1 = "$tmp1"; push(@rm_files, $tmp1, $tmp2); } else { $File2 = "$tmp1"; push(@rm_files, $tmp1); } } else { $File2 = "$Path.svn/text-base/$File.svn-base"; } D("File1 = \"$File1\"\n"); D("File2 = \"$File2\"\n"); if (!is_url($File1)) { (-e $File1) || (warn("$File1: File not found\n"), return); (-e $File2) || (warn("$File2: File not found" . length($opt_revision) ? "" : ", is not under version " . "control\n" ), return ); } my $use_reverse = 0; if (defined($rev_diff{$Cmd})) { ($rev_diff{$Cmd} eq "1") && ($use_reverse = 1); } if ($use_reverse) { mysyst("$Cmd $File1 $File2"); } else { mysyst("$Cmd $File2 $File1"); } for my $curr_rm (@rm_files) { D("Removing tempfile \"$curr_rm\"..."); unlink($curr_rm) || warn("$progname: $curr_rm: " . "Can't delete temporary file: $!\n"); } if (!length($opt_revision) && $has_conflict) { print("$progname: Write y and press ENTER if the conflict " . "in $File1 is resolved: "); if (<STDIN> =~ /^y$/i) { print("$progname: OK, marking $File1 as resolved.\n"); mysyst("$CMD_SVN resolved $File1"); } } # Sleep one second after $Cmd is done to make it easier to interrupt # the thing with CTRL-C if there are many files sleep(1) if (scalar(@mod_array) > 1); # }}} } # diff_file() sub D { # {{{ $Debug || return; my @call_info = caller; chomp(my $Txt = shift); my $File = $call_info[1]; $File =~ s#\\#/#g; $File =~ s#^.*/(.*?)$#$1#; print(STDERR "$File:$call_info[2] $$ $Txt\n"); return(""); # }}} } # D() sub deb_wait { # {{{ $Debug || return; print("debug: Press ENTER..."); <STDIN>; # }}} } # deb_wait() sub is_url { # {{{ my $Url = shift; my $Retval = ($Url =~ m#^\S+://\S+/#) ? 1 : 0; D("is_url(\"$Url\") returns \"$Retval\"."); return($Retval); # }}} } # is_url() sub mysyst { # {{{ my @Args = @_; my $system_txt = sprintf("system(\"%s\");", join("\", \"", @Args)); D("$system_txt"); deb_wait(); system(@_); # }}} } # mysyst() sub read_rcfile { # {{{ my $File = shift; D("read_rcfile(\"$File\")"); if (open(RcFP, "<$File")) { my $all_rc = join("", <RcFP>); close(RcFP); D("\$all_rc \x7B\x7B\x7B\n$all_rc\n\x7D\x7D\x7D"); my $el_top = $all_rc; $el_top =~ s/<!--.*?-->//gsx; $el_top =~ s{ <svndiffrc\b(.*?)>(.*?)</svndiffrc> } { my $el_svndiffrc = $2; D("Inside <svndiffrc></svndiffrc>"); D("\$el_svndiffrc \x7B\x7B\x7B\n$el_svndiffrc\n\x7D\x7D\x7D"); $el_svndiffrc =~ s{ <diffprog\b(.*?)>(.*?)</diffprog> } { $Cmd = xml_to_txt($2); D("read_rcfile(): \$Cmd = \"$Cmd\""); ""; }sex; $el_svndiffrc =~ s{ <svnclient\b(.*?)>(.*?)</svnclient> } { $CMD_SVN = xml_to_txt($2); D("read_rcfile(): \$CMD_SVN = \"$CMD_SVN\""); ""; }sex; $el_svndiffrc =~ s{ <reversediffs\b(.*?)>(.*?)</reversediffs> } { my $el_reversediffs = $2; D("Inside <reversediffs></reversediffs>"); $el_reversediffs =~ s{ <program\b(.*?)>(.*?)</program> } { my $el_program = $2; D("Inside <program></program>"); my ($Name, $Reverse) = ( "", ""); $el_program =~ s{ <name\b(.*?)>(.*?)</name> } { $Name = xml_to_txt($2); D("Name = \"$Name\""); ""; }sex; $el_program =~ s{ <reverse\b(.*?)>(.*?)</reverse> } { $Reverse = xml_to_txt($2); D("Reverse = \"$Reverse\""); ""; }sex; if (length($Name)) { $rev_diff{$Name} = ($Reverse eq "1" ? 1 : 0); D("\$rev_diff{$Name} = \"$rev_diff{$Name}\""); } else { warn("$progname: $File: Found empty " . "<name></name> element.\n"); } ""; }gsex; ""; }sex; print_leftover($el_svndiffrc, "svndiffrc"); }sex; print_leftover($el_top, "top"); } else { warn("$progname: $File: Can't open rc file for read: $!\n"); } # }}} } # read_rcfile() sub print_leftover { # Print all non-whitespace in a string, used to spot erroneous XML. {{{ $Debug || return(""); my ($Txt, $Element) = @_; $Txt =~ s/^\s+//gs; $Txt =~ s/\s+$//gs; $Txt =~ s/\s+/ /g; defined($Element) || ($Element = "[unknown]"); if ($Txt =~ /\S/) { warn("$progname: Leftover: $Element: \"$Txt\"\n"); } return(""); # }}} } # print_leftover() sub txt_to_xml { # {{{ my $Txt = shift; $Txt =~ s/&/&/gs; $Txt =~ s/</</gs; $Txt =~ s/>/>/gs; return($Txt); # }}} } # txt_to_xml() sub xml_to_txt { # {{{ my $Txt = shift; $Txt =~ s/</</gs; $Txt =~ s/>/>/gs; $Txt =~ s/&/&/gs; return($Txt); # }}} } # xml_to_txt() sub usage { # Send the help message to stdout {{{ my $Retval = shift; print(<<END); Usage: $progname [options] [file [...]] "file" can also be an URL, but then the --revision option has to be specified. Options: -C, --conflict Only run diff on conflicted files. --create-rc Send a configuration file example to stdout. To create a new ~/.svndiffrc file, write $progname --create-rc >~/.svndiffrc -c, --diffcmd x Use x as the diff command. Default: "$Cmd". -e, --svncmd x Use x as the svn executable. Default: "$CMD_SVN". -h, --help Show this help. -p, --diffargs x Use x as parameters to the diff program. -r, --revision x Run a $Cmd command against previous revisions: 111:222 Compare r111 and r222. 123 Compare your working file against r123. If the file is an URL, the second revision is set to HEAD. {2001-05-17T18:12:16Z}:900 Compare between a specific point in time with r900. END exit($Retval); # }}} } # usage() # Plain Old Documentation (POD) {{{ =pod =head1 NAME svndiff =head1 REVISION $Id$ =head1 SYNOPSIS svndiff [options] [file [...]] =head1 DESCRIPTION Run the specified diff program on every modified file in current directory and all subdirectories or on the files specified on the command line. An URL to a file can also be specified, but then the --revision option has to be specified. The program needs the L<svn(1)> commandline client to run. =over 4 =item B<-C>, B<--conflict> Only run diff on conflicted files. =item B<--create-rc> Send a configuration file example to stdout. To create a new F<~/.svndiffrc> file, write $progname --create-rc >~/.svndiffrc =item B<-c>, B<--diffcmd> x Use x as the diff command. Default: "svndiff". =item B<-e>, B<--svncmd> x Use x as the svn executable. Example: svndiff -e /usr/local/bin/svn-1.0 =item B<-p>, B<--diffargs> x Use x as parameters to the diff program. =item B<-h>, B<--help> Print a brief help summary. =item B<-r>, B<--revision> x Run the external diff command against previous revisions: 111:222 Compare r111 and r222. 123 Compare your working file against r123. If the file is an URL, the second revision is set to HEAD. =back =head1 FILES =over 4 =item F<~/.svndiffrc> A configuration file where you can store your own settings. It is a standard XML file with this structure: <svndiffrc> <diffprog>vimdiff</diffprog> <svnclient>svn</svnclient> <reversediffs> <program> <name>vimdiff</name> <reverse>1</reverse> </program> <!-- Several "program" element groups can be specified --> </reversediffs> </svndiffrc> (Whitespace and linebreaks are optional.) The string inside the C<diffprog> elements can be set to whatever your diff program is called as, the default string is "vimdiff". You can also define an alternative svn(1) client to use inside the C<svnclient> elements. The default value here is of course "svn". When using visual diff viewers (for example B<vimdiff>), the program sometimes expects the file names to be switched on the command line so your modified file appears in the correct window. By creating a C<E<lt>programE<gt>E<lt>/programE<gt>> section, programs can be instructed to take arguments the opposite way. If you for example use the B<meld> program and you want your modified file to be in the left window, add this to the file (I<inside> the C<E<lt>reversediffsE<gt>E<lt>/reversediffsE<gt>> elements): <program> <name>meld</name> <reverse>1</reverse> </program> The value in the C<reverse> element have to be B<1>, all other values will count as B<0>. =back =head1 ENVIRONMENT VARIABLES =over 4 =item I<SVNDIFFRC> Path to a configuration file in another location than F<~/.svndiffrc> . =back =head1 USAGE TIPS =head2 vimdiff mappings The standard diff program used in this script is L<vimdiff(1)> which in fact is the Vim editor called with another name. The main reasons for this are because Vim is Free and widely available, portable, console based and an effective diff tool. The following macros makes moving differences between windows easier (can also be put into F<~/.vimrc>): " F1: Move differences from the other window to the current window. map <f1> :diffget<cr>]cz. " F2: Move differences from the current window to the other window. map <f2> :diffput<cr>]c " F12: Update the syntax highlighting and the diffs. Use this if your " diff isn’t properly updated. noremap <f12> :syntax sync fromstart<cr>:diffu<cr> inoremap <f12> <esc>:syntax sync fromstart<cr>:diffu<cr>a =head1 AUTHOR Made by Øyvind A. Holm S<E<lt>sunny _AT_ sunbase.orgE<gt>>. =head1 COPYRIGHT Copyleft © Øyvind A. Holm <sunny@sunbase.org> This is free software; see the file F<COPYING> for legalese stuff. This file is part of the svnutils project — L<http://svnutils.tigris.org> =head1 LICENCE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =head1 SEE ALSO L<svn(1)> =cut # }}} # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w : # End of file $Id$
--alston 14:16, 5 December 2006 (GMT)
pah
--james 14:36, 5 December 2006 (GMT)
wow, I just installed something a bit like this on the SuSE 10.1 image. Talk about coincidence --Catherine 17:11, 5 December 2006 (GMT)