1818import copy
1919import logging
2020import re
21+ import tempfile
2122
2223# cStringIO doesn't support unicode in 2.5
2324try :
@@ -477,11 +478,26 @@ def lineno(self):
477478 # XXX header += srcname
478479 # double source filename line is encountered
479480 # attempt to restart from this second line
480- re_filename = b"^--- ([^\t ]+)"
481- match = re .match (re_filename , line )
481+
482+ # Files dated at Unix epoch don't exist, e.g.:
483+ # '1970-01-01 01:00:00.000000000 +0100'
484+ # They include timezone offsets.
485+ # .. which can be parsed (if we remove the nanoseconds)
486+ # .. by strptime() with:
487+ # '%Y-%m-%d %H:%M:%S %z'
488+ # .. but unfortunately this relies on the OSes libc
489+ # strptime function and %z support is patchy, so we drop
490+ # everything from the . onwards and group the year and time
491+ # separately.
492+ re_filename_date_time = b"^--- ([^\t ]+)(?:\s([0-9-]+)\s([0-9:]+)|.*)"
493+ match = re .match (re_filename_date_time , line )
482494 # todo: support spaces in filenames
483495 if match :
484496 srcname = match .group (1 ).strip ()
497+ date = match .group (2 )
498+ time = match .group (3 )
499+ if (date == b'1970-01-01' or date == b'1969-12-31' ) and time .split (b':' ,1 )[1 ] == b'00:00' :
500+ srcname = b'/dev/null'
485501 else :
486502 warning ("skipping invalid filename at line %d" % (lineno + 1 ))
487503 self .errors += 1
@@ -516,8 +532,8 @@ def lineno(self):
516532 filenames = False
517533 headscan = True
518534 else :
519- re_filename = b"^\+\+\+ ([^\t ]+)"
520- match = re .match (re_filename , line )
535+ re_filename_date_time = b"^\+\+\+ ([^\t ]+)(?:\s([0-9-]+)\s([0-9:]+)|.* )"
536+ match = re .match (re_filename_date_time , line )
521537 if not match :
522538 warning ("skipping invalid patch - no target filename at line %d" % (lineno + 1 ))
523539 self .errors += 1
@@ -526,12 +542,18 @@ def lineno(self):
526542 filenames = False
527543 headscan = True
528544 else :
545+ tgtname = match .group (1 ).strip ()
546+ date = match .group (2 )
547+ time = match .group (3 )
548+ if (date == b'1970-01-01' or date == b'1969-12-31' ) and time .split (b':' ,1 )[1 ] == b'00:00' :
549+ tgtname = b'/dev/null'
529550 if p : # for the first run p is None
530551 self .items .append (p )
531552 p = Patch ()
532553 p .source = srcname
533554 srcname = None
534- p .target = match .group (1 ).strip ()
555+ p .target = tgtname
556+ tgtname = None
535557 p .header = header
536558 header = []
537559 # switch to hunkhead state
@@ -729,16 +751,17 @@ def _normalize_filenames(self):
729751 while p .target .startswith (b".." + sep ):
730752 p .target = p .target .partition (sep )[2 ]
731753 # absolute paths are not allowed
732- if xisabs (p .source ) or xisabs (p .target ):
754+ if (xisabs (p .source ) and p .source != b'/dev/null' ) or \
755+ (xisabs (p .target ) and p .target != b'/dev/null' ):
733756 warning ("error: absolute paths are not allowed - file no.%d" % (i + 1 ))
734757 self .warnings += 1
735- if xisabs (p .source ):
758+ if xisabs (p .source ) and p . source != b'/dev/null' :
736759 warning ("stripping absolute path from source name '%s'" % p .source )
737760 p .source = xstrip (p .source )
738- if xisabs (p .target ):
761+ if xisabs (p .target ) and p . target != b'/dev/null' :
739762 warning ("stripping absolute path from target name '%s'" % p .target )
740763 p .target = xstrip (p .target )
741-
764+
742765 self .items [i ].source = p .source
743766 self .items [i ].target = p .target
744767
@@ -800,12 +823,23 @@ def diffstat(self):
800823 return output
801824
802825
803- def findfile (self , old , new ):
804- """ return name of file to be patched or None """
805- if exists (old ):
806- return old
826+ def findfiles (self , old , new ):
827+ """ return tuple of source file, target file """
828+ if old == b'/dev/null' :
829+ handle , abspath = tempfile .mkstemp (suffix = b'pypatch' )
830+ # The source file must contain a line for the hunk matching to succeed.
831+ os .write (handle , b' ' )
832+ os .close (handle )
833+ if not exists (new ):
834+ handle = open (new , 'wb' )
835+ handle .close ()
836+ return abspath , new
837+ elif exists (old ):
838+ return old , old
807839 elif exists (new ):
808- return new
840+ return new , new
841+ elif new == b'/dev/null' :
842+ return None , None
809843 else :
810844 # [w] Google Code generates broken patches with its online editor
811845 debug ("broken patch from Google Code, stripping prefixes.." )
@@ -814,10 +848,10 @@ def findfile(self, old, new):
814848 debug (" %s" % old )
815849 debug (" %s" % new )
816850 if exists (old ):
817- return old
851+ return old , old
818852 elif exists (new ):
819- return new
820- return None
853+ return new , new
854+ return None , None
821855
822856
823857 def apply (self , strip = 0 , root = None ):
@@ -848,27 +882,27 @@ def apply(self, strip=0, root=None):
848882 debug ("stripping %s leading component(s) from:" % strip )
849883 debug (" %s" % p .source )
850884 debug (" %s" % p .target )
851- old = pathstrip (p .source , strip )
852- new = pathstrip (p .target , strip )
885+ old = p . source if p . source == b'/dev/null' else pathstrip (p .source , strip )
886+ new = p . target if p . target == b'/dev/null' else pathstrip (p .target , strip )
853887 else :
854888 old , new = p .source , p .target
855889
856- filename = self .findfile (old , new )
890+ filenameo , filenamen = self .findfiles (old , new )
857891
858- if not filename :
892+ if not filenameo or not filenamen :
859893 warning ("source/target file does not exist:\n --- %s\n +++ %s" % (old , new ))
860894 errors += 1
861895 continue
862- if not isfile (filename ):
863- warning ("not a file - %s" % filename )
896+ if not isfile (filenameo ):
897+ warning ("not a file - %s" % filenameo )
864898 errors += 1
865899 continue
866900
867901 # [ ] check absolute paths security here
868- debug ("processing %d/%d:\t %s" % (i + 1 , total , filename ))
902+ debug ("processing %d/%d:\t %s" % (i + 1 , total , filenamen ))
869903
870904 # validate before patching
871- f2fp = open (filename , 'rb' )
905+ f2fp = open (filenameo , 'rb' )
872906 hunkno = 0
873907 hunk = p .hunks [hunkno ]
874908 hunkfind = []
@@ -891,7 +925,7 @@ def apply(self, strip=0, root=None):
891925 if line .rstrip (b"\r \n " ) == hunkfind [hunklineno ]:
892926 hunklineno += 1
893927 else :
894- info ("file %d/%d:\t %s" % (i + 1 , total , filename ))
928+ info ("file %d/%d:\t %s" % (i + 1 , total , filenamen ))
895929 info (" hunk no.%d doesn't match source file at line %d" % (hunkno + 1 , lineno + 1 ))
896930 info (" expected: %s" % hunkfind [hunklineno ])
897931 info (" actual : %s" % line .rstrip (b"\r \n " ))
@@ -911,8 +945,8 @@ def apply(self, strip=0, root=None):
911945 break
912946
913947 # check if processed line is the last line
914- if lineno + 1 == hunk .startsrc + len (hunkfind )- 1 :
915- debug (" hunk no.%d for file %s -- is ready to be patched" % (hunkno + 1 , filename ))
948+ if len ( hunkfind ) == 0 or lineno + 1 == hunk .startsrc + len (hunkfind )- 1 :
949+ debug (" hunk no.%d for file %s -- is ready to be patched" % (hunkno + 1 , filenamen ))
916950 hunkno += 1
917951 validhunks += 1
918952 if hunkno < len (p .hunks ):
@@ -924,34 +958,39 @@ def apply(self, strip=0, root=None):
924958 break
925959 else :
926960 if hunkno < len (p .hunks ):
927- warning ("premature end of source file %s at hunk %d" % (filename , hunkno + 1 ))
961+ warning ("premature end of source file %s at hunk %d" % (filenameo , hunkno + 1 ))
928962 errors += 1
929963
930964 f2fp .close ()
931965
932966 if validhunks < len (p .hunks ):
933- if self ._match_file_hunks (filename , p .hunks ):
934- warning ("already patched %s" % filename )
967+ if self ._match_file_hunks (filenameo , p .hunks ):
968+ warning ("already patched %s" % filenameo )
935969 else :
936- warning ("source file is different - %s" % filename )
970+ warning ("source file is different - %s" % filenameo )
937971 errors += 1
938972 if canpatch :
939- backupname = filename + b".orig"
973+ backupname = filenamen + b".orig"
940974 if exists (backupname ):
941975 warning ("can't backup original file to %s - aborting" % backupname )
942976 else :
943977 import shutil
944- shutil .move (filename , backupname )
945- if self .write_hunks (backupname , filename , p .hunks ):
946- info ("successfully patched %d/%d:\t %s" % (i + 1 , total , filename ))
978+ shutil .move (filenamen , backupname )
979+ if self .write_hunks (backupname if filenameo == filenamen else filenameo , filenamen , p .hunks ):
980+ info ("successfully patched %d/%d:\t %s" % (i + 1 , total , filenamen ))
947981 os .unlink (backupname )
982+ if new == b'/dev/null' :
983+ # check that filename is of size 0 and delete it.
984+ if os .path .getsize (filenamen ) > 0 :
985+ warning ("expected patched file to be empty as it's marked as deletion:\t %s" % filenamen )
986+ os .unlink (filenamen )
948987 else :
949988 errors += 1
950- warning ("error patching file %s" % filename )
951- shutil .copy (filename , filename + ".invalid" )
952- warning ("invalid version is saved to %s" % filename + ".invalid" )
989+ warning ("error patching file %s" % filenamen )
990+ shutil .copy (filenamen , filenamen + ".invalid" )
991+ warning ("invalid version is saved to %s" % filenamen + ".invalid" )
953992 # todo: proper rejects
954- shutil .move (backupname , filename )
993+ shutil .move (backupname , filenamen )
955994
956995 if root :
957996 os .chdir (prevdir )
0 commit comments