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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
|
#!/usr/bin/perl -w
# (c) 2018, Lars Kurth <lars.kurth@citrix.com>
#
# Add maintainers to patches generated with git format-patch
#
# Usage: perl scripts/add_maintainers.pl [OPTIONS] -patchdir <patchdir>
#
# Prerequisites: Execute
# git format-patch ... -o <patchdir> ...
#
# ./scripts/get_maintainer.pl is present in the tree
#
# Licensed under the terms of the GNU GPL License version 2
use strict;
use Getopt::Long qw(:config no_auto_abbrev);
use File::Basename;
use List::MoreUtils qw(uniq);
use IO::Handle;
sub getmaintainers ($$$);
sub gettagsfrompatch ($$$;$);
sub normalize ($$);
sub insert ($$$$);
sub hastag ($$);
# Tool Variables
my $tool = $0;
my $get_maintainer = $tool;
$get_maintainer =~ s/add_maintainers/get_maintainer/;
my $usage = <<EOT;
OPTIONS:
--------
USAGE: $tool [options] (--patchdir | -d | -o) <patchdir>
--reroll-count <n> | -v <n>
Choose patch files for specific version. This results into the
following filters on <patchdir>
0: default - *.patch
>1: v<n>*.patch
--patchcc (header|commit|comment|none) | -p (header|commit|comment|none)
Insert CC lines into *.patch files in the specified location.
When `none` is specified, the *.patch files are not changed.
See LOCATIONS for a definition of the various locations.
The default is `header`.
--covercc (header|end|none) | -c (header|end|none)
Insert CC lines into cover letter in the specified location. See
When `none` is specified, the cover letter is not changed.
LOCATIONS for a definition of the various locations.
The default is `header`.
--tagscc
In addition to the output of get_maintainer.pl, include email
addresses from commit tags (e.g., Reviewed-by, Tested-by, ...) in
the list of CC lines to insert.
These extra lines will be inserted as specified by the --patchcc
and --covercc options. When used with `--patchcc commit`,
this will duplicate e-mail addresses in the commit message.
--tags | -t
As above, but the insert location is special-cased: e-mail addresses
will always be inserted into the `header` of patches and the cover letter.
--get-maintainers=<program>
Run <program> instead of $get_maintainer.
(Passing `true` for <program> suppresses the usual computation
of CCs, from files touched by patches and MAINTAINERS.)
--arg <argument> | -a <argument> ...
Arguments passed on to get_maintainer.pl
This option can be used multiple times, e.g. -a <a1> -a <a2> ...
--verbose
Show more output
--help | -h
Show this help information
LOCATIONS:
----------
*.patch and cover letters files consist of several sections relevant
to processing:
<header>: This is the email header containing email related information
It ends with the Subject: line
<commit>: This is the email body that ends up in the commit message.
It ends with ---. CC lines added here will be checked
into the git tree on commit. Only applicable to normal
patch files.
<comment>: This is the 'comment for reviewers' section, after the
--- but before the diff actually starts. CCs added here
are processed by git send-email, but are not checked into
the git tree on commit. Only applicable to normal patch
files.
<end>: The part of a cover letter just before `-- ` (which normally
begins a diffstat). Only applicable to cover letters.
DEFAULT BEHAVIOUR:
------------------
* get_maintainer is called on each patch to find email addresses
of maintainers/reviewers for that patch
* All of the above addresses are added to the CC mail headers
of each patch
* All of the above addresses are added to the CC mail headers
of the cover letter
WORKFLOW:
---------
This script is intended to be used as part of the following workflow
Step 1: git format-patch ... -o <patchdir> ...
Step 2: ./scripts/add_maintainers.pl -d <patchdir>
This overwrites *.patch files in <patchdir> but makes a backup
Step 3: git send-email -to xen-devel\@lists.xenproject.org <patchdir>/*.patch
EOT
# Constants and functions related to LOCATIONS
# Constants for -p|--patchcc and -c|--covercc option processing
my @plocations= ("header", "commit", "comment", "none");
my @clocations= ("header", "end", "none");
# Hash is used to determine which mode value maps onto which search string
my %inssearch = (
"header" => "Date:", # Insert before Date:
"commit" => "Signed-off-by:", # Insert before Signed-off-by:
"comment" => "---", # Insert after ---
"end" => "-- ", # Insert before '-- '
);
# Hash is used to determine whether for a given mode we insert CCs after
# the search string or before
my %insafter = (
"header" => 0,
"commit" => 0,
"comment" => 1,
"end" => 0,
);
# The following subroutines take a areference to arrays of
# - @header: contains CCs from *-by: tags and TOs from mailing lists
# - @cc: contains all other CC's
# It will then apply the corect locations on the input file
sub applylocation_header ($$$) {
my ($file, $rheader, $rcc) = @_;
my $insert = join("\n", uniq (@$rheader, @$rcc));
insert($file , $insert, $inssearch{header}, $insafter{header});
}
sub applymixedlocation ($$$$) {
my ($file, $rheader, $rcc, $mode) = @_;
my $header = join("\n", @$rheader);
my $cc = join("\n", @$rcc);
# Insert snippets into files
insert($file , $cc, $inssearch{$mode}, $insafter{$mode});
# The header
insert($file , $header, $inssearch{header}, $insafter{header});
}
sub applylocation_commit($$$) {
my ($file, $rheader, $rcc) = @_;
applymixedlocation($file, $rheader, $rcc, "commit");
}
# Use a different name to make sure perl doesn't throw a syntax error
sub applylocation_comment ($$$) {
my ($file, $rheader, $rcc) = @_;
applymixedlocation($file, $rheader, $rcc, "comment");
}
sub applylocation_end ($$$) {
my ($file, $rheader, $rcc) = @_;
applymixedlocation($file, $rheader, $rcc, "end");
}
sub applylocation_none ($$$) {
return;
}
# Hash for location functions
my %applylocation = (
"header" => \&applylocation_header,
"commit" => \&applylocation_commit,
"comment" => \&applylocation_comment,
"end" => \&applylocation_end,
"none" => \&applylocation_none,
);
# Arguments / Options
my $help = 0;
my $patch_dir = 0;
my @get_maintainer_args = ();
my $verbose = 0;
my $rerollcount = 0;
my $tags = 0;
my $tagscc = 0;
my $plocation = "header";
my $clocation = "header";
# Constants
# Keep these as constants, in case we want to make these configurable
# in future
my $CC = "Cc:"; # Note: git-send-mail requires Cc:
my $TO = "To:";
my $cover_letter = "0000-cover-letter.patch";
my $patch_ext = ".patch";
my $maintainers = "MAINTAINERS";
if (!GetOptions(
'd|o|patchdir=s' => \$patch_dir,
'v|reroll-count=i' => \$rerollcount,
'p|patchcc=s' => \$plocation,
'c|covercc=s' => \$clocation,
't|tags' => \$tags,
'tagscc' => \$tagscc,
'a|arg=s' => \@get_maintainer_args,
'get-maintainers=s' => \$get_maintainer,
'verbose' => \$verbose,
'h|help' => \$help,
)) {
die "$tool: invalid argument - use --help if necessary\n";
}
if ($help) {
print $usage;
exit 0;
}
if (!$patch_dir) {
die "$tool: Directory -d|--patchdir not specified\n";
}
if (! -e $patch_dir) {
die "$tool: Directory $patch_dir does not exist\n";
}
# Calculate the $patch_prefix
my $patch_prefix = "";
if ($rerollcount == 0) {
# If the user didn't specify -v and we are here, then
# - either the directory is empty
# - or it contains some version of a patch
# In this case we search for the first patch and
# work out the version
$!=0;
my @coverletters = glob($patch_dir.'/*'.$patch_ext);
if (!$! && scalar @coverletters) {
if ($coverletters[0] =~ /\/v([0-9]+)-\Q$cover_letter\E/) {
$rerollcount = $1;
}
}
}
if ($rerollcount > 0) {
$patch_prefix = "v".$rerollcount."-";
}
if ( ! grep $_ eq $plocation, @plocations) {
die "$tool: Invalid -p|--patchcc value\n";
}
if ( ! grep $_ eq $clocation, @clocations) {
die "$tool: Invalid -c|--covercc value\n";
}
# Get the list of patches
my $has_cover_letter = 0;
my $cover_letter_file;
my $pattern = $patch_dir.'/'.$patch_prefix.'[0-9][0-9][0-9][0-9]*'.$patch_ext;
$!=0;
my @patches = glob($pattern);
if ($!) {
die "$tool: Directory $patch_dir contains no patches\n";
}
if (!scalar @patches) {
die "$tool: Directory $patch_dir contains no matching patches.\n".
"Please try --reroll-count <n> | -v <n>\n";
}
# Do the actual processing
my $file;
my @combined_header;
my @combined_cc;
foreach my $file (@patches) {
if ($file =~ /\/\Q$patch_prefix$cover_letter\E/) {
$has_cover_letter = 1;
$cover_letter_file = $file;
} else {
my @header; # To: lists returned by get_maintainers.pl
my @headerpatch;# To: entries in *.patch
#
# Also includes CC's from tags as we do not want
# entries in the body such as
# CC: lars.kurth@citrix.com
# ...
# Tested-by: lars.kurth@citrix.com
my @cc; # Cc: maintainers returned by get_maintainers.pl
my @ccpatch; # Cc: entries in *.patch
my @extrapatch; # Cc: for AB, RB, RAB in *.patch
print "Processing: ".basename($file)."\n";
# Read tags from output of get_maintainers.pl
# Lists go into @header and everything else into @cc
getmaintainers($file, \@header, \@cc);
# Read all lines with CC & TO from the patch file (these will
# likely come from the commit message). Also read tags.
gettagsfrompatch($file, \@headerpatch, \@ccpatch, \@extrapatch);
# With -t|--tags only add @extrapatch to @header and @combined_header
# With --tagscc treat tags as CC that came from the *.patch file
if ($tags && !$tagscc) {
# Copy these always onto the TO related arrays
push @header, @extrapatch;
push @combined_header, @extrapatch;
} elsif ($tagscc) {
# Treat these as if they came from CC's
push @ccpatch, @extrapatch;
push @combined_cc, @extrapatch;
}
# In this section we normalize the lists. We remove entries
# that are already in the patch, from @cc and @to
my @header_only = normalize(\@header, \@headerpatch);
my @cc_only = normalize(\@cc, \@ccpatch);
# Apply the location
$applylocation{$plocation}($file, \@header_only, \@cc_only);
}
}
# Deal with the cover letter
if ($has_cover_letter) {
my @headerpatch; # Entries inserted at the header
my @ccpatch; # Cc: entries in *.patch
print "Processing: ".basename($cover_letter_file)."\n";
# Read all lines with CC & TO from the patch file such that subsequent
# calls don't lead to duplication
gettagsfrompatch($cover_letter_file, \@headerpatch, \@ccpatch);
# In this section we normalize the lists. We remove entries
# that are already in the patch, from @cc and @to
my @header_only = normalize(\@combined_header, \@headerpatch);
my @cc_only = normalize(\@combined_cc, \@ccpatch);
# Apply the location
$applylocation{$clocation}($cover_letter_file, \@header_only, \@cc_only);
print "\nDon't forget to add the subject and message to ".
$cover_letter_file."\n";
}
print "Then perform:\n".
"git send-email -to xen-devel\@lists.xenproject.org ".
$patch_dir.'/'.$patch_prefix."*.patch"."\n";
exit 0;
my $getmailinglists_done = 0;
my @mailinglists = ();
sub getmailinglists () {
# Read mailing list from MAINTAINERS file and copy
# a list of e-mail addresses to @mailinglists
if (!$getmailinglists_done) {
if (-e $maintainers) {
my $fh;
my $line;
open($fh, "<", $maintainers) or die $!;
while (my $line = <$fh>) {
chomp $line;
if ($line =~ /^L:[[:blank:]]+/m) {
push @mailinglists, $';
}
}
$fh->error and die $!;
close $fh or die $!;
} else {
print "Warning: file '$maintainers' does not exist\n";
print "Warning: Mailing lists will be treated as CC's\n";
}
# Don't try again, even if the MAINTAINERS file does not exist
$getmailinglists_done = 1;
# Remove any duplicates
@mailinglists = uniq @mailinglists;
}
}
sub ismailinglist ($) {
my ($check) = @_;
# Get the mailing list information
getmailinglists();
# Do the check
if ( grep { $_ eq $check} @mailinglists) {
return 1;
}
return 0;
}
sub getmaintainers ($$$) {
my ($file, $rto, $rcc) = @_;
my $fh;
open($fh, "-|", $get_maintainer, @get_maintainer_args, $file)
or die "Failed to open '$get_maintainer'\n";
while(my $line = <$fh>) {
chomp $line;
# Keep lists and CC's separately as we dont want them in
# the commit message under a Cc: line
if (ismailinglist($line)) {
push @$rto, $TO." ".$line;
push @combined_header, $TO." ".$line;
} else {
push @$rcc, $CC." ".$line;
push @combined_cc, $CC." ".$line;
}
}
$fh->error and die $!;
close $fh or die $!;
}
sub gettagsfrompatch ($$$;$) {
my ($file, $rto, $rcc, $rextra) = @_;
my $fh;
open($fh, "<", $file)
or die "Failed to open '$file'\n";
while(my $line = <$fh>) {
chomp $line;
my $nline;
if (hastag($line, $TO)) {
push @$rto, $line;
push @combined_header, $line;
}
if (hastag($line, $CC)) {
push @$rcc, $line;
push @combined_cc, $line;
}
# If there is an $rextra, then get various tags and add
# email addresses to the CC list
if ($rextra && $line =~ /^[-0-9a-z]+-by:[[:blank:]]+/mi) {
push @$rextra, $CC." ".$';
}
}
$fh->error and die $!;
close $fh or die $!;
}
sub hastag ($$) {
my ($line, $tag) = @_;
if ($line =~ m{^\Q$tag\E}i) {
return 1;
}
return 0;
}
sub normalize ($$) {
my ($ra, $rb) = @_;
# This function is used to normalize lists of tags or CC / TO lists
# It returns a list of the unique elements
# in @$ra, excluding any which are in @$rb.
# Comparisons are case-insensitive.
my @aonly = ();
my %seen;
my $item;
foreach $item (@$rb) {
$seen{lc($item)} = 1;
}
foreach $item (@$ra) {
unless ($seen{lc($item)}++) {
# it's not in %seen, so add to @aonly
push @aonly, $item;
}
}
return @aonly;
}
sub readfile ($) {
my ($file) = @_;
my $fh;
my $content;
open($fh, "<", $file)
or die "Could not open file '$file' $!";
$content = do { local $/; <$fh> };
$fh->error and die $!;
close $fh or die $!;
return $content;
}
sub writefile ($$) {
my ($content, $file) = @_;
my $fh;
open($fh, ">", "$file.tmp")
or die "Could not open file '$file.tmp' $!";
print $fh $content or die $!;
close $fh or die $!;
rename "$file.tmp", $file or die "Could not rename '$file' into place $!";
}
sub insert ($$$$) {
my ($file, $insert, $delimiter, $insafter) = @_;
my $content;
if ($insert eq "") {
# Nothing to insert
return;
}
# Read file
$content = readfile($file) or die $!;
# Split the string and generate new content
if ($content =~ /^\Q$delimiter\E/mi) {
if ($insafter) {
writefile($`.$delimiter."\n".$insert."\n".$', $file);
if ($verbose) {
print "\nInserted into ".basename($file).' after "'.
$delimiter."'"."\n-----\n".$insert."\n-----\n";
}
} else {
writefile($`.$insert."\n".$delimiter.$', $file);
if ($verbose) {
print "\nInserted into ".basename($file).' before "'.
$delimiter."'"."\n-----\n".$insert."\n-----\n";
}
}
} else {
print "Error: Didn't find '$delimiter' in '$file'\n";
}
}
|