~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: 2010-09-29 22:03:03 UTC
  • mfrom: (5416.2.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100929220303-cr95h8iwtggco721
(mbp) Add 'break-lock --force'

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004 - 2006 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
13
13
#
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
import re
18
18
 
19
19
 
 
20
binary_files_re = 'Binary files (.*) and (.*) differ\n'
 
21
 
 
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
 
20
31
class PatchSyntax(Exception):
21
32
    def __init__(self, msg):
22
33
        Exception.__init__(self, msg)
58
69
def get_patch_names(iter_lines):
59
70
    try:
60
71
        line = iter_lines.next()
 
72
        match = re.match(binary_files_re, line)
 
73
        if match is not None:
 
74
            raise BinaryFiles(match.group(1), match.group(2))
61
75
        if not line.startswith("--- "):
62
76
            raise MalformedPatchHeader("No orig name", line)
63
77
        else:
93
107
    range = int(range)
94
108
    return (pos, range)
95
109
 
96
 
 
 
110
 
97
111
def hunk_from_header(line):
 
112
    import re
98
113
    matches = re.match(r'\@\@ ([^@]*) \@\@( (.*))?\n', line)
99
114
    if matches is None:
100
115
        raise MalformedHunkHeader("Does not match format.", line)
164
179
        return InsertLine(line[1:])
165
180
    elif line.startswith("-"):
166
181
        return RemoveLine(line[1:])
167
 
    elif line == NO_NL:
168
 
        return NO_NL
169
182
    else:
170
183
        raise MalformedLine("Unknown line type", line)
171
184
__pychecker__=""
237
250
        return shift
238
251
 
239
252
 
240
 
def iter_hunks(iter_lines):
 
253
def iter_hunks(iter_lines, allow_dirty=False):
 
254
    '''
 
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
 
259
    '''
241
260
    hunk = None
242
261
    for line in iter_lines:
243
262
        if line == "\n":
247
266
            continue
248
267
        if hunk is not None:
249
268
            yield hunk
250
 
        hunk = hunk_from_header(line)
 
269
        try:
 
270
            hunk = hunk_from_header(line)
 
271
        except MalformedHunkHeader:
 
272
            if allow_dirty:
 
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.
 
276
                return
 
277
            raise
251
278
        orig_size = 0
252
279
        mod_size = 0
253
280
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
261
288
        yield hunk
262
289
 
263
290
 
264
 
class Patch:
 
291
class BinaryPatch(object):
265
292
    def __init__(self, oldname, newname):
266
293
        self.oldname = oldname
267
294
        self.newname = newname
 
295
 
 
296
    def __str__(self):
 
297
        return 'Binary files %s and %s differ\n' % (self.oldname, self.newname)
 
298
 
 
299
 
 
300
class Patch(BinaryPatch):
 
301
 
 
302
    def __init__(self, oldname, newname):
 
303
        BinaryPatch.__init__(self, oldname, newname)
268
304
        self.hunks = []
269
305
 
270
306
    def __str__(self):
271
 
        ret = self.get_header() 
 
307
        ret = self.get_header()
272
308
        ret += "".join([str(h) for h in self.hunks])
273
309
        return ret
274
310
 
275
311
    def get_header(self):
276
312
        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
277
313
 
278
 
    def stats_str(self):
279
 
        """Return a string of patch statistics"""
 
314
    def stats_values(self):
 
315
        """Calculate the number of inserts and removes."""
280
316
        removes = 0
281
317
        inserts = 0
282
318
        for hunk in self.hunks:
285
321
                     inserts+=1;
286
322
                elif isinstance(line, RemoveLine):
287
323
                     removes+=1;
 
324
        return (inserts, removes, len(self.hunks))
 
325
 
 
326
    def stats_str(self):
 
327
        """Return a string of patch statistics"""
288
328
        return "%i inserts, %i removes in %i hunks" % \
289
 
            (inserts, removes, len(self.hunks))
 
329
            self.stats_values()
290
330
 
291
331
    def pos_in_mod(self, position):
292
332
        newpos = position
296
336
                return None
297
337
            newpos += shift
298
338
        return newpos
299
 
            
 
339
 
300
340
    def iter_inserted(self):
301
341
        """Iteraties through inserted lines
302
 
        
 
342
 
303
343
        :return: Pair of line number, line
304
344
        :rtype: iterator of (int, InsertLine)
305
345
        """
313
353
                    pos += 1
314
354
 
315
355
 
316
 
def parse_patch(iter_lines):
317
 
    (orig_name, mod_name) = get_patch_names(iter_lines)
318
 
    patch = Patch(orig_name, mod_name)
319
 
    for hunk in iter_hunks(iter_lines):
320
 
        patch.hunks.append(hunk)
321
 
    return patch
322
 
 
323
 
 
324
 
def iter_file_patch(iter_lines):
 
356
def parse_patch(iter_lines, allow_dirty=False):
 
357
    '''
 
358
    :arg iter_lines: iterable of lines to parse
 
359
    :kwarg allow_dirty: If True, allow the patch to have trailing junk.
 
360
        Default False
 
361
    '''
 
362
    iter_lines = iter_lines_handle_nl(iter_lines)
 
363
    try:
 
364
        (orig_name, mod_name) = get_patch_names(iter_lines)
 
365
    except BinaryFiles, e:
 
366
        return BinaryPatch(e.orig_name, e.mod_name)
 
367
    else:
 
368
        patch = Patch(orig_name, mod_name)
 
369
        for hunk in iter_hunks(iter_lines, allow_dirty):
 
370
            patch.hunks.append(hunk)
 
371
        return patch
 
372
 
 
373
 
 
374
def iter_file_patch(iter_lines, allow_dirty=False):
 
375
    '''
 
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.
 
382
    '''
 
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)
325
389
    saved_lines = []
