~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: Vincent Ladeuil
  • Date: 2016-01-27 13:36:17 UTC
  • mto: This revision was merged to the branch mainline in revision 6614.
  • Revision ID: v.ladeuil+lp@free.fr-20160127133617-gteit32e0nu3938n
Use ssl module for the match_hostname function

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
17
 
 
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)
 
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
 
 
29
import re
 
30
 
 
31
 
 
32
binary_files_re = 'Binary files (.*) and (.*) differ\n'
55
33
 
56
34
 
57
35
def get_patch_names(iter_lines):
 
36
    line = iter_lines.next()
58
37
    try:
59
 
        line = iter_lines.next()
 
38
        match = re.match(binary_files_re, line)
 
39
        if match is not None:
 
40
            raise BinaryFiles(match.group(1), match.group(2))
60
41
        if not line.startswith("--- "):
61
42
            raise MalformedPatchHeader("No orig name", line)
62
43
        else:
92
73
    range = int(range)
93
74
    return (pos, range)
94
75
 
95
 
 
 
76
 
96
77
def hunk_from_header(line):
97
 
    if not line.startswith("@@") or not line.endswith("@@\n") \
98
 
        or not len(line) > 4:
99
 
        raise MalformedHunkHeader("Does not start and end with @@.", line)
 
78
    import re
 
79
    matches = re.match(r'\@\@ ([^@]*) \@\@( (.*))?\n', line)
 
80
    if matches is None:
 
81
        raise MalformedHunkHeader("Does not match format.", line)
100
82
    try:
101
 
        (orig, mod) = line[3:-4].split(" ")
102
 
    except Exception, e:
 
83
        (orig, mod) = matches.group(1).split(" ")
 
84
    except (ValueError, IndexError), e:
103
85
        raise MalformedHunkHeader(str(e), line)
104
86
    if not orig.startswith('-') or not mod.startswith('+'):
105
87
        raise MalformedHunkHeader("Positions don't start with + or -.", line)
106
88
    try:
107
89
        (orig_pos, orig_range) = parse_range(orig[1:])
108
90
        (mod_pos, mod_range) = parse_range(mod[1:])
109
 
    except Exception, e:
 
91
    except (ValueError, IndexError), e:
110
92
        raise MalformedHunkHeader(str(e), line)
111
93
    if mod_range < 0 or orig_range < 0:
112
94
        raise MalformedHunkHeader("Hunk range is negative", line)
113
 
    return Hunk(orig_pos, orig_range, mod_pos, mod_range)
 
95
    tail = matches.group(3)
 
96
    return Hunk(orig_pos, orig_range, mod_pos, mod_range, tail)
114
97
 
115
98
 
116
99
class HunkLine:
162
145
        return InsertLine(line[1:])
163
146
    elif line.startswith("-"):
164
147
        return RemoveLine(line[1:])
165
 
    elif line == NO_NL:
166
 
        return NO_NL
167
148
    else:
168
149
        raise MalformedLine("Unknown line type", line)
169
150
__pychecker__=""
170
151
 
171
152
 
172
153
class Hunk:
173
 
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range):
 
154
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
174
155
        self.orig_pos = orig_pos
175
156
        self.orig_range = orig_range
176
157
        self.mod_pos = mod_pos
177
158
        self.mod_range = mod_range
 
159
        self.tail = tail
178
160
        self.lines = []
179
161
 
180
162
    def get_header(self):
181
 
        return "@@ -%s +%s @@\n" % (self.range_str(self.orig_pos, 
182
 
                                                   self.orig_range),
183
 
                                    self.range_str(self.mod_pos, 
184
 
                                                   self.mod_range))
 
163
        if self.tail is None:
 
164
            tail_str = ''
 
165
        else:
 
166
            tail_str = ' ' + self.tail
 
167
        return "@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
 
168
                                                     self.orig_range),
 
169
                                      self.range_str(self.mod_pos,
 
170
                                                     self.mod_range),
 
171
                                      tail_str)
185
172
 
186
173
    def range_str(self, pos, range):
