22"""
33 Patch utility to apply unified diffs
44
5- Brute-force line-by-line non-recursive parsing
5+ Brute-force line-by-line non-recursive parsing
66
77 Copyright (c) 2008-2016 anatoly techtonik
88 Available under the terms of MIT license
@@ -54,7 +54,7 @@ def tostr(b):
5454
5555 # [ ] figure out how to print non-utf-8 filenames without
5656 # information loss
57- return b .decode ('utf-8' )
57+ return b .decode ('utf-8' )
5858
5959
6060#------------------------------------------------
@@ -233,7 +233,7 @@ class Patch(object):
233233 If used as an iterable, returns hunks.
234234 """
235235 def __init__ (self ):
236- self .source = None
236+ self .source = None
237237 self .target = None
238238 self .hunks = []
239239 self .hunkends = []
@@ -339,7 +339,7 @@ def lineno(self):
339339
340340 # regexp to match start of hunk, used groups - 1,3,4,6
341341 re_hunk_start = re .compile (b"^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@" )
342-
342+
343343 self .errors = 0
344344 # temp buffers for header and filenames info
345345 header = []
@@ -375,7 +375,7 @@ def lineno(self):
375375 else :
376376 info ("%d unparsed bytes left at the end of stream" % len (b'' .join (header )))
377377 self .warnings += 1
378- # TODO check for \No new line at the end..
378+ # TODO check for \No new line at the end..
379379 # TODO test for unparsed bytes
380380 # otherwise error += 1
381381 # this is actually a loop exit
@@ -408,7 +408,7 @@ def lineno(self):
408408 p .hunkends ["lf" ] += 1
409409 elif line .endswith (b"\r " ):
410410 p .hunkends ["cr" ] += 1
411-
411+
412412 if line .startswith (b"-" ):
413413 hunkactual ["linessrc" ] += 1
414414 elif line .startswith (b"+" ):
@@ -519,7 +519,7 @@ def lineno(self):
519519 headscan = True
520520 else :
521521 if tgtname != None :
522- # XXX seems to be a dead branch
522+ # XXX seems to be a dead branch
523523 warning ("skipping invalid patch - double target at line %d" % (lineno + 1 ))
524524 self .errors += 1
525525 srcname = None
@@ -612,7 +612,7 @@ def lineno(self):
612612 warning ("error: no patch data found!" )
613613 return False
614614 else : # extra data at the end of file
615- pass
615+ pass
616616 else :
617617 warning ("error: patch stream is incomplete!" )
618618 self .errors += 1
@@ -638,7 +638,7 @@ def lineno(self):
638638 # --------
639639
640640 self ._normalize_filenames ()
641-
641+
642642 return (self .errors == 0 )
643643
644644 def _detect_type (self , p ):
@@ -681,14 +681,14 @@ def _detect_type(self, p):
681681 return GIT
682682
683683 # HG check
684- #
684+ #
685685 # - for plain HG format header is like "diff -r b2d9961ff1f5 filename"
686686 # - for Git-style HG patches it is "diff --git a/oldname b/newname"
687687 # - filename starts with a/, b/ or is equal to /dev/null
688688 # - exported changesets also contain the header
689689 # # HG changeset patch
690690 # # User name@example.com
691- # ...
691+ # ...
692692 # TODO add MQ
693693 # TODO add revision info
694694 if len (p .header ) > 0 :
@@ -817,7 +817,7 @@ def diffstat(self):
817817 hist = "+" * int (iwidth ) + "-" * int (dwidth )
818818 # -- /calculating +- histogram --
819819 output += (format % (tostr (names [i ]), str (insert [i ] + delete [i ]), hist ))
820-
820+
821821 output += (" %d files changed, %d insertions(+), %d deletions(-), %+d bytes"
822822 % (len (names ), sum (insert ), sum (delete ), delta ))
823823 return output
@@ -854,6 +854,10 @@ def findfiles(self, old, new):
854854 return new , new
855855 return None , None
856856
857+ def _strip_prefix (self , filename ):
858+ if filename .startswith (b'a/' ) or filename .startswith (b'b/' ):
859+ return filename [2 :]
860+ return filename
857861
858862 def apply (self , strip = 0 , root = None ):
859863 """ Apply parsed patch, optionally stripping leading components
@@ -890,12 +894,25 @@ def apply(self, strip=0, root=None):
890894
891895 filenameo , filenamen = self .findfiles (old , new )
892896
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 )
899916 errors += 1
900917 continue
901918
@@ -1077,7 +1094,7 @@ class NoMatch(Exception):
10771094
10781095 def patch_stream (self , instream , hunks ):
10791096 """ Generator that yields stream patched with hunks iterable
1080-
1097+
10811098 Converts lineends in hunk lines to the best suitable format
10821099 autodetected from input
10831100 """
@@ -1130,7 +1147,7 @@ def get_line():
11301147 yield line2write .rstrip (b"\r \n " )+ newline
11311148 else : # newlines are mixed
11321149 yield line2write
1133-
1150+
11341151 for line in instream :
11351152 yield line
11361153
0 commit comments