~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

Streamline _walkdirs_utf8 for utf8 file systems, reducing time to traverse a mozilla tree from 1s to .6 seconds. (Robert Collins)

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