~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: Jelmer Vernooij
  • Date: 2011-12-19 10:58:39 UTC
  • mfrom: (6383 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6386.
  • Revision ID: jelmer@canonical.com-20111219105839-uji05ck4rkm1mj4j
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004 - 2006, 2008 Aaron Bentley, Canonical Ltd
 
1
# Copyright (C) 2005-2010 Aaron Bentley, Canonical Ltd
2
2
# <aaron.bentley@utoronto.ca>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
 
 
19
 
class PatchSyntax(Exception):
20
 
    def __init__(self, msg):
21
 
        Exception.__init__(self, msg)
22
 
 
23
 
 
24
 
class MalformedPatchHeader(PatchSyntax):
25
 
    def __init__(self, desc, line):
26
 
        self.desc = desc
27
 
        self.line = line
28
 
        msg = "Malformed patch header.  %s\n%r" % (self.desc, self.line)
29
 
        PatchSyntax.__init__(self, msg)
30
 
 
31
 
 
32
 
class MalformedHunkHeader(PatchSyntax):
33
 
    def __init__(self, desc, line):
34
 
        self.desc = desc
35
 
        self.line = line
36
 
        msg = "Malformed hunk header.  %s\n%r" % (self.desc, self.line)
37
 
        PatchSyntax.__init__(self, msg)
38
 
 
39
 
 
40
 
class MalformedLine(PatchSyntax):
41
 
    def __init__(self, desc, line):
42
 
        self.desc = desc
43
 
        self.line = line
44
 
        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
45
 
        PatchSyntax.__init__(self, msg)
46
 
 
47
 
 
48
 
class PatchConflict(Exception):
49
 
    def __init__(self, line_no, orig_line, patch_line):
50
 
        orig = orig_line.rstrip('\n')
51
 
        patch = str(patch_line).rstrip('\n')
52
 
        msg = 'Text contents mismatch at line %d.  Original has "%s",'\
53
 
            ' but patch says it should be "%s"' % (line_no, orig, patch)
54
 
        Exception.__init__(self, msg)
55
 
 
 
18
from __future__ import absolute_import
 
19
 
 
20
from bzrlib.errors import (
 
21
    BinaryFiles,
 
22
    MalformedHunkHeader,
 
23
    MalformedLine,
 
24
    MalformedPatchHeader,
 
25
    PatchConflict,
 
26
    PatchSyntax,
 
27
    )
 
28
 
 
29
import re
 
30
 
 
31
 
 
32
binary_files_re = 'Binary files (.*) and (.*) differ\n'
56
33
 
57
34
def get_patch_names(iter_lines):
58
35
    try:
59
36
        line = iter_lines.next()
 
37
        match = re.match(binary_files_re, line)
 
38
        if match is not None:
 
39
            raise BinaryFiles(match.group(1), match.group(2))
60
40
        if not line.startswith("--- "):
61
41
            raise MalformedPatchHeader("No orig name", line)
62
42
        else:
235
215
        return shift
236
216
 
237
217
 
238
 
def iter_hunks(iter_lines):
 
218
def iter_hunks(iter_lines, allow_dirty=False):
 
219
    '''
 
220
    :arg iter_lines: iterable of lines to parse for hunks
 
221
    :kwarg allow_dirty: If True, when we encounter something that is not
 
222
        a hunk header when we're looking for one, assume the rest of the lines
 
223
        are not part of the patch (comments or other junk).  Default False
 
224
    '''
239
225
    hunk = None
240
226
    for line in iter_lines:
241
227
        if line == "\n":
245
231
            continue
246
232
        if hunk is not None:
247
233
            yield hunk
248
 
        hunk = hunk_from_header(line)
 
234
        try:
 
235
            hunk = hunk_from_header(line)
 
236
        except MalformedHunkHeader:
 
237
            if allow_dirty:
 
238
                # If the line isn't a hunk header, then we've reached the end
 
239
                # of this patch and there's "junk" at the end.  Ignore the
 
240
                # rest of this patch.
 
241
                return
 
242
            raise
249
243
        orig_size = 0
250
244
        mod_size = 0
251
245
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
259
253
        yield hunk
260
254
 
261
255
 
262
 
class Patch:
 
256
class BinaryPatch(object):
263
257
    def __init__(self, oldname, newname):
264
258
        self.oldname = oldname
265
259
        self.newname = newname
 
260
 
 
261
    def __str__(self):
 
262
        return 'Binary files %s and %s differ\n' % (self.oldname, self.newname)
 
263
 
 
264
 
 
265
class Patch(BinaryPatch):
 
266
 
 
267
    def __init__(self, oldname, newname):
 
268
        BinaryPatch.__init__(self, oldname, newname)
266
269
        self.hunks = []
267
270
 
268
271
    def __str__(self):
315
318
                    pos += 1
316
319
 
317
320
 
318
 
def parse_patch(iter_lines):
 
321
def parse_patch(iter_lines, allow_dirty=False):
 
322
    '''
 
323
    :arg iter_lines: iterable of lines to parse
 
324
    :kwarg allow_dirty: If True, allow the patch to have trailing junk.
 
325
        Default False
 
326
    '''
319
327
    iter_lines = iter_lines_handle_nl(iter_lines)
320
 
    (orig_name, mod_name) = get_patch_names(iter_lines)
321
 
    patch = Patch(orig_name, mod_name)
322
 
    for hunk in iter_hunks(iter_lines):
323
 
        patch.hunks.append(hunk)
324
 
    return patch
325
 
 
326
 
 
327
 
def iter_file_patch(iter_lines):
 
328
    try:
 
329
        (orig_name, mod_name) = get_patch_names(iter_lines)
 
330
    except BinaryFiles, e:
 
331
        return BinaryPatch(e.orig_name, e.mod_name)
 
332
    else:
 
333
        patch = Patch(orig_name, mod_name)
 
334
        for hunk in iter_hunks(iter_lines, allow_dirty):
 
335
            patch.hunks.append(hunk)
 
336
        return patch
 
337
 
 
338
 
 
339
def iter_file_patch(iter_lines, allow_dirty=False):
 
340
    '''
 
341
    :arg iter_lines: iterable of lines to parse for patches
 
342
    :kwarg allow_dirty: If True, allow comments and other non-patch text
 
343
        before the first patch.  Note that the algorithm here can only find
 
344
        such text before any patches have been found.  Comments after the
 
345
        first patch are stripped away in iter_hunks() if it is also passed
 
346
        allow_dirty=True.  Default False.
 
347
    '''
 
348
    ### FIXME: Docstring is not quite true.  We allow certain comments no
 
349
    # matter what, If they startwith '===', '***', or '#' Someone should
 
350
    # reexamine this logic and decide if we should include those in
 
351
    # allow_dirty or restrict those to only being before the patch is found
 
352
    # (as allow_dirty does).
 
353
    regex = re.compile(binary_files_re)
328
354
    saved_lines = []
329
355
    orig_range = 0
 
356
    beginning = True
330
357
    for line in iter_lines:
331
358
        if line.startswith('=== ') or line.startswith('*** '):
332
359
            continue
335
362
        elif orig_range > 0:
336
363
            if line.startswith('-') or line.startswith(' '):
337
364
                orig_range -= 1
338
 
        elif line.startswith('--- '):
339
 
            if len(saved_lines) > 0:
 
365
        elif line.startswith('--- ') or regex.match(line):
 
366
            if allow_dirty and beginning:
 
367
                # Patches can have "junk" at the beginning
 
368
                # Stripping junk from the end of patches is handled when we
 
369
                # parse the patch
 
370
                beginning = False
 
371
            elif len(saved_lines) > 0:
340
372
                yield saved_lines
341
373
            saved_lines = []
342
374
        elif line.startswith('@@'):
368
400
        yield last_line
369
401
 
370
402
 
371
 
def parse_patches(iter_lines):
372
 
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
 
403
def parse_patches(iter_lines, allow_dirty=False):
 
404
    '''
 
405
    :arg iter_lines: iterable of lines to parse for patches
 
406
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
 
407
        selected places.  This includes comments before and after a patch
 
408
        for instance.  Default False.
 
409
    '''
 
410
    return [parse_patch(f.__iter__(), allow_dirty) for f in
 
411
                        iter_file_patch(iter_lines, allow_dirty)]
373
412
 
374
413
 
375
414
def difference_index(atext, btext):