#!/usr/bin/perl -w ############################################################################### # ----- BEGIN LICENSE BLOCK ----- # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Patch Maker. # # The Initial Developer of the Original Code is # Gervase Markham . # Portions created by the Initial Developer are Copyright (C) 2002 # the Initial Developer. All Rights Reserved. # # Contributor(s): # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # This software was written to the Glory of God. # ----- END LICENSE BLOCK ----- ############################################################################### ############################################################################### # This is version $pm_version = "2.0pre2"; # of Patch Maker. # # Patch Maker currently requires the following external programs: # unzip, diff (build mode), cvs (cvs mode), patch, your editor ############################################################################### ############################################################################### # # Configuration - fill in your values here. # # $editor - the name of your editor my $editor = "nedit"; # http://www.nedit.org . It rocks. # # $editor_modal - Set to "&" if you have a graphical editor, "" if you have # a text mode editor. This has no effect on Windows. my $editor_modal = "&"; # # $datadir - where you want Patch Maker to keep its files. Remember to use # "\\" for "\" on Windows. my $datadir = "/home/gerv/pm"; # # $cvsroot - set this if you use a different cvs login for checkin and # add ops. E.g. I do most stuff against cvs-mirror but check # in to cvs.mozilla.org. my $cvsroot = ':pserver:gerv%gerv.net@cvs.mozilla.org:/cvsroot'; # # $exepath - location of exe to run in relation to set buildpath (pmsp). # - If you don't understand this, leave it as it is for Mozilla. my $exepath = File::Spec->catfile(File::Spec->updir(), "mozilla"); # ############################################################################### ############################################################################### # # Subroutines # ############################################################################### use File::Spec; use File::Copy; use File::Basename; use Cwd; ############################################################################### # Return the CVS or build (depending on mode) files as a list or # space-separated string. Returns empty list or string if there are no files. ############################################################################### sub get_file_list() { # If there's no file list, but there is a patch, we need to get the # filenames out of it. if (!-e $file_list && -e $cvsdiff_file) { open(CVSDIFF, "<$cvsdiff_file") or die "$execname: Can't open file '$cvsdiff_file' for reading. $!\n"; open(FILELIST, ">$file_list") or die "$execname: Can't open file '$file_list' for writing. $!\n"; while () { if (/Index: (.*)/) { # Currently, we only do this for Unix, because find isn't # guaranteed to exist on Windows. if (!-e $1 && $unix) { # If the file doesn't have the right path on it, we work out the # filename and try and track it down. $1 =~ /.*\/(.*)/; print FILELIST `find . -name $1`; } else { print FILELIST $1 . "\n"; } } } } open(FILELIST, "<$file_list") or return wantarray ? () : ""; my @files; while (my $key = ) { chomp $key; # Backwards compatibility stuff next if ($key =~ /\s*#/); # Comments last if ($key =~ /^---/); # Separator # Forwards compatibility stuff - later, this may be used for metadata. # It allows @foo=bar for key/value per-patch metadata, and # filename@foo=bar&baz=quux for per-file metadata. next if ($key =~ /^\@/); $key =~ s/(.*)\@.*/$1/ if ($key =~ /\@/); next if ($key eq ""); push(@files, $key); } close FILELIST; return wantarray ? @files : join(" ", @files); } ############################################################################### # Translate a cvs file to a build file or vice versa. This function abstracts # the difference between the two. ############################################################################### sub findmatchingfile( $ ) { return ($mode eq "cvs") ? findbuildfile(@_) : findcvsfile(@_); } ############################################################################### # Find the CVS file for a given build file. # Returns undef if chromelist file is not found or no matches in it. ############################################################################### sub findcvsfile( $ ) { my ($file) = @_; chomp $file; open(CHROMELIST, "<$chromelist_file") or return undef; while () { last if m/\Q$file\E\s+\(/; } $_ && s/.*\((.*)\).*\n?/$1/; return $_; } ############################################################################### # Find the build file for a given CVS file. # Returns undef if chromelist file is not found or no matches in it. ############################################################################### sub findbuildfile( $ ) { my ($file) = @_; chomp $file; open(CHROMELIST, "<$chromelist_file") or return undef; while () { last if m/\Q$file)\E/; } $_ && s/([^\s]+)\s+\(.*\).*\n?/$1/; return $_; } ############################################################################### # Print and run a given command line. ############################################################################### sub pexec( $ ) { my ($exec) = @_; print $exec . "\n"; system("$exec"); } ############################################################################### # Print a "deprecated" message. ############################################################################### sub deprecate( $ ) { print "*" x 78 . "\n" . " Note: The name you used for this command is deprecated, and may " . "disappear.\n The new name for this command is $_[0].\n" . "*" x 78 . "\n"; } ############################################################################### # Print a "deprecated" message. ############################################################################### sub ask( $ ) { my $message = (shift @_) . " [y]? "; print $message; return ( =~ m/y(es)?\n/i); } ############################################################################### # # Functions implementing Patch Maker commands # ############################################################################### ############################################################################### # Add # Status: DONE, TESTED # Adds one or more filenames to the file list. ############################################################################### sub pmadd( @ ) { my @files = get_file_list(); my @addfiles; if (!scalar(@_)) { print "$execname: Please give some files to add to the current patch.\n"; } foreach my $addfile (@_) { if (grep /^\Q$addfile\E$/, @files) { print "$execname: '$addfile' is already part of this patch.\n"; } elsif (!-e $addfile) { print "File '$addfile' does not exist - not adding.\n"; } else { print "Adding file '$addfile'.\n"; copy("$addfile", "$addfile.bak") if ($mode eq "build"); push (@addfiles, $addfile); } } # Add the new files to the end of the list open(FILELIST, ">>$file_list") or die "$execname: Can't open file '$file_list' for appending. $!\n"; foreach my $addfile (@addfiles) { print FILELIST $addfile . "\n"; } close FILELIST; } ############################################################################### # CVS Add (cvs mode only) # Status: DONE, but horrible impl; needs rewriting # Need to check the status of each file in the CVS special files, # and only add the ones which have not been added. # # Does a cvs add on all files in the file list which have not been added. ############################################################################### sub pmcvsadd() { pexec("cvs " . ($cvsroot ? "-d$cvsroot " : "") . "-z3 add " . get_file_list()); } ############################################################################### # Checkin (cvs mode only) # Status: DONE # Does a cvs checkin on all files in the file list. ############################################################################### sub pmcheckin() { # All other args to pmcheckin get passed to cvs as post-mode commands. # Good for checking in on branches. my $args = join(' ', @ARGV); # We need to be really sure we know what we are doing here... pmwhich(); pmupdate(); pmcvsdiff(); pmview(); print "*" x 60 . "\n" . "*** Have you made sure that the tree is green and open? ***\n" . "*" x 60 . "\n\n"; if (ask("Are you sure you want to check in")) { pexec("cvs " . ($cvsroot ? "-d$cvsroot " : "") . "-z3 -e$editor ci $args " . get_file_list()); } else { print ("$execname: Checkin aborted.\n"); } } ############################################################################### # Copy (cvs mode only) # Status - pm1 copy - needs work ############################################################################### sub pmcopy() { if (!$buildpath) { print "$execname: No buildpath set - use pmsp to set one.\n"; exit; } # We force a copy of all files on -f or if we haven't copied this patch # before. my $arg = shift @ARGV || ""; my $force; $force = 1 if ($arg eq "-f" || (!(-e File::Spec->catfile($buildpath, "$patchref.copied")))); my @files = get_file_list(); foreach $cvsfile (@files) { $buildfile = findmatchingfile($cvsfile); if (!$buildfile) { print "Not copying '$cvsfile' - can't find correct chrome path.\n"; next; } $buildfile = File::Spec->catfile($buildpath, $buildfile); # Make sure the files exist, otherwise the user has probably got their # files list wrong. if (!(-e $cvsfile)) { print " Warning: original file '$cvsfile' does not exist.\n"; next; } elsif (!(-e $buildfile)) { next if (!ask("Warning: '$buildfile' does not exist. Copy anyway")); } else { # Abort if the file has not changed and we are not forced. # Note: -M gives "days since last modification", so larger == older next if ((-M $cvsfile > -M $buildfile) && !$force); } print "cp $cvsfile\n -> $buildfile\n"; copy($cvsfile, $buildfile); } # 'touch' my $copied_file = File::Spec->catfile($buildpath, "$patchref.copied"); open(FILELIST, ">>$copied_file") or die "$execname: Can't touch file '$copied_file'. $!\n"; close FILELIST; } ############################################################################### # Diff # Status: DONE, TESTED ############################################################################### sub pmdiff() { return ($mode eq "cvs") ? pmcvsdiff() : pmbuilddiff(); } ############################################################################### # Diff (build mode only) # Status: DONE ############################################################################### sub pmbuilddiff() { # All other args to pmdiff get passed to diff my $args = join(' ', @ARGV); my @files = get_file_list(); # Create the new diff my $diff = ""; foreach my $file (@files) { $diff .= `diff -u $args $file.bak $file`; } # If the new diff has zero size, don't clobber the old one. if ($diff ne "") { # Write the chrome version of the diff open(CHROMEDIFF, ">$chromediff_file") or die "$execname: Can't open file '$chromediff_file' for writing. $!\n"; print CHROMEDIFF $diff; close CHROMEDIFF; # Change to the CVS file paths my @badfiles; foreach my $buildfile (@files) { my $cvsfile = findcvsfile($buildfile); if ($cvsfile) { $cvsfile = File::Spec->canonpath($cvsfile); $diff =~ s/\Q$buildfile\E/$cvsfile/g; } else { push (@badfiles, $buildfile); } } if (@badfiles) { print "\nWarning: no CVS equivalent found for the following files:\n"; foreach (@badfiles) { print "$_\n"; } print "\nThe CVS version of your patch may not work correctly.\n"; } # Write the cvs version of the diff open(DIFF, ">$cvsdiff_file") or die "$execname: Can't open file '$cvsdiff_file' for writing. $!\n"; print DIFF $diff; close DIFF; print "Patch size is "; print -s "$cvsdiff_file"; print " bytes\n"; } else { print "\n*** Warning: diff has zero size. Not overwriting. ***\n\n"; } } ############################################################################### # Diff (cvs mode only) # Status: DONE ############################################################################### sub pmcvsdiff() { # All other args to pmcvsdiff get passed to diff my $args = join(' ', @ARGV); # 'touch' open(FILELIST, ">>$cvsdiff_file") or die "$execname: Can't touch file '$cvsdiff_file'. $!\n"; close FILELIST; # Back up the old diff move($cvsdiff_file, "$cvsdiff_file.bak"); my $files = get_file_list(); if (!$files) { # Avoid running a cvs diff on the entire tree. print "$execname: No files in the file list to diff.\n"; exit; } # Create the new one pexec("cvs diff -uN $args $files > $cvsdiff_file\n"); # If it has zero size, restore the backup. This guards against the # situation where they forgot to apply any patches before running # diff - otherwise they might clobber their only copy. Otherwise, # print size info and clear up. if (-s "$cvsdiff_file") { print "Patch size is "; print -s "$cvsdiff_file"; print " bytes\n"; unlink "$cvsdiff_file.bak"; } else { print "\n*** Warning: diff has zero size. Not overwriting. ***\n\n"; move("$cvsdiff_file.bak", $cvsdiff_file); return 0; } return 1; } ############################################################################### # Edit # Status: DONE, TESTED ############################################################################### sub pmedit() { my @editfiles; my @files = get_file_list(); $files[0] || pmlist() && exit; # Arguments to pmedit are interpreted as globs of files in the list - # e.g. pmedit *.cpp . my $regexp = shift @ARGV; # If no arguments, edit them all. @editfiles = @files if !$regexp; # Can have more than one glob; you get the file if it matches any. while ($regexp) { # The following code attempts to turn a glob syntax into a regexp one # by first escaping all special regexp symbols from file's pathname... $regexp =~ s/([\[\$\\({}).+\/])/\\$1/g; # ...and converting glob syntax to regexp. $regexp =~ s/\*/.\*/g; $regexp =~ s/\?/.?/g; $regexp =~ s/\#/[0-9]/g; push(@editfiles, grep(/$regexp/i, @files)); $regexp = shift @ARGV; } if (@editfiles) { $editfiles = '"' . join( '" "', @editfiles ) . '"'; $editfiles =~ s/\s+$//; pexec("$editor $editfiles $editor_modal"); } elsif (@files) { print "$execname: No filenames match the string given.\n"; } else { # No files. pmlist(); } exit; } ############################################################################### # Grep # Status: DONE, TESTED ############################################################################### sub pmgrep() { my @files = get_file_list(); my $pattern = join(" ", @ARGV); print "\n"; foreach my $file (@files) { open(FILELIST, "<$file"); while () { if (m/$pattern/i) { print "* " . $file . " : " . $. . " :\n" . $_; } } close FILELIST; } } ############################################################################### # Help # Status: To be updated ############################################################################### sub pmhelp() { print <<"EOF"; Patch Maker Command Reference ----------------------------- Use either the short form (pml) or the long form (pmlist) of any command. The abbreviation is indicated by capital letters. Patch Maker has two modes - CVS mode for working with any CVS tree, and build mode for working with a Mozilla nightly build (no CVS tree required.) Commands available in both CVS and build modes: pmHelp - prints this message. pmList - prints the list of files in the current patch. In build mode, use -a ("all") to get the CVS equivalents also. pmDiff - does a diff -u for all the files in the file list. Extra arguments to this command are passed through to diff - for example, "-w". It won't clobber your old diff if the new one turns out to have zero size. In build mode, it produces both the chrome and CVS versions of the diff, and places them in the data directory. pmView - brings up the CVS version of your diff in an editor window. This is so you can sanity-check it. In build mode, use -c to see the chrome version. pmPatch - patches your diff back in to the build you are currently sitting in. Takes a -R to back the patch out again. pmEdit - brings up in your editor any files in the current patch which match the pattern. Pattern is a simple glob. No arguments opens all files. pmWhich - prints the current patch reference pmSwitch - changes Patch Maker to work with a new patch reference pmAdd - adds those filenames to the current patch pmRemove - removes those filenames from the current patch pmUnJar - Unjars your chrome. pmGrep - searches all added files for a given string, case-insensitively Commands only available in CVS mode: pmUpdate - does a CVS update of all your files. pmCvsAdd - does a CVS add of all your files. (Those which are already checked in will fail with a harmless error message.) pmcheckIn - does a CVS checkin of all your files pmSetPath - sets the path to an install of your piece of software. pmeXecute - executes that software. Set the exe name by editing the script. pmCopy - copies all changed files into the install pointed to by pmsp. You can also use "pm --version" to get the software version. For further documentation, see http://www.gerv.net/software/patch-maker/ . Report bugs to: . EOF exit; } ############################################################################### # Init # Status: DONE ############################################################################### sub pminit() { my @commands = qw{add a diff d edit e flist f grep g help h list l nojar n patch p remove r switch s unjar uj view v which w cvsadd ca copy c checkin i make m setpath sp update u execute x}; my $force_platform = shift @ARGV || ""; if ((!$force_platform && ($unix || $darwin)) || $force_platform =~ /unix/i) { # Go to the pm directory $0 =~ /(.*)\//; if ($1) { chdir("$1") or die "$execname: Can't change directory to '$1'. $!\n"; } print "Initialising Patch Maker in this directory.\n"; print "Creating symlinks...\n"; foreach $command (@commands) { system("ln -s pm pm$command"); } } elsif ((!$force_platform && $win32) || $force_platform =~ /win/i) { print "Initialising Patch Maker in this directory.\n"; print "Creating batch files...\n"; my $cwd = getcwd(); $cwd =~ s/\//\\/g; foreach $command (@commands) { open(BATFILE, ">pm$command.bat"); print BATFILE "\@$^X \"$cwd\\pm\" --$command %1 %2 %3 %4 %5 %6 %7 %8 %9"; close BATFILE; } # Do pm itself open(BATFILE, ">pm.bat"); print BATFILE "\@$^X \"$cwd\\pm\" %1 %2 %3 %4 %5 %6 %7 %8 %9"; close BATFILE; } else { print "Your platform does not require initialisation of Patch Manager.\n"; print "You should invoke Patch Manager as e.g. 'pm --add' or 'pm -a'.\n"; } } ############################################################################### # List # Status: DONE (same as pm2 except variable renaming) ############################################################################### sub pmlist() { # cat to console (in pairs if -a specified) my @files = get_file_list(); if ($files[0]) { print "File list for patch '$patchref':\n\n"; @files = sort @files; foreach my $list_file (@files) { print $list_file . "\n"; if ($chromelist_file && defined($ARGV[0]) && $ARGV[0] eq "-a") { my $matchingfile = findmatchingfile($list_file); $matchingfile && print " -> $matchingfile\n"; } } } else { print "\n$execname: patch '$patchref' has no files at the moment.\n"; } print "\n"; } ############################################################################### # Make (cvs mode only) # Status: Not written. ############################################################################### sub pmmake() { print "*** PM_ERROR_NOT_IMPLEMENTED ***\n"; print "pmmake may make an appearance in a later version :-)\n"; return; } ############################################################################### # Patch # Status: DONE ############################################################################### sub pmpatch() { return ($mode eq "cvs") ? pmcvspatch(@_) : pmbuildpatch(@_); } ############################################################################### # Patch (cvs mode only) # Status: DONE ############################################################################### sub pmcvspatch() { # All extra args to pmpatch get passed to patch. The most common one to use # in this context is -R. $args = join(' ', @ARGV); pexec("patch -p0 $args < $cvsdiff_file"); } ############################################################################### # Patch (build mode only) # Status: DONE ############################################################################### sub pmbuildpatch() { # All extra args to pmpatch get passed to patch. The most common one to use # in this context is -R. my $args = join(' ', @ARGV); $args =~ s/\s+$//; if (!(-e "$chromediff_file")) { if (-e "$cvsdiff_file") { # The diff has been imported from elsewhere. Convert to a chromediff. print "This diff has been imported from elsewhere.\n"; print "Converting it for use with Patch Maker...\n\n"; open(DIFF, "<$cvsdiff_file") or die "$execname: Can't open file '$cvsdiff_file' for reading. $!\n"; open(CHROMEDIFF, ">$chromediff_file") or die "$execname: Can't open file '$chromediff_file' for writing. $!\n"; while (my $line = ) { my ($cvsfile, $buildfile); # We need to try and make it most likely that we find the right build # file for the CVS file, even if the patch doesn't include complete # path information. If it's a CVS diff, it'll have an RCS file: line # with the full info on it. if ($line =~ m/^RCS file: \/cvsroot\/mozilla\/(.+),.*/) { $cvsfile = $1; } elsif ($line =~ m/^(\+\+\+)\s+([^\s]+)\s+.*/) { # If it's not a CVS diff, all we'll have is the +++ and --- lines. # This is the case with a patch from another Patch Maker. In that # case, we just have to take what we have, and try to match it. # This is not 100%, but it should be good enough. $cvsfile = $2; } if ($cvsfile) { $buildfile = findbuildfile($cvsfile); if ($buildfile) { # 'touch' open(FILELIST, ">>$file_list") or die "$execname: Can't touch file '$file_list'. $!\n"; close FILELIST; pmadd($buildfile); $line =~ s/\Q$cvsfile\E/$buildfile/; } else { print "$execname: Unable to find a local match for '$cvsfile'.\n"; print "Patch Maker can't understand this CVS patch.\n"; exit; } } print CHROMEDIFF $line; } close CHROMEDIFF; close DIFF; } } if (-e "$chromediff_file") { # If the .bak files don't exist, we are patching into a new install. So, # we need to create them. my @files = get_file_list(); if ($files[0] && (!-e "$files[0].bak")) { print "Patching into a new install - backing up files.\n\n"; foreach my $buildfile (@files) { copy($buildfile, "$buildfile.bak"); } } pexec("patch -p0 $args < $chromediff_file"); } else { print "$execname: No patch to apply for patch reference '$patchref'.\n"; } } ############################################################################### # Remove # Status: DONE, TESTED ############################################################################### sub pmremove() { my @buildfiles = get_file_list(); # We need to find which removefiles are in the buildfiles list. If any are # not, we need to print an error. # Convert lists to hashes with value 1 my %buildfiles; my %removefiles; foreach my $buildfile (@buildfiles) { $buildfiles{$buildfile} = 1; } foreach my $removefile (@ARGV) { $removefiles{$removefile} = 1; } # Set both hash values to 0 on all matches foreach my $buildfile(keys %buildfiles) { if (defined($removefiles{$buildfile})) { $buildfiles{$buildfile} = 0; $removefiles{$buildfile} = 0; print "Removing file '$buildfile' from this patch.\n"; copy("$buildfile.bak", "$buildfile") if ($mode eq "build"); } } # Print an error for removefiles which are still 1 foreach my $removefile (keys %removefiles) { if ($removefiles{$removefile} == 1) { print "$execname: File '$removefile' is not part of this patch.\n"; } } # Rewrite the file list with the buildfiles which are still 1 open(FILELIST, ">$file_list") or die "$execname: Can't open file '$file_list' for writing. $!\n"; foreach my $buildfile (@buildfiles) { if ($buildfiles{$buildfile}) { print FILELIST $buildfile . "\n"; } } close FILELIST; } ############################################################################### # Setpath (cvs mode only) # Status: DONE ############################################################################### sub pmsetpath() { my $newpath = shift @ARGV; if (!$newpath) { print "$execname: No new path given.\n"; exit; } # Add chrome dir if necessary. if ($newpath =~ /mozilla/i && !($newpath =~ /chrome/i)) { print "You forgot the chrome directory; adding it for you.\n"; $newpath = File::Spec->catdir($newpath, "chrome"); } my $buildpath_file = File::Spec->catfile($datadir, "buildpath"); open(BUILDPATH, ">$buildpath_file") or die "$execname: Can't open file `$buildpath_file'.\n"; print BUILDPATH $newpath; close BUILDPATH; # Unjar chrome if necessary and user requests it. if (!(-d File::Spec->catdir($newpath, "comm"))) { pmunjar() if ask("Would you like your chrome unjarred"); } print "New build path is $newpath\n"; } ############################################################################### # Switch # Status: DONE, TESTED ############################################################################### sub pmswitch() { # Do we have an argument? $new_patchref = shift @ARGV; if ($new_patchref) { open(CURRENT, ">$patchref_file") or die "$execname: Can't open file '$patchref_file' for writing. $!\n"; print CURRENT $new_patchref; print "You were working on patch '$patchref'.\n"; print "You are now working on "; if (!(-e File::Spec->catfile($datadir, "$new_patchref.files"))) { print "(new) "; } print "patch '$new_patchref'.\n"; close CURRENT; } else { print "$execname: No new patch reference given.\n"; exit; } } ############################################################################### # Unjar # Status: Need to get directory name right. ############################################################################### sub pmunjar() { if ($mode eq "cvs" && !$buildpath) { print "$execname: You have not yet set a path to the build whose chrome "; print "you wish to unjar.\n"; exit; } print "Unjarring chrome...\n\n"; chdir $buildpath; while (<*.jar>) { # This is a best guess situation for which chrome we want my $notme = ($darwin) ? "win|unix" : ($win32 ? "mac|unix" : "mac|win"); next if (m/.*($notme)\.jar/); # embed.jar duplicates other stuff. next if (m/.*embed\.jar/); print $_ . "\n"; # Unzip it into a directory named after the jar file. # XXX Make sure -d switch is supported on Windows m/^(.*)\.jar$/; system("unzip -q $_ -d $1"); } # Fix up installed-chrome.txt. # Put everything in a directory named after the jar. local $^I = ".orig"; local @ARGV = ("installed-chrome.txt"); while (<>) { s/\.?jar[!:]//g; print; } # Delete the rdf files; they'll get regenerated from installed-chrome.txt. unlink(glob("*.rdf")); print "\nChrome unjarred.\n"; } ############################################################################### # Update (cvs mode only) # Status: DONE ############################################################################### sub pmupdate() { # All other args to pmupdate get passed to cvs as post-mode commands. # Good for checking in on branches. my $args = join ' ', @ARGV; pexec("cvs -z3 update $args " . get_file_list()); } ############################################################################### # Version # Status: DONE ############################################################################### sub pmversion() { print "\nPatch Maker version $pm_version.\n\n"; print "Author: Gervase Markham .\n"; print "URL: http://www.gerv.net/software/patch-maker/\n\n"; } ############################################################################### # View # Status: DONE, except error if FNF. ############################################################################### sub pmview() { if ($chromelist_file && defined($ARGV[0]) && $ARGV[0] =~ /^-c/) { if ($chromediff_file && -e $chromediff_file) { pexec("$editor \"$chromediff_file\" $editor_modal"); } else { print "$execname: No chromediff file to view.\n"; } } else { if ($cvsdiff_file && -e $cvsdiff_file) { pexec("$editor \"$cvsdiff_file\" $editor_modal"); } else { print "$execname: No diff file to view.\n"; } } } ############################################################################### # Which # Status: DONE ############################################################################### sub pmwhich() { print "You are currently working on patch '$patchref'.\n"; } ############################################################################### # Execute # Status: being written ############################################################################### sub pmexecute() { if (!$buildpath) { print "$execname: No buildpath set - use pmsp to set one.\n"; exit; } # All other args to pmexecute get passed to the app my $args = join ' ', @ARGV; # Sync versions if necessary pmcopy(); system(File::Spec->catfile($buildpath, $exepath), $args); } ############################################################################### # # Main code. # ############################################################################### # Work out my platform. # The BSD bit is a wild-ass guess. $unix = ($^O eq "linux" || $^O =~ /bsd/i) ? 1 : 0; $darwin = ($^O eq "darwin") ? 1 : 0; $win32 = ($^O eq "MSWin32" || $^O eq "cygwin") ? 1 : 0; # Work out what command I am executing by looking at my executed name $command = basename($0); $execname = $command; # For error messages. if ($command =~ /^pm(\.pl)?$/i) { # We have been invoked as "pm -" or "pm --" rather than # via a symlink. So we need to calculate the command to execute from the # first parameter. $command = shift @ARGV; if (defined($command)) { # We need to make the e.g. '-d' or '--diff' match the command names, so # cut off initial dashes if present. $command =~ s/^--?//; } } else { # We have been invoked via a symlink. Cut off "pm" $command =~ s/^pm//; } $command || (print "$execname: No command name given.\n" && exit); # This command has to be caught before we try and create the data dir. if ($command eq "init") { pminit(); exit; } # Check for existence of datadir. if (!(-d $datadir)) { print "Configured data directory '$datadir' does not exist.\n"; print "(To configure the data directory, edit the script.)\n"; print "It needs to exist for Patch Maker to continue.\n"; if (ask("Would you like to create it")) { mkdir($datadir, 0755); } else { exit; } } # Work out my mode and the build path if ((-e "installed-chrome.txt") && basename(getcwd()) eq "chrome") { # "Build" mode - we are in the chrome directory of a Mozilla build. $mode = "build"; # $buildpath isn't used in build mode $buildpath = "."; } else { # Assume "CVS" mode if not build mode. $mode = "cvs"; # Try to get the current buildpath - where the install of Mozilla that we are # testing with is. No error here - we don't have to be used with Mozilla. my $buildpath_file = File::Spec->catfile($datadir, "buildpath"); open(BUILDPATH, "<$buildpath_file") && ($buildpath = ) && close BUILDPATH && chomp $buildpath; } if ($buildpath) { # Check for chromelist.txt $chromelist_file = File::Spec->catfile($buildpath, "chromelist.txt"); $chromelist_file = "" if (!(-e $chromelist_file)); } # Get the current patch reference $patchref_file = File::Spec->catfile($datadir, "patchref"); (open(CURRENT, "<$patchref_file") && ($patchref = ) && close CURRENT) or $patchref = "_no_patch_reference_"; chomp $patchref; # The file list is the list of files in the current patch, in their # canonical locations. # # In cvs mode, it's list of files inside the cvs checkout point. # In build mode, it's a list of files inside the chrome directory # of your install point. $file_list = File::Spec->catfile($datadir, "$patchref.files"); # There are two sorts of diff. The CVS diff has the unadorned filename; it's # the one that gets attached to Bugzilla bugs and can be applied to other # people's CVS trees. The chromediff is specific to build mode. It has the same # diff contents but different file paths in it, and can be applied to another # Mozilla installation's chrome directory. $chromediff_file = File::Spec->catfile($datadir, "$patchref.chromediff"); $cvsdiff_file = File::Spec->catfile($datadir, "$patchref.diff"); # Execute the correct command. # First, commands whose names are common to both modes. # "Unshared implementation" means the commands _do_ different things in # different modes. if ($command eq "add" || $command eq "a") { pmadd(@ARGV); } elsif ($command eq "diff" || $command eq "d") # Unshared implementation { pmdiff(); } elsif ($command eq "edit" || $command eq "e") { pmedit(); } elsif ($command eq "flist" || $command eq "f") # Deprecated { deprecate("l or list"); pmlist(); } elsif ($command eq "grep" || $command eq "g") { pmgrep(); } elsif ($command eq "help" || $command eq "h") { pmhelp(); } elsif ($command eq "list" || $command eq "l") { pmlist(); } elsif ($command eq "nojar" || $command eq "n") # Deprecated { deprecate("uj or unjar"); pmunjar(); } elsif ($command eq "patch" || $command eq "p") # Unshared implementation { pmpatch(); } elsif ($command eq "remove" || $command eq "r") { pmremove(); } elsif ($command eq "switch" || $command eq "s") { pmswitch(); } elsif ($command eq "unjar" || $command eq "uj") { pmunjar(); } elsif ($command eq "view" || $command eq "v") { pmview(); } elsif ($command eq "version") { pmversion(); } elsif ($command eq "which" || $command eq "w") { pmwhich(); } # CVS mode-only commands elsif ($mode eq "cvs") { if ($command eq "copy" || $command eq "c") { pmcopy(); } if ($command eq "cvsadd" || $command eq "ca") { pmcvsadd(); } elsif ($command eq "checkin" || $command eq "i") { pmcheckin(); } elsif ($command eq "make" || $command eq "m") { pmmake(); } elsif ($command eq "setpath" || $command eq "sp") { pmsetpath(); } elsif ($command eq "update" || $command eq "u") { pmupdate(); } elsif ($command eq "execute" || $command eq "x") { pmexecute(); } } else { print "$execname: Command '$command' not recognised.\n"; }