187
174
        """Return a file range, special-casing for 1-line files.
212
199
            return self.shift_to_mod_lines(pos)
213
200
 
214
201
    def shift_to_mod_lines(self, pos):
215
 
        assert (pos >= self.orig_pos-1 and pos <= self.orig_pos+self.orig_range)
216
202
        position = self.orig_pos-1
217
203
        shift = 0
218
204
        for line in self.lines:
230
216
        return shift
231
217
 
232
218
 
233
 
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
    '''
234
226
    hunk = None
235
227
    for line in iter_lines:
236
228
        if line == "\n":
240
232
            continue
241
233
        if hunk is not None:
242
234
            yield hunk
243
 
        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
244
244
        orig_size = 0
245
245
        mod_size = 0
246
246
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
254
254
        yield hunk
255
255
 
256
256
 
257
 
class Patch:
 
257
class BinaryPatch(object):
258
258
    def __init__(self, oldname, newname):
259
259
        self.oldname = oldname
260
260
        self.newname = newname
 
261
 
 
262
    def __str__(self):
 
263
        return 'Binary files %s and %s differ\n' % (self.oldname, self.newname)
 
264
 
 
265
 
 
266
class Patch(BinaryPatch):
 
267
 
 
268
    def __init__(self, oldname, newname):
 
269
        BinaryPatch.__init__(self, oldname, newname)
261
270
        self.hunks = []
262
271
 
263
272
    def __str__(self):
264
 
        ret = self.get_header() 
 
273
        ret = self.get_header()
265
274
        ret += "".join([str(h) for h in self.hunks])
266
275
        return ret
267
276
 
268
277
    def get_header(self):
269
278
        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
270
279
 
271
 
    def stats_str(self):
272
 
        """Return a string of patch statistics"""
 
280
    def stats_values(self):
 
281
        """Calculate the number of inserts and removes."""
273
282
        removes = 0
274
283
        inserts = 0
275
284
        for hunk in self.hunks:
278
287
                     inserts+=1;
279
288
                elif isinstance(line, RemoveLine):
280
289
                     removes+=1;
 
290
        return (inserts, removes, len(self.hunks))
 
291
 
 
292
    def stats_str(self):
 
293
        """Return a string of patch statistics"""
281
294
        return "%i inserts, %i removes in %i hunks" % \
282
 
            (inserts, removes, len(self.hunks))
 
295
            self.stats_values()
283
296
 
284
297
    def pos_in_mod(self, position):
285
298
        newpos = position
289
302
                return None
290
303
            newpos += shift
291
304
        return newpos
292
 
            
 
305
 
293
306
    def iter_inserted(self):
294
307
        """Iteraties through inserted lines
295
 
        
 
308
 
296
309
        :return: Pair of line number, line
297
310
        :rtype: iterator of (int, InsertLine)
