get_maintainer.pl 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115
  1. #!/usr/bin/perl -w
  2. # (c) 2007, Joe Perches <joe@perches.com>
  3. # created from checkpatch.pl
  4. #
  5. # Print selected MAINTAINERS information for
  6. # the files modified in a patch or for a file
  7. #
  8. # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
  9. # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
  10. #
  11. # Licensed under the terms of the GNU GPL License version 2
  12. use strict;
  13. my $P = $0;
  14. my $V = '0.22';
  15. use Getopt::Long qw(:config no_auto_abbrev);
  16. my $lk_path = "./";
  17. my $email = 1;
  18. my $email_usename = 1;
  19. my $email_maintainer = 1;
  20. my $email_list = 1;
  21. my $email_subscriber_list = 0;
  22. my $email_git = 1;
  23. my $email_git_penguin_chiefs = 0;
  24. my $email_git_min_signatures = 1;
  25. my $email_git_max_maintainers = 5;
  26. my $email_git_min_percent = 5;
  27. my $email_git_since = "1-year-ago";
  28. my $email_git_blame = 0;
  29. my $email_remove_duplicates = 1;
  30. my $output_multiline = 1;
  31. my $output_separator = ", ";
  32. my $output_roles = 0;
  33. my $output_rolestats = 0;
  34. my $scm = 0;
  35. my $web = 0;
  36. my $subsystem = 0;
  37. my $status = 0;
  38. my $keywords = 1;
  39. my $from_filename = 0;
  40. my $pattern_depth = 0;
  41. my $version = 0;
  42. my $help = 0;
  43. my $exit = 0;
  44. my @penguin_chief = ();
  45. push(@penguin_chief,"Linus Torvalds:torvalds\@linux-foundation.org");
  46. #Andrew wants in on most everything - 2009/01/14
  47. #push(@penguin_chief,"Andrew Morton:akpm\@linux-foundation.org");
  48. my @penguin_chief_names = ();
  49. foreach my $chief (@penguin_chief) {
  50. if ($chief =~ m/^(.*):(.*)/) {
  51. my $chief_name = $1;
  52. my $chief_addr = $2;
  53. push(@penguin_chief_names, $chief_name);
  54. }
  55. }
  56. my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
  57. # rfc822 email address - preloaded methods go here.
  58. my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
  59. my $rfc822_char = '[\\000-\\377]';
  60. if (!GetOptions(
  61. 'email!' => \$email,
  62. 'git!' => \$email_git,
  63. 'git-chief-penguins!' => \$email_git_penguin_chiefs,
  64. 'git-min-signatures=i' => \$email_git_min_signatures,
  65. 'git-max-maintainers=i' => \$email_git_max_maintainers,
  66. 'git-min-percent=i' => \$email_git_min_percent,
  67. 'git-since=s' => \$email_git_since,
  68. 'git-blame!' => \$email_git_blame,
  69. 'remove-duplicates!' => \$email_remove_duplicates,
  70. 'm!' => \$email_maintainer,
  71. 'n!' => \$email_usename,
  72. 'l!' => \$email_list,
  73. 's!' => \$email_subscriber_list,
  74. 'multiline!' => \$output_multiline,
  75. 'roles!' => \$output_roles,
  76. 'rolestats!' => \$output_rolestats,
  77. 'separator=s' => \$output_separator,
  78. 'subsystem!' => \$subsystem,
  79. 'status!' => \$status,
  80. 'scm!' => \$scm,
  81. 'web!' => \$web,
  82. 'pattern-depth=i' => \$pattern_depth,
  83. 'k|keywords!' => \$keywords,
  84. 'f|file' => \$from_filename,
  85. 'v|version' => \$version,
  86. 'h|help' => \$help,
  87. )) {
  88. die "$P: invalid argument - use --help if necessary\n";
  89. }
  90. if ($help != 0) {
  91. usage();
  92. exit 0;
  93. }
  94. if ($version != 0) {
  95. print("${P} ${V}\n");
  96. exit 0;
  97. }
  98. if ($#ARGV < 0) {
  99. usage();
  100. die "$P: argument missing: patchfile or -f file please\n";
  101. }
  102. if ($output_separator ne ", ") {
  103. $output_multiline = 0;
  104. }
  105. if ($output_rolestats) {
  106. $output_roles = 1;
  107. }
  108. my $selections = $email + $scm + $status + $subsystem + $web;
  109. if ($selections == 0) {
  110. usage();
  111. die "$P: Missing required option: email, scm, status, subsystem or web\n";
  112. }
  113. if ($email &&
  114. ($email_maintainer + $email_list + $email_subscriber_list +
  115. $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
  116. usage();
  117. die "$P: Please select at least 1 email option\n";
  118. }
  119. if (!top_of_kernel_tree($lk_path)) {
  120. die "$P: The current directory does not appear to be "
  121. . "a linux kernel source tree.\n";
  122. }
  123. ## Read MAINTAINERS for type/value pairs
  124. my @typevalue = ();
  125. my %keyword_hash;
  126. open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n";
  127. while (<MAINT>) {
  128. my $line = $_;
  129. if ($line =~ m/^(\C):\s*(.*)/) {
  130. my $type = $1;
  131. my $value = $2;
  132. ##Filename pattern matching
  133. if ($type eq "F" || $type eq "X") {
  134. $value =~ s@\.@\\\.@g; ##Convert . to \.
  135. $value =~ s/\*/\.\*/g; ##Convert * to .*
  136. $value =~ s/\?/\./g; ##Convert ? to .
  137. ##if pattern is a directory and it lacks a trailing slash, add one
  138. if ((-d $value)) {
  139. $value =~ s@([^/])$@$1/@;
  140. }
  141. } elsif ($type eq "K") {
  142. $keyword_hash{@typevalue} = $value;
  143. }
  144. push(@typevalue, "$type:$value");
  145. } elsif (!/^(\s)*$/) {
  146. $line =~ s/\n$//g;
  147. push(@typevalue, $line);
  148. }
  149. }
  150. close(MAINT);
  151. my %mailmap;
  152. if ($email_remove_duplicates) {
  153. open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n";
  154. while (<MAILMAP>) {
  155. my $line = $_;
  156. next if ($line =~ m/^\s*#/);
  157. next if ($line =~ m/^\s*$/);
  158. my ($name, $address) = parse_email($line);
  159. $line = format_email($name, $address);
  160. next if ($line =~ m/^\s*$/);
  161. if (exists($mailmap{$name})) {
  162. my $obj = $mailmap{$name};
  163. push(@$obj, $address);
  164. } else {
  165. my @arr = ($address);
  166. $mailmap{$name} = \@arr;
  167. }
  168. }
  169. close(MAILMAP);
  170. }
  171. ## use the filenames on the command line or find the filenames in the patchfiles
  172. my @files = ();
  173. my @range = ();
  174. my @keyword_tvi = ();
  175. foreach my $file (@ARGV) {
  176. ##if $file is a directory and it lacks a trailing slash, add one
  177. if ((-d $file)) {
  178. $file =~ s@([^/])$@$1/@;
  179. } elsif (!(-f $file)) {
  180. die "$P: file '${file}' not found\n";
  181. }
  182. if ($from_filename) {
  183. push(@files, $file);
  184. if (-f $file && $keywords) {
  185. open(FILE, "<$file") or die "$P: Can't open ${file}\n";
  186. while (<FILE>) {
  187. my $patch_line = $_;
  188. foreach my $line (keys %keyword_hash) {
  189. if ($patch_line =~ m/^.*$keyword_hash{$line}/x) {
  190. push(@keyword_tvi, $line);
  191. }
  192. }
  193. }
  194. close(FILE);
  195. }
  196. } else {
  197. my $file_cnt = @files;
  198. my $lastfile;
  199. open(PATCH, "<$file") or die "$P: Can't open ${file}\n";
  200. while (<PATCH>) {
  201. my $patch_line = $_;
  202. if (m/^\+\+\+\s+(\S+)/) {
  203. my $filename = $1;
  204. $filename =~ s@^[^/]*/@@;
  205. $filename =~ s@\n@@;
  206. $lastfile = $filename;
  207. push(@files, $filename);
  208. } elsif (m/^\@\@ -(\d+),(\d+)/) {
  209. if ($email_git_blame) {
  210. push(@range, "$lastfile:$1:$2");
  211. }
  212. } elsif ($keywords) {
  213. foreach my $line (keys %keyword_hash) {
  214. if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
  215. push(@keyword_tvi, $line);
  216. }
  217. }
  218. }
  219. }
  220. close(PATCH);
  221. if ($file_cnt == @files) {
  222. warn "$P: file '${file}' doesn't appear to be a patch. "
  223. . "Add -f to options?\n";
  224. }
  225. @files = sort_and_uniq(@files);
  226. }
  227. }
  228. my @email_to = ();
  229. my @list_to = ();
  230. my @scm = ();
  231. my @web = ();
  232. my @subsystem = ();
  233. my @status = ();
  234. # Find responsible parties
  235. foreach my $file (@files) {
  236. #Do not match excluded file patterns
  237. my $exclude = 0;
  238. foreach my $line (@typevalue) {
  239. if ($line =~ m/^(\C):\s*(.*)/) {
  240. my $type = $1;
  241. my $value = $2;
  242. if ($type eq 'X') {
  243. if (file_match_pattern($file, $value)) {
  244. $exclude = 1;
  245. last;
  246. }
  247. }
  248. }
  249. }
  250. if (!$exclude) {
  251. my $tvi = 0;
  252. my %hash;
  253. foreach my $line (@typevalue) {
  254. if ($line =~ m/^(\C):\s*(.*)/) {
  255. my $type = $1;
  256. my $value = $2;
  257. if ($type eq 'F') {
  258. if (file_match_pattern($file, $value)) {
  259. my $value_pd = ($value =~ tr@/@@);
  260. my $file_pd = ($file =~ tr@/@@);
  261. $value_pd++ if (substr($value,-1,1) ne "/");
  262. if ($pattern_depth == 0 ||
  263. (($file_pd - $value_pd) < $pattern_depth)) {
  264. $hash{$tvi} = $value_pd;
  265. }
  266. }
  267. }
  268. }
  269. $tvi++;
  270. }
  271. foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
  272. add_categories($line);
  273. }
  274. }
  275. if ($email && $email_git) {
  276. recent_git_signoffs($file);
  277. }
  278. if ($email && $email_git_blame) {
  279. git_assign_blame($file);
  280. }
  281. }
  282. if ($keywords) {
  283. @keyword_tvi = sort_and_uniq(@keyword_tvi);
  284. foreach my $line (@keyword_tvi) {
  285. add_categories($line);
  286. }
  287. }
  288. if ($email) {
  289. foreach my $chief (@penguin_chief) {
  290. if ($chief =~ m/^(.*):(.*)/) {
  291. my $email_address;
  292. $email_address = format_email($1, $2);
  293. if ($email_git_penguin_chiefs) {
  294. push(@email_to, [$email_address, 'chief penguin']);
  295. } else {
  296. @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
  297. }
  298. }
  299. }
  300. }
  301. if ($email || $email_list) {
  302. my @to = ();
  303. if ($email) {
  304. @to = (@to, @email_to);
  305. }
  306. if ($email_list) {
  307. @to = (@to, @list_to);
  308. }
  309. output(merge_email(@to));
  310. }
  311. if ($scm) {
  312. @scm = uniq(@scm);
  313. output(@scm);
  314. }
  315. if ($status) {
  316. @status = uniq(@status);
  317. output(@status);
  318. }
  319. if ($subsystem) {
  320. @subsystem = uniq(@subsystem);
  321. output(@subsystem);
  322. }
  323. if ($web) {
  324. @web = uniq(@web);
  325. output(@web);
  326. }
  327. exit($exit);
  328. sub file_match_pattern {
  329. my ($file, $pattern) = @_;
  330. if (substr($pattern, -1) eq "/") {
  331. if ($file =~ m@^$pattern@) {
  332. return 1;
  333. }
  334. } else {
  335. if ($file =~ m@^$pattern@) {
  336. my $s1 = ($file =~ tr@/@@);
  337. my $s2 = ($pattern =~ tr@/@@);
  338. if ($s1 == $s2) {
  339. return 1;
  340. }
  341. }
  342. }
  343. return 0;
  344. }
  345. sub usage {
  346. print <<EOT;
  347. usage: $P [options] patchfile
  348. $P [options] -f file|directory
  349. version: $V
  350. MAINTAINER field selection options:
  351. --email => print email address(es) if any
  352. --git => include recent git \*-by: signers
  353. --git-chief-penguins => include ${penguin_chiefs}
  354. --git-min-signatures => number of signatures required (default: 1)
  355. --git-max-maintainers => maximum maintainers to add (default: 5)
  356. --git-min-percent => minimum percentage of commits required (default: 5)
  357. --git-since => git history to use (default: 1-year-ago)
  358. --git-blame => use git blame to find modified commits for patch or file
  359. --m => include maintainer(s) if any
  360. --n => include name 'Full Name <addr\@domain.tld>'
  361. --l => include list(s) if any
  362. --s => include subscriber only list(s) if any
  363. --remove-duplicates => minimize duplicate email names/addresses
  364. --roles => show roles (status:subsystem, git-signer, list, etc...)
  365. --rolestats => show roles and statistics (commits/total_commits, %)
  366. --scm => print SCM tree(s) if any
  367. --status => print status if any
  368. --subsystem => print subsystem name if any
  369. --web => print website(s) if any
  370. Output type options:
  371. --separator [, ] => separator for multiple entries on 1 line
  372. using --separator also sets --nomultiline if --separator is not [, ]
  373. --multiline => print 1 entry per line
  374. Other options:
  375. --pattern-depth => Number of pattern directory traversals (default: 0 (all))
  376. --keywords => scan patch for keywords (default: 1 (on))
  377. --version => show version
  378. --help => show this help information
  379. Default options:
  380. [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
  381. Notes:
  382. Using "-f directory" may give unexpected results:
  383. Used with "--git", git signators for _all_ files in and below
  384. directory are examined as git recurses directories.
  385. Any specified X: (exclude) pattern matches are _not_ ignored.
  386. Used with "--nogit", directory is used as a pattern match,
  387. no individual file within the directory or subdirectory
  388. is matched.
  389. Used with "--git-blame", does not iterate all files in directory
  390. Using "--git-blame" is slow and may add old committers and authors
  391. that are no longer active maintainers to the output.
  392. Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
  393. other automated tools that expect only ["name"] <email address>
  394. may not work because of additional output after <email address>.
  395. Using "--rolestats" and "--git-blame" shows the #/total=% commits,
  396. not the percentage of the entire file authored. # of commits is
  397. not a good measure of amount of code authored. 1 major commit may
  398. contain a thousand lines, 5 trivial commits may modify a single line.
  399. EOT
  400. }
  401. sub top_of_kernel_tree {
  402. my ($lk_path) = @_;
  403. if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
  404. $lk_path .= "/";
  405. }
  406. if ( (-f "${lk_path}COPYING")
  407. && (-f "${lk_path}CREDITS")
  408. && (-f "${lk_path}Kbuild")
  409. && (-f "${lk_path}MAINTAINERS")
  410. && (-f "${lk_path}Makefile")
  411. && (-f "${lk_path}README")
  412. && (-d "${lk_path}Documentation")
  413. && (-d "${lk_path}arch")
  414. && (-d "${lk_path}include")
  415. && (-d "${lk_path}drivers")
  416. && (-d "${lk_path}fs")
  417. && (-d "${lk_path}init")
  418. && (-d "${lk_path}ipc")
  419. && (-d "${lk_path}kernel")
  420. && (-d "${lk_path}lib")
  421. && (-d "${lk_path}scripts")) {
  422. return 1;
  423. }
  424. return 0;
  425. }
  426. sub parse_email {
  427. my ($formatted_email) = @_;
  428. my $name = "";
  429. my $address = "";
  430. if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
  431. $name = $1;
  432. $address = $2;
  433. } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
  434. $address = $1;
  435. } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
  436. $address = $1;
  437. }
  438. $name =~ s/^\s+|\s+$//g;
  439. $name =~ s/^\"|\"$//g;
  440. $address =~ s/^\s+|\s+$//g;
  441. if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars
  442. $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
  443. $name = "\"$name\"";
  444. }
  445. return ($name, $address);
  446. }
  447. sub format_email {
  448. my ($name, $address) = @_;
  449. my $formatted_email;
  450. $name =~ s/^\s+|\s+$//g;
  451. $name =~ s/^\"|\"$//g;
  452. $address =~ s/^\s+|\s+$//g;
  453. if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars
  454. $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
  455. $name = "\"$name\"";
  456. }
  457. if ($email_usename) {
  458. if ("$name" eq "") {
  459. $formatted_email = "$address";
  460. } else {
  461. $formatted_email = "$name <${address}>";
  462. }
  463. } else {
  464. $formatted_email = $address;
  465. }
  466. return $formatted_email;
  467. }
  468. sub find_starting_index {
  469. my ($index) = @_;
  470. while ($index > 0) {
  471. my $tv = $typevalue[$index];
  472. if (!($tv =~ m/^(\C):\s*(.*)/)) {
  473. last;
  474. }
  475. $index--;
  476. }
  477. return $index;
  478. }
  479. sub find_ending_index {
  480. my ($index) = @_;
  481. while ($index < @typevalue) {
  482. my $tv = $typevalue[$index];
  483. if (!($tv =~ m/^(\C):\s*(.*)/)) {
  484. last;
  485. }
  486. $index++;
  487. }
  488. return $index;
  489. }
  490. sub get_maintainer_role {
  491. my ($index) = @_;
  492. my $i;
  493. my $start = find_starting_index($index);
  494. my $end = find_ending_index($index);
  495. my $role;
  496. my $subsystem = $typevalue[$start];
  497. if (length($subsystem) > 20) {
  498. $subsystem = substr($subsystem, 0, 17);
  499. $subsystem =~ s/\s*$//;
  500. $subsystem = $subsystem . "...";
  501. }
  502. for ($i = $start + 1; $i < $end; $i++) {
  503. my $tv = $typevalue[$i];
  504. if ($tv =~ m/^(\C):\s*(.*)/) {
  505. my $ptype = $1;
  506. my $pvalue = $2;
  507. if ($ptype eq "S") {
  508. $role = $pvalue;
  509. }
  510. }
  511. }
  512. $role = lc($role);
  513. if ($role eq "supported") {
  514. $role = "supporter";
  515. } elsif ($role eq "maintained") {
  516. $role = "maintainer";
  517. } elsif ($role eq "odd fixes") {
  518. $role = "odd fixer";
  519. } elsif ($role eq "orphan") {
  520. $role = "orphan minder";
  521. } elsif ($role eq "obsolete") {
  522. $role = "obsolete minder";
  523. } elsif ($role eq "buried alive in reporters") {
  524. $role = "chief penguin";
  525. }
  526. return $role . ":" . $subsystem;
  527. }
  528. sub get_list_role {
  529. my ($index) = @_;
  530. my $i;
  531. my $start = find_starting_index($index);
  532. my $end = find_ending_index($index);
  533. my $subsystem = $typevalue[$start];
  534. if (length($subsystem) > 20) {
  535. $subsystem = substr($subsystem, 0, 17);
  536. $subsystem =~ s/\s*$//;
  537. $subsystem = $subsystem . "...";
  538. }
  539. if ($subsystem eq "THE REST") {
  540. $subsystem = "";
  541. }
  542. return $subsystem;
  543. }
  544. sub add_categories {
  545. my ($index) = @_;
  546. my $i;
  547. my $start = find_starting_index($index);
  548. my $end = find_ending_index($index);
  549. push(@subsystem, $typevalue[$start]);
  550. for ($i = $start + 1; $i < $end; $i++) {
  551. my $tv = $typevalue[$i];
  552. if ($tv =~ m/^(\C):\s*(.*)/) {
  553. my $ptype = $1;
  554. my $pvalue = $2;
  555. if ($ptype eq "L") {
  556. my $list_address = $pvalue;
  557. my $list_additional = "";
  558. my $list_role = get_list_role($i);
  559. if ($list_role ne "") {
  560. $list_role = ":" . $list_role;
  561. }
  562. if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
  563. $list_address = $1;
  564. $list_additional = $2;
  565. }
  566. if ($list_additional =~ m/subscribers-only/) {
  567. if ($email_subscriber_list) {
  568. push(@list_to, [$list_address, "subscriber list${list_role}"]);
  569. }
  570. } else {
  571. if ($email_list) {
  572. push(@list_to, [$list_address, "open list${list_role}"]);
  573. }
  574. }
  575. } elsif ($ptype eq "M") {
  576. my ($name, $address) = parse_email($pvalue);
  577. if ($name eq "") {
  578. if ($i > 0) {
  579. my $tv = $typevalue[$i - 1];
  580. if ($tv =~ m/^(\C):\s*(.*)/) {
  581. if ($1 eq "P") {
  582. $name = $2;
  583. $pvalue = format_email($name, $address);
  584. }
  585. }
  586. }
  587. }
  588. if ($email_maintainer) {
  589. my $role = get_maintainer_role($i);
  590. push_email_addresses($pvalue, $role);
  591. }
  592. } elsif ($ptype eq "T") {
  593. push(@scm, $pvalue);
  594. } elsif ($ptype eq "W") {
  595. push(@web, $pvalue);
  596. } elsif ($ptype eq "S") {
  597. push(@status, $pvalue);
  598. }
  599. }
  600. }
  601. }
  602. my %email_hash_name;
  603. my %email_hash_address;
  604. sub email_inuse {
  605. my ($name, $address) = @_;
  606. return 1 if (($name eq "") && ($address eq ""));
  607. return 1 if (($name ne "") && exists($email_hash_name{$name}));
  608. return 1 if (($address ne "") && exists($email_hash_address{$address}));
  609. return 0;
  610. }
  611. sub push_email_address {
  612. my ($line, $role) = @_;
  613. my ($name, $address) = parse_email($line);
  614. if ($address eq "") {
  615. return 0;
  616. }
  617. if (!$email_remove_duplicates) {
  618. push(@email_to, [format_email($name, $address), $role]);
  619. } elsif (!email_inuse($name, $address)) {
  620. push(@email_to, [format_email($name, $address), $role]);
  621. $email_hash_name{$name}++;
  622. $email_hash_address{$address}++;
  623. }
  624. return 1;
  625. }
  626. sub push_email_addresses {
  627. my ($address, $role) = @_;
  628. my @address_list = ();
  629. if (rfc822_valid($address)) {
  630. push_email_address($address, $role);
  631. } elsif (@address_list = rfc822_validlist($address)) {
  632. my $array_count = shift(@address_list);
  633. while (my $entry = shift(@address_list)) {
  634. push_email_address($entry, $role);
  635. }
  636. } else {
  637. if (!push_email_address($address, $role)) {
  638. warn("Invalid MAINTAINERS address: '" . $address . "'\n");
  639. }
  640. }
  641. }
  642. sub add_role {
  643. my ($line, $role) = @_;
  644. my ($name, $address) = parse_email($line);
  645. my $email = format_email($name, $address);
  646. foreach my $entry (@email_to) {
  647. if ($email_remove_duplicates) {
  648. my ($entry_name, $entry_address) = parse_email($entry->[0]);
  649. if ($name eq $entry_name || $address eq $entry_address) {
  650. if ($entry->[1] eq "") {
  651. $entry->[1] = "$role";
  652. } else {
  653. $entry->[1] = "$entry->[1],$role";
  654. }
  655. }
  656. } else {
  657. if ($email eq $entry->[0]) {
  658. if ($entry->[1] eq "") {
  659. $entry->[1] = "$role";
  660. } else {
  661. $entry->[1] = "$entry->[1],$role";
  662. }
  663. }
  664. }
  665. }
  666. }
  667. sub which {
  668. my ($bin) = @_;
  669. foreach my $path (split(/:/, $ENV{PATH})) {
  670. if (-e "$path/$bin") {
  671. return "$path/$bin";
  672. }
  673. }
  674. return "";
  675. }
  676. sub mailmap {
  677. my @lines = @_;
  678. my %hash;
  679. foreach my $line (@lines) {
  680. my ($name, $address) = parse_email($line);
  681. if (!exists($hash{$name})) {
  682. $hash{$name} = $address;
  683. } elsif ($address ne $hash{$name}) {
  684. $address = $hash{$name};
  685. $line = format_email($name, $address);
  686. }
  687. if (exists($mailmap{$name})) {
  688. my $obj = $mailmap{$name};
  689. foreach my $map_address (@$obj) {
  690. if (($map_address eq $address) &&
  691. ($map_address ne $hash{$name})) {
  692. $line = format_email($name, $hash{$name});
  693. }
  694. }
  695. }
  696. }
  697. return @lines;
  698. }
  699. sub recent_git_signoffs {
  700. my ($file) = @_;
  701. my $sign_offs = "";
  702. my $cmd = "";
  703. my $output = "";
  704. my $count = 0;
  705. my @lines = ();
  706. my %hash;
  707. my $total_sign_offs;
  708. if (which("git") eq "") {
  709. warn("$P: git not found. Add --nogit to options?\n");
  710. return;
  711. }
  712. if (!(-d ".git")) {
  713. warn("$P: .git directory not found. Use a git repository for better results.\n");
  714. warn("$P: perhaps 'git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git'\n");
  715. return;
  716. }
  717. $cmd = "git log --since=${email_git_since} -- ${file}";
  718. $output = `${cmd}`;
  719. $output =~ s/^\s*//gm;
  720. @lines = split("\n", $output);
  721. @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
  722. if (!$email_git_penguin_chiefs) {
  723. @lines = grep(!/${penguin_chiefs}/i, @lines);
  724. }
  725. # cut -f2- -d":"
  726. s/.*:\s*(.+)\s*/$1/ for (@lines);
  727. $total_sign_offs = @lines;
  728. foreach my $line (@lines) {
  729. my ($name, $address) = parse_email($line);
  730. $line = format_email($name, $address);
  731. }
  732. if ($email_remove_duplicates) {
  733. @lines = mailmap(@lines);
  734. }
  735. @lines = sort(@lines);
  736. # uniq -c
  737. $hash{$_}++ for @lines;
  738. # sort -rn
  739. foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
  740. my $sign_offs = $hash{$line};
  741. my $role;
  742. $count++;
  743. last if ($sign_offs < $email_git_min_signatures ||
  744. $count > $email_git_max_maintainers ||
  745. $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
  746. push_email_address($line, '');
  747. $role = "git-signer";
  748. if ($output_rolestats) {
  749. my $percent = sprintf("%.0f", $sign_offs * 100 / $total_sign_offs);
  750. $role = "$role:$sign_offs/$total_sign_offs=$percent%";
  751. }
  752. add_role($line, $role);
  753. }
  754. }
  755. sub save_commits {
  756. my ($cmd, @commits) = @_;
  757. my $output;
  758. my @lines = ();
  759. $output = `${cmd}`;
  760. @lines = split("\n", $output);
  761. foreach my $line (@lines) {
  762. if ($line =~ m/^(\w+) /) {
  763. push (@commits, $1);
  764. }
  765. }
  766. return @commits;
  767. }
  768. sub git_assign_blame {
  769. my ($file) = @_;
  770. my @lines = ();
  771. my @commits = ();
  772. my $cmd;
  773. my $output;
  774. my %hash;
  775. my $total_sign_offs;
  776. my $count;
  777. if (@range) {
  778. foreach my $file_range_diff (@range) {
  779. next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
  780. my $diff_file = $1;
  781. my $diff_start = $2;
  782. my $diff_length = $3;
  783. next if (!("$file" eq "$diff_file"));
  784. $cmd = "git blame -l -L $diff_start,+$diff_length $file";
  785. @commits = save_commits($cmd, @commits);
  786. }
  787. } else {
  788. if (-f $file) {
  789. $cmd = "git blame -l $file";
  790. @commits = save_commits($cmd, @commits);
  791. }
  792. }
  793. $total_sign_offs = 0;
  794. @commits = uniq(@commits);
  795. foreach my $commit (@commits) {
  796. $cmd = "git log -1 ${commit}";
  797. $output = `${cmd}`;
  798. $output =~ s/^\s*//gm;
  799. @lines = split("\n", $output);
  800. @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
  801. if (!$email_git_penguin_chiefs) {
  802. @lines = grep(!/${penguin_chiefs}/i, @lines);
  803. }
  804. # cut -f2- -d":"
  805. s/.*:\s*(.+)\s*/$1/ for (@lines);
  806. $total_sign_offs += @lines;
  807. if ($email_remove_duplicates) {
  808. @lines = mailmap(@lines);
  809. }
  810. $hash{$_}++ for @lines;
  811. }
  812. $count = 0;
  813. foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
  814. my $sign_offs = $hash{$line};
  815. my $role;
  816. $count++;
  817. last if ($sign_offs < $email_git_min_signatures ||
  818. $count > $email_git_max_maintainers ||
  819. $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
  820. push_email_address($line, '');
  821. if ($from_filename) {
  822. $role = "commits";
  823. } else {
  824. $role = "modified commits";
  825. }
  826. if ($output_rolestats) {
  827. my $percent = sprintf("%.0f", $sign_offs * 100 / $total_sign_offs);
  828. $role = "$role:$sign_offs/$total_sign_offs=$percent%";
  829. }
  830. add_role($line, $role);
  831. }
  832. }
  833. sub uniq {
  834. my @parms = @_;
  835. my %saw;
  836. @parms = grep(!$saw{$_}++, @parms);
  837. return @parms;
  838. }
  839. sub sort_and_uniq {
  840. my @parms = @_;
  841. my %saw;
  842. @parms = sort @parms;
  843. @parms = grep(!$saw{$_}++, @parms);
  844. return @parms;
  845. }
  846. sub merge_email {
  847. my @lines;
  848. my %saw;
  849. for (@_) {
  850. my ($address, $role) = @$_;
  851. if (!$saw{$address}) {
  852. if ($output_roles) {
  853. push @lines, "$address ($role)";
  854. } else {
  855. push @lines, $address;
  856. }
  857. $saw{$address} = 1;
  858. }
  859. }
  860. return @lines;
  861. }
  862. sub output {
  863. my @parms = @_;
  864. if ($output_multiline) {
  865. foreach my $line (@parms) {
  866. print("${line}\n");
  867. }
  868. } else {
  869. print(join($output_separator, @parms));
  870. print("\n");
  871. }
  872. }
  873. my $rfc822re;
  874. sub make_rfc822re {
  875. # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
  876. # comment. We must allow for rfc822_lwsp (or comments) after each of these.
  877. # This regexp will only work on addresses which have had comments stripped
  878. # and replaced with rfc822_lwsp.
  879. my $specials = '()<>@,;:\\\\".\\[\\]';
  880. my $controls = '\\000-\\037\\177';
  881. my $dtext = "[^\\[\\]\\r\\\\]";
  882. my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
  883. my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
  884. # Use zero-width assertion to spot the limit of an atom. A simple
  885. # $rfc822_lwsp* causes the regexp engine to hang occasionally.
  886. my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
  887. my $word = "(?:$atom|$quoted_string)";
  888. my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
  889. my $sub_domain = "(?:$atom|$domain_literal)";
  890. my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
  891. my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
  892. my $phrase = "$word*";
  893. my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
  894. my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
  895. my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
  896. my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
  897. my $address = "(?:$mailbox|$group)";
  898. return "$rfc822_lwsp*$address";
  899. }
  900. sub rfc822_strip_comments {
  901. my $s = shift;
  902. # Recursively remove comments, and replace with a single space. The simpler
  903. # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
  904. # chars in atoms, for example.
  905. while ($s =~ s/^((?:[^"\\]|\\.)*
  906. (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
  907. \((?:[^()\\]|\\.)*\)/$1 /osx) {}
  908. return $s;
  909. }
  910. # valid: returns true if the parameter is an RFC822 valid address
  911. #
  912. sub rfc822_valid ($) {
  913. my $s = rfc822_strip_comments(shift);
  914. if (!$rfc822re) {
  915. $rfc822re = make_rfc822re();
  916. }
  917. return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
  918. }
  919. # validlist: In scalar context, returns true if the parameter is an RFC822
  920. # valid list of addresses.
  921. #
  922. # In list context, returns an empty list on failure (an invalid
  923. # address was found); otherwise a list whose first element is the
  924. # number of addresses found and whose remaining elements are the
  925. # addresses. This is needed to disambiguate failure (invalid)
  926. # from success with no addresses found, because an empty string is
  927. # a valid list.
  928. sub rfc822_validlist ($) {
  929. my $s = rfc822_strip_comments(shift);
  930. if (!$rfc822re) {
  931. $rfc822re = make_rfc822re();
  932. }
  933. # * null list items are valid according to the RFC
  934. # * the '1' business is to aid in distinguishing failure from no results
  935. my @r;
  936. if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
  937. $s =~ m/^$rfc822_char*$/) {
  938. while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
  939. push @r, $1;
  940. }
  941. return wantarray ? (scalar(@r), @r) : 1;
  942. }
  943. else {
  944. return wantarray ? () : 0;
  945. }
  946. }