#!/usr/bin/perl -w # NAME: patchit.pl # AIM: 24/03/2012 Accept a patch file as input, and apply the patches # This was NOT written as a replacement of the 'patch' tool, which does a # very good job, but to better # understand the unified diff notation, and # how to apply it. # UGH: With MSVC10 must deal with BOM (Byte Order Mark), like # UTF-8 EF BB BF 239 187 191 # ======================================================================= use strict; use warnings; use File::Basename; # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] ) use Cwd; my $os = $^O; my $perl_dir = '/home/geoff/bin'; my $PATH_SEP = '/'; my $temp_dir = '/tmp'; if ($os =~ /win/i) { $perl_dir = 'C:\GTools\perl'; $temp_dir = $perl_dir; $PATH_SEP = "\\"; } unshift(@INC, $perl_dir); require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n"; # log file stuff our ($LF); my $pgmname = $0; if ($pgmname =~ /(\\|\/)/) { my @tmpsp = split(/(\\|\/)/,$pgmname); $pgmname = $tmpsp[-1]; } my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt"; open_log($outfile); # user variables my $VERS = "0.0.1 2012-03-24"; my $load_log = 0; my $in_file = ''; my $verbosity = 0; my $debug_on = 0; my $def_file = 'def_file'; my $out_xml = ''; my $work_dir = ''; # directory where the patches are to be applied my $strip_count = 0; # strip this count off the path my $files_reversed = 0; # reverse new and old in patch my $strip_bom_utf8 = 1; ### program variables my @warnings = (); my $cwd = cwd(); my @new_files = (); ### DEBUG my $dbg_01 = 0; # show ALL lines my $dbg_02 = 0; # show loaded diff file lines my $dbg_03 = 0; # show lines from file to be patched my $dbg_04 = 0; # show the bumping of $cpl_cnt for DEBUG ONLY my $dbg_05 = 0; # show stored/skipped lines my $dbg_06 = 0; # show lines from old file my $dbg_07 = 0; # show lines from patch input file my $dbg_08 = 0; # show lines when merging my $dbg_09 = 0; # show all patched file lines my $dbg_10 = 0; # show patch file lines my $dbg_11 = 0; # show OLDFILE lines my $dbg_14 = 0; # show check MORE on line difference # setup a 'function' to call # like $new_hash{'function'} = \&foo; # call the a function like $stg = $func->(); my ($path_function); $path_function = \&path_d2u; if ($os =~ /win/i) { $path_function = \&path_u2d; } sub VERB1() { return $verbosity >= 1; } sub VERB2() { return $verbosity >= 2; } sub VERB5() { return $verbosity >= 5; } sub VERB9() { return $verbosity >= 9; } sub show_warnings($) { my ($val) = @_; if (@warnings) { prt( "\nGot ".scalar @warnings." WARNINGS...\n" ); foreach my $itm (@warnings) { prt("$itm\n"); } prt("\n"); } else { prt( "\nNo warnings issued.\n\n" ) if (VERB9()); } } sub pgm_exit($$) { my ($val,$msg) = @_; if (length($msg)) { $msg .= "\n" if (!($msg =~ /\n$/)); prt($msg); } show_warnings($val); close_log($outfile,$load_log); exit($val); } sub prtw($) { my ($tx) = shift; $tx =~ s/\n$//; prt("$tx\n"); push(@warnings,$tx); } # get_counts($lcnts,\$off1,\$len1,\$off2,\$len2); # Normally @@ -57,10 +57,16 # Also must resolve @@ -1 +1 @@ and @@ -0,0 +1 @@ # UGH and '-1 +0,0', '-1,2 +1', '-1 +0,0' sub get_counts($$$$$) { my ($lcnts,$roff1,$rlen1,$roff2,$rlen2) = @_; ${$roff1} = -9999; ${$rlen1} = -9999; ${$roff2} = -9999; ${$rlen2} = -9999; my ($cnt,$old,$new,$msg,$i); my @arr = split(/\s+/,$lcnts); $cnt = scalar @arr; if ($cnt != 2) { $msg = "WARNING:1: CNTS [$lcnts] did NOT split in 2 on space! Got $cnt?"; for ($i = 0; $i < $cnt; $i++) { $msg .= " $arr[$i]"; } prtw("$msg\n"); return 1; } $old = $arr[0]; $new = $arr[1]; if ( !($old =~ /^-/) || !($new =~ /^\+/)) { prtw("WARNING:2: CNTS [$lcnts] did NOT resolve with - or + [$old] [$new]!\n"); return 2; } $old = substr($old,1); @arr = split(",",$old); $cnt = scalar @arr; if ($cnt == 1) { ${$roff1} = $arr[0]; ${$rlen1} = $arr[0]; } elsif ($cnt == 2) { ${$roff1} = $arr[0]; ${$rlen1} = $arr[1]; } else { prtw("WARNING:3: CNTS [$lcnts] OLD did NOT resolve [$old] [$new]!\n"); return 3; } $new = substr($new,1); @arr = split(",",$new); $cnt = scalar @arr; if ($cnt == 1) { ${$roff2} = $arr[0]; ${$rlen2} = $arr[0]; } elsif ($cnt == 2) { ${$roff2} = $arr[0]; ${$rlen2} = $arr[1]; } else { prtw("WARNING:4: CNTS [$lcnts] OLD did NOT resolve [$old] [$new]!\n"); return 4; } return 0; } sub get_counts_FAILED_ON_SOME($$$$$) { my ($lcnts,$roff1,$rlen1,$roff2,$rlen2) = @_; ${$roff1} = -9999; ${$rlen1} = -9999; ${$roff2} = -9999; ${$rlen2} = -9999; my $len = length($lcnts); my ($i,$ch,$tag,$cnt); $cnt = 0; $tag = ''; for ($i = 0; $i < $len; $i++) { $ch = substr($lcnts,$i,1); if (length($tag) == 0) { if ($ch =~ /\s/) { next; } elsif (($ch eq '-')||($ch eq '+')||($ch =~ /\d/)) { $tag .= $ch; # start the tag } } elsif ($ch =~ /\d/) { if ( (length($tag) == 1) && (($tag eq '+')||($tag eq '-')) ) { $tag = ''; # REMOVE '-' and '+' indicators } $tag .= $ch; } else { # is NOT '-' or '+' or a digit - assume END if ($cnt == 0) { ${$roff1} = $tag; } elsif ($cnt == 1) { ${$rlen1} = $tag; } elsif ($cnt == 2) { ${$roff2} = $tag; } elsif ($cnt == 3) { ${$rlen2} = $tag; } $cnt++; # bump counter $tag = ''; # and CLEAR tag } } if (length($tag) && ($cnt == 3)) { ${$rlen2} = $tag; $cnt++; } if ($cnt != 4) { prtw("WARNING: CNTS [$lcnts] did NOT resolve (-${$roff1},${$rlen1} +${$roff2},${$rlen2})!\n"); } } sub get_diff_files($$$) { my ($lin,$rfil1,$rfil2) = @_; #my @arr = split(/s+/,$lin); my @arr = space_split($lin); if (scalar @arr < 2) { pgm_exit(1,"ERROR: Line [$lin] did NOT split correctly!\n"); } my ($fil,$cnt); $cnt = 0; foreach $fil (@arr) { next if ($fil =~ /^-/); if ($cnt == 0) { ${$rfil1} = $fil; $cnt++; } elsif ($cnt == 1) { ${$rfil2} = $fil; $cnt++; } } return $cnt; } sub strip_file_path($) { my $file = shift; return $file if ($strip_count == 0); $file = $path_function->($file); my $len = length($file); my $cnt = 0; my ($i,$ch); for ($i = 0; $i < $len; $i++) { $ch = substr($file,$i,1); if ($ch eq $PATH_SEP) { $cnt++; if ($cnt == $strip_count) { return substr($file,($i+1)); } } } return $file; } sub line_has_utf8_bom($) { my $line = shift; my $len = length($line); my ($ch,$val1,$val2,$val3); # UTF-8 EF BB BF 239 187 191 if ($len > 3) { $ch = substr($line,0,1); $val1 = ord($ch); $ch = substr($line,1,1); $val2 = ord($ch); $ch = substr($line,2,1); $val3 = ord($ch); if (($val1 == 239)&&($val2 == 187)&&($val3 == 191)) { return 1; } } return 0; } sub remove_utf8_bom_from_line($) { my $line = shift; if (line_has_utf8_bom($line)) { $line = substr($line,3); } return $line; } sub remove_utf_bom($$) { my ($ff,$ra) = @_; my $line = ${$ra}[0]; # get first line my $nline = remove_utf8_bom_from_line($line); if ($line ne $nline) { prt("NOTE: File [$ff] is UTF-8 encoding. BOM removed.\n"); ${$ra}[0] = $nline; } } # if (get_diff_file_lines($ff,\@oldlines)) { sub get_diff_file_lines($$) { my ($ff,$ra) = @_; my $lncnt = 0; if (open INF, "<$ff") { @{$ra} = ; close INF; $lncnt = scalar @{$ra}; remove_utf_bom($ff,$ra) if ($strip_bom_utf8); } return $lncnt; } # if (lines_match($plline,$dline)) { sub lines_match($$) { my ($pl,$dl) = @_; return 1 if ($pl eq $dl); return 0; } sub write_patched_file($$) { my ($rh,$ff) = @_; my $dnlist = 0; my ($dcnt,$i,$dline,$plline,$msg2,$len,$tlen); my $mcnt = 0; my $errcnt = 0; my $failed = ''; if (defined ${$rh}{$ff}) { my $rhh = ${$rh}{$ff}; my $rpla = ${$rhh}{'PATCHED_LINES'}; my $len1 = scalar @{$rpla}; $msg2 = ''; if ((defined ${$rhh}{'NEW_LINES'})&&(defined ${$rhh}{'NEW_FILE'})) { my $rnla = ${$rhh}{'NEW_LINES'}; my $ffnew = ${$rhh}{'NEW_FILE'}; my $len2 = scalar @{$rnla}; $len = ($len1 < $len2) ? $len1 : $len2; #if ($len1 == $len2) { if ($len) { prt("\n") if (VERB9() || $dbg_09); prt("Display of patched $len1 lines, file [$ff], compared to $len2 lines, file [$ffnew]\n"); $dcnt = 0; for ($i = 0; $i < $len; $i++) { $dline = ${$rpla}[$i]; $plline = ${$rnla}[$i]; chomp $plline; if (lines_match($dline,$plline)) { $msg2 = "ok"; $mcnt++; } else { $msg2 = "NM [$plline]"; if (line_has_utf8_bom($dline)) { $msg2 .= " due UTF-8 BOM"; } $errcnt++; if (VERB9() || $dbg_14) { # hmmm, check more on difference my ($tlen1,$tlen2,$j,$ch,$val); $tlen1 = length($dline); $tlen2 = length($plline); $tlen = ($tlen1 < $tlen2) ? $tlen1 : $tlen2; for ($j = 0; $j < $tlen; $j++) { $ch = substr($dline,$j,1); if ($ch ne substr($plline,$j,1) ) { $val = ord($ch); $failed .= "<- here offset $j [$ch]$val ne "; $ch = substr($plline,$j,1); $val = ord($ch); $failed .= "[$ch]$val"; if (line_has_utf8_bom($dline)) { $failed .= " due UTF-8 BOM"; } $j++; last; } $failed .= $ch; } $failed .= "\n"; $failed .= "old [$dline]$tlen1\n"; $failed .= "new [$plline]$tlen2\n"; } } $dcnt++; prt("$dcnt: [$dline] $msg2\n") if (VERB9() || $dbg_09); } if ($i < $len1) { prt("Balance ".($len1 - $i)." in patched file.\n"); for ( ; $i < $len1; $i++) { $dline = ${$rpla}[$i]; $dcnt++; prt("$dcnt: [$dline]\n") if (VERB9() || $dbg_09);; $errcnt++; } } elsif ($i < $len2) { prt("Balance ".($len2 - $i)." in NEW file.\n"); for ( ; $i < $len2; $i++) { $plline = ${$rnla}[$i]; chomp $plline; $dcnt++; prt("$dcnt: [$plline]\n") if (VERB9() || $dbg_09);; $errcnt++; } } $dnlist = 1; if ($mcnt == $dcnt) { prt("A perfect match $dcnt of $mcnt.\n"); } else { prt("Match FAILED on $errcnt lines, matched $mcnt!\n"); prt("$failed\n") if (length($failed)); } } else { $msg2 .= " lengths $len1 and $len2 different!"; } } else { $msg2 .= " 'NEW_LINES' or 'NEW_FILE' NOT defined!"; } if (!$dnlist) { $dcnt = 0; prt("\n") if (VERB9() || $dbg_09); prt("Display of patched file $ff with new lines... $msg2\n"); foreach $dline (@{$rpla}) { $dcnt++; prt("$dcnt: [$dline]\n") if (VERB9() || $dbg_09); } } } } sub show_patch_lines($$$$) { my ($rh,$rdifflines,$rpatchlines,$rcnt_stack) = @_; my $maxplcnt = scalar @{$rpatchlines}; my $maxstack = scalar @{$rcnt_stack}; my $oldlncnt = scalar @{$rdifflines}; my $dcnt = 0; my ($rpla,$pltype,$plline,$i,$off); my ($rca,$off1,$len1,$off2,$len2,$endp1,$endp2,$icnt); my ($poff1,$plen1,$poff2,$plen2,$jcnt,$roff); $off = 0; if ($off < $maxstack) { $rca = ${$rcnt_stack}[$off]; $off1 = ${$rca}[0]; $len1 = ${$rca}[1]; $off2 = ${$rca}[2]; $len2 = ${$rca}[3]; $endp1 = $off1 + $len1; $endp2 = $off2 + $len2; $off++; } else { pgm_exit(1,"ERROR:show_patch_lines: No counts available! Aborting...\n"); } prt("\n") if (VERB9() || $dbg_10); prt("List of $maxplcnt patch lines from [$in_file]. old file has $oldlncnt lines.\n"); prt("Counts $maxstack (-$off1,$len1 +$off2,$len2)\n"); $icnt = 0; $jcnt = 0; $roff = 0; for ($i = 0; $i < $maxplcnt; $i++) { $rpla = ${$rpatchlines}[$i]; $pltype = ${$rpla}[0]; # get TYPE of line 'S', '+', '-'... $plline = ${$rpla}[1]; # and the line $dcnt++; $icnt++ if ($pltype ne '-'); if ($pltype ne '+') { # count of current file lines $jcnt++; $roff++; } prt("$roff:$dcnt:$icnt:$jcnt: $pltype [$plline]\n") if (VERB9() || $dbg_10); if ($icnt == $len2) { if ($off < $maxstack) { $rca = ${$rcnt_stack}[$off]; $poff1 = $off1; $plen1 = $len1; $poff2 = $off2; $plen2 = $len2; $off1 = ${$rca}[0]; $len1 = ${$rca}[1]; $off2 = ${$rca}[2]; $len2 = ${$rca}[3]; $endp1 = $off1 - $roff - 1; # get old file lines to SKIP $roff = $off1 - 1; # set the new OLD file offset prt("Next counts (-$off1,$len1 +$off2,$len2) SKIP $endp1\n"); $endp1 = $off1 + $len1; $endp2 = $off2 + $len2; $off++; } $icnt = 0; $jcnt = 0; } } $endp1 = $oldlncnt - $roff; if ($roff == $oldlncnt) { prt("Calculated actual line count $roff equals old line count $oldlncnt.\n"); } else { prt("Calculated actual line count $roff, plus $endp1 lines to equal old line count $oldlncnt.\n"); } } sub merge_file_lines($$$$$$) { my ($ff,$rpatchedlines,$rpatches,$rnewlines,$bgnpatch,$rroff) = @_; my $oflcnt = scalar @{$rpatchedlines}; my $pflcnt = scalar @{$rpatches}; prt("\n") if (VERB9() || $dbg_08); prt("Merge $oflcnt old file lines with $pflcnt patch line...\n"); my $cof = 0; my $cpf = 0; my $errcnt = 0; my ($msg2,$oline); my ($rpla,$pltype,$plline); for ($cpf = 0; $cpf < $pflcnt; $cpf++) { $rpla = ${$rpatches}[$cpf]; $pltype = ${$rpla}[0]; # get TYPE of line 'S', '+', '-'... $plline = ${$rpla}[1]; # and the line $msg2 = $pltype; if ($pltype eq 'S') { if ($cof < $oflcnt) { $oline = ${$rpatchedlines}[$cof]; $cof++; if (lines_match($oline,$plline)) { $msg2 .= ' matched1'; } else { $msg2 .= " NM1 [$plline]"; $errcnt++; } push(@{$rnewlines},$oline); ${$rroff}++; prt("${$rroff}:$bgnpatch: [$oline] stored $msg2\n") if (VERB9() || $dbg_08); $bgnpatch++; } else { pgm_exit(1,"ERROR:1: Ran out of lines $pltype [$plline]\n"); } } elsif ($pltype eq '+') { push(@{$rnewlines},$plline); prt("${$rroff}:$bgnpatch:$cpf: [$plline] added $msg2\n") if (VERB9() || $dbg_08); $bgnpatch++; } elsif ($pltype eq '-') { if ($cof < $oflcnt) { $oline = ${$rpatchedlines}[$cof]; $cof++; if (lines_match($oline,$plline)) { $msg2 .= ' matched1'; } else { $msg2 .= " NM1 [$plline]"; $errcnt++; } prt("$bgnpatch: [$oline] dropped $msg2\n") if (VERB9() || $dbg_08); $bgnpatch++; } else { pgm_exit(1,"ERROR:2: Ran out of lines\n"); } } else { pgm_exit(1,"ERROR: Uncased TYPE [$pltype]!\n"); } } return $errcnt; } sub test_patch_lines($$$$$) { my ($rh,$ff,$rcnt_stack,$rdifflines,$rpatchlines) = @_; my ($dcnt,$dline,$msg); my ($rca,$off1,$len1,$off2,$len2,$off,$endp2); my ($inpatch,$rpla,$pltype,$plline,$cpl_cnt,$prevline); my ($pladd,$pltot,$plsub,$msg2,$pstart); my ($i,$i2,$icnt,$bgnpatch,$endp1); my ($poff1,$plen1,$poff2,$plen2,$skcnt); my $rcnt = scalar @{$rcnt_stack}; my $maxplcnt = scalar @{$rpatchlines}; my $diffcnt = scalar @{$rdifflines}; $dcnt = 0; $inpatch = 0; $cpl_cnt = 0; # start at zero to maxplcnt $prevline = ''; $pladd = 0; $pltot = 0; $plsub = 0; $pstart = 0; my $revind = 0; my $roff = 0; # calculate REAL offset in OLDFILE my $jcnt = 0; $cpl_cnt = 0; # run a test my @newlines = (); my @patchedlines = (); my @patches = (); $i = 0; if (defined ${$rh}{$ff}) { my $rhh = ${$rh}{$ff}; ${$rhh}{'PATCHED_LINES'} = \@newlines; } else { pgm_exit(1,"ERROR: [$ff] NOT defined in ref hash!\n"); } # get FIRST set of lengths and offsets # ------------------------------------ $off = 0; if ($off < $rcnt) { $rca = ${$rcnt_stack}[$off]; $off1 = ${$rca}[0]; $len1 = ${$rca}[1]; $off2 = ${$rca}[2]; $len2 = ${$rca}[3]; $endp1 = $off1 + $len1; $endp2 = $off2 + $len2; $off++; } else { pgm_exit(1,"ERROR: No counts available! Aborting...\n"); } prt("\n") if (VERB9() || $dbg_05); prt("Test patch to [$ff] $diffcnt lines... $rcnt chunks, $maxplcnt lines...\n"); if ( ($off1 == 0) && ($len1 == 0) ) { prt("Patch CNTS (-$off1,$len1 +$off2,$len2) $endp1 of $diffcnt, $endp2 NEW FILE\n"); @patches = (); $dcnt = 0; prt("Storing length $len2 of $maxplcnt patch lines from [$in_file], begin at $cpl_cnt\n") if ($cpl_cnt < $maxplcnt); for ( ; $cpl_cnt < $maxplcnt; $cpl_cnt++) { $rpla = ${$rpatchlines}[$cpl_cnt]; $pltype = ${$rpla}[0]; # get TYPE of line 'S', '+', '-'... $plline = ${$rpla}[1]; # and the line $dcnt++; prt("$dcnt: $pltype [$plline]\n") if (VERB9() || $dbg_05); push(@patches,[$pltype,$plline]); last if ($dcnt == $len2); } merge_file_lines($ff,\@patchedlines,\@patches,\@newlines,$bgnpatch,\$roff); ### write_patched_file($rh,$ff); return $revind; } $skcnt = $off1; $skcnt -= 1 if ($skcnt); prt("Patch CNTS$off: (-$off1,$len1 +$off2,$len2) SKIP $skcnt\n"); while ( $i < $diffcnt ) { # prt("Patch CNTS (-$off1,$len1 +$off2,$len2) $endp1 of $diffcnt, $endp2\n"); #if ($off1 > 1) { if ($skcnt) { #prt("Skipping ".($off1-1)." lines in old file [$ff]\n"); prt("Skipping $skcnt lines in old file [$ff]\n"); $jcnt = 0; for ( ; $i < $diffcnt; $i++) { $dline = ${$rdifflines}[$i]; chomp $dline; $i2 = $i + 1; #last if ($i2 == $endp1); #last if ($jcnt == $endp1); push(@newlines,$dline); $roff++; # bump calc offset in OLDFILE $msg = "$roff:$i2: [$dline] stored as is and skipped"; prt("$msg\n") if (VERB9() || $dbg_05); $jcnt++; if ($jcnt == $skcnt) { $i++; # bump last; # and exit } } } else { prt("No lines ($len1,$off1 $len2,$off2) to skip in old file [$ff]\n"); } @patchedlines = (); prt("Storing length $len1 lines from old file [$ff], from $i of $diffcnt\n") if ($i < $diffcnt); $bgnpatch = $i + 1; $icnt = 0; for ( ;$i < $diffcnt ; $i++) { $dline = ${$rdifflines}[$i]; chomp $dline; $i2 = $i + 1; $icnt++; $msg = "$i2:$icnt: [$dline] "; prt("$msg\n") if (VERB9() || $dbg_06); push(@patchedlines,$dline); # last if ($icnt == $len1); if ($icnt == $len1) { $i++; last; } } @patches = (); $dcnt = 0; prt("Storing length $len2 of $maxplcnt patch lines from [$in_file], begin at $cpl_cnt\n") if ($cpl_cnt < $maxplcnt); for ( ; $cpl_cnt < $maxplcnt; $cpl_cnt++) { $rpla = ${$rpatchlines}[$cpl_cnt]; $pltype = ${$rpla}[0]; # get TYPE of line 'S', '+', '-'... $plline = ${$rpla}[1]; # and the line $dcnt++ if ($pltype ne '-'); prt("$dcnt: $pltype [$plline]\n") if (VERB9() || $dbg_07); push(@patches,[$pltype,$plline]); # last if ($dcnt == $len2); if ($dcnt == $len2) { $cpl_cnt++; last; } } if ($icnt || $dcnt) { merge_file_lines($ff,\@patchedlines,\@patches,\@newlines,$bgnpatch,\$roff); prt("Done merge "); if ($off < $rcnt) { prt("- continue to next..."); } elsif ($i < $diffcnt) { prt("- store balance next..."); } else { prt("- end."); } prt("\n"); } #if ($revind) { # prt("Appears this patch already applied!\n"); #} # get NEXT chunk to process if ($off < $rcnt) { $rca = ${$rcnt_stack}[$off]; $poff1 = $off1; $plen1 = $len1; $poff2 = $off2; $plen2 = $len2; $off1 = ${$rca}[0]; $len1 = ${$rca}[1]; $off2 = ${$rca}[2]; $len2 = ${$rca}[3]; $endp1 = $off1 + $len1; $endp2 = $off2 + $len2; $off++; $skcnt = $off1 - ($poff1 + $plen1); prt("Patch CNTS$off: (-$off1,$len1 +$off2,$len2) SKIP $skcnt\n"); } else { if ( $i < $diffcnt ) { prt("Store balance ".($diffcnt - $i)." lines from old file [$ff]\n"); for ( ; $i < $diffcnt; $i++) { $dline = ${$rdifflines}[$i]; chomp $dline; $i2 = $i + 1; push(@newlines,$dline); $msg = "$i2: [$dline] stored in new"; prt("$msg\n") if (VERB9() || $dbg_07); } } } } ### write_patched_file($rh,$ff); ### pgm_exit(1,"TEMP EXIT"); return $revind; } sub show_oldfile_lines($$$) { my ($rh,$ff,$rdifflines) = @_; my $oldlncnt = scalar @{$rdifflines}; if ($oldlncnt && (VERB9() || $dbg_03)) { # show the FILE LINE my ($msg); prt("\n") if (VERB9() || $dbg_11); prt("List $oldlncnt lines in [$ff] loaded for patching...\n"); $oldlncnt = 0; foreach $msg (@{$rdifflines}) { $oldlncnt++; chomp $msg; prt("$oldlncnt: [$msg]\n") if (VERB9() || $dbg_11); } } } # deal_with_patches($ff,\@cnt_stack,\@oldlines); sub deal_with_patches($$$$$) { my ($rh,$ff,$rcnt_stack,$rdifflines,$rpatchlines) = @_; my ($dcnt,$dline,$msg); my ($rca,$off1,$len1,$off2,$len2,$off,$endp); my ($inpatch,$rpla,$pltype,$plline,$cpl_cnt,$prevline); my ($pladd,$pltot,$plsub,$msg2,$pstart); my ($i,$i2,$rhh,$ffnew); my $rcnt = scalar @{$rcnt_stack}; my $maxplcnt = scalar @{$rpatchlines}; my $diffcnt = scalar @{$rdifflines}; if (defined ${$rh}{$ff}) { $rhh = ${$rh}{$ff}; if (defined ${$rhh}{'NEW_FILE'}) { $ffnew = ${$rhh}{'NEW_FILE'}; } else { pgm_exit(1,"ERROR: New file name to patch NOT in hash!\n"); } } else { pgm_exit(1,"ERROR: New file name to patch NOT in hash!\n"); } if (VERB9() || $dbg_02) { show_oldfile_lines($rh,$ff,$rdifflines); show_patch_lines($rh,$rdifflines,$rpatchlines,$rcnt_stack); } if ($diffcnt) { # was a NEW file loaded to patch # run the patching test_patch_lines($rh,$ff,$rcnt_stack,$rdifflines,$rpatchlines); write_patched_file($rh,$ff); } else { prt("No file [$ffnew] found to patch! Perhaps adjust --strip (-p) paramter.\n"); } } sub process_in_file($) { my ($inf) = @_; if (! open INF, "<$inf") { pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); } my @lines = ; close INF; my $lncnt = scalar @lines; prt("Processing $lncnt lines, from [$inf]...\n"); my ($line,$inc,$lnn,$ofiles,$lcnts,$aline,$mline,$sline,$pfile,$mfile); my ($indiff,$gotnums,$lnncnt,$plncnt,$mlncnt,$cntlnn,$tmp,$extra); my ($innums,$off1,$len1,$off2,$len2,$ccnt,$ncnt,$msg,$islast); my ($file1,$file2,$targd,$spfile,$smfile,$spfile2,$smfile2,@arr); my ($poff1,$plen1,$poff2,$plen2,$skcnt); my @oldlines = (); my @newlines = (); my ($newlncnt, $oldlncnt); my @cnt_stack = (); my @patchlines = (); my ($dline,$dcnt,$chunk_cnt,$newfile,$file_count); my $ffold = ''; # preceeded by '---' my $ffnew = ''; # preceeded by '+++' my %hash = (); my $rh = \%hash; $targd = $work_dir; if (length($targd)) { $targd .= $PATH_SEP if ( !($targd =~ /(\\|\/)$/) ); $targd = $path_function->($targd); } $indiff = 0; $gotnums = 0; $lnncnt = 0; $plncnt = 0; $mlncnt = 0; $innums = 0; $islast = 0; $lnn = 0; $cntlnn = 0; $lcnts = ''; $file1 = ''; $file2 = ''; $oldlncnt = 0; $newlncnt = 0; $chunk_cnt = 0; $newfile = 0; $file_count = 0; $off1 = $len1 = $off2 = $len2 = 0; foreach $line (@lines) { chomp $line; $lnn++; # assumption either a line begin # diff [options] file1 file2 # being with '@@...' the line counts # examples # @@ -1,4 +1,17 @@ and @@ -0,0 +1,138 @@ # begin with ' ', '+' or '-' = diff block # else ignore if ($line =~ /^diff\s+(.+)$/) { $ofiles = $1; $file1 = ''; $file2 = ''; get_diff_files($ofiles,\$file1,\$file2); if ($lnncnt || $plncnt || $mlncnt) { $ccnt = $lnncnt + $mlncnt; $ncnt = $lnncnt + $plncnt; #prt("$cntlnn: Prev CNTS ".($lnncnt + $mlncnt)." ".($lnncnt + $plncnt)." [$lcnts] ($off1,$len1 $off2,$len2)\n"); $msg = "$cntlnn: Prev CNT1 [$lcnts] ($off1,$len1 $off2,$len2) -$ccnt +$ncnt"; if (($ccnt == $len1) && ($ncnt == $len2)) { $msg .= " ok"; } elsif (($ncnt == $len1) && ($ccnt == $len2)) { $msg .= " okR"; } else { $msg .= " CHECK COUNTS!"; } $msg .= " -$oldlncnt -$newlncnt" if ($oldlncnt || $newlncnt); prt("$msg\n"); ### if ($oldlncnt) { my %h = (); my @a = @oldlines; $h{'OLD_LINES'} = \@a; $h{'OLD_FILE'} = $ffold; my @b = @newlines; $h{'NEW_LINES'} = \@b; $h{'NEW_FILE'} = $ffnew; $h{'PATCH_LINES'} = [ @patchlines ]; $h{'PATCH_FILE'} = $inf; my @c = @cnt_stack; $h{'COUNT_STACK'} = \@c; ${$rh}{$ffold} = \%h; deal_with_patches($rh,$ffold,\@cnt_stack,\@oldlines,\@patchlines); ###} else { ### prt("NO lines from [$ffold]\n") if (VERB9() || $dbg_02); ###} } # ok, start a NEW diff # ==================== $oldlncnt = 0; $file_count++; # this should ALWAYS be in the order 'oldFile' 'newFile' # the patch is intended to update the 'oldFile' to equal the 'newFile' # Such a patch applied can be 'reversed' - taken out - by -R $msg = "\n$lnn:$file_count: DIFF [$ofiles]"; if (length($file1) && length($file2)) { # note GNU patch IGNORES this 'diff ...' line #$spfile = strip_file_path($file1); #$ff .= $spfile; #if (-f $ff) { # @oldlines = (); # if (get_diff_file_lines($ff,\@oldlines)) { # $oldlncnt = scalar @oldlines; # $msg .= " found [$ff] $oldlncnt lines"; # } else { # $msg .= " found [$ff] but LOAD FAILED"; # } #} else { # $msg .= " [$ff] NOT FOUND!" #} } else { prtw("WARNING:$lnn: CHECK ME\nLine [$line] did NOT yield 2 files!\nGot 1 [$file1] 2 [$file2]\n"); } $msg .= " PATCH CNTS cleared"; prt("$msg\n"); $lnncnt = 0; $plncnt = 0; $mlncnt = 0; $lcnts = ''; $innums = 0; $islast = 0; $chunk_cnt = 0; @cnt_stack = (); # clear the COUNT stack $newfile = 0; # set when counts done @patchlines = (); } elsif ($line =~ /^\s{1}(.*)$/) { # SAME LINE $sline = $1; $lnncnt++; push(@patchlines,["S",$sline]); $ccnt = $lnncnt + $mlncnt; $ncnt = $lnncnt + $plncnt; $msg = "$lnn:$ccnt:$ncnt: S [$sline]"; if (($ccnt == $len1) && ($ncnt == $len2)) { $msg .= " last"; $islast = 1; } elsif (($ncnt == $len1) && ($ccnt == $len2)) { $msg .= " Rlast"; $islast = 1; } prt("$msg\n") if (VERB9() || $dbg_01); } elsif ($line =~ /^\+\+\+\s+(.*)$/) { # +++ NEW FILES $pfile = $1; $msg = "$lnn: PF [$pfile]"; @arr = space_split($pfile); $pfile = strip_quotes($arr[0]); if (length($pfile)) { $ffnew = $targd; $smfile2 = strip_file_path($pfile); $ffnew .= $smfile2; if (-f $ffnew) { @newlines = (); if (get_diff_file_lines($ffnew,\@newlines)) { $newlncnt = scalar @newlines; $msg .= " found [$ffnew] $newlncnt lines"; } else { $msg .= " found [$ffnew] but LOAD FAILED"; } } else { $msg .= " [$ffnew] NOT FOUND!" } } prt("$msg\n"); } elsif ($line =~ /^\+(.*)$/) { # ADDED LINE $aline = $1; $plncnt++; push(@patchlines,["+",$aline]); $ccnt = $lnncnt + $mlncnt; $ncnt = $lnncnt + $plncnt; prt("$lnn:$ccnt:$ncnt: + [$aline]\n") if (VERB9() || $dbg_01); } elsif ($line =~ /^---\s+(.+)\s*$/) { # --- OLD FILE $mfile = $1; $msg = "$lnn: MF [$mfile]"; @arr = space_split($mfile); $mfile = strip_quotes($arr[0]); if (length($mfile)) { $ffold = $targd; $smfile2 = strip_file_path($mfile); $ffold .= $smfile2; if (-f $ffold) { @oldlines = (); if (get_diff_file_lines($ffold,\@oldlines)) { $oldlncnt = scalar @oldlines; $msg .= " found [$ffold] $oldlncnt lines"; } else { $msg .= " found [$ffold] but LOAD FAILED"; } } else { $msg .= " [$ffold] NOT FOUND!" } } prt("$msg\n"); } elsif ($line =~ /^-(.*)$/) { # DELETE LINE $mline = $1; $mlncnt++; push(@patchlines,["-",$mline]); $ccnt = $lnncnt + $mlncnt; $ncnt = $lnncnt + $plncnt; prt("$lnn:$ccnt:$ncnt: - [$mline]\n") if (VERB9() || $dbg_01); } elsif ($line =~ /^\@\@\s+(.+)\s+\@\@\s*(.*)$/) { $tmp = $1; $extra = $2; # git puts this after the numbers - is actually the next line!!! # BUT is NOT included in the count!? if ($lnncnt || $plncnt || $mlncnt) { $ccnt = $lnncnt + $mlncnt; $ncnt = $lnncnt + $plncnt; #prt("$cntlnn: Prev CNT2 ".($lnncnt + $plncnt)." ".($lnncnt + $mlncnt)." [$lcnts] ($off1,$len1 $off2,$len2)\n"); $msg = "$cntlnn: Prev CNT2 [$lcnts] ($off1,$len1 $off2,$len2) -$ccnt +$ncnt"; if (($ccnt == $len1) && ($ncnt == $len2)) { $msg .= " ok"; } elsif (($ncnt == $len1) && ($ccnt == $len2)) { $msg .= " okR"; } else { $msg .= " CHECK COUNTS!"; } $msg .= " -$oldlncnt -$newlncnt" if ($oldlncnt || $newlncnt); prt("$msg\n"); } $lcnts = $tmp; $poff1 = $off1; $plen1 = $len1; $poff2 = $off2; $plen2 = $len2; get_counts($lcnts,\$off1,\$len1,\$off2,\$len2); $chunk_cnt++; # count another chunk $msg = "$lnn:$chunk_cnt: D\@CNTS [$lcnts] ($off1,$len1 $off2,$len2)"; $skcnt = $off1 - ($poff1 + $plen1); $skcnt -= 1 if ($skcnt && !@cnt_stack); if ( ($off1 == 0) && ($len1 == 0) ) { $newfile = 1; $msg .= " NEW file, so should be NOT FOUND!"; push(@new_files,$ffold); } else { $msg .= " skip $skcnt"; } prt("$msg\n"); push(@cnt_stack, [ $off1, $len1, $off2, $len2 ]); # add counts to patch line - to separate chunks # push(@patchlines,["N", "$off1, $len1, $off2, $len2"]); $lnncnt = 0; $plncnt = 0; $mlncnt = 0; $cntlnn = $lnn; $innums = 1; prt("$lnn: s [$extra] (but NOT in count)\n") if ( $extra && length($extra) ) } elsif ($line =~ /^index\s+/) { # git ' index ...' line } elsif ($line =~ /^\\\s+/) { prt("$lnn: DC [$line]\n"); } else { prt("$lnn: ??? [$line] CHECK THIS\n"); } } # END OF PATCH FILE - deal with last diff found if ($lnncnt || $plncnt || $mlncnt) { $ccnt = $lnncnt + $mlncnt; $ncnt = $lnncnt + $plncnt; #prt("$cntlnn: Prev CNTS ".($lnncnt + $mlncnt)." ".($lnncnt + $plncnt)." [$lcnts] ($off1,$len1 $off2,$len2)\n"); $msg = "$cntlnn: Prev CNTE [$lcnts] ($off1,$len1 $off2,$len2) -$ccnt +$ncnt"; if (($ccnt == $len1) && ($ncnt == $len2)) { $msg .= " ok"; } elsif (($ncnt == $len1) && ($ccnt == $len2)) { $msg .= " okR"; } else { $msg .= " CHECK COUNTS!"; } $msg .= " -$oldlncnt -$newlncnt" if ($oldlncnt || $newlncnt); prt("$msg\n"); if (!$oldlncnt) { prt("NO lines from [$ffold]\n") if (VERB9() || $dbg_02); } my %h = (); my @a = @oldlines; $h{'OLD_LINES'} = \@a; $h{'OLD_FILE'} = $ffold; my @b = @newlines; $h{'NEW_LINES'} = \@b; $h{'NEW_FILE'} = $ffnew; $h{'PATCH_LINES'} = [ @patchlines ]; $h{'PATCH_FILE'} = $inf; my @c = @cnt_stack; $h{'COUNT_STACK'} = \@c; ${$rh}{$ffold} = \%h; deal_with_patches($rh,$ffold,\@cnt_stack,\@oldlines,\@patchlines); } # SHOW any NEW files found in the 'patch' file - like '@@ -0,0 +n,n @@' ${$rh}{'NEW_FILES_IN_PATCH'} = \@new_files; return $rh; } sub show_ref_hoh($$) { my ($inf,$rh) = @_; # SHOW any NEW files found in the 'patch' file - like '@@ -0,0 +n,n @@' my ($ra,$ncnt,$ffold,$type,$val); my %done = (); $ncnt = scalar keys($rh); prt("\nRef has contains $ncnt keys...\n"); if ( defined ${$rh}{'NEW_FILES_IN_PATCH'} ) { $ra = ${$rh}{'NEW_FILES_IN_PATCH'}; $ncnt = scalar @{$ra}; if ($ncnt) { prt("Patch file [$inf] lists $ncnt NEW files...\n"); $ncnt = 0; foreach $ffold (sort @{$ra}) { $ncnt++; prt("$ncnt: [$ffold]\n"); $done{$ffold} = 1; } } } my @diff_list = (); foreach $ffold (keys %{$rh}) { $val = ${$rh}{$ffold}; $type = ref $val; if ($type eq 'HASH') { if ( ! defined $done{$ffold} ) { $done{$ffold} = 1; push(@diff_list,$ffold); } } } $ncnt = scalar @diff_list; prt("\nPatch file [$inf] lists $ncnt DIFF files...\n"); $ncnt = 0; foreach $ffold (sort @diff_list) { $ncnt++; prt("$ncnt: [$ffold]\n"); } } ######################################### ### MAIN ### parse_args(@ARGV); my $ref_hoh = process_in_file($in_file); show_ref_hoh($in_file,$ref_hoh); pgm_exit(0,""); ######################################## sub need_arg { my ($arg,@av) = @_; pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av); } sub parse_args { my (@av) = @_; my ($arg,$sarg); while (@av) { $arg = $av[0]; if ($arg =~ /^-/) { $sarg = substr($arg,1); $sarg = substr($sarg,1) while ($sarg =~ /^-/); if (($sarg =~ /^h/i)||($sarg eq '?')) { give_help(); pgm_exit(0,"Help exit(0)"); } elsif ($sarg =~ /^v/) { if ($sarg =~ /^v.*(\d+)$/) { $verbosity = $1; } else { while ($sarg =~ /^v/) { $verbosity++; $sarg = substr($sarg,1); } } prt("Verbosity = $verbosity\n") if (VERB1()); } elsif ($sarg =~ /^l/) { $load_log = 1; prt("Set to load log at end.\n") if (VERB1()); } elsif ($sarg =~ /^d/) { need_arg(@av); shift @av; $sarg = $av[0]; $work_dir = $sarg; prt("Set work directory to [$work_dir].\n") if (VERB1()); pgm_exit(1,"ERROR: Work directory [$work_dir] DOES NOT EXIST!\n") if (! -d $work_dir); } elsif (($sarg =~ /^p/)||($sarg eq 'strip')) { need_arg(@av); shift @av; $sarg = $av[0]; if ($sarg =~ /^\d+$/) { $strip_count = $sarg; } else { pgm_exit(1,"ERROR: Argument $arg MUST be followed by an integer NUMBER! (not $sarg)\n"); } #} elsif (($sarg eq 'reverse')||($sarg eq 'R')) { # $files_reversed = 1; # prt("Set to reverse new old files.\n"); } else { pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n"); } } else { $in_file = $arg; prt("Set input to [$in_file]\n") if (VERB1()); } shift @av; } if ((length($in_file) == 0) && $debug_on) { $in_file = $def_file; prt("Set DEFAULT input to [$in_file]\n"); } if (length($in_file) == 0) { pgm_exit(1,"ERROR: No input files found in command!\n"); } if (! -f $in_file) { pgm_exit(1,"ERROR: Unable to find in file [$in_file]! Check name, location...\n"); } } sub patch_help { my $txt = < mods.diff \$ patch < mods.diff # apply the patches This will modify the 'oldFile' to match the 'newFile' The mods.diff file will start with diff -u oldFile newFile --- oldFile date time +++ newFile date time New lines will be added, and removed lines deleted and modified lines exchanged After patch 'oldFile' and 'newFile' will be the SAME A patch can be reversed using -R, in which case the effects of the patch will be reversed - taken out C:\\FGCVS>patch --help Usage: patch [OPTION]... [ORIGFILE [PATCHFILE]] Input options: -p NUM --strip=NUM Strip NUM leading components from file names. -F LINES --fuzz LINES Set the fuzz factor to LINES for inexact matching. -l --ignore-whitespace Ignore white space changes between patch and input. -c --context Interpret the patch as a context difference. -e --ed Interpret the patch as an ed script. -n --normal Interpret the patch as a normal difference. -u --unified Interpret the patch as a unified difference. -N --forward Ignore patches that appear to be reversed or already applied. -R --reverse Assume patches were created with old and new files swapped. -i PATCHFILE --input=PATCHFILE Read patch from PATCHFILE instead of stdin. Output options: -o FILE --output=FILE Output patched files to FILE. -r FILE --reject-file=FILE Output rejects to FILE. -D NAME --ifdef=NAME Make merged if-then-else output using NAME. -E --remove-empty-files Remove output files that are empty after patching. -Z --set-utc Set times of patched files, assuming diff uses UTC (GMT). -T --set-time Likewise, assuming local time. --quoting-style=WORD output file names using quoting style WORD. Valid WORDs are: literal, shell, shell-always, c, escape. Default is taken from QUOTING_STYLE env variable, or 'shell' if unset. Backup and version control options: -b --backup Back up the original contents of each file. --backup-if-mismatch Back up if the patch does not match exactly. --no-backup-if-mismatch Back up mismatches only if otherwise requested. -V STYLE --version-control=STYLE Use STYLE version control. STYLE is either 'simple', 'numbered', or 'existing'. -B PREFIX --prefix=PREFIX Prepend PREFIX to backup file names. -Y PREFIX --basename-prefix=PREFIX Prepend PREFIX to backup file basenames. -z SUFFIX --suffix=SUFFIX Append SUFFIX to backup file names. -g NUM --get=NUM Get files from RCS etc. if positive; ask if negative. Miscellaneous options: -t --batch Ask no questions; skip bad-Prereq patches; assume reversed. -f --force Like -t, but ignore bad-Prereq patches, and assume unreversed. -s --quiet --silent Work silently unless an error occurs. --verbose Output extra information about the work being done. --dry-run Do not actually change any files; just print what would happen. --posix Conform to the POSIX standard. --unified-reject-files Create unified reject files. --global-reject-file=file Put all rejects into one file. -d DIR --directory=DIR Change the working directory to DIR first. --binary Read and write data in binary mode (no effect on this platform). -v --version Output version info. --help Output this help. Report bugs to . Report bugs of this Windows port to . EOF return $txt; } sub give_help { prt("$pgmname: version $VERS\n"); prt("Usage: $pgmname [options] in-file\n"); prt("Options:\n"); prt(" --help (-h or -?) = This help, and exit 0.\n"); prt(" --dir (-d) = Set WORK directory.\n"); # prt(" --reverse (-R) = Assume patch created with old and new reversed.\n"); prt(" --strip (-p) = Strip leading path count.\n"); prt(" --verb[n] (-v) = Bump [or set] verbosity. def=$verbosity\n"); prt(" --load (-l) = Load LOG at end. ($outfile)\n"); #prt(" --out (-o) = Write output to this file.\n"); } # BOM Representations of byte order marks by encoding # UTF-8 EF BB BF 239 187 191 # UTF-16 (BE) FE FF 254 255 # UTF-16 (LE) FF FE 255 254 # UTF-32 (BE) 00 00 FE FF 0 0 254 255 # UTF-32 (LE) FF FE 00 00 255 254 0 0 # UTF-7 2B 2F 76 38 2B 2F 76 39 2B 2F 76 2B 2B 2F 76 2F # UTF-1 F7 64 4C 247 100 76 # UTF-EBCDIC DD 73 66 73 221 115 102 115 # SCSU 0E FE FF 14 254 255 # BOCU-1 FB EE 28 251 238 40 # GB-18030 84 31 95 33 132 49 149 51 # eof - patchit.pl