PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/tools/mfc/mfc.pl

https://bitbucket.org/freebsd/freebsd-head/
Perl | 450 lines | 358 code | 34 blank | 58 comment | 33 complexity | 10b1827b89c8d54c2448cb17c987e766 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, LGPL-2.0, LGPL-2.1, BSD-2-Clause, 0BSD, JSON, AGPL-1.0, GPL-2.0
  1. #! /usr/bin/env perl
  2. #
  3. # mfc - perl script to generate patchsets from commit mail or message-id.
  4. #
  5. # Copyright (c) 2006 Florent Thoumie <flz@FreeBSD.org>
  6. # All rights reserved.
  7. #
  8. # Redistribution and use in source and binary forms, with or without
  9. # modification, are permitted provided that the following conditions
  10. # are met:
  11. # 1. Redistributions of source code must retain the above copyright
  12. # notice, this list of conditions and the following disclaimer.
  13. # 2. Redistributions in binary form must reproduce the above copyright
  14. # notice, this list of conditions and the following disclaimer in the
  15. # documentation and/or other materials provided with the distribution.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  18. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  21. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  23. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  24. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  25. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  26. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  27. # SUCH DAMAGE.
  28. #
  29. # $FreeBSD$
  30. #
  31. # This perl scripts only uses programs that are part of the base system.
  32. # Since some people use NO_FOO options, here's the list of used programs :
  33. # - cvs
  34. # - fetch
  35. # - perl (with getopt module)
  36. # - mkdir, cat, chmod, grep (hopefully everybody has them)
  37. # - cdiff or colordiff (optional)
  38. #
  39. # This script is using 3 environment variables :
  40. # - MFCHOME: directory where patches, scripts and commit message will be stored.
  41. # - MFCCVSROOT: alternative CVSROOT used to generate diffs for new/dead files.
  42. # - MFCLOGIN: define this to your freefall login if you have commit rights.
  43. #
  44. # TODO: Look for XXX in the file.
  45. #
  46. use strict;
  47. use warnings;
  48. use Env;
  49. use Env qw(MFCHOME MFCLOGIN MFCCVSROOT);
  50. use Getopt::Std;
  51. use IO::Handle;
  52. my $mfchome = $MFCHOME ? $MFCHOME : "/var/tmp/mfc";
  53. my $mfclogin = $MFCLOGIN ? $MFCLOGIN : "";
  54. my $cvsroot = $MFCCVSROOT ? $MFCCVSROOT : ':pserver:anoncvs@anoncvs.at.FreeBSD.org:/home/ncvs';
  55. my $version = "1.1.0";
  56. my %opt;
  57. my $commit_author;
  58. my $commit_date;
  59. my %mfc_files = ( );
  60. my %new_files = ( );
  61. my %dead_files = ( );
  62. my @msgids = ( );
  63. my @logmsg = ( );
  64. my @commitmail = ( );
  65. my $commiturl;
  66. my @prs;
  67. my @submitted_by;
  68. my @reviewed_by;
  69. my @obtained_from;
  70. my $cdiff;
  71. my $answer;
  72. my $mfc_func = \&mfc_headers;
  73. sub init()
  74. {
  75. # Enable autoflush of output to always show prompts. Without this,
  76. # piping output will fail to display a prompt.
  77. autoflush STDOUT 1;
  78. # Look for pre-requisites.
  79. my @reqs = ( "fetch", "cvs", "mkdir", "cat", "chmod", "grep" );
  80. my $cmd;
  81. foreach (@reqs) {
  82. $cmd = `which $_`;
  83. die "$_ is missing. Please check pre-requisites." if ($cmd =~ /^$/);
  84. }
  85. $cdiff = `which cdiff`;
  86. $cdiff = `which colordiff` if ($cdiff =~ /^$/);
  87. # Parse command-line options.
  88. my $opt_string = 'bf:hi:m:s:v';
  89. getopts( "$opt_string", \%opt ) or usage();
  90. usage() if !$opt{i} or $opt{h};
  91. @msgids = split / /, $opt{m} if (defined($opt{m}));
  92. }
  93. sub usage()
  94. {
  95. print STDERR << "EOF";
  96. $0 version $version
  97. Usage: $0 [-v] -h
  98. $0 [-vb] -f file -i id
  99. $0 [-vb] -m msg-id -i id
  100. $0 [-vb] -s query -i id
  101. Options:
  102. -b : generate a backout patch
  103. -f file : commit mail file to use ('-' for stdin)
  104. -h : this (help) message
  105. -i id : identifier used to save commit log message and patch
  106. -m msg-id : message-id referring to the original commit (you can use more than one)
  107. -s query : search commit mail archives (a filename with his revision is a good search)
  108. -v : be a little more verbose
  109. Examples:
  110. $0 -m 200601081417.k08EH4EN027418 -i uscanner
  111. $0 -s "param.h 1.41" -i move_acpi
  112. $0 -f commit.txt -i id
  113. $0 -m "200601081417.k08EH4EN027418 200601110806.k0B86m9C054798" -i consecutive
  114. Please report bugs to: Florent Thoumie <flz\@FreeBSD.org>
  115. EOF
  116. exit 1;
  117. }
  118. sub previous_revision($)
  119. {
  120. my ($rev) = @_;
  121. my @rev;
  122. # XXX - I'm not sure this is working as it should.
  123. return 0 if ($rev =~ /^1\.1$/);
  124. @rev = split '\.', $rev;
  125. return undef unless @rev;
  126. if (($#rev % 2) != 1) {
  127. pop @rev;
  128. return join ".", @rev;
  129. }
  130. if ($rev[-1] == 1) {
  131. pop @rev;
  132. return &previous_revision(join ".", @rev);
  133. } else {
  134. $rev[-1]--;
  135. return join ".", @rev;
  136. }
  137. }
  138. sub fetch_mail($)
  139. {
  140. my $msgid = $_[0];
  141. my $url = "";
  142. $msgid =~ s/<//;
  143. $msgid =~ s/>//;
  144. $msgid =~ s/@.*//;
  145. $url = `fetch -q -o - 'http://www.freebsd.org/cgi/mid.cgi?id=$msgid'| grep getmsg.cgi | head -n 1`;
  146. if ($url =~ /^$/) {
  147. print "No mail found for Message-Id <$msgid>.\n";
  148. exit 1;
  149. }
  150. $url =~ s/.*href="(.*)".*/$1/;
  151. $url =~ s/\n$/\+raw/;
  152. $url = "http://www.freebsd.org/cgi/$url";
  153. return $url;
  154. }
  155. sub search_mail($)
  156. {
  157. my $query = $_[0];
  158. $query =~ s/\s+/+/g;
  159. # XXX - I guess we could take 5 first results instead of just the first
  160. # but it has been working correctly for each search I've made so ...
  161. my $result = `fetch -q -o - 'http://www.freebsd.org/cgi/search.cgi?words=$query&max=1&sort=score&index=recent&source=cvs-all' | grep getmsg.cgi`;
  162. $result =~ s/.*href="(.*)">.*/http:\/\/www.freebsd.org\/cgi\/$1+raw/;
  163. if ($result =~ /^$/) {
  164. print "No commit mail found for '$query'.\n";
  165. exit 1;
  166. }
  167. return $result;
  168. }
  169. sub fetch_diff($)
  170. {
  171. my $name = $_[0];
  172. my $old = $mfc_files{$name}{"from"};
  173. my $new = $mfc_files{$name}{"to"};
  174. # CVSWeb uses rcsdiff instead of cvs rdiff, that's a problem for deleted and new files.
  175. # Need to use cvs to generate reversed diff for backout commits.
  176. if ($opt{b}) {
  177. print " Generating reversed diff for $name using cvs diff...\n";
  178. system("cvs -d $cvsroot diff -u -j$new -j$old $name >> $mfchome/$opt{i}/patch 2>/dev/null");
  179. } elsif (exists($new_files{$name}) or exists($dead_files{$name})) {
  180. print " Generating diff for $name using cvs rdiff...\n";
  181. system("cvs -d $cvsroot rdiff -u -r$old -r$new $name >> $mfchome/$opt{i}/patch 2>/dev/null");
  182. } else {
  183. print " Fetching diff for $name from cvsweb.freebsd.org...\n";
  184. system("fetch -q -o - \"http://www.freebsd.org/cgi/cvsweb.cgi/$name.diff?r1=$old&r2=$new\" >> $mfchome/$opt{i}/patch");
  185. }
  186. }
  187. sub mfc_headers($)
  188. {
  189. if ($_[0] =~ /^$/) {
  190. $mfc_func = \&mfc_author;
  191. } elsif ($_[0] =~ /^(\w+)\s+(\S+\s\S+\s\S+)$/) {
  192. # Skipped headers (probably a copy/paste from sobomax MFC reminder).
  193. mfc_author($_[0]);
  194. } else {
  195. if ($_[0] =~ /^Message-Id:\s*(\S+)$/ and ($opt{v} or $opt{s})) {
  196. print "Message-Id is $1.\n";
  197. }
  198. }
  199. }
  200. sub mfc_author($)
  201. {
  202. if (!($_[0] =~ /^(\w+)\s+(\S+\s\S+\s\S+)$/)) {
  203. die "Can't determine commit author and date.";
  204. }
  205. $commit_author = $1;
  206. $commit_date = $2;
  207. print "Committed by $commit_author on $commit_date.\n";
  208. $mfc_func = \&mfc_modified_files;
  209. }
  210. sub mfc_modified_files($)
  211. {
  212. if ($_[0] =~ /^\s+Log:/) {
  213. $mfc_func = \&mfc_log;
  214. } else {
  215. # Nothing
  216. }
  217. }
  218. sub mfc_log($)
  219. {
  220. if ($_[0] =~ /^\s*Revision\s+Changes\s+Path\s*$/) {
  221. $mfc_func = \&mfc_revisions;
  222. } else {
  223. push(@logmsg, $_[0]);
  224. }
  225. }
  226. sub mfc_revisions($)
  227. {
  228. my $name;
  229. my $rev;
  230. my $prev;
  231. return if ($_[0] =~ /^$/);
  232. if (!($_[0] =~ /^\s+(\S+)\s+\S+\s+\S+\s+(\S+)/)) {
  233. # Probably two consecutive cut/paste commit mails.
  234. $mfc_func = \&mfc_headers;
  235. mfc_headers($_[0]);
  236. return;
  237. } else {
  238. $_[0] =~ /\s+(\S+)\s+\S+\s+\S+\s+(\S+)/;
  239. $name = $2;
  240. $rev = $1;
  241. $new_files{$name} = undef if ($_[0] =~ /\(new\)$/);
  242. $dead_files{$name} = undef if ($_[0] =~ /\(dead\)$/);
  243. if (defined($mfc_files{$name}{"from"})) {
  244. $prev = previous_revision($rev);
  245. if ($mfc_files{$name}{"to"} =~ /^$prev$/) {
  246. $mfc_files{$name}{"to"} = $rev;
  247. } else {
  248. die "Non-consecutive revisions found for $name.";
  249. }
  250. } else {
  251. $mfc_files{$name}{"to"} = $rev;
  252. $mfc_files{$name}{"from"} = previous_revision($rev);
  253. }
  254. }
  255. }
  256. sub strip_log(@) {
  257. my $tmp;
  258. while ($#logmsg >= 0 and ($logmsg[$#logmsg] =~ /^\s*$/ or $logmsg[$#logmsg] =~ /^\s\s\w+(\s\w+)*:\s+\w+(\s+\w+)*/)) {
  259. $tmp = pop(@logmsg);
  260. $tmp =~ s/^\s*//;
  261. chomp($tmp);
  262. if ($tmp =~ /^PR:\s+(.*)/) {
  263. push(@prs, $1);
  264. }
  265. if ($tmp =~ /^Submitted by:\s+(.*)/) {
  266. push(@submitted_by, $1);
  267. }
  268. if ($tmp =~ /^Reviewed by:\s+(.*)/) {
  269. push(@reviewed_by, $1);
  270. }
  271. if ($tmp =~ /^Obtained from:\s+(.*)/) {
  272. push(@obtained_from, $1);
  273. }
  274. }
  275. }
  276. sub print_epilog {
  277. my $tmp;
  278. if ($#prs >= 0) {
  279. $tmp = join(", ", @prs);
  280. chomp($tmp);
  281. print MSG "PR:\t\t$tmp\n";
  282. }
  283. if ($#submitted_by >= 0) {
  284. $tmp = join(", ", @submitted_by);
  285. chomp($tmp);
  286. print MSG "Submitted by:\t$tmp\n";
  287. }
  288. if ($#reviewed_by >= 0) {
  289. $tmp = join(", ", @reviewed_by);
  290. chomp($tmp);
  291. print MSG "Reviewed by:\t$tmp\n";
  292. }
  293. if ($#obtained_from >= 0) {
  294. $tmp = join(", ", @obtained_from);
  295. chomp($tmp);
  296. print MSG "Obtained from:\t$tmp\n";
  297. }
  298. }
  299. init();
  300. if ($opt{s}) {
  301. print "Searching commit mail on www.freebsd.org...\n";
  302. $commiturl = search_mail($opt{s});
  303. print "Fetching commit mail from www.freebsd.org...\n";
  304. @commitmail = `fetch -q -o - $commiturl`;
  305. $mfc_func->($_) foreach (@commitmail);
  306. strip_log(@logmsg);
  307. } elsif ($opt{f}) {
  308. open MAIL, $opt{f} || die "Can't open $opt{f} for reading.";
  309. @commitmail = <MAIL>;
  310. close MAIL;
  311. $mfc_func->($_) foreach (@commitmail);
  312. strip_log(@logmsg);
  313. } else { # $opt{m}
  314. foreach (@msgids) {
  315. print "Fetching commit mail from www.freebsd.org...\n";
  316. $commiturl = fetch_mail($_);
  317. @commitmail = `fetch -q -o - $commiturl`;
  318. $mfc_func->($_) foreach (@commitmail);
  319. strip_log(@logmsg);
  320. }
  321. }
  322. die "Doesn't seem you gave me a real commit mail." if ($mfc_func == \&mfc_headers);
  323. die "No file affected by commit?" if (scalar(keys(%mfc_files)) == 0);
  324. # Create directory and truncate patch file.
  325. system("mkdir -p $mfchome/$opt{i}");
  326. system("cat /dev/null > $mfchome/$opt{i}/patch");
  327. if ($opt{v} or $opt{s}) {
  328. # Print files touched by commit(s).
  329. print "Files touched by commit(s):\n";
  330. print " ", $_, ": rev ", $mfc_files{$_}{"from"}, " -> ", $mfc_files{$_}{"to"}, "\n" foreach (keys(%mfc_files));
  331. }
  332. if ($opt{s}) {
  333. print "Is it the commit you were looking for ? [Yn] ";
  334. $answer = <STDIN>;
  335. chomp($answer);
  336. if ($answer =~ /^[Nn]$/) {
  337. print "Sorry that I couldn't help you.\n";
  338. exit 0;
  339. }
  340. }
  341. # Generating patch.
  342. print "Processing patch...\n";
  343. fetch_diff($_) foreach (keys(%mfc_files));
  344. if ($mfclogin) {
  345. # Create commit message from previous commit message.
  346. print "Processing commit message...\n";
  347. # Chop empty lines Template lines like "Approved by: (might be dangerous)".
  348. open MSG, "> $mfchome/$opt{i}/msg" || die "Can't open $mfchome/$opt{i}/msg for writing.";
  349. if ($opt{b}) {
  350. print MSG "Backout this commit:\n\n";
  351. } else {
  352. print MSG "MFC:\n\n";
  353. }
  354. # Append merged file names and revisions to the commit message.
  355. print MSG $_ foreach (@logmsg);
  356. if (!$opt{b}) {
  357. print MSG "\n";
  358. print MSG " ", $_, ": rev ", $mfc_files{$_}{"from"}, " -> ", $mfc_files{$_}{"to"}, "\n" foreach (keys(%mfc_files));
  359. }
  360. # Append useful info gathered from Submitted/Obtained/... lines.
  361. print MSG "\n";
  362. print_epilog();
  363. close MSG;
  364. # Create commit script.
  365. print "Processing commit script...\n";
  366. open SCRIPT, "> $mfchome/$opt{i}/script" || die "Can't open $mfchome/$opt{i}/script for writing.";
  367. print SCRIPT "#! /bin/sh\n\n";
  368. print SCRIPT "# This script has been automatically generated by $0.\n\n";
  369. print SCRIPT "export CVSROOT=\"$mfclogin\@ncvs.freebsd.org:/home/ncvs\"\n\n";
  370. if (scalar(keys(%new_files)) or scalar(keys(%dead_files))) {
  371. if (scalar(keys(%new_files))) {
  372. print SCRIPT "cvs add";
  373. print SCRIPT " \\\n $_" foreach (keys(%new_files));
  374. print SCRIPT "\n";
  375. }
  376. if (scalar(keys(%dead_files))) {
  377. print SCRIPT "cvs rm -f";
  378. print SCRIPT " \\\n $_" foreach (keys(%dead_files));
  379. print SCRIPT "\n";
  380. }
  381. }
  382. print SCRIPT "cvs diff";
  383. print SCRIPT " \\\n $_" foreach (keys(%mfc_files));
  384. if ($cdiff =~ /^$/) {
  385. print SCRIPT "\n";
  386. } else {
  387. print SCRIPT " | $cdiff";
  388. }
  389. print SCRIPT "cvs ci";
  390. print SCRIPT " \\\n $_" foreach (keys(%mfc_files));
  391. print SCRIPT "\n";
  392. close SCRIPT;
  393. system("chmod a+x $mfchome/$opt{i}/script");
  394. }
  395. print "Done, output directory is $mfchome/$opt{i}/\n";
  396. exit 0;