~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: 2008-05-06 11:40:10 UTC
  • mfrom: (3400.1.3 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20080506114010-jwclr2qtiekvawjg
Remove erroneous creation of branch-name file in cmd_branch

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
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
 
 
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29
17
import re
30
18
 
31
19
 
32
 
binary_files_re = 'Binary files (.*) and (.*) differ\n'
 
20
class PatchSyntax(Exception):
 
21
    def __init__(self, msg):
 
22
        Exception.__init__(self, msg)
 
23
 
 
24
 
 
25
class MalformedPatchHeader(PatchSyntax):
 
26
    def __init__(self, desc, line):
 
27
        self.desc = desc
 
28
        self.line = line
 
29
        msg = "Malformed patch header.  %s\n%r" % (self.desc, self.line)
 
30
        PatchSyntax.__init__(self, msg)
 
31
 
 
32
 
 
33
class MalformedHunkHeader(PatchSyntax):
 
34
    def __init__(self, desc, line):
 
35
        self.desc = desc
 
36
        self.line = line
 
37
        msg = "Malformed hunk header.  %s\n%r" % (self.desc, self.line)
 
38
        PatchSyntax.__init__(self, msg)
 
39
 
 
40
 
 
41
class MalformedLine(PatchSyntax):
 
42
    def __init__(self, desc, line):
 
43
        self.desc = desc
 
44
        self.line = line
 
45
        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
 
46
        PatchSyntax.__init__(self, msg)
 
47
 
 
48
 
 
49
class PatchConflict(Exception):
 
50
    def __init__(self, line_no, orig_line, patch_line):
 
51
        orig = orig_line.rstrip('\n')
 
52
        patch = str(patch_line).rstrip('\n')
 
53
        msg = 'Text contents mismatch at line %d.  Original has "%s",'\
 
54
            ' but patch says it should be "%s"' % (line_no, orig, patch)
 
55
        Exception.__init__(self, msg)
 
56
 
33
57
 
34
58
def get_patch_names(iter_lines):
35
59
    try:
36
60
        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
61
        if not line.startswith("--- "):
41
62
            raise MalformedPatchHeader("No orig name", line)
42
63
        else:
72
93
    range = int(range)
73
94
    return (pos, range)
74
95
 
75
 
 
 
96
 
76
97
def hunk_from_header(line):
77
 
    import re
78
98
    matches = re.match(r'\@\@ ([^@]*) \@\@( (.*))?\n', line)
79
99
    if matches is None:
80
100
        raise MalformedHunkHeader("Does not match format.", line)
144
164
        return InsertLine(line[1:])
145
165
    elif line.startswith("-"):
146
166
        return RemoveLine(line[1:])
 
167
    elif line == NO_NL:
 
168
        return NO_NL
147
169
    else:
148
170
        raise MalformedLine("Unknown line type", line)
149
171
__pychecker__=""
198
220
            return self.shift_to_mod_lines(pos)
199
221
 
200
222
    def shift_to_mod_lines(self, pos):
 
223
        assert (pos >= self.orig_pos-1 and pos <= self.orig_pos+self.orig_range)
201
224
        position = self.orig_pos-1
202
225
        shift = 0
203
226
        for line in self.lines:
215
238
        return shift
216
239
 
217
240
 
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
 
    '''
 
241
def iter_hunks(iter_lines):
225
242
    hunk = None
226
243
    for line in iter_lines:
227
244
        if line == "\n":
231
248
            continue
232
249
        if hunk is not None:
233
250
            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
 
251
        hunk = hunk_from_header(line)
243
252
        orig_size = 0
244
253
        mod_size = 0
245
254
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
253
262
        yield hunk
254
263
 
255
264
 
256
 
class BinaryPatch(object):
 
265
class Patch:
257
266
    def __init__(self, oldname, newname):
258
267
        self.oldname = oldname
259
268
        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
269
        self.hunks = []
270
270
 
271
271
    def __str__(self):
272
 
        ret = self.get_header()
 
272
        ret = self.get_header() 
273
273
        ret += "".join([str(h) for h in self.hunks])
274
274
        return ret
275
275
 
276
276
    def get_header(self):
277
277
        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
278
278
 
279
 
    def stats_values(self):
280
 
        """Calculate the number of inserts and removes."""
 
279
    def stats_str(self):
 
280
        """Return a string of patch statistics"""
281
281
        removes = 0
282
282
        inserts = 0
283
283
        for hunk in self.hunks:
286
286
                     inserts+=1;
287
287
                elif isinstance(line, RemoveLine):
288
288
                     removes+=1;
289
 
        return (inserts, removes, len(self.hunks))
290
 
 
291
 
    def stats_str(self):
292
 
        """Return a string of patch statistics"""
293
289
        return "%i inserts, %i removes in %i hunks" % \
294
 
            self.stats_values()
 
290
            (inserts, removes, len(self.hunks))
295
291
 
296
292
    def pos_in_mod(self, position):
297
293
        newpos = position
301
297
                return None
302
298
            newpos += shift
303
299
        return newpos
304
 
 
 
300
            
305
301
    def iter_inserted(self):
306
302
        """Iteraties through inserted lines
307
 
 
 
303
        
308
304
        :return: Pair of line number, line
309
305
        :rtype: iterator of (int, InsertLine)
310
306
        """
318
314
                    pos += 1
319
315
 
320
316
 
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
 
    '''
327
 
    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)
 
