Blob Blame History Raw
#!/usr/bin/perl

#############################################################################
# Copyright (c) 2015 Micro Focus
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# 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, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#############################################################################

# Check that the a patch is in the format produced by scripts/refresh_patch.sh

use strict;
use warnings;

my $errors = 0;
my $git_renames = 0;
my %reported;
our $file;

sub err {
	return if ($reported{"$file:$_[0]"}++);
	print STDERR "$file:$.: error: @_\n";
	$errors = 1;
}

sub check_filename {
	my $filename = shift;

	if ($filename !~ m:^(/dev/null\b|[ab]/):) {
		err("Patch not in -p ab format");
	}
	if ($filename =~ /\s\S/) {
		err("Timestamp after filename");
	}
	if ($filename =~ /\/\//) {
		err("Double slash in filename");
	}
}

sub do_patch {
	my $fh = shift;
	my $fn = shift;
	my ($in_payload, $in_hunk, $maybe_eof, $last_noncontext);
	my $xen_prefix = "patches.xen/";
	my $is_xen = $xen_prefix eq substr($fn,0,length($xen_prefix));

	my @lines = <$fh>;
	chomp(@lines);
	for (my $i = 1; $i < scalar(@lines); $i++) {
		my $cur = $lines[$i];
		$. = $i + 1;
		if ($cur =~ /^similarity index \d+%/ &&
					$lines[$i-1] =~ /^diff --git/) {
			$in_payload = 1;
			$in_hunk = 0;
			$maybe_eof = 0;
			err("git diff -M patches fail with sequences-patch.sh --fast");
			$git_renames = 1;
		}
		if ($cur =~ /^\+\+\+ / && $lines[$i-1] =~ /^--- /) {
			$in_payload = 1;
			$in_hunk = 0;
			$maybe_eof = 0;
			(my $new = $cur) =~ s/^\+\+\+ //;
			(my $old = $lines[$i-1]) =~ s/^--- //;
			if (!$is_xen) { check_filename($new); }
			if (!$is_xen) { check_filename($old); }
			if (!$is_xen && $i > 2 && $lines[$i-2] =~ /^={20}/ &&
						$lines[$i-3] =~ /^Index: /) {
				err("Superfluous Index: line in patch");
			}
			next;
		}
		next unless $in_payload;
		if ($cur =~ /^(diff |Index: |--- |\\ No newline at end of file)/) {
			$in_hunk = 0;
			$maybe_eof = 0;
			next;
		} elsif ($cur =~ /^@@ /) {
			$in_hunk = 1;
			$last_noncontext = $i;
			$maybe_eof = 0;
			next;
		}
		next unless $in_hunk;
		# "-- " can be used as signature delimiter and is sometimes
		# written as "--"
		if ($cur =~ /^-- ?$/) {
			$maybe_eof = 1;
			next;
		}
		if ($cur =~ /^[-+]/) {
			$last_noncontext = $i;
			next;
		}
		# Blank lines seem to be OK for git am
		if ($cur =~ /^$|^ /) {
			next;
		}
		# Try to ignore junk at the end of patch files
		if ($i - $last_noncontext <= 3 && !$maybe_eof) {
			err("Malformed patch (missing leading space in context line?)");
		}
	}
}

if (!@ARGV) {
	die "Usage: $0 <patch>...\n";
}
if ($ARGV[0] eq "--stdin") {
	shift;
	$file = "<stdin>";
	if (scalar(@ARGV)) {
		$file = $ARGV[0];
	}
	open(my $fh, '-');
	do_patch($fh, $file);
	close($fh);
} else {
	for $file (@ARGV) {
		open(my $fh, '<', $file) or die "$file: $!\n";
		do_patch($fh, $file);
		close($fh);
	}
}

if ($errors) {
	printf STDERR ("Please refresh the patch%s using scripts/refresh_patch.sh\n", (scalar(@ARGV) > 1 ? "es" : ""));
	if ($git_renames) {
		print STDERR "Use --no-renames when generating patches from git\n";
	}
	exit(1);
}
exit(0);