298
311
        """
305
318
                if isinstance(line, ContextLine):
306
319
                    pos += 1
307
320
 
308
 
 
309
 
def parse_patch(iter_lines):
310
 
    (orig_name, mod_name) = get_patch_names(iter_lines)
311
 
    patch = Patch(orig_name, mod_name)
312
 
    for hunk in iter_hunks(iter_lines):
313
 
        patch.hunks.append(hunk)
314
 
    return patch
315
 
 
316
 
 
317
 
def iter_file_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
    '''
 
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, 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).
 
353
    regex = re.compile(binary_files_re)
318
354
    saved_lines = []
 
355
    dirty_head = []
319
356
    orig_range = 0
 
357
    beginning = True
 
358
 
320
359
    for line in iter_lines:
321
 
        if line.startswith('=== ') or line.startswith('*** '):
 
360
        if line.startswith('=== '):
 
361
            if len(saved_lines) > 0:
 
362
                if keep_dirty and len(dirty_head) > 0:
 
363
                    yield {'saved_lines': saved_lines,
 
364
                           'dirty_head': dirty_head}
 
365
                    dirty_head = []
 
366
                else:
 
367
                    yield saved_lines
 
368
                saved_lines = []
 
369
            dirty_head.append(line)
 
370
            continue
 
371
        if line.startswith('*** '):
322
372
            continue
323
373
        if line.startswith('#'):
324
374
            continue
325
375
        elif orig_range > 0:
326
376
            if line.startswith('-') or line.startswith(' '):
327
377
                orig_range -= 1
328
 
        elif line.startswith('--- '):
329
 
            if len(saved_lines) > 0:
330
 
                yield saved_lines
 
378
        elif line.startswith('--- ') or regex.match(line):
 
379
            if allow_dirty and beginning:
 
380
                # Patches can have "junk" at the beginning
 
381
                # Stripping junk from the end of patches is handled when we
 
382
                # parse the patch
 
383
                beginning = False
 
384
            elif len(saved_lines) > 0:
 
385
                if keep_dirty and len(dirty_head) > 0:
 
386
                    yield {'saved_lines': saved_lines,
 
387
                           'dirty_head': dirty_head}
 
388
                    dirty_head = []
 
389
                else:
 
390
                    yield saved_lines
331
391
            saved_lines = []
332
392
        elif line.startswith('@@'):
333
393
            hunk = hunk_from_header(line)
334
394
            orig_range = hunk.orig_range
335
395
        saved_lines.append(line)
336
396
    if len(saved_lines) > 0:
337
 
        yield saved_lines
 
397
        if keep_dirty and len(dirty_head) > 0:
 
398
            yield {'saved_lines': saved_lines,
 
399
                   'dirty_head': dirty_head}
 
400
        else:
 
401
            yield saved_lines
338
402
 
339
403
 
340
404
def iter_lines_handle_nl(iter_lines):
347
411
    last_line = None
348
412
    for line in iter_lines:
349
413
        if line == NO_NL:
350
 
            assert last_line.endswith('\n')
 
414
            if not last_line.endswith('\n'):
 
415
                raise AssertionError()
351
416
            last_line = last_line[:-1]
352
417
            line = None
353
418
        if last_line is not None:
357
422
        yield last_line
358
423
 
359
424
 
360
 
def parse_patches(iter_lines):
361
 
    iter_lines = iter_lines_handle_nl(iter_lines)
362
 
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
 
425
def parse_patches(iter_lines, allow_dirty=False, keep_dirty=False):
 
426
    '''
 
427
    :arg iter_lines: iterable of lines to parse for patches
 
428
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
 
429
        selected places.  This includes comments before and after a patch
 
430
        for instance.  Default False.
 
431
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
 
432
        Default False.
 
433
    '''
 
434
    patches = []
 
435
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
 
436
        if 'dirty_head' in patch_lines:
 
437
            patches.append({'patch': parse_patch(
 
438
                patch_lines['saved_lines'], allow_dirty),
 
439
                            'dirty_head': patch_lines['dirty_head']})
 
440
        else:
 
441
            patches.append(parse_patch(patch_lines, allow_dirty))
 
442
    return patches
363
443
 
364
444
 
365
445
def difference_index(atext, btext):
385
465
    """Iterate through a series of lines with a patch applied.
386
466
    This handles a single file, and does exact, not fuzzy patching.
387
467
    """
388
 
    if orig_lines is not None:
389
 
        orig_lines = orig_lines.__iter__()
 
468
    patch_lines = iter_lines_handle_nl(iter(patch_lines))
 
469
    get_patch_names(patch_lines)
 
470
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
 
471
 
 
472
 
 
473
def iter_patched_from_hunks(orig_lines, hunks):
 
474
    """Iterate through a series of lines with a patch applied.
 
475
    This handles a single file, and does exact, not fuzzy patching.
 
476
 
 
477
    :param orig_lines: The unpatched lines.
 
478
    :param hunks: An iterable of Hunk instances.
 
479
    """
390
480
    seen_patch = []
391
 
    patch_lines = iter_lines_handle_nl(patch_lines.__iter__())
392
 
    get_patch_names(patch_lines)
393
481
    line_no = 1
394
 
    for hunk in iter_hunks(patch_lines):
 
482
    if orig_lines is not None:
 
483
        orig_lines = iter(orig_lines)
 
484
    for hunk in hunks:
395
485
        while line_no < hunk.orig_pos:
396
486
            orig_line = orig_lines.next()
397
487
            yield orig_line
407
497
                if isinstance(hunk_line, ContextLine):
408
498
                    yield orig_line
409
499
                else:
410
 
                    assert isinstance(hunk_line, RemoveLine)
 
500
                    if not isinstance(hunk_line, RemoveLine):
 
501
                        raise AssertionError(hunk_line)
411
502
                line_no += 1
412
503
    if orig_lines is not None:
413
504
        for line in orig_lines: