aboutsummaryrefslogtreecommitdiffstats
path: root/git-repository--apply-patch
blob: a4bdf8b6ce653ec03443cd35ee411e9d17f24acb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/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 <>" --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 }