home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume39 / makepatch / part01 / makepatch.pl < prev    next >
Encoding:
Perl Script  |  1993-09-12  |  11.4 KB  |  449 lines

  1. #!/usr/local/bin/perl
  2. # makepatch.pl -- generate batch of patches.
  3. # SCCS Status     : @(#)@ makepatch.pl    1.8
  4. # Author          : Johan Vromans
  5. # Created On      : Tue Jul  7 20:39:39 1992
  6. # Last Modified By: Johan Vromans
  7. # Last Modified On: Sat Sep 11 23:00:27 1993
  8. # Update Count    : 129
  9. # Status          : Released to USEnet.
  10. #
  11. # Generate a patch from two files or directories.
  12. #
  13. # Resembles "diff -c -r -N", but:
  14. #
  15. #   - always recursive
  16. #   - handles 'patchlevel.h' first
  17. #   - supplies 'Index:' and 'Prereq:' lines
  18. #   - can use manifest file
  19. #   - generates shell commands to remove files
  20. #   - manipulates manifest files
  21. #
  22. ################################################################
  23. #
  24. # Usage:
  25. #   makepatch <old-dir> <new-dir
  26. #     This will compare all files in <new-dir> against the files in
  27. #     <old-dir>, and generate a bunch of patches to transform every
  28. #     file in <old-dir> into the corresponding one in <new-dir>.
  29. #     Files that appear in <new-dir> but not in <old-dir> are created.
  30. #     For files that appear in <old-dir> but not in <new-dir>
  31. #     'rm'-commands are generated at the beginning of the patch.
  32. # Using MANIFEST files:
  33. #   makepatch -oldmanifest <oldmanifest> -newmanifest <newmanifest> \
  34. #           <new-dir> <old-dir>
  35. #     <oldmanifest> and <newmanifest> list the files in <old-dir>
  36. #     and <new-dir> that are to be examined.
  37. #     Only the files that are named will be examined. 
  38. #     <oldmanifest> should contain the names of the files relative to
  39. #     <old-dir> and <newmanifest> should contain the names of the files
  40. #     relative to <new-dir>.
  41. #   makepatch -manifest <manifest> <new-dir> <old-dir>
  42. #     This is a simplified form of the above example.
  43. #     <manifest> applies to both <old-dir> and <new-dir>.
  44. #   makepatch -filelist [ -prefix xxx ] manifest
  45. #
  46. #     The filenames are extracted from the manifest file,
  47. #     optionally prefixed, sorted and written to standard output.
  48. #
  49. # Examples:
  50. #   % makepatch -verbose emacs-18.58 emacs-18.59 > emacs-18.58-18.59.diff
  51. #   % (cd emacs-18.58; find . -type f -print > MANIFEST)
  52. #   % (cd emacs-18.59; find . -type f -print > MANIFEST)
  53. #   % makepatch -verbose \
  54. #         -oldmanifest emacs-18.58/MANIFEST \
  55. #         -newmanifest emacs-18.59/MANIFEST \
  56. #         emacs-18.58 emacs-18.59 > emacs-18.58-18.59.diff
  57. #
  58. #   % makepatch -filelist -prefix emacs-18.59/ emacs-18.59/MANIFEST |
  59. #    gtar -zcvf emacs-18.59.tar.Z -T -
  60. #
  61. ################################################################
  62.  
  63. &stdio;
  64. &options;
  65. ($old, $new) = @ARGV;
  66.  
  67. print STDERR ("This is makepatch.pl version 1.8\n") if $opt_verbose;
  68.  
  69. if ( defined $opt_filelist ) {
  70.     @new = &domanifest (shift (@ARGV));
  71.     foreach ( @new ) {
  72.     print STDOUT ($opt_prefix, $_, "\n");
  73.     }
  74.     exit (0);
  75. }
  76.  
  77. $tmpfile = $ENV{"TMPDIR"} || "/usr/tmp";
  78. $thepatch = "$tmpfile/mp$$.p";
  79. $tmpfile .= "/mp$$.t";
  80. open (PATCH, ">$thepatch") || die ("$thepatch: $!\n");
  81. $patched = $created = 0;
  82. &doit ($old, $new);
  83. &wrapup;
  84. exit (0);
  85.  
  86. ################ Subroutines ################
  87.  
  88. sub doit {
  89.     local ($old, $new) = @_;
  90.  
  91.     if ( -f $old && -f $new ) {
  92.     # Two files.
  93.     if ( $opt_verbose ) {
  94.         print STDERR ("Old file = $old.\n",
  95.               "New file = $new.\n");
  96.     }
  97.     &dodiff ("", $old, "", $new);
  98.     }
  99.     elsif ( -f $old && -d $new ) {
  100.     # File and dir -> File and dir/File.
  101.     $new = ( $new =~ m|^\./?$| ) ? "" : "$new/";
  102.     if ( $opt_verbose ) {
  103.         print STDERR ("Old file = $old.\n",
  104.               "New file = $new$old.\n");
  105.     }
  106.     &dodiff ("", $old, $new, $old);
  107.     }
  108.     elsif ( -f $new && -d $old ) {
  109.     $old = ( $old =~ m|^\./?$| ) ? "" : "$old/";
  110.     if ( $opt_verbose ) {
  111.         print STDERR ("Old file = $old$new.\n",
  112.               "New file = $new.\n");
  113.     }
  114.     &dodiff ($old, $new, "", $new);
  115.     }
  116.     else {
  117.     # Should be two directories.
  118.     local (@old, @new);
  119.     if ( defined $opt_oldmanifest ) {
  120.         @old = &domanifest ($opt_oldmanifest);
  121.     }
  122.     else {
  123.         @old = &make_filelist ($old);
  124.     }
  125.     if ( defined $opt_newmanifest ) {
  126.         @new = &domanifest ($opt_newmanifest);
  127.     }
  128.     else {
  129.         @new = &make_filelist ($new);
  130.     }
  131.  
  132.     $new = ( $new =~ m|^\./?$| ) ? "" : "$new/";
  133.     $old = ( $old =~ m|^\./?$| ) ? "" : "$old/";
  134.  
  135.     if ( $opt_verbose ) {
  136.         local ($old) = $old; chop ($old);
  137.         local ($new) = $new; chop ($new);
  138.         print STDERR ("Old dir = $old, file list = ",
  139.               defined $opt_oldmanifest ? $opt_oldmanifest : "<*>",
  140.               ", ", 0+@old, " files.\n");
  141.         print STDERR ("New dir = $new, file list = ",
  142.               defined $opt_newmanifest ? $opt_newmanifest : "<*>",
  143.               ", ", 0+@new, " files.\n");
  144.     }
  145.     if ( $opt_debug ) {
  146.         print STDERR ("Old: @old\nNew: @new\n");
  147.     }
  148.  
  149.     # Handle patchlevel file first.
  150.     $opt_patchlevel = (grep (/patchlevel\.h/, @new))[0]
  151.         unless defined $opt_patchlevel;
  152.  
  153.     if ( defined $opt_patchlevel && $opt_patchlevel ne "" ) {
  154.         if ( ! -f "$new$opt_patchlevel" ) {
  155.         die ("$new$opt_patchlevel: $!\n");
  156.         }
  157.         if ( -f "$old$opt_patchlevel" ) {
  158.         &dodiff ($old, $opt_patchlevel, $new, $opt_patchlevel);
  159.         }
  160.         else {
  161.         $created++;
  162.         &dodiff ("", "/dev/null", $new, $opt_patchlevel);
  163.         }
  164.     }
  165.     else {
  166.         undef $opt_patchlevel;
  167.     }
  168.  
  169.     # Process the filelists.
  170.     while ( @old + @new ) {
  171.  
  172.         $o = shift (@old) unless defined $o;
  173.         $n = shift (@new) unless defined $n;
  174.         
  175.         if ( !defined $o || $o gt $n ) {
  176.         # New file.
  177.         if ( defined $opt_patchlevel && $n eq $opt_patchlevel ) {
  178.             undef $opt_patchlevel;
  179.         }
  180.         else {
  181.             $created++;
  182.             &dodiff ("", "/dev/null", $new, $n);
  183.         }
  184.         undef $n;
  185.         }
  186.         elsif ( !defined $n || $o lt $n ) {
  187.         # Obsolete (removed) file.
  188.         push (@goners, $o);
  189.         undef $o;
  190.         }
  191.         elsif ( $o eq $n ) {
  192.         # Same file.
  193.         if ( defined $opt_patchlevel && $n eq $opt_patchlevel ) {
  194.             undef $opt_patchlevel;
  195.         }
  196.         else {
  197.             &dodiff ($old, $o, $new, $n);
  198.         }
  199.         undef $n;
  200.         undef $o;
  201.         }
  202.     }
  203.     }
  204. }
  205.  
  206. sub make_filelist {
  207.     local ($dir, $disp) = @_;
  208.  
  209.     # Return a list of files, sorted, for this directory.
  210.     # Recurses.
  211.  
  212.     local (@ret);
  213.     local (*DIR);
  214.     local (@tmp);
  215.     local ($fname);
  216.  
  217.     $disp = "" unless defined $disp;
  218.  
  219.     print STDERR ("+ recurse $dir\n") if $opt_trace;
  220.     opendir (DIR, $dir) || die ("$dir: $!\n");
  221.     @tmp = sort (readdir (DIR));
  222.     closedir (DIR);
  223.     print STDERR ("Dir $dir: ", 0+@tmp, " entries\n") if $opt_debug;
  224.  
  225.     @ret = ();
  226.     foreach $file ( @tmp ) {
  227.  
  228.     # Skip unwanted files.
  229.     next if $file =~ /^\.\.?$/; # dot and dotdot
  230.     next if $file =~ /~$/;    # editor backup files
  231.  
  232.     # Push on the list.
  233.     $fname = "$dir/$file";
  234.     if ( -d $fname && ( $opt_follow || ! -l $fname ) ) {
  235.         # Recurse.
  236.         push (@ret, &make_filelist ($fname, "$disp$file/"));
  237.     }
  238.     elsif ( -f _ ) {
  239.         push (@ret, $disp . $file);
  240.     }
  241.     else {
  242.         print STDERR ("Ignored $fname: not a file\n");
  243.     }
  244.     }
  245.     @ret;
  246. }
  247.  
  248. sub domanifest {
  249.     local ($man) = @_;
  250.     local (*MAN);
  251.     local (@ret) = ();
  252.  
  253.     open (MAN, $man) || die ("$man: $!\n");
  254.     while ( <MAN> ) {
  255.     if ( $. == 2 && /^[-=_\s]*$/ ) {
  256.         @ret = ();
  257.         next;
  258.     }
  259.     next if /^#/;
  260.     next unless /\S/;
  261.     $_ = $` if /\s/;
  262.     push (@ret, $_);
  263.     }
  264.     close (MAN);
  265.     @ret = sort @ret unless defined $opt_nosort;
  266.     @ret;
  267. }
  268.  
  269. sub dodiff {
  270.     local ($olddir, $old, $newdir, $new) = @_;
  271.  
  272.     # Produce a patch hunk.
  273.  
  274.     local ($cmd) = "$opt_diff '$olddir$old' '$newdir$new'";
  275.     print STDERR ("+ ", $cmd, "\n") if $opt_trace;
  276.     $result = system ("$cmd > $tmpfile");
  277.     printf STDERR ("+> result = 0x%x\n", $result) 
  278.     if $result && $opt_debug;
  279.  
  280.     return unless $result == 0x100;    # no diffs
  281.     $patched++;
  282.  
  283.     # print PATCH ($cmd, "\n");
  284.     print PATCH ("Index: ", $new, "\n");
  285.  
  286.     # Try to find a prereq.
  287.     # The RCS code is based on a suggestion by jima@netcom.com, who also
  288.     # pointed out that patch requires blanks around the prereq string.
  289.     open (OLD, $olddir . $old);
  290.     while ( <OLD> ) {
  291.     next unless (/\@\(\#\)/        # SCCS header
  292.              || /\$Header:/     # RCS Header
  293.              || /\$Id:/);     # RCS Header
  294.     next unless $' =~ /\s\d+(\.\d+)*\s/; # e.g. 5.4
  295.     print PATCH ("Prereq: $&\n");
  296.     last;
  297.     }
  298.     close (OLD);
  299.  
  300.     # Copy patch.
  301.     open (TMP, $tmpfile);
  302.     print PATCH <TMP>;
  303.     close (TMP);
  304. }
  305.  
  306. sub wrapup {
  307.     if ( $opt_verbose ) {
  308.     local ($goners) = scalar (@goners);
  309.     print STDERR ("Collecting: $patched patch",
  310.               $patched == 1 ? "" : "es");
  311.     print STDERR (" ($created new file", 
  312.               $created == 1 ? "" : "s", ")") if $created;
  313.     print STDERR (", $goners goner", 
  314.               $goners == 1 ? "" : "s") if $goners;
  315.     print STDERR (".\n");
  316.     }
  317.     if ( @goners ) {
  318.     print STDOUT 
  319.         ("# Please remove the following file",
  320.          @goners == 1 ? "" : "s", " before applying this patch.\n",
  321.          "# (You can feed this patch to 'sh' to do so.)\n",
  322.          "\n");
  323.     foreach ( @goners ) {
  324.         print STDOUT ("rm -f ", $_, "\n");
  325.     }
  326.     print STDOUT ("exit\n\n");
  327.     }
  328.  
  329.     # Copy patch.
  330.     open (PATCH, $thepatch);
  331.     print while <PATCH>;
  332.     close (PATCH);
  333.  
  334.     # Cleanup.
  335.     unlink ($tmpfile, $thepatch);
  336. }
  337.  
  338. sub stdio {
  339.     # Since output to STDERR seems to be character based (and slow),
  340.     # we connect STDERR to STDOUT if they both point to the terminal.
  341.     if ( -t STDOUT && -t STDERR ) {
  342.     close (STDERR);
  343.     open (STDERR, '>&STDOUT');
  344.     select (STDERR); $| = 1;
  345.     select (STDOUT);
  346.     }
  347. }
  348.  
  349. sub options {
  350.     local ($opt_manifest);
  351.     local ($opt_quiet);
  352.  
  353.     # Defaults...
  354.     $opt_diff = "diff -c";
  355.     $opt_verbose = 1;
  356.     $opt_follow = 0;
  357.  
  358.     # Process options, if any...
  359.     if ( $ARGV[0] =~ /^-/ ) {
  360.     require "newgetopt.pl";
  361.  
  362.     # Aliases.
  363.     *opt_man = *opt_manifest;
  364.     *opt_oldman = *opt_oldmanifest;
  365.     *opt_newman = *opt_newmanifest;
  366.     *opt_v = *opt_verbose;
  367.     *opt_list = *opt_filelist;
  368.  
  369.     if ( ! &NGetOpt ("patchlevel=s", "diff=s", 
  370.              "manifest=s", "newmanifest=s", "oldmanifest=s",
  371.              "man=s", "newman=s", "oldman=s", "follow",
  372.              "list", "filelist", "prefix=s", "nosort",
  373.              "quiet", "verbose", "v", "help", "debug", "trace")
  374.         || defined $opt_help ) {
  375.         &usage;
  376.     }
  377.     $opt_trace = 1 if defined $opt_debug;
  378.     $opt_verbose = 0 if defined $opt_quiet;
  379.     if ( defined $opt_prefix ) {
  380.         die ("$0: option \"-prefix\" requires \"-filelist\"\n")
  381.         unless defined $opt_filelist;
  382.     }
  383.     if ( defined $opt_nosort ) {
  384.         die ("$0: option \"-nosort\" requires \"-filelist\"\n")
  385.         unless defined $opt_filelist;
  386.     }
  387.     if ( defined $opt_filelist ) {
  388.         die ("$0: option \"-filelist\" only uses \"-manifest\"\n")
  389.         if defined $opt_oldmanifest || defined $opt_newmanifest;
  390.     }
  391.     if ( defined $opt_manifest ) {
  392.         die ("$0: do not use \"-manifest\" with \"-oldmanifest\"".
  393.          " or \"-newmanifest\"\n")
  394.         if defined $opt_newmanifest || defined $opt_oldmanifest;
  395.         $opt_newmanifest = $opt_oldmanifest = $opt_manifest;
  396.     }
  397.     }
  398.  
  399.     # Argument check.
  400.     if ( defined $opt_filelist ) {
  401.     if ( defined $opt_manifest ) {
  402.         &usage if @ARGV;
  403.         @ARGV = ( $opt_manifest );
  404.     }
  405.     else {
  406.         &usage unless @ARGV == 1;
  407.     }
  408.     }
  409.     else {
  410.     &usage unless @ARGV == 2;
  411.     }
  412. }
  413.  
  414. sub usage {
  415.     print STDERR <<EoU;
  416. This is makepatch.pl version 1.8
  417.  
  418. Usage: $0 [options] old new
  419. Usage: $0 -filelist [ -prefix XXX ] [ -nosort ] [ -manifest ] file
  420.  
  421. Makepatch options:
  422.    -diff cmd        diff command to use, default \"$opt_diff\"
  423.    -patchlevel file    file to use as patchlevel.h
  424.    -man[ifest] file    list of files for old and new dir
  425.    -newman[ifest] file    list of files for new dir
  426.    -oldman[ifest] file    list of files for old dir
  427.    -follow        follow symbolic links
  428. Filelist options:
  429.    -[file]list        extract filenames from manifest file
  430.    -prefix XXX        add a prefix to these filenames
  431.    -nosort        do not sort manifest entries
  432. General options:
  433.    -verbose        verbose output (default)
  434.    -quiet        no verbose output
  435.    -help        this message
  436. EoU
  437.     exit (1);
  438. }
  439.