1
# Copyright (C) 2005-2010 Aaron Bentley, Canonical Ltd
1
# Copyright (C) 2004 - 2006, 2008 Aaron Bentley, Canonical Ltd
2
2
# <aaron.bentley@utoronto.ca>
4
4
# This program is free software; you can redistribute it and/or modify
14
14
# You should have received a copy of the GNU General Public License
15
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
binary_files_re = 'Binary files (.*) and (.*) differ\n'
23
class BinaryFiles(Exception):
25
def __init__(self, orig_name, mod_name):
26
self.orig_name = orig_name
27
self.mod_name = mod_name
28
Exception.__init__(self, 'Binary files section encountered.')
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31
19
class PatchSyntax(Exception):
69
57
def get_patch_names(iter_lines):
71
59
line = iter_lines.next()
72
match = re.match(binary_files_re, line)
74
raise BinaryFiles(match.group(1), match.group(2))
75
60
if not line.startswith("--- "):
76
61
raise MalformedPatchHeader("No orig name", line)
179
164
return InsertLine(line[1:])
180
165
elif line.startswith("-"):
181
166
return RemoveLine(line[1:])
183
170
raise MalformedLine("Unknown line type", line)
253
def iter_hunks(iter_lines, allow_dirty=False):
255
:arg iter_lines: iterable of lines to parse for hunks
256
:kwarg allow_dirty: If True, when we encounter something that is not
257
a hunk header when we're looking for one, assume the rest of the lines
258
are not part of the patch (comments or other junk). Default False
240
def iter_hunks(iter_lines):
261
242
for line in iter_lines:
267
248
if hunk is not None:
270
hunk = hunk_from_header(line)
271
except MalformedHunkHeader:
273
# If the line isn't a hunk header, then we've reached the end
274
# of this patch and there's "junk" at the end. Ignore the
275
# rest of this patch.
250
hunk = hunk_from_header(line)
280
253
while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
291
class BinaryPatch(object):
292
265
def __init__(self, oldname, newname):
293
266
self.oldname = oldname
294
267
self.newname = newname
297
return 'Binary files %s and %s differ\n' % (self.oldname, self.newname)
300
class Patch(BinaryPatch):
302
def __init__(self, oldname, newname):
303
BinaryPatch.__init__(self, oldname, newname)
306
270
def __str__(self):
307
ret = self.get_header()
271
ret = self.get_header()
308
272
ret += "".join([str(h) for h in self.hunks])
356
def parse_patch(iter_lines, allow_dirty=False):
358
:arg iter_lines: iterable of lines to parse
359
:kwarg allow_dirty: If True, allow the patch to have trailing junk.
362
iter_lines = iter_lines_handle_nl(iter_lines)
364
(orig_name, mod_name) = get_patch_names(iter_lines)
365
except BinaryFiles, e:
366
return BinaryPatch(e.orig_name, e.mod_name)
368
patch = Patch(orig_name, mod_name)
369
for hunk in iter_hunks(iter_lines, allow_dirty):
370
patch.hunks.append(hunk)
374
def iter_file_patch(iter_lines, allow_dirty=False):
376
:arg iter_lines: iterable of lines to parse for patches
377
:kwarg allow_dirty: If True, allow comments and other non-patch text
378
before the first patch. Note that the algorithm here can only find
379
such text before any patches have been found. Comments after the
380
first patch are stripped away in iter_hunks() if it is also passed
381
allow_dirty=True. Default False.
383
### FIXME: Docstring is not quite true. We allow certain comments no
384
# matter what, If they startwith '===', '***', or '#' Someone should
385
# reexamine this logic and decide if we should include those in
386
# allow_dirty or restrict those to only being before the patch is found
387
# (as allow_dirty does).
388
regex = re.compile(binary_files_re)
320
def parse_patch(iter_lines):
321
(orig_name, mod_name) = get_patch_names(iter_lines)
322
patch = Patch(orig_name, mod_name)
323
for hunk in iter_hunks(iter_lines):
324
patch.hunks.append(hunk)
328
def iter_file_patch(iter_lines):
392
331
for line in iter_lines:
393
332
if line.startswith('=== ') or line.startswith('*** '):
397
336
elif orig_range > 0:
398
337
if line.startswith('-') or line.startswith(' '):
400
elif line.startswith('--- ') or regex.match(line):
401
if allow_dirty and beginning:
402
# Patches can have "junk" at the beginning
403
# Stripping junk from the end of patches is handled when we
406
elif len(saved_lines) > 0:
339
elif line.startswith('--- '):
340
if len(saved_lines) > 0:
407
341
yield saved_lines
409
343
elif line.startswith('@@'):
438
def parse_patches(iter_lines, allow_dirty=False):
440
:arg iter_lines: iterable of lines to parse for patches
441
:kwarg allow_dirty: If True, allow text that's not part of the patch at
442
selected places. This includes comments before and after a patch
443
for instance. Default False.
445
return [parse_patch(f.__iter__(), allow_dirty) for f in
446
iter_file_patch(iter_lines, allow_dirty)]
372
def parse_patches(iter_lines):
373
iter_lines = iter_lines_handle_nl(iter_lines)
374
return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
449
377
def difference_index(atext, btext):