#!/usr/bin/perl -w =head1 NAME p4d2p - convert diffs from Perforce to patch format =head1 SYNOPSIS p4d2p [-hiv] [-o comment] [-p command] [files ...] =head2 Options: -h Display this help message -i[suffix] Edit files in place; append suffix to original names -o comment Output the comment instead of the old file version -p command Use the command instead of `p4 print -q` to get files -v Output progress messages =head1 DESCRIPTION Change diffs in Perforce format (from `p4 diff`, `p4 diff2` or `p4 describe`) to a form suitable for input to the GNU `patch` program. Insert headers and diffs, but don't remove anything. Edit each file in place if file names are given; otherwise filter from input to output. =head1 EXAMPLES p4 diff -du ...@ver8_0 | p4d2p -o "-r v80" | patch -p3 p4 diff2 -dc ...@Beta1 ...@Beta2 | p4d2p -o "-r Beta1" | patch -p5 p4 describe -du 123 | p4d2p -o "@122" | patch -p2 =head1 KNOWN BUGS RCS format diffs (from `p4 diff -dn`) are not converted. =head1 SEE ALSO To generate a patch from a change number: L<http://public.activestate.com/gsar/APC/perl-current/Porting/p4genpatch> To generate a patch for added files: L<http://public.activestate.com/gsar/APC/perl-5.6.x/Porting/p4desc> Perforce L<http://www.perforce.com/perforce/technical.html> GNU `patch` L<http://www.fsf.org/manual/diffutils-2.8.1/html_node/Multiple-Patches.html> =head1 AUTHOR John Kristian <jkristian@docent.com>. Thanks to Gurusamy Sarathy for inspiration; but I accept all blame. =cut use Getopt::Std; use POSIX qw(strftime); use Time::Local qw(timegm); use vars qw($opt_h $opt_i $opt_o $opt_p $opt_v); if (!getopts('hi::o:p:v') || $opt_h) { open(STDOUT, '>&STDERR'); exec('perldoc', $0); exit(1); # exec failed } if (not @ARGV) {@ARGV = '-'; undef $^I;} elsif (defined($opt_i)) {$^I = $opt_i; warn "-i$opt_i\n" if $opt_v;} # else $^I comes from Perl's -i option; e.g. perl -i.bak p4d2p ... my $Z = timegm(localtime(0)) / 60; $Z = ($Z >= 0) ? sprintf('+%02d%02d', $Z/60, $Z%60) : sprintf('-%02d%02d',-$Z/60,-$Z%60); my $timeFormat = "%Y-%m-%d %H:%M:%S $Z"; # ISO 8601 my $now = strftime($timeFormat, localtime); my $epoch = "1970-01-01 00:00:00Z"; my ($oldFile, $oldNote, $newFile, $newNote, @movedFiles); while (<>) { # `p4 diff` header if (m<^==== //(.+?)(\#\d+) +- +(.+?) +====( \(.+\))?$>) { ($oldFile, $oldNote, $newFile) = ($1, $2, $3); $oldNote = " " . ($opt_o || $oldNote); my (@stat) = stat($newFile); $newNote = ((@stat && defined($stat[9])) ? strftime($timeFormat, localtime($stat[9])) # time last modified : $now); $newNote = " $newNote"; } # `p4 diff2` header elsif (m<^==== //(.+?)(\#\d+) +\(.+?\) +- +(.+?)(\#\d+) +\(.+?\) +==== +\w+$>) { ($oldFile, $oldNote, $newFile, $newNote) = ($1, $2, $3, $4); $oldNote = " " . ($opt_o || $oldNote); $newNote = " $newNote"; } # `p4 describe` header elsif (m<^==== //(.+?)(\#\d+) .*?====( \(.+\))?$>) { ($newFile, $newNote) = ($1, $2); $oldFile = $newFile; $oldNote = " " . ($opt_o || (($newNote =~ m<\#(\d+)>) && "#" . ($1-1))); # the previous version $newNote = " $newNote"; } # unified diff (the preferred format for `patch`) elsif (defined($oldFile) && m<^\@\@\s.*\s\@\@$>) { warn "emitting diff -u header\n" if $opt_v; print("Index: //$oldFile\n", "--- $oldFile$oldNote\n", "+++ $oldFile$newNote\n"); undef $oldFile; } # context diff elsif (defined($oldFile) && m<^\*+$>) { warn "emitting diff -c header\n" if $opt_v; print("Index: //$oldFile\n", "*** $oldFile$oldNote\n", "--- $oldFile$newNote\n"); undef $oldFile; } # default diff (not recommended for `patch`) elsif (defined($oldFile) && m<^\d+(,\d+)?[acd]\d+>) { warn "emitting diff header (not recommended for `patch`)\n" if $opt_v; print("Index: $oldFile\n", "diff -r //$oldFile $newFile\n"); undef $oldFile; } # `p4 describe` add, branch or delete elsif (m<^\.\.\. (//.+?\#\d+ (add|branch|delete))$>) { push @movedFiles, $1; } } continue { print; # echo all input if (eof) { if (@movedFiles) { my ($verb, @file); for (@movedFiles) { ($newFile, $newNote, $verb) = m<//(.+?)(\#\d+) (\w*)$>; warn $verb if $opt_v; $oldFile = $newFile; if ($verb eq 'delete') { $oldNote = "#" . (($newNote =~ m<\#(\d+)>) && ($1-1)); # the previous version $newNote = $epoch; @file = p4print("//$oldFile$oldNote"); } else { # add or branch $oldNote = $epoch; @file = p4print("//$newFile$newNote"); } if (!$? && @file) { $oldNote = " " . ($opt_o || $oldNote); $newNote = " $newNote"; my $lines = (@file <= 1) ? "" : ("," . @file); print("\nIndex: //$newFile\n", "--- $oldFile$oldNote\n", "+++ $newFile$newNote\n"); if ($verb eq 'delete') { print("\@\@ -1$lines +0,0 \@\@\n"); print(join("-", "", @file)); } else { # add or branch print("\@\@ -0,0 +1$lines \@\@\n"); print(join("+", "", @file)); } print("\n\\ No newline at end of file\n") if ($file[$#file] !~ m<\n$>); } } @movedFiles = (); } undef $oldFile; } } sub p4print { my ($name) = @_; my $cmd = ($opt_p || "p4 print -q")." \"$name\""; # Sadly, executing `p4 print` will consume some input. # Which is one reason not to emit files immediately # upon reading their names from the input stream. my @file = `$cmd`; warn "status $? from $cmd" if ($?); return @file; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#9 | 3909 | John Kristian |
Added option -i (similar to perl -i). Use the local time zone for output times. |
||
#8 | 3887 | John Kristian |
Made $timeFormat more human-readable (and more like GNU `diff`). Output '1970-01-01 00:00:00Z' as the $oldNote of an added file or $newNote of a deleted file. |
||
#7 | 3886 | John Kristian | renamed variables | ||
#6 | 3885 | John Kristian | improved POD | ||
#5 | 3884 | John Kristian | output a patch for deleted files, too | ||
#4 | 3883 | John Kristian | output a patch for added files, too | ||
#3 | 3882 | John Kristian | improved POD | ||
#2 | 3881 | John Kristian | Combined the interface (command line syntax) of p4d2p with the implementation of p4diff2patch. | ||
#1 | 3879 | John Kristian | http://search.cpan.org/src/JHI/perl-5.8.1-RC4/Porting/p4d2p |