~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: Patch Queue Manager
  • Date: 2015-04-21 05:32:33 UTC
  • mfrom: (6602.1.1 bzr.dev)
  • Revision ID: pqm@pqm.ubuntu.com-20150421053233-x63rhby1q3612v2h
(richard-wilbur) (jelmer)Make bzr build reproducible for Debian. (Jelmer
 Vernooij)

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 __future__ import absolute_import
 
19
 
 
20
from bzrlib.errors import (
 
21
    BinaryFiles,
 
22
    MalformedHunkHeader,
 
23
    MalformedLine,
 
24
    MalformedPatchHeader,
 
25
    PatchConflict,
 
26
    PatchSyntax,
 
27
    )
 
28
 
17
29
import re
18
30
 
19
31
 
20
32
binary_files_re = 'Binary files (.*) and (.*) differ\n'
21
33
 
22
34
 
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
35
def get_patch_names(iter_lines):
 
36
    line = iter_lines.next()
70
37
    try:
71
 
        line = iter_lines.next()
72
38
        match = re.match(binary_files_re, line)
73
39
        if match is not None:
74
40
            raise BinaryFiles(match.group(1), match.group(2))
250
216
        return shift
251
217
 
252
218
 
253
 
def iter_hunks(iter_lines):
 
219
def iter_hunks(iter_lines, allow_dirty=False):
 
220
    '''
 
221
    :arg iter_lines: iterable of lines to parse for hunks
 
222
    :kwarg allow_dirty: If True, when we encounter something that is not
 
223
        a hunk header when we're looking for one, assume the rest of the lines
 
224
        are not part of the patch (comments or other junk).  Default False
 
225
    '''
254
226
    hunk = None
255
227
    for line in iter_lines:
256
228
        if line == "\n":
260
232
            continue
261
233
        if hunk is not None:
262
234
            yield hunk
263
 
        hunk = hunk_from_header(line)
 
235
        try:
 
236
            hunk = hunk_from_header(line)
 
237
        except MalformedHunkHeader:
 
238
            if allow_dirty:
 
239
                # If the line isn't a hunk header, then we've reached the end
 
240
                # of this patch and there's "junk" at the end.  Ignore the
 
241
                # rest of this patch.
 
242
                return
 
243
            raise
264
244
        orig_size = 0
265
245
        mod_size = 0
266
246
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
338
318
                if isinstance(line, ContextLine):
339
319
                    pos += 1
340
320
 
341
 
 
342
 
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
    '''
343
327
    iter_lines = iter_lines_handle_nl(iter_lines)
344
328
    try:
345
329
        (orig_name, mod_name) = get_patch_names(iter_lines)
347
331
        return BinaryPatch(e.orig_name, e.mod_name)
348
332
    else:
349
333
        patch = Patch(orig_name, mod_name)
350
 
        for hunk in iter_hunks(iter_lines):
 
334
        for hunk in iter_hunks(iter_lines, allow_dirty):
351
335
            patch.hunks.append(hunk)
352
336
        return patch
353
337
 
354
338
 
355
 
def iter_file_patch(iter_lines):
 
339
def iter_file_patch(iter_lines, allow_dirty=False, keep_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).
356
353
    regex = re.compile(binary_files_re)
357
354
    saved_lines = []
 
355
    dirty_head = []
358
356
    orig_range = 0
 
357
    beginning = True
 
358
 
359
359
    for line in iter_lines:
360
 
        if line.startswith('=== ') or line.startswith('*** '):
 
360
        if line.startswith('=== '):
 
361
            dirty_head.append(line)
 
362
            continue
 
363
        if line.startswith('*** '):
361
364
            continue
362
365
        if line.startswith('#'):
363
366
            continue
365
368
            if line.startswith('-') or line.startswith(' '):
366
369
                orig_range -= 1
367
370
        elif line.startswith('--- ') or regex.match(line):
368
 
            if len(saved_lines) > 0:
369
 
                yield saved_lines
 
371
            if allow_dirty and beginning:
 
372
                # Patches can have "junk" at the beginning
 
373
                # Stripping junk from the end of patches is handled when we
 
374
                # parse the patch
 
375
                beginning = False
 
376
            elif len(saved_lines) > 0:
 
377
                if keep_dirty and len(dirty_head) > 0:
 
378
                    yield {'saved_lines': saved_lines,
 
379
                           'dirty_head': dirty_head}
 
380
                    dirty_head = []
 
381
                else:
 
382
                    yield saved_lines
370
383
            saved_lines = []
371
384
        elif line.startswith('@@'):
372
385
            hunk = hunk_from_header(line)
373
386
            orig_range = hunk.orig_range
374
387
        saved_lines.append(line)
375
388
    if len(saved_lines) > 0:
376
 
        yield saved_lines
 
389
        if keep_dirty and len(dirty_head) > 0:
 
390
            yield {'saved_lines': saved_lines,
 
391
                   'dirty_head': dirty_head}
 
392
        else:
 
393
            yield saved_lines
377
394
 
378
395
 
379
396
def iter_lines_handle_nl(iter_lines):
397
414
        yield last_line
398
415
 
399
416
 
400
 
def parse_patches(iter_lines):
401
 
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
 
417
def parse_patches(iter_lines, allow_dirty=False, keep_dirty=False):
 
418
    '''
 
419
    :arg iter_lines: iterable of lines to parse for patches
 
420
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
 
421
        selected places.  This includes comments before and after a patch
 
422
        for instance.  Default False.
 
423
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
 
424
        Default False.
 
425
    '''
 
426
    patches = []
 
427
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
 
428
        if 'dirty_head' in patch_lines:
 
429
            patches.append({'patch': parse_patch(
 
430
                patch_lines['saved_lines'], allow_dirty),
 
431
                            'dirty_head': patch_lines['dirty_head']})
 
432
        else:
 
433
            patches.append(parse_patch(patch_lines, allow_dirty))
 
434
    return patches
402
435
 
403
436
 
404
437
def difference_index(atext, btext):