~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: Jelmer Vernooij
  • Date: 2011-12-05 14:12:23 UTC
  • mto: This revision was merged to the branch mainline in revision 6348.
  • Revision ID: jelmer@samba.org-20111205141223-8qxae4h37satlzgq
Move more functionality to vf_search.

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