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
@@ -654,7 +676,7 @@ def _detect_type(self, p):
654
676
break
655
677
if p .header [idx ].startswith (b'diff --git a/' ):
656
678
if (idx + 1 < len (p .header )
657
- and re .match (b'index \\ w{7}..\\ w{7} \\ d{6}' , p .header [idx + 1 ])):
679
+ and re .match (b'(?: index \\ w{7}..\\ w{7} \\ d{6}|new file mode \\ d*) ' , p .header [idx + 1 ])):
658
680
if DVCS :
659
681
return GIT
660
682
@@ -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,24 @@ 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 = 'pypatch' )
830
+ abspath = abspath .encode ()
831
+ # The source file must contain a line for the hunk matching to succeed.
832
+ os .write (handle , b' ' )
833
+ os .close (handle )
834
+ if not exists (new ):
835
+ handle = open (new , 'wb' )
836
+ handle .close ()
837
+ return abspath , new
838
+ elif exists (old ):
839
+ return old , old
807
840
elif exists (new ):
808
- return new
841
+ return new , new
842
+ elif new == b'/dev/null' :
843
+ return None , None
809
844
else :
810
845
# [w] Google Code generates broken patches with its online editor
811
846
debug ("broken patch from Google Code, stripping prefixes.." )
@@ -814,10 +849,10 @@ def findfile(self, old, new):
814
849
debug (" %s" % old )
815
850
debug (" %s" % new )
816
851
if exists (old ):
817
- return old
852
+ return old , old
818
853
elif exists (new ):
819
- return new
820
- return None
854
+ return new , new
855
+ return None , None
821
856
822
857
823
858
def apply (self , strip = 0 , root = None ):
@@ -848,27 +883,27 @@ def apply(self, strip=0, root=None):
848
883
debug ("stripping %s leading component(s) from:" % strip )
849
884
debug (" %s" % p .source )
850
885
debug (" %s" % p .target )
851
- old = pathstrip (p .source , strip )
852
- new = pathstrip (p .target , strip )
886
+ old = p . source if p . source == b'/dev/null' else pathstrip (p .source , strip )
887
+ new = p . target if p . target == b'/dev/null' else pathstrip (p .target , strip )
853
888
else :
854
889
old , new = p .source , p .target
855
890
856
- filename = self .findfile (old , new )
891
+ filenameo , filenamen = self .findfiles (old , new )
857
892
858
- if not filename :
893
+ if not filenameo or not filenamen :
859
894
warning ("source/target file does not exist:\n --- %s\n +++ %s" % (old , new ))
860
895
errors += 1
861
896
continue
862
- if not isfile (filename ):
863
- warning ("not a file - %s" % filename )
897
+ if not isfile (filenameo ):
898
+ warning ("not a file - %s" % filenameo )
864
899
errors += 1
865
900
continue
866
901
867
902
# [ ] check absolute paths security here
868
- debug ("processing %d/%d:\t %s" % (i + 1 , total , filename ))
903
+ debug ("processing %d/%d:\t %s" % (i + 1 , total , filenamen ))
869
904
870
905
# validate before patching
871
- f2fp = open (filename , 'rb' )
906
+ f2fp = open (filenameo , 'rb' )
872
907
hunkno = 0
873
908
hunk = p .hunks [hunkno ]
874
909
hunkfind = []
@@ -891,7 +926,7 @@ def apply(self, strip=0, root=None):
891
926
if line .rstrip (b"\r \n " ) == hunkfind [hunklineno ]:
892
927
hunklineno += 1
893
928
else :
894
- info ("file %d/%d:\t %s" % (i + 1 , total , filename ))
929
+ info ("file %d/%d:\t %s" % (i + 1 , total , filenamen ))
895
930
info (" hunk no.%d doesn't match source file at line %d" % (hunkno + 1 , lineno + 1 ))
896
931
info (" expected: %s" % hunkfind [hunklineno ])
897
932
info (" actual : %s" % line .rstrip (b"\r \n " ))
@@ -911,8 +946,8 @@ def apply(self, strip=0, root=None):
911
946
break
912
947
913
948
# 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 ))
949
+ if len ( hunkfind ) == 0 or lineno + 1 == hunk .startsrc + len (hunkfind )- 1 :
950
+ debug (" hunk no.%d for file %s -- is ready to be patched" % (hunkno + 1 , filenamen ))
916
951
hunkno += 1
917
952
validhunks += 1
918
953
if hunkno < len (p .hunks ):
@@ -924,34 +959,39 @@ def apply(self, strip=0, root=None):
924
959
break
925
960
else :
926
961
if hunkno < len (p .hunks ):
927
- warning ("premature end of source file %s at hunk %d" % (filename , hunkno + 1 ))
962
+ warning ("premature end of source file %s at hunk %d" % (filenameo , hunkno + 1 ))
928
963
errors += 1
929
964
930
965
f2fp .close ()
931
966
932
967
if validhunks < len (p .hunks ):
933
- if self ._match_file_hunks (filename , p .hunks ):
934
- warning ("already patched %s" % filename )
968
+ if self ._match_file_hunks (filenameo , p .hunks ):
969
+ warning ("already patched %s" % filenameo )
935
970
else :
936
- warning ("source file is different - %s" % filename )
971
+ warning ("source file is different - %s" % filenameo )
937
972
errors += 1
938
973
if canpatch :
939
- backupname = filename + b".orig"
974
+ backupname = filenamen + b".orig"
940
975
if exists (backupname ):
941
976
warning ("can't backup original file to %s - aborting" % backupname )
942
977
else :
943
978
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 ))
979
+ shutil .move (filenamen , backupname )
980
+ if self .write_hunks (backupname if filenameo == filenamen else filenameo , filenamen , p .hunks ):
981
+ info ("successfully patched %d/%d:\t %s" % (i + 1 , total , filenamen ))
947
982
os .unlink (backupname )
983
+ if new == b'/dev/null' :
984
+ # check that filename is of size 0 and delete it.
985
+ if os .path .getsize (filenamen ) > 0 :
986
+ warning ("expected patched file to be empty as it's marked as deletion:\t %s" % filenamen )
987
+ os .unlink (filenamen )
948
988
else :
949
989
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" )
990
+ warning ("error patching file %s" % filenamen )
991
+ shutil .copy (filenamen , filenamen + ".invalid" )
992
+ warning ("invalid version is saved to %s" % filenamen + ".invalid" )
953
993
# todo: proper rejects
954
- shutil .move (backupname , filename )
994
+ shutil .move (backupname , filenamen )
955
995
956
996
if root :
957
997
os .chdir (prevdir )
0 commit comments