move to archive, cleaning up for golive
This commit is contained in:
514
archive/slack-runtime/dist/slack-diff
vendored
Executable file
514
archive/slack-runtime/dist/slack-diff
vendored
Executable file
@@ -0,0 +1,514 @@
|
||||
#!/usr/bin/perl -w
|
||||
# $Id: slack-diff 122 2006-09-27 07:34:32Z alan $
|
||||
# vim:sw=2
|
||||
# vim600:fdm=marker
|
||||
# Copyright (C) 2004-2006 Alan Sundell <alan@sundell.net>
|
||||
# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY.
|
||||
# See the file COPYING for details.
|
||||
#
|
||||
# This script is a wrapper for diff that gives output about special files
|
||||
# and file modes. (diff can only compare regular files)
|
||||
|
||||
require 5.006;
|
||||
use warnings FATAL => qw(all);
|
||||
use strict;
|
||||
use sigtrap qw(die untrapped normal-signals
|
||||
stack-trace any error-signals);
|
||||
|
||||
use Errno;
|
||||
use File::stat;
|
||||
use File::Basename;
|
||||
use File::Find;
|
||||
use Getopt::Long;
|
||||
use POSIX qw(SIGPIPE strftime);
|
||||
use Fcntl qw(:mode); # provides things like S_IFMT that POSIX does not
|
||||
|
||||
|
||||
my $VERSION = '0.1';
|
||||
(my $PROG = $0) =~ s#.*/##;
|
||||
my @diff; # diff program to use
|
||||
my $exit = 0; # our exit code
|
||||
|
||||
sub compare ($$);
|
||||
sub recursive_compare ($$);
|
||||
sub filetype_to_string ($;$);
|
||||
sub compare_files ($$);
|
||||
sub diff ($$);
|
||||
|
||||
########################################
|
||||
# Environment
|
||||
# Helpful prefix to die messages
|
||||
$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; };
|
||||
# Set a reasonable umask
|
||||
umask 077;
|
||||
# Autoflush on STDOUT
|
||||
$|=1;
|
||||
# Autoflush on STDERR
|
||||
select((select(STDERR), $|=1)[0]);
|
||||
|
||||
# Default options
|
||||
my %opt = (
|
||||
fakediff => 1,
|
||||
perms => 1,
|
||||
'new-file' => 1,
|
||||
diff => 'diff',
|
||||
);
|
||||
|
||||
# Config and option parsing
|
||||
my $usage = <<EOF;
|
||||
Usage: $PROG [options] <file1> <file2>
|
||||
$PROG -r <dir1> <dir2>
|
||||
|
||||
Options:
|
||||
-u, -U NUM, --unified=NUM
|
||||
Tell diff to use unified output format.
|
||||
--diff PROG
|
||||
Use this program for diffing, instead of "$opt{diff}"
|
||||
--fakediff
|
||||
Make a fake diff for file modes and other things that are not file
|
||||
contents. Default is on, can be disabled with --nofakediff.
|
||||
--perms
|
||||
Care about owner, group, and permissions when doing fakediff.
|
||||
Default is on, can be disabled with --noperms.
|
||||
-r, --recursive
|
||||
Recursively compare directories.
|
||||
-N, --new-file
|
||||
Treat missing files as empty. Default is on, can be disabled with
|
||||
--nonew-file.
|
||||
--unidirectional-new-file
|
||||
Treat only missing files in the first directory as empty.
|
||||
--from-file
|
||||
Treat arguments as a list of files from which to read filenames to
|
||||
compare, two lines at a time.
|
||||
-0, --null
|
||||
Use NULLs instead of newlines as the separator in --from-file mode
|
||||
--devnullhack
|
||||
You have a version of diff that can't deal with -N when not in
|
||||
recursive mode, so we need to feed it /dev/null instead of the
|
||||
missing file. Default is on, can be disabled with --nodevnullhack.
|
||||
--version
|
||||
Output version info
|
||||
--help
|
||||
Output this help text
|
||||
|
||||
Exit codes:
|
||||
0 Found no differences
|
||||
1 Found a difference
|
||||
2 Had a serious error
|
||||
3 Found a difference and had a serious error
|
||||
EOF
|
||||
|
||||
{
|
||||
Getopt::Long::Configure ("bundling");
|
||||
GetOptions(\%opt,
|
||||
'help|h|?',
|
||||
'version',
|
||||
'null|0',
|
||||
'devnullhack',
|
||||
'new-file|N',
|
||||
'u',
|
||||
'unified|U=i',
|
||||
'recursive|r',
|
||||
'from-file',
|
||||
'unidirectional-new-file',
|
||||
'fakediff!',
|
||||
'perms!',
|
||||
'diff=s',
|
||||
) or die $usage;
|
||||
if ($opt{help}) {
|
||||
print $usage;
|
||||
exit 0;
|
||||
}
|
||||
if ($opt{version}) {
|
||||
print "$PROG version $VERSION\n";
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($opt{diff}) {
|
||||
# We split on spaces here to be useful -- so that people can give
|
||||
# their diff options.
|
||||
@diff = split(/\s+/, $opt{diff});
|
||||
} else {
|
||||
die "$PROG: No diff program!\n";
|
||||
}
|
||||
|
||||
if ($opt{'u'}) {
|
||||
push @diff, '-u';
|
||||
} elsif ($opt{'unified'}) {
|
||||
$opt{'u'} = 1; # We use this value later
|
||||
push @diff, "--unified=$opt{'unified'}";
|
||||
}
|
||||
|
||||
if (not $opt{'devnullhack'}) {
|
||||
push @diff, '-N';
|
||||
}
|
||||
|
||||
# usually, sigpipe would be someone quitting their pager, so don't sweat it
|
||||
$SIG{PIPE} = sub { exit $exit };
|
||||
|
||||
if ($opt{'from-file'}) {
|
||||
local $/ = "\0" if $opt{'null'};
|
||||
while (my $old = <>) {
|
||||
my $new = <>;
|
||||
die "Uneven number of lines in --from-file mode!\n"
|
||||
if not defined $new;
|
||||
chomp($old);
|
||||
chomp($new);
|
||||
$exit |= compare($old, $new);
|
||||
}
|
||||
} else {
|
||||
die $usage unless $#ARGV == 1;
|
||||
$exit |= compare($ARGV[0], $ARGV[1]);
|
||||
}
|
||||
exit $exit;
|
||||
|
||||
##
|
||||
# Subroutines
|
||||
|
||||
sub compare ($$) {
|
||||
my ($old, $new) = @_;
|
||||
|
||||
if ($opt{recursive}) {
|
||||
return recursive_compare($old, $new);
|
||||
} else {
|
||||
return compare_files($old, $new);
|
||||
}
|
||||
}
|
||||
|
||||
# compare two directories. We do this by walking down the *new*
|
||||
# directory, and comparing everything that's there to the stuff in
|
||||
# the old directory
|
||||
sub recursive_compare ($$) {
|
||||
my ($olddir, $newdir) = @_;
|
||||
my ($retval, $basere, $wanted);
|
||||
my (%seen);
|
||||
|
||||
$retval = 0;
|
||||
|
||||
if (-d $newdir) {
|
||||
$basere = qr(^$newdir);
|
||||
$wanted = sub {
|
||||
my ($newfile) = $_;
|
||||
my $oldfile = $newfile;
|
||||
|
||||
$oldfile =~ s#$basere#$olddir#;
|
||||
$seen{$oldfile} = 1;
|
||||
$retval |= compare_files($oldfile, $newfile);
|
||||
};
|
||||
|
||||
eval { find({ wanted => $wanted , no_chdir => 1}, $newdir) };
|
||||
if ($@) {
|
||||
warn "$PROG: error during find: $@\n";
|
||||
$retval |= 2;
|
||||
}
|
||||
}
|
||||
return $retval
|
||||
if $opt{'unidirectional-new-file'};
|
||||
|
||||
# If we're not unidirectional, we want to go through the old directory
|
||||
# and diff any files we didn't see in the newdir.
|
||||
if (-d $olddir) {
|
||||
$basere = qr(^$olddir);
|
||||
$wanted = sub {
|
||||
my ($oldfile) = $_;
|
||||
my $newfile;
|
||||
|
||||
return if $seen{$oldfile};
|
||||
$newfile = $oldfile;
|
||||
|
||||
$newfile =~ s#$basere#$newdir#;
|
||||
$retval |= compare_files($oldfile, $newfile);
|
||||
};
|
||||
|
||||
eval { find({ wanted => $wanted , no_chdir => 1}, $olddir) };
|
||||
if ($@) {
|
||||
warn "$PROG: error during find: $@\n";
|
||||
$retval |= 2;
|
||||
}
|
||||
}
|
||||
return $retval;
|
||||
}
|
||||
|
||||
# filetype_to_string(mode)
|
||||
# filetype_to_string(mode, plural)
|
||||
#
|
||||
# Takes a mode returned from stat(), returns a noune describing the filetype,
|
||||
# e.g. "directory", "symlink".
|
||||
# If the "plural" argument is provided and true, returns the plural form of
|
||||
# the noun, e.g. "directories", "symlinks".
|
||||
sub filetype_to_string ($;$) {
|
||||
my ($mode, $plural) = @_;
|
||||
|
||||
if (S_ISREG($mode)) {
|
||||
return "regular file".($plural ? "s" : "");
|
||||
} elsif (S_ISDIR($mode)) {
|
||||
return "director".($plural ? "ies" : "y");
|
||||
} elsif (S_ISLNK($mode)) {
|
||||
return "symlink".($plural ? "s" : "");
|
||||
} elsif (S_ISBLK($mode)) {
|
||||
return "block device".($plural ? "s" : "");
|
||||
} elsif (S_ISCHR($mode)) {
|
||||
return "character device".($plural ? "s" : "");
|
||||
} elsif (S_ISFIFO($mode)) {
|
||||
return "fifo".($plural ? "s" : "");
|
||||
} elsif (S_ISSOCK($mode)) {
|
||||
return "socket".($plural ? "s" : "");
|
||||
} else {
|
||||
return "unknown filetype".($plural ? "s" : "");
|
||||
}
|
||||
}
|
||||
|
||||
# compare_files(oldfile, newfile)
|
||||
# This is the actual diffing routine. It's quite long because we need to
|
||||
# deal with all sorts of special cases. It will print to STDOUT a
|
||||
# description of the differences between the two files. For regular files,
|
||||
# diff(1) will be run to show the differences.
|
||||
#
|
||||
# return codes:
|
||||
# 1 found a difference
|
||||
# 2 had an error
|
||||
# 3 found a difference and had an error
|
||||
sub compare_files ($$) {
|
||||
my ($oldname, $newname) = @_;
|
||||
my ($old, $new); # stat buffers
|
||||
my $return = 0;
|
||||
|
||||
# Get rid of unsightly double slashes
|
||||
$oldname =~ s#//#/#g;
|
||||
$newname =~ s#//#/#g;
|
||||
|
||||
eval { $old = lstat($oldname); };
|
||||
if (not defined $old and not $!{ENOENT}) {
|
||||
warn "$PROG: Could not stat $oldname: $!\n";
|
||||
return 2;
|
||||
}
|
||||
eval { $new = lstat($newname); };
|
||||
if (not defined $new and not $!{ENOENT}) {
|
||||
warn "$PROG: Could not stat $newname: $!\n";
|
||||
return 2;
|
||||
}
|
||||
# At this point, $old or $new should only be undefined if the
|
||||
# file does not exist.
|
||||
|
||||
if (defined $old and defined $new) {
|
||||
if (S_IFMT($old->mode) != S_IFMT($new->mode)) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('filetype',
|
||||
$oldname => filetype_to_string($old->mode),
|
||||
$newname => filetype_to_string($new->mode),
|
||||
);
|
||||
} else {
|
||||
print "File types differ between ".
|
||||
filetype_to_string($old->mode)." $oldname and ".
|
||||
filetype_to_string($new->mode)." $newname\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if ($old->nlink != $new->nlink) {
|
||||
# In recursive mode, we don't care about link counts in directories,
|
||||
# as we'll pick that up with what files do and don't exist.
|
||||
unless ($opt{recursive} and S_ISDIR($old->mode)) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('nlink',
|
||||
$oldname => $old->nlink,
|
||||
$newname => $new->nlink,
|
||||
);
|
||||
} else {
|
||||
print "Link counts differ between ".
|
||||
filetype_to_string($old->mode, 1).
|
||||
" $oldname and $newname\n";
|
||||
}
|
||||
$return = 1;
|
||||
}
|
||||
}
|
||||
if ($old->uid != $new->uid and $opt{perms}) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('uid',
|
||||
$oldname => $old->uid,
|
||||
$newname => $new->uid,
|
||||
);
|
||||
} else {
|
||||
print "Owner differs between ".
|
||||
filetype_to_string($old->mode, 1).
|
||||
" $oldname and $newname\n";
|
||||
}
|
||||
$return = 1;
|
||||
}
|
||||
if ($old->gid != $new->gid and $opt{perms}) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('gid',
|
||||
$oldname => $old->gid,
|
||||
$newname => $new->gid,
|
||||
);
|
||||
} else {
|
||||
print "Group differs between ".
|
||||
filetype_to_string($old->mode, 1).
|
||||
" $oldname and $newname\n";
|
||||
}
|
||||
$return = 1;
|
||||
}
|
||||
if (S_IMODE($old->mode) != S_IMODE($new->mode) and $opt{perms}) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('mode',
|
||||
$oldname => sprintf('%04o', S_IMODE($old->mode)),
|
||||
$newname => sprintf('%04o', S_IMODE($new->mode)),
|
||||
);
|
||||
} else {
|
||||
print "Modes differ between ".
|
||||
filetype_to_string($old->mode, 1).
|
||||
" $oldname and $newname\n";
|
||||
}
|
||||
$return = 1;
|
||||
}
|
||||
|
||||
# We don't want to compare anything more about sockets, fifos, or
|
||||
# directories, once we've checked the permissions and link counts
|
||||
if (S_ISSOCK($old->mode) or
|
||||
S_ISFIFO($old->mode) or
|
||||
S_ISDIR($old->mode)) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
# Check device file devs, and that's it for them
|
||||
if (S_ISCHR($old->mode) or
|
||||
S_ISBLK($old->mode)) {
|
||||
if ($old->rdev != $new->rdev) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('rdev',
|
||||
$oldname => $old->rdev,
|
||||
$newname => $new->rdev,
|
||||
);
|
||||
} else {
|
||||
print "Device numbers differ between ".
|
||||
filetype_to_string($old->mode, 1).
|
||||
" $oldname and $newname\n";
|
||||
}
|
||||
$return = 1;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
# Compare the targets of symlinks
|
||||
if (S_ISLNK($old->mode)) {
|
||||
my $oldtarget = readlink $oldname
|
||||
or (warn("$PROG: Could not readlink($oldname): $!\n"),
|
||||
return $return | 2);
|
||||
my $newtarget = readlink $newname
|
||||
or (warn("$PROG: Could not readlink($newname): $!\n"),
|
||||
return $return | 2);
|
||||
if ($oldtarget ne $newtarget) {
|
||||
if ($opt{fakediff}) {
|
||||
fakediff('target',
|
||||
$oldname => $oldtarget,
|
||||
$newname => $newtarget,
|
||||
);
|
||||
} else {
|
||||
print "Symlink targets differ between $oldname and $newname\n";
|
||||
}
|
||||
$return = 1;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
if (not S_ISREG($old->mode)) {
|
||||
warn "$PROG: Don't know what to do with file mode $old->mode!\n";
|
||||
return 2;
|
||||
}
|
||||
} elsif (not defined $old and not defined $new) {
|
||||
print "Neither $oldname nor $newname exists\n";
|
||||
return $return;
|
||||
} elsif (not defined $old) {
|
||||
if (not S_ISREG($new->mode) or not $opt{'new-file'}) {
|
||||
print "Only in ".dirname($newname).": ".
|
||||
filetype_to_string($new->mode)." ".basename($newname)."\n";
|
||||
return 1;
|
||||
} elsif ($opt{'devnullhack'}) {
|
||||
$oldname = '/dev/null';
|
||||
}
|
||||
} elsif (not defined $new) {
|
||||
if (not S_ISREG($old->mode) or not $opt{'new-file'}) {
|
||||
print "Only in ".dirname($oldname).": ".
|
||||
filetype_to_string($old->mode)." ".basename($oldname)."\n";
|
||||
return 1;
|
||||
} elsif ($opt{'devnullhack'}) {
|
||||
$newname = '/dev/null';
|
||||
}
|
||||
}
|
||||
# They are regular files! We can actually run diff!
|
||||
return diff($oldname, $newname) | $return;
|
||||
}
|
||||
|
||||
sub diff ($$) {
|
||||
my ($oldname, $newname) = @_;
|
||||
my @command = (@diff, $oldname, $newname);
|
||||
my $status;
|
||||
|
||||
# If we're not specifying unified diff, we need to print a header
|
||||
# to indicate what's being diffed. (I'm not sure if this actually would
|
||||
# work for patch, but it does tell our user what's going on).
|
||||
# FIXME: We only need to specify this if the files are different
|
||||
print "@command\n"
|
||||
if not $opt{u};
|
||||
|
||||
{
|
||||
# There is a bug in perl with use warnings FATAL => qw(all)
|
||||
# that will cause the child process from system() to stick
|
||||
# around if there is a warning generated.
|
||||
# Shut off warnings -- we'll catch the error below.
|
||||
no warnings;
|
||||
$status = system(@command);
|
||||
}
|
||||
return 0 if ($status == 0);
|
||||
if ($? == -1) {
|
||||
die "$PROG: failed to execute '@command': $!\n";
|
||||
}
|
||||
if ($? & 128) {
|
||||
die "$PROG: '@command' dumped core\n";
|
||||
}
|
||||
if (my $sig = $? & 127) {
|
||||
die "$PROG: '@command' caught sig $sig\n"
|
||||
unless ($sig == SIGPIPE);
|
||||
}
|
||||
if (my $exit = $? >> 8) {
|
||||
if ($exit == 1) {
|
||||
return 1;
|
||||
} else {
|
||||
die "$PROG: '@command' returned $exit\n";
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
sub fakediff ($$) {
|
||||
my ($type, $oldname, $oldvalue, $newname, $newvalue) = @_;
|
||||
|
||||
return unless $opt{fakediff};
|
||||
my $time = strftime('%F %T.000000000 %z', localtime(0));
|
||||
|
||||
# We add a suffix onto the filenames to show we're not actually looking
|
||||
# at file contents. There's no good way to indicate this that's compatible
|
||||
# with patch, and this is simple enough.
|
||||
$oldname .= '#~~' . $type;
|
||||
$newname .= '#~~' . $type;
|
||||
|
||||
if ($opt{u}) {
|
||||
# fake up a unified diff
|
||||
print <<EOF;
|
||||
--- $oldname\t$time
|
||||
+++ $newname\t$time
|
||||
@@ -1 +1 @@
|
||||
-$oldvalue
|
||||
+$newvalue
|
||||
EOF
|
||||
} else {
|
||||
print <<EOF;
|
||||
diff $oldname $newname
|
||||
1c1
|
||||
< $oldvalue
|
||||
---
|
||||
> $newvalue
|
||||
EOF
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user