326
390
    orig_range = 0
 
391
    beginning = True
327
392
    for line in iter_lines:
328
393
        if line.startswith('=== ') or line.startswith('*** '):
329
394
            continue
332
397
        elif orig_range > 0:
333
398
            if line.startswith('-') or line.startswith(' '):
334
399
                orig_range -= 1
335
 
        elif line.startswith('--- '):
336
 
            if len(saved_lines) > 0:
 
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
 
404
                # parse the patch
 
405
                beginning = False
 
406
            elif len(saved_lines) > 0:
337
407
                yield saved_lines
338
408
            saved_lines = []
339
409
        elif line.startswith('@@'):
365
435
        yield last_line
366
436
 
367
437
 
368
 
def parse_patches(iter_lines):
369
 
    iter_lines = iter_lines_handle_nl(iter_lines)
370
 
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
 
438
def parse_patches(iter_lines, allow_dirty=False):
 
439
    '''
 
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.
 
444
    '''
 
445
    return [parse_patch(f.__iter__(), allow_dirty) for f in
 
446
                        iter_file_patch(iter_lines, allow_dirty)]
371
447
 
372
448
 
373
449
def difference_index(atext, btext):
393
469
    """Iterate through a series of lines with a patch applied.
394
470
    This handles a single file, and does exact, not fuzzy patching.
395
471
    """
396
 
    if orig_lines is not None:
397
 
        orig_lines = orig_lines.__iter__()
 
472
    patch_lines = iter_lines_handle_nl(iter(patch_lines))
 
473
    get_patch_names(patch_lines)
 
474
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
 
475
 
 
476
 
 
477
def iter_patched_from_hunks(orig_lines, hunks):
 
478
    """Iterate through a series of lines with a patch applied.
 
479
    This handles a single file, and does exact, not fuzzy patching.
 
480
 
 
481
    :param orig_lines: The unpatched lines.
 
482
    :param hunks: An iterable of Hunk instances.
 
483
    """
398
484
    seen_patch = []
399
 
    patch_lines = iter_lines_handle_nl(patch_lines.__iter__())
400
 
    get_patch_names(patch_lines)
401
485
    line_no = 1
402
 
    for hunk in iter_hunks(patch_lines):
 
486
    if orig_lines is not None:
 
487
        orig_lines = iter(orig_lines)
 
488
    for hunk in hunks:
403
489
        while line_no < hunk.orig_pos:
404
490
            orig_line = orig_lines.next()
405
491
            yield orig_line