2
2
"""
3
3
Patch utility to apply unified diffs
4
4
5
- Brute-force line-by-line non-recursive parsing
5
+ Brute-force line-by-line non-recursive parsing
6
6
7
7
Copyright (c) 2008-2016 anatoly techtonik
8
8
Available under the terms of MIT license
@@ -54,7 +54,7 @@ def tostr(b):
54
54
55
55
# [ ] figure out how to print non-utf-8 filenames without
56
56
# information loss
57
- return b .decode ('utf-8' )
57
+ return b .decode ('utf-8' )
58
58
59
59
60
60
#------------------------------------------------
@@ -233,7 +233,7 @@ class Patch(object):
233
233
If used as an iterable, returns hunks.
234
234
"""
235
235
def __init__ (self ):
236
- self .source = None
236
+ self .source = None
237
237
self .target = None
238
238
self .hunks = []
239
239
self .hunkends = []
@@ -339,7 +339,7 @@ def lineno(self):
339
339
340
340
# regexp to match start of hunk, used groups - 1,3,4,6
341
341
re_hunk_start = re .compile (b"^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@" )
342
-
342
+
343
343
self .errors = 0
344
344
# temp buffers for header and filenames info
345
345
header = []
@@ -375,7 +375,7 @@ def lineno(self):
375
375
else :
376
376
info ("%d unparsed bytes left at the end of stream" % len (b'' .join (header )))
377
377
self .warnings += 1
378
- # TODO check for \No new line at the end..
378
+ # TODO check for \No new line at the end..
379
379
# TODO test for unparsed bytes
380
380
# otherwise error += 1
381
381
# this is actually a loop exit
@@ -408,7 +408,7 @@ def lineno(self):
408
408
p .hunkends ["lf" ] += 1
409
409
elif line .endswith (b"\r " ):
410
410
p .hunkends ["cr" ] += 1
411
-
411
+
412
412
if line .startswith (b"-" ):
413
413
hunkactual ["linessrc" ] += 1
414
414
elif line .startswith (b"+" ):
@@ -519,7 +519,7 @@ def lineno(self):
519
519
headscan = True
520
520
else :
521
521
if tgtname != None :
522
- # XXX seems to be a dead branch
522
+ # XXX seems to be a dead branch
523
523
warning ("skipping invalid patch - double target at line %d" % (lineno + 1 ))
524
524
self .errors += 1
525
525
srcname = None
@@ -612,7 +612,7 @@ def lineno(self):
612
612
warning ("error: no patch data found!" )
613
613
return False
614
614
else : # extra data at the end of file
615
- pass
615
+ pass
616
616
else :
617
617
warning ("error: patch stream is incomplete!" )
618
618
self .errors += 1
@@ -638,7 +638,7 @@ def lineno(self):
638
638
# --------
639
639
640
640
self ._normalize_filenames ()
641
-
641
+
642
642
return (self .errors == 0 )
643
643
644
644
def _detect_type (self , p ):
@@ -681,14 +681,14 @@ def _detect_type(self, p):
681
681
return GIT
682
682
683
683
# HG check
684
- #
684
+ #
685
685
# - for plain HG format header is like "diff -r b2d9961ff1f5 filename"
686
686
# - for Git-style HG patches it is "diff --git a/oldname b/newname"
687
687
# - filename starts with a/, b/ or is equal to /dev/null
688
688
# - exported changesets also contain the header
689
689
# # HG changeset patch
690
690
# # User name@example.com
691
- # ...
691
+ # ...
692
692
# TODO add MQ
693
693
# TODO add revision info
694
694
if len (p .header ) > 0 :
@@ -817,7 +817,7 @@ def diffstat(self):
817
817
hist = "+" * int (iwidth ) + "-" * int (dwidth )
818
818
# -- /calculating +- histogram --
819
819
output += (format % (tostr (names [i ]), str (insert [i ] + delete [i ]), hist ))
820
-
820
+
821
821
output += (" %d files changed, %d insertions(+), %d deletions(-), %+d bytes"
822
822
% (len (names ), sum (insert ), sum (delete ), delta ))
823
823
return output
@@ -854,6 +854,10 @@ def findfiles(self, old, new):
854
854
return new , new
855
855
return None , None
856
856
857
+ def _strip_prefix (self , filename ):
858
+ if filename .startswith (b'a/' ) or filename .startswith (b'b/' ):
859
+ return filename [2 :]
860
+ return filename
857
861
858
862
def apply (self , strip = 0 , root = None ):
859
863
""" Apply parsed patch, optionally stripping leading components
@@ -890,12 +894,25 @@ def apply(self, strip=0, root=None):
890
894
891
895
filenameo , filenamen = self .findfiles (old , new )
892
896
893
- if not filenameo or not filenamen :
894
- warning ("source/target file does not exist:\n --- %s\n +++ %s" % (old , new ))
895
- errors += 1
896
- continue
897
- if not isfile (filenameo ):
898
- warning ("not a file - %s" % filenameo )
897
+ if not filename :
898
+ if "dev/null" in old :
899
+ # this is a file creation
900
+ filename = self ._strip_prefix (new )
901
+ # I wish there would be something more clean to get the full contents
902
+ new_file = "" .join (s [1 :] for s in p .hunks [0 ].text )
903
+ with open (filename , "wb" ) as f :
904
+ f .write (new_file )
905
+ continue
906
+ elif "dev/null" in new :
907
+ # this is a file removal
908
+ os .remove (self ._strip_prefix (old ))
909
+ continue
910
+ else :
911
+ warning ("source/target file does not exist:\n --- %s\n +++ %s" % (old , new ))
912
+ errors += 1
913
+ continue
914
+ if not isfile (filename ):
915
+ warning ("not a file - %s" % filename )
899
916
errors += 1
900
917
continue
901
918
@@ -1077,7 +1094,7 @@ class NoMatch(Exception):
1077
1094
1078
1095
def patch_stream (self , instream , hunks ):
1079
1096
""" Generator that yields stream patched with hunks iterable
1080
-
1097
+
1081
1098
Converts lineends in hunk lines to the best suitable format
1082
1099
autodetected from input
1083
1100
"""
@@ -1130,7 +1147,7 @@ def get_line():
1130
1147
yield line2write .rstrip (b"\r \n " )+ newline
1131
1148
else : # newlines are mixed
1132
1149
yield line2write
1133
-
1150
+
1134
1151
for line in instream :
1135
1152
yield line
1136
1153
0 commit comments