~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-10-13 06:08:53 UTC
  • mfrom: (4737.1.1 merge-2.0-into-devel)
  • Revision ID: pqm@pqm.ubuntu.com-20091013060853-erk2aaj80fnkrv25
(andrew) Merge lp:bzr/2.0 into lp:bzr, including fixes for #322807,
        #389413, #402623 and documentation improvements.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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>
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
 
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'
 
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
 
33
56
 
34
57
def get_patch_names(iter_lines):
35
58
    try:
36
59
        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))
40
60
        if not line.startswith("--- "):
41
61
            raise MalformedPatchHeader("No orig name", line)
42
62
        else:
215
235
        return shift
216
236
 
217
237
 
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
 
    '''
 
238
def iter_hunks(iter_lines):
225
239
    hunk = None
226
240
    for line in iter_lines:
227
241
        if line == "\n":
231
245
            continue
232
246
        if hunk is not None:
233
247
            yield hunk
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
 
248
        hunk = hunk_from_header(line)
243
249
        orig_size = 0
244
250
        mod_size = 0
245
251
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
253
259
        yield hunk
254
260
 
255
261
 
256
 
class BinaryPatch(object):
 
262
class Patch:
257
263
    def __init__(self, oldname, newname):
258
264
        self.oldname = oldname
259
265
        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)
269
266
        self.hunks = []
270
267
 
271
268
    def __str__(self):
318
315
                    pos += 1
319
316
 
320
317
 
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
 
    '''
 
318
def parse_patch(iter_lines):
327
319
    iter_lines = iter_lines_handle_nl(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)
 
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):
354
328
    saved_lines = []
355
329
    orig_range = 0
356
 
    beginning = True
357
330
    for line in iter_lines:
358
331
        if line.startswith('=== ') or line.startswith('*** '):
359
332
            continue
362
335
        elif orig_range > 0:
363
336
            if line.startswith('-') or line.startswith(' '):
364
337
                orig_range -= 1
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:
 
338
        elif line.startswith('--- '):
 
339
            if len(saved_lines) > 0:
372
340
                yield saved_lines
373
341
            saved_lines = []
374
342
        elif line.startswith('@@'):
400
368
        yield last_line
401
369
 
402
370
 
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)]
 
371
def parse_patches(iter_lines):
 
372
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
412
373
 
413
374
 
414
375
def difference_index(atext, btext):