317
def parse_patch(iter_lines):
 
318
    (orig_name, mod_name) = get_patch_names(iter_lines)
 
319
    patch = Patch(orig_name, mod_name)
 
320
    for hunk in iter_hunks(iter_lines):
 
321
        patch.hunks.append(hunk)
 
322
    return patch
 
323
 
 
324
 
 
325
def iter_file_patch(iter_lines):
354
326
    saved_lines = []
355
327
    orig_range = 0
356
 
    beginning = True
357
328
    for line in iter_lines:
358
329
        if line.startswith('=== ') or line.startswith('*** '):
359
330
            continue
362
333
        elif orig_range > 0:
363
334
            if line.startswith('-') or line.startswith(' '):
364
335
                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:
 
336
        elif line.startswith('--- '):
 
337
            if len(saved_lines) > 0:
372
338
                yield saved_lines
373
339
            saved_lines = []
374
340
        elif line.startswith('@@'):
389
355
    last_line = None
390
356
    for line in iter_lines:
391
357
        if line == NO_NL:
392
 
            if not last_line.endswith('\n'):
393
 
                raise AssertionError()
 
358
            assert last_line.endswith('\n')
394
359
            last_line = last_line[:-1]
395
360
            line = None
396
361
        if last_line is not None:
400
365
        yield last_line
401
366
 
402
367
 
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)]
 
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)]
412
371
 
413
372
 
414
373
def difference_index(atext, btext):
434
393
    """Iterate through a series of lines with a patch applied.
435
394
    This handles a single file, and does exact, not fuzzy patching.
436
395
    """
437
 
    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__())
438
400
    get_patch_names(patch_lines)
439
 
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
440
 
 
441
 
 
442
 
def iter_patched_from_hunks(orig_lines, hunks):
443
 
    """Iterate through a series of lines with a patch applied.
444
 
    This handles a single file, and does exact, not fuzzy patching.
445
 
 
446
 
    :param orig_lines: The unpatched lines.
447
 
    :param hunks: An iterable of Hunk instances.
448
 
    """
449
 
    seen_patch = []
450
401
    line_no = 1
451
 
    if orig_lines is not None:
452
 
        orig_lines = iter(orig_lines)
453
 
    for hunk in hunks:
 
402
    for hunk in iter_hunks(patch_lines):
454
403
        while line_no < hunk.orig_pos:
455
404
            orig_line = orig_lines.next()
456
405
            yield orig_line
466
415
                if isinstance(hunk_line, ContextLine):
467
416
                    yield orig_line
468
417
                else:
469
 
                    if not isinstance(hunk_line, RemoveLine):
470
 
                        raise AssertionError(hunk_line)
 
418
                    assert isinstance(hunk_line, RemoveLine)
471
419
                line_no += 1
472
420
    if orig_lines is not None:
473
421
        for line in orig_lines: