#!/usr/bin/perl # this script can be used instead of "/usr/bin/patch [options] -i xxx.patch" use strict; use warnings; my @argv = @ARGV; my $verbose; my @patches; while ($argv[-1] =~ /\.(patch|diff)$/) { unshift @patches, pop @argv; } @patches or die "git-repository--apply-patch can not work with compressed patches\n"; # happens when the patch is passed through stdin my @opts; while (@argv) { my $s = shift @argv; if ($s eq '-b') { # we don't want .xxx files } elsif ($s eq '--suffix') { # we don't want .xxx files shift @argv; } elsif ($s eq '-i') { # ignore "-i" } else { push @opts, $s; } } # we really don't want .orig when using git (even when there are hunks) push @opts, '--no-backup-if-mismatch'; foreach my $patch_file (@patches) { my @header = get_patch_header($patch_file); if (grep { /^Subject:/ } @header) { my $patch_file_ = fix_git_patch($patch_file); system_("git am " . ($patch_file_ || $patch_file)); $patch_file_ and unlink $patch_file_; } else { system_("patch @opts -i $patch_file"); my ($patch_name) = $patch_file =~ m!([^/]*)\.(patch|diff)$!; system_('git add .'); git_commit(commit_line_from_patch_name($patch_name) . (@header ? "\n\n" . join('', cleanup_patch_header(@header)) : ''), # use the date of the patch for the commit: (stat($patch_file))[9] . " +0000"); } } sub system_ { my ($cmd) = @_; print "$cmd\n" if $verbose; system($cmd) == 0 or die "$cmd failed\n"; } sub git_commit { my ($msg, $date) = @_; $ENV{GIT_AUTHOR_DATE} = $date; open(my $F, '| git commit -q --author="unknown author " --file=-'); print $F $msg; close $F or die "git commit failed\n"; } sub commit_line_from_patch_name { my ($name) = @_; # remove prefix (eg: "libtool-1.5.26-xxx" => "xxx") my $re_name = qr([a-z][\w+]*([_-][a-z][\w+]*)*)i; my $re_rc = qr((rc\d*|RC\d+|beta\d*|pre\d*|p\d+|test)); my $re_special_version = qr([a-z]([._-]$re_rc?)?|[._-]?$re_rc?|[a-z]); $name =~ s/^ $re_name [._-] \d+ (\.\d+)+ $re_special_version [._-]//x; if (my $pkg_name = $ENV{PKG_NAME}) { $name =~ s/^\Q$pkg_name\E[_-]//; } # replace "-" (resp. "_") with spaces if there is no spaces nor "_" (resp. "-") if ($name !~ /[\s_]/ && $name !~ /--/) { $name =~ s/-/ /g; } elsif ($name !~ /[\s-]/ && $name !~ /__/) { $name =~ s/_/ /g; } $name; } sub get_patch_header { my ($file) = @_; open(my $F, '<', $file) or die "can not open $file: $!\n"; my @header; while (my $s = <$F>) { last if $s =~ /^--- /; push @header, $s; } pop @header while @header && $header[-1] !~ /^\s*$/; @header; } sub cleanup_patch_header { my (@header) = @_; my @r; foreach (@header) { s/^##\s// or last; push @r, $_; } @r == @header and return @r; @header; } # "git format-patch" and "git am" do not agree how to handle commit logs when # the first line is not separated from the rest. # eg: # # > Subject: [PATCH 01/34] Delay NSS initialization until actually used # > - since NSS is allergic (ie becomes non-functional) after forking, delay # > it's initialization until really needed, ie lazy init in rpmDigestInit() # # workarounding by transforming header to: # # > Subject: [PATCH 01/34] Delay NSS initialization until actually used # > # > - since NSS is allergic (ie becomes non-functional) after forking, delay # > it's initialization until really needed, ie lazy init in rpmDigestInit() sub fix_git_patch { my ($file) = @_; open(my $F, '<', $file) or die "can not open $file: $!\n"; my ($last_line, @l); while (my $s = <$F>) { push @l, $s; if ($s !~ /^\S+:\s/ && $last_line && $last_line =~ /^Subject:/) { # argh, we are in the header, but the value is weird # applying the fix $l[-1] = "\n" . $l[-1]; push @l, <$F>; output("$file.tmp", @l); return "$file.tmp"; } elsif ($s =~ /^\s*$/ || $s =~ /^--- /) { last; } $last_line = $s; } undef; } sub output { my $f = shift; open(my $F, '>', $f) or die "output in file $f failed: $!\n"; print $F $_ foreach @_; 1 }