18
18
import copy
19
19
import logging
20
20
import re
21
+ import tempfile
21
22
22
23
# cStringIO doesn't support unicode in 2.5
23
24
try :
@@ -477,11 +478,26 @@ def lineno(self):
477
478
# XXX header += srcname
478
479
# double source filename line is encountered
479
480
# 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 )
482
494
# todo: support spaces in filenames
483
495
if match :
484
496
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'
485
501
else :
486
502
warning ("skipping invalid filename at line %d" % (lineno + 1 ))
487
503
self .errors += 1
@@ -516,8 +532,8 @@ def lineno(self):
516
532
filenames = False
517
533
headscan = True
518
534
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 )
521
537
if not match :
522
538
warning ("skipping invalid patch - no target filename at line %d" % (lineno + 1 ))
523
539
self .errors += 1
@@ -526,12 +542,18 @@ def lineno(self):
526
542
filenames = False
527
543
headscan = True
528
544
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'
529
550
if p : # for the first run p is None
530
551
self .items .append (p )
531
552
p = Patch ()
532
553
p .source = srcname
533
554
srcname = None
534
- p .target = match .group (1 ).strip ()
555
+ p .target = tgtname
556
+ tgtname = None
535
557
p .header = header
536
558
header = []
537
559
# switch to hunkhead state
@@ -729,16 +751,17 @@ def _normalize_filenames(self):
729
751
while p .target .startswith (b".." + sep ):
730
752
p .target = p .target .partition (sep )[2 ]
731
753
# 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' ):
733
756
warning ("error: absolute paths are not allowed - file no.%d" % (i + 1 ))
734
757
self .warnings += 1
735
- if xisabs (p .source ):
758
+ if xisabs (p .source ) and p . source != b'/dev/null' :
736
759
warning ("stripping absolute path from source name '%s'" % p .source )
737
760
p .source = xstrip (p .source )
738
- if xisabs (p .target ):
761
+ if xisabs (p .target ) and p . target != b'/dev/null' :
739
762
warning ("stripping absolute path from target name '%s'" % p .target )
740
763
p .target = xstrip (p .target )
741
-
764
+
742
765
self .items [i ].source = p .source
743
766
self .items [i ].target = p .target
744
767
@@ -800,12 +823,23 @@ def diffstat(self):
800
823
return output
801
824
802
825
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
807
839
elif exists (new ):
808
- return new
840
+ return new , new
841
+ elif new == b'/dev/null' :
842
+ return None , None
809
843
else :
810
844
# [w] Google Code generates broken patches with its online editor
811
845
debug ("broken patch from Google Code, stripping prefixes.." )
@@ -814,10 +848,10 @@ def findfile(self, old, new):
814
848
debug (" %s" % old )
815
849
debug (" %s" % new )
816
850
if exists (old ):
817
- return old
851
+ return old , old
818
852
elif exists (new ):
819
- return new
820
- return None
853
+ return new , new
854
+ return None , None
821
855
822
856
823
857
def apply (self , strip = 0 , root = None ):
@@ -848,27 +882,27 @@ def apply(self, strip=0, root=None):
848
882
debug ("stripping %s leading component(s) from:" % strip )
849
883
debug (" %s" % p .source )
850
884
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 )
853
887
else :
854
888
old , new = p .source , p .target
855
889
856
- filename = self .findfile (old , new )
890
+ filenameo , filenamen = self .findfiles (old , new )
857
891
858
- if not filename :
892
+ if not filenameo or not filenamen :
859
893
warning ("source/target file does not exist:\n --- %s\n +++ %s" % (old , new ))
860
894
errors += 1
861
895
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 )
864
898
errors += 1
865
899
continue
866
900
867
901
# [ ] 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 ))
869
903
870
904
# validate before patching
871
- f2fp = open (filename , 'rb' )
905
+ f2fp = open (filenameo , 'rb' )
872
906
hunkno = 0
873
907
hunk = p .hunks [hunkno ]
874
908
hunkfind = []
@@ -891,7 +925,7 @@ def apply(self, strip=0, root=None):
891
925
if line .rstrip (b"\r \n " ) == hunkfind [hunklineno ]:
892
926
hunklineno += 1
893
927
else :
894
- info ("file %d/%d:\t %s" % (i + 1 , total , filename ))
928
+ info ("file %d/%d:\t %s" % (i + 1 , total , filenamen ))
895
929
info (" hunk no.%d doesn't match source file at line %d" % (hunkno + 1 , lineno + 1 ))
896
930
info (" expected: %s" % hunkfind [hunklineno ])
897
931
info (" actual : %s" % line .rstrip (b"\r \n " ))
@@ -911,8 +945,8 @@ def apply(self, strip=0, root=None):
911
945
break
912
946
913
947
# 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 ))
916
950
hunkno += 1
917
951
validhunks += 1
918
952
if hunkno < len (p .hunks ):
@@ -924,34 +958,39 @@ def apply(self, strip=0, root=None):
924
958
break
925
959
else :
926
960
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 ))
928
962
errors += 1
929
963
930
964
f2fp .close ()
931
965
932
966
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 )
935
969
else :
936
- warning ("source file is different - %s" % filename )
970
+ warning ("source file is different - %s" % filenameo )
937
971
errors += 1
938
972
if canpatch :
939
- backupname = filename + b".orig"
973
+ backupname = filenamen + b".orig"
940
974
if exists (backupname ):
941
975
warning ("can't backup original file to %s - aborting" % backupname )
942
976
else :
943
977
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 ))
947
981
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 )
948
987
else :
949
988
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" )
953
992
# todo: proper rejects
954
- shutil .move (backupname , filename )
993
+ shutil .move (backupname , filenamen )
955
994
956
995
if root :
957
996
os .chdir (prevdir )
0 commit comments