Attached are scripts I use to make `cvs diff' and `cvs update' behave better. I
use these both from the command line and from Didier's patcher.el. [and I
develop entirely on Windows! The scripts therefore are well-tested on this
platform.] They are based on scripts written by Martin, but extensively hacked
by me.
You also need cvs-mods for the scripts to work.
Here are the descriptions:
cvs-diff:
Front end to \`cvs diff'.
Does the following in addition to a simple \`cvs diff':
-- removes ChangeLog diffs from the diff output, and (unless -no-changelog
was specified) prepends the entries to the beginning of the diff. This
is useful for submitting a diff to the maintainers.
-- removes the generated files auto-autoloads.el, custom-load.el, depend,
and configure from the output.
-- fixes CVS bug when specifying files to diff in multiple directories.
-- hacks the line used by `patch' to contain the directory relative to
the workspace root, so you can easily apply this patch to another
workspace.
cvs-update:
Front end to \`cvs update'.
Does the following in addition to a simple \`cvs update':
-- if the tree was checked out R/W using \`checkout -w', keep it that way.
-- if configure or configure.in are checked out, rerun autoconf.
-- when updating ChangeLogs, automatically resolve conflicts.
-- output a list of conflicting files at the end.
--
ben
I'm sometimes slow in getting around to reading my mail, so if you
want to reach me faster, call 520-661-6661.
See
http://www.666.com/ben/chronic-pain/ for the hell I've been
through.
: #-*- Perl -*-
eval 'exec perl -w -S $0 ${1+"$@"}' # Portability kludge
if 0;
### cvs-update --- front end to `cvs update'
## Copyright (C) 2001 Ben Wing.
## Author: Ben Wing <ben(a)xemacs.org>, Martin Buchholz <martin(a)xemacs.org>
## Original Author: Martin Buchholz <martin(a)xemacs.org>
## Maintainer: Ben Wing <ben(a)xemacs.org>
## This file is part of XEmacs.
## XEmacs 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, or (at your option)
## any later version.
## XEmacs 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 XEmacs; see the file COPYING. If not, write to the Free
## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
## 02111-1307, USA.
use strict;
use File::stat;
(my $myName = $0) =~ s@.*/@@; my $usage="
Usage: $myName [cvs-update-args ...]
Front end to \`cvs update'.
Does the following in addition to a simple \`cvs update':
-- if the tree was checked out R/W using \`checkout -w', keep it that way.
-- if configure or configure.in are checked out, rerun autoconf.
-- when updating ChangeLogs, automatically resolve conflicts.
-- output a list of conflicting files at the end.
";
die $usage if grep (/^--help$/, @ARGV);
my $debug = defined $ENV{VERBOSE} || defined $ENV{DEBUG};
die "Not a CVS directory tree.\n" unless -d "CVS";
# Check the read/writeness of the first plain file in the current directory.
opendir (DOT, ".") or die "opendir: $!";
my $rw;
while (my $file = readdir (DOT)) {
if (-f $file) {
$rw = -w $file ? "-w" : "-r";
print "Using r/w flag $rw\n" if $debug;
last;
}
}
die "Can't find any regular files here!\n" if !defined $rw;
# this construct forks a subprocess, with the parent reading the
# child's output and the child executing the code within braces.
if (!open (CVS, "-|"))
{
open STDERR, ">&STDOUT";
exec 'cvs', $rw, '-q', 'update', @ARGV;
}
my @conflicts = ();
while (<CVS>) {
print;
chomp;
my ($code, $file) = split(' ');
next unless grep ($code eq $_, qw(U P M C));
next unless -r $file;
my ($dirname, $basename) = &ParsePath ($file);
if ($basename eq 'ChangeLog' && $code eq 'C') {
print "Automatically resolving conflict in $file\n";
my $contents = &FileContents ($file);
# Resolve conflicts in the obvious way...
if ($contents =~
s/^<{7} \S+\n((?:.*\n)*?)={7}\n((?:.*\n)*?)>{7} \S+\n/$1$2/mg) {
open (FILE, "> $file") or die "$file: $!\n";
# the following was `sleep 1', but i still occasionally saw
# spurious "cvs server: file `lisp/ChangeLog' had a conflict and
# has not been modified" errors
sleep 2; # CVS will get confused if we resolve the conflict too `quickly'.
print FILE $contents or die "$file: $!\n";
close FILE or die "$file: $!\n";
} else {
print "WARNING: Unexpected resolution error in $file\n";
push @conflicts, $file;
}
} elsif ($code eq 'C') {
push @conflicts, $file;
}
if ($rw eq '-w' && ($basename eq 'configure' ||
$basename eq 'configure.in')) {
system "set +x; cd $dirname && autoconf &&
lib-src/config.values.sh";
}
}
if (@conflicts) {
print "\n\nThe following files had merge conflicts:\n\n";
print (join '\n', @conflicts);
}
sub ParsePath {
my $pos = rindex ($_[0], "/");
return ($pos > 0 ? (substr ($_[0], 0, $pos), substr ($_[0], $pos+1)) :
$pos == -1 ? ('.', $_[0]) :
("/", substr ($_[0], 1)));
}
sub FileContents {
local $/ = undef;
open (FILE, "< $_[0]") or die "$_[0]: $!";
my $retval = scalar <FILE>;
# must hack away CRLF junk.
$retval =~ s/\r\n/\n/g;
return $retval;
}
: #-*- Perl -*-
eval 'exec perl -w -S $0 ${1+"$@"}' # Portability kludge
if 0;
### cvs-mods --- list all modified files in a CVS tree, w/o server access
## Copyright (C) 2001 Ben Wing.
## Author: Ben Wing <ben(a)xemacs.org>, Martin Buchholz <martin(a)xemacs.org>
## Original Author: Martin Buchholz <martin(a)xemacs.org>
## Maintainer: Ben Wing <ben(a)xemacs.org>
## This file is part of XEmacs.
## XEmacs 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, or (at your option)
## any later version.
## XEmacs 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 XEmacs; see the file COPYING. If not, write to the Free
## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
## 02111-1307, USA.
use strict;
use File::stat;
(my $myName = $0) =~ s@.*/@@; my $usage="
Usage: $myName [-v] [--help] [dir|file ...]
Finds and outputs all modified CVS files specified on the command line
or within directory trees specified on the command line. This does not
contact the CVS server, so it is very fast, and is useful to speed up
other CVS-based scripts.
";
my $verbose = 0;
while (@ARGV && ($_ = $ARGV[0], /^-/)) {
shift;
last if /^--$/;
if (/^-v$/) { $verbose = 1; }
if (/^--help$/) { die $usage; }
}
sub ParsePath {
my $pos = rindex ($_[0], "/");
return ($pos > 0 ? (substr ($_[0], 0, $pos), substr ($_[0], $pos+1)) :
$pos == -1 ? ('.', $_[0]) :
("/", substr ($_[0], 1)));
}
sub CheckModified {
return unless $_[0] =~ m:^/:;
my ($name, $revision, $timestamp) = (split('/', $_[0]))[1,2,3];
my $file = "$_[1]/$name";
$file =~ s:^\./::;
if (! -e $file) {
print "Removed: " if $verbose;
print "$file\n";
} else {
my $file_timestamp = scalar gmtime (stat($file)->mtime);
return if $timestamp eq $file_timestamp; # Unchanged
print "Modified: " if $verbose;
print "$file\n";
}
}
@ARGV = ('.') if (! @ARGV);
while (my $arg = shift @ARGV) {
if (-d $arg) {
die "Not a CVS directory tree.\n" unless -d "$arg/CVS";
open(FIND, "find $arg -type d -print |") or die "$!";
while (<FIND>) {
chomp (my $dir = $_);
my $entries = "$dir/CVS/Entries";
next unless -r "$entries";
open (ENTRIES, "$entries") or die "$!";
while (<ENTRIES>) {
&CheckModified ($_, $dir);
}
close (ENTRIES);
}
close (FIND);
} else {
my ($dir, $file) = &ParsePath ($arg);
my $entries = "$dir/CVS/Entries";
next unless -r "$entries";
open (ENTRIES, "$entries") or die "$!";
while (<ENTRIES>) {
&CheckModified ($_, $dir) if m:^/$file/:;
}
close (ENTRIES);
}
}
: #-*- Perl -*-
eval 'exec perl -w -S $0 ${1+"$@"}' # Portability kludge
if 0;
### cvs-diff --- front end to `cvs diff'
## Copyright (C) 2001 Ben Wing.
## Author: Ben Wing <ben(a)xemacs.org>, Martin Buchholz <martin(a)xemacs.org>
## Original Author: Martin Buchholz <martin(a)xemacs.org>
## Maintainer: Ben Wing <ben(a)xemacs.org>
## This file is part of XEmacs.
## XEmacs 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, or (at your option)
## any later version.
## XEmacs 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 XEmacs; see the file COPYING. If not, write to the Free
## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
## 02111-1307, USA.
use strict;
use Cwd;
use Getopt::Long;
(my $myName = $0) =~ s@.*/@@; my $usage="
Usage: $myName [-no-changelog] [diff-arg ...] [files ...]
Front end to \`cvs diff'.
Does the following in addition to a simple \`cvs diff':
-- removes ChangeLog diffs from the diff output, and (unless -no-changelog
was specified) prepends the entries to the beginning of the diff. This
is useful for submitting a diff to the maintainers.
-- removes the generated files auto-autoloads.el, custom-load.el, depend,
and configure from the output.
-- fixes CVS bug when specifying files to diff in multiple directories.
-- hacks the line used by `patch' to contain the directory relative to
the workspace root, so you can easily apply this patch to another
workspace.
";
my $debug = defined $ENV{VERBOSE} || defined $ENV{DEBUG};
sub ParsePath {
my $pos = rindex ($_[0], "/");
return ($pos > 0 ? (substr ($_[0], 0, $pos), substr ($_[0], $pos+1)) :
$pos == -1 ? ('.', $_[0]) :
("/", substr ($_[0], 1)));
}
# Find the current directory relative to the root of the CVS workspace.
die "Not a CVS directory tree.\n" unless -d "CVS";
my $root = cwd ();
my $rootrel = "";
while (-d "$root/../CVS") {
($root, my $root_component) = ParsePath ($root);
$rootrel = "$root_component/$rootrel";
}
my @SAVE_ARGV = @ARGV;
# Extract out our own and all diff options so that we can reliably get
# the actual files to `cvs diff'.
my %options = ();
&GetOptions (
\%options,
# our own options
'no-changelog',
# diff options
'l', 'R', 'D=s', 'N', 'r=s',
'ifdef=s',
'i', 'ignore-case',
'w', 'ignore-all-space',
'b', 'ignore-space-change',
'B', 'ignore-blank-lines',
'I=s', 'ignore-matching-lines=s',
'binary',
'a', 'text',
'c', 'C=i', 'context:i',
'u', 'U=i', 'unified:i',
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9',
'L=s', 'label=s',
'p', 'show-c-function',
'F=s', 'show-function-line=s',
'q', 'brief',
'e', 'ed',
'n', 'rcs',
'y', 'side-by-side',
'w=i', 'width=i',
'left-column',
'suppress-common-lines',
'DNAME', 'ifdef=s',
'old-group-format=s',
'new-group-format=s',
'unchanged-group-format=s',
'changed-group-format=s',
'line-format=s',
'old-line-format=s',
'new-line-format=s',
'unchanged-line-format=s',
'l', 'paginate',
't', 'expand-tabs',
'T', 'initial-tab',
'r', 'recursive',
'N', 'new-file',
'P', 'unidirectional-new-file',
's', 'report-identical-files',
'x=s', 'exclude=s',
'X=s', 'exclude-from=s',
'S=s', 'starting-file=s',
'horizon-lines=i',
'd', 'minimal',
'H', 'speed-large-files',
'v', 'version',
'help',
);
die $usage if $options{"help"};
# extract options, but eliminate --no-changelog
my @OPTIONS = grep (!/(-|--|\+)no-changelog/,
@SAVE_ARGV[0 .. (@SAVE_ARGV - @ARGV - 1)]);
# print "ARGV: @ARGV\n";
# print "SAVE_ARGV: @SAVE_ARGV\n";
# print "OPTIONS: @OPTIONS\n";
select(STDOUT); $| = 1;
# now output the ChangeLogs.
@ARGV = ('.') if (! @ARGV);
if (!$options{"no-changelog"}) {
foreach my $file (@ARGV) {
if (-d $file) {
chomp (my @changelogs = `find $file -name ChangeLog -print`);
foreach my $changelog (@changelogs) {
# use cvs-mods to filter out non-modified ChangeLogs; this greatly
# speeds things up
if (`cvs-mods $changelog`) {
print "Checking $changelog\n";
open (CHANGELOG, "cvs diff -w @OPTIONS $changelog | sed -n 's/^\+//p' |
grep -v '^\\+\\+\\+' |");
if (my @slurp = <CHANGELOG>) {
print "\n$changelog:\n\n";
print @slurp;
}
}
close (CHANGELOG);
}
}
}
}
print "\n";
# this construct forks a subprocess, with the parent reading the
# child's output and the child executing the code within braces.
if (!open (CVS, "-|"))
{
open STDERR, ">&STDOUT";
# cvs has an evil bug that if I give a list of files containing
# both files in the current directory and in subdirectories, cvs
# ignores the files in the current directory. simplest just to
# call `cvs diff' once per file.
foreach my $file (@ARGV) {
# do not use -w option here by default -- explicitly specify it if
# you want it. Otherwise, you end up with output that looks right
# but will fail in all sorts of random ways if you try to use
# `patch' with it. Furthermore, it doesn't actually make the output
# much (if any) shorter in most cases.
die "Can't exec @_: $!\n" if system ('cvs', 'diff',
@OPTIONS, $file) < 0;
}
exit 0;
}
my $file;
my $print = 1;
while (<CVS>) {
# track files as they go by.
if (/^Index: (.*)/) {
$file = $1;
# look for files to remove.
$print = !($file =~
/(ChangeLog|auto-autoloads.el|custom-load.el|depend|configure)$/);
}
# hack patch file lines to include the directory.
s!^(---|\+\+\+) (\S+)!$1 $rootrel$file!;
print if $print;
}