/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: Joe Julian
  • Date: 2010-01-10 02:25:31 UTC
  • mto: (4634.119.7 2.0)
  • mto: This revision was merged to the branch mainline in revision 4959.
  • Revision ID: joe@julianfamily.org-20100110022531-wqk61rsagz8xsiga
Added MANIFEST.in to allow bdist_rpm to have all the required include files and tools. bdist_rpm will still fail to build correctly on some distributions due to a disttools bug http://bugs.python.org/issue644744

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, 2008 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 .errors import (
21
 
    BzrError,
22
 
    )
23
 
 
24
17
import re
25
18
 
26
19
 
27
 
binary_files_re = b'Binary files (.*) and (.*) differ\n'
28
 
 
29
 
 
30
 
class PatchSyntax(BzrError):
31
 
    """Base class for patch syntax errors."""
32
 
 
33
 
 
34
 
class BinaryFiles(BzrError):
35
 
 
36
 
    _fmt = 'Binary files section encountered.'
 
20
binary_files_re = 'Binary files (.*) and (.*) differ\n'
 
21
 
 
22
 
 
23
class BinaryFiles(Exception):
37
24
 
38
25
    def __init__(self, orig_name, mod_name):
39
26
        self.orig_name = orig_name
40
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)
41
34
 
42
35
 
43
36
class MalformedPatchHeader(PatchSyntax):
44
 
 
45
 
    _fmt = "Malformed patch header.  %(desc)s\n%(line)r"
46
 
 
47
 
    def __init__(self, desc, line):
48
 
        self.desc = desc
49
 
        self.line = line
 
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
50
 
51
51
 
52
52
class MalformedLine(PatchSyntax):
53
 
 
54
 
    _fmt = "Malformed line.  %(desc)s\n%(line)r"
55
 
 
56
53
    def __init__(self, desc, line):
57
54
        self.desc = desc
58
55
        self.line = line
59
 
 
60
 
 
61
 
class PatchConflict(BzrError):
62
 
 
63
 
    _fmt = ('Text contents mismatch at line %(line_no)d.  Original has '
64
 
            '"%(orig_line)s", but patch says it should be "%(patch_line)s"')
65
 
 
 
56
        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
 
57
        PatchSyntax.__init__(self, msg)
 
58
 
 
59
 
 
60
class PatchConflict(Exception):
66
61
    def __init__(self, line_no, orig_line, patch_line):
67
 
        self.line_no = line_no
68
 
        self.orig_line = orig_line.rstrip('\n')
69
 
        self.patch_line = patch_line.rstrip('\n')
70
 
 
71
 
 
72
 
class MalformedHunkHeader(PatchSyntax):
73
 
 
74
 
    _fmt = "Malformed hunk header.  %(desc)s\n%(line)r"
75
 
 
76
 
    def __init__(self, desc, line):
77
 
        self.desc = desc
78
 
        self.line = 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)
79
67
 
80
68
 
81
69
def get_patch_names(iter_lines):
82
 
    line = next(iter_lines)
83
70
    try:
 
71
        line = iter_lines.next()
84
72
        match = re.match(binary_files_re, line)
85
73
        if match is not None:
86
74
            raise BinaryFiles(match.group(1), match.group(2))
87
 
        if not line.startswith(b"--- "):
 
75
        if not line.startswith("--- "):
88
76
            raise MalformedPatchHeader("No orig name", line)
89
77
        else:
90
 
            orig_name = line[4:].rstrip(b"\n")
 
78
            orig_name = line[4:].rstrip("\n")
91
79
    except StopIteration:
92
80
        raise MalformedPatchHeader("No orig line", "")
93
81
    try:
94
 
        line = next(iter_lines)
95
 
        if not line.startswith(b"+++ "):
 
82
        line = iter_lines.next()
 
83
        if not line.startswith("+++ "):
96
84
            raise PatchSyntax("No mod name")
97
85
        else:
98
 
            mod_name = line[4:].rstrip(b"\n")
 
86
            mod_name = line[4:].rstrip("\n")
99
87
    except StopIteration:
100
88
        raise MalformedPatchHeader("No mod line", "")
101
89
    return (orig_name, mod_name)
109
97
    :return: the position and range, as a tuple
110
98
    :rtype: (int, int)
111
99
    """
112
 
    tmp = textrange.split(b',')
 
100
    tmp = textrange.split(',')
113
101
    if len(tmp) == 1:
114
102
        pos = tmp[0]
115
 
        range = b"1"
 
103
        range = "1"
116
104
    else:
117
105
        (pos, range) = tmp
118
106
    pos = int(pos)
122
110
 
123
111
def hunk_from_header(line):
124
112
    import re
125
 
    matches = re.match(br'\@\@ ([^@]*) \@\@( (.*))?\n', line)
 
113
    matches = re.match(r'\@\@ ([^@]*) \@\@( (.*))?\n', line)
126
114
    if matches is None:
127
115
        raise MalformedHunkHeader("Does not match format.", line)
128
116
    try:
129
 
        (orig, mod) = matches.group(1).split(b" ")
130
 
    except (ValueError, IndexError) as e:
 
117
        (orig, mod) = matches.group(1).split(" ")
 
118
    except (ValueError, IndexError), e:
131
119
        raise MalformedHunkHeader(str(e), line)
132
 
    if not orig.startswith(b'-') or not mod.startswith(b'+'):
 
120
    if not orig.startswith('-') or not mod.startswith('+'):
133
121
        raise MalformedHunkHeader("Positions don't start with + or -.", line)
134
122
    try:
135
123
        (orig_pos, orig_range) = parse_range(orig[1:])
136
124
        (mod_pos, mod_range) = parse_range(mod[1:])
137
 
    except (ValueError, IndexError) as e:
 
125
    except (ValueError, IndexError), e:
138
126
        raise MalformedHunkHeader(str(e), line)
139
127
    if mod_range < 0 or orig_range < 0:
140
128
        raise MalformedHunkHeader("Hunk range is negative", line)
142
130
    return Hunk(orig_pos, orig_range, mod_pos, mod_range, tail)
143
131
 
144
132
 
145
 
class HunkLine(object):
146
 
 
 
133
class HunkLine:
147
134
    def __init__(self, contents):
148
135
        self.contents = contents
149
136
 
150
137
    def get_str(self, leadchar):
151
 
        if self.contents == b"\n" and leadchar == b" " and False:
152
 
            return b"\n"
153
 
        if not self.contents.endswith(b'\n'):
154
 
            terminator = b'\n' + NO_NL
 
138
        if self.contents == "\n" and leadchar == " " and False:
 
139
            return "\n"
 
140
        if not self.contents.endswith('\n'):
 
141
            terminator = '\n' + NO_NL
155
142
        else:
156
 
            terminator = b''
 
143
            terminator = ''
157
144
        return leadchar + self.contents + terminator
158
145
 
159
 
    def as_bytes(self):
160
 
        raise NotImplementedError
161
 
 
162
146
 
163
147
class ContextLine(HunkLine):
164
 
 
165
148
    def __init__(self, contents):
166
149
        HunkLine.__init__(self, contents)
167
150
 
168
 
    def as_bytes(self):
169
 
        return self.get_str(b" ")
 
151
    def __str__(self):
 
152
        return self.get_str(" ")
170
153
 
171
154
 
172
155
class InsertLine(HunkLine):
173
156
    def __init__(self, contents):
174
157
        HunkLine.__init__(self, contents)
175
158
 
176
 
    def as_bytes(self):
177
 
        return self.get_str(b"+")
 
159
    def __str__(self):
 
160
        return self.get_str("+")
178
161
 
179
162
 
180
163
class RemoveLine(HunkLine):
181
164
    def __init__(self, contents):
182
165
        HunkLine.__init__(self, contents)
183
166
 
184
 
    def as_bytes(self):
185
 
        return self.get_str(b"-")
186
 
 
187
 
 
188
 
NO_NL = b'\\ No newline at end of file\n'
189
 
__pychecker__ = "no-returnvalues"
190
 
 
 
167
    def __str__(self):
 
168
        return self.get_str("-")
 
169
 
 
170
NO_NL = '\\ No newline at end of file\n'
 
171
__pychecker__="no-returnvalues"
191
172
 
192
173
def parse_line(line):
193
 
    if line.startswith(b"\n"):
 
174
    if line.startswith("\n"):
194
175
        return ContextLine(line)
195
 
    elif line.startswith(b" "):
 
176
    elif line.startswith(" "):
196
177
        return ContextLine(line[1:])
197
 
    elif line.startswith(b"+"):
 
178
    elif line.startswith("+"):
198
179
        return InsertLine(line[1:])
199
 
    elif line.startswith(b"-"):
 
180
    elif line.startswith("-"):
200
181
        return RemoveLine(line[1:])
201
182
    else:
202
183
        raise MalformedLine("Unknown line type", line)
203
 
 
204
 
 
205
 
__pychecker__ = ""
206
 
 
207
 
 
208
 
class Hunk(object):
209
 
 
 
184
__pychecker__=""
 
185
 
 
186
 
 
187
class Hunk:
210
188
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
211
189
        self.orig_pos = orig_pos
212
190
        self.orig_range = orig_range
217
195
 
218
196
    def get_header(self):
219
197
        if self.tail is None:
220
 
            tail_str = b''
 
198
            tail_str = ''
221
199
        else:
222
 
            tail_str = b' ' + self.tail
223
 
        return b"@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
224
 
                                                      self.orig_range),
225
 
                                       self.range_str(self.mod_pos,
226
 
                                                      self.mod_range),
227
 
                                       tail_str)
 
200
            tail_str = ' ' + self.tail
 
201
        return "@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
 
202
                                                     self.orig_range),
 
203
                                      self.range_str(self.mod_pos,
 
204
                                                     self.mod_range),
 
205
                                      tail_str)
228
206
 
229
207
    def range_str(self, pos, range):
230
208
        """Return a file range, special-casing for 1-line files.
236
214
        :return: a string in the format 1,4 except when range == pos == 1
237
215
        """
238
216
        if range == 1:
239
 
            return b"%i" % pos
 
217
            return "%i" % pos
240
218
        else:
241
 
            return b"%i,%i" % (pos, range)
 
219
            return "%i,%i" % (pos, range)
242
220
 
243
 
    def as_bytes(self):
 
221
    def __str__(self):
244
222
        lines = [self.get_header()]
245
223
        for line in self.lines:
246
 
            lines.append(line.as_bytes())
247
 
        return b"".join(lines)
248
 
 
249
 
    __bytes__ = as_bytes
 
224
            lines.append(str(line))
 
225
        return "".join(lines)
250
226
 
251
227
    def shift_to_mod(self, pos):
252
 
        if pos < self.orig_pos - 1:
 
228
        if pos < self.orig_pos-1:
253
229
            return 0
254
 
        elif pos > self.orig_pos + self.orig_range:
 
230
        elif pos > self.orig_pos+self.orig_range:
255
231
            return self.mod_range - self.orig_range
256
232
        else:
257
233
            return self.shift_to_mod_lines(pos)
258
234
 
259
235
    def shift_to_mod_lines(self, pos):
260
 
        position = self.orig_pos - 1
 
236
        position = self.orig_pos-1
261
237
        shift = 0
262
238
        for line in self.lines:
263
239
            if isinstance(line, InsertLine):
274
250
        return shift
275
251
 
276
252
 
277
 
def iter_hunks(iter_lines, allow_dirty=False):
278
 
    '''
279
 
    :arg iter_lines: iterable of lines to parse for hunks
280
 
    :kwarg allow_dirty: If True, when we encounter something that is not
281
 
        a hunk header when we're looking for one, assume the rest of the lines
282
 
        are not part of the patch (comments or other junk).  Default False
283
 
    '''
 
253
def iter_hunks(iter_lines):
284
254
    hunk = None
285
255
    for line in iter_lines:
286
 
        if line == b"\n":
 
256
        if line == "\n":
287
257
            if hunk is not None:
288
258
                yield hunk
289
259
                hunk = None
290
260
            continue
291
261
        if hunk is not None:
292
262
            yield hunk
293
 
        try:
294
 
            hunk = hunk_from_header(line)
295
 
        except MalformedHunkHeader:
296
 
            if allow_dirty:
297
 
                # If the line isn't a hunk header, then we've reached the end
298
 
                # of this patch and there's "junk" at the end.  Ignore the
299
 
                # rest of this patch.
300
 
                return
301
 
            raise
 
263
        hunk = hunk_from_header(line)
302
264
        orig_size = 0
303
265
        mod_size = 0
304
266
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
305
 
            hunk_line = parse_line(next(iter_lines))
 
267
            hunk_line = parse_line(iter_lines.next())
306
268
            hunk.lines.append(hunk_line)
307
269
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
308
270
                orig_size += 1
313
275
 
314
276
 
315
277
class BinaryPatch(object):
316
 
 
317
278
    def __init__(self, oldname, newname):
318
279
        self.oldname = oldname
319
280
        self.newname = newname
320
281
 
321
 
    def as_bytes(self):
322
 
        return b'Binary files %s and %s differ\n' % (self.oldname, self.newname)
 
282
    def __str__(self):
 
283
        return 'Binary files %s and %s differ\n' % (self.oldname, self.newname)
323
284
 
324
285
 
325
286
class Patch(BinaryPatch):
328
289
        BinaryPatch.__init__(self, oldname, newname)
329
290
        self.hunks = []
330
291
 
331
 
    def as_bytes(self):
 
292
    def __str__(self):
332
293
        ret = self.get_header()
333
 
        ret += b"".join([h.as_bytes() for h in self.hunks])
 
294
        ret += "".join([str(h) for h in self.hunks])
334
295
        return ret
335
296
 
336
297
    def get_header(self):
337
 
        return b"--- %s\n+++ %s\n" % (self.oldname, self.newname)
 
298
        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
338
299
 
339
300
    def stats_values(self):
340
301
        """Calculate the number of inserts and removes."""
343
304
        for hunk in self.hunks:
344
305
            for line in hunk.lines:
345
306
                if isinstance(line, InsertLine):
346
 
                    inserts += 1
 
307
                     inserts+=1;
347
308
                elif isinstance(line, RemoveLine):
348
 
                    removes += 1
 
309
                     removes+=1;
349
310
        return (inserts, removes, len(self.hunks))
350
311
 
351
312
    def stats_str(self):
369
330
        :rtype: iterator of (int, InsertLine)
370
331
        """
371
332
        for hunk in self.hunks:
372
 
            pos = hunk.mod_pos - 1
 
333
            pos = hunk.mod_pos - 1;
373
334
            for line in hunk.lines:
374
335
                if isinstance(line, InsertLine):
375
336
                    yield (pos, line)
378
339
                    pos += 1
379
340
 
380
341
 
381
 
def parse_patch(iter_lines, allow_dirty=False):
382
 
    '''
383
 
    :arg iter_lines: iterable of lines to parse
384
 
    :kwarg allow_dirty: If True, allow the patch to have trailing junk.
385
 
        Default False
386
 
    '''
 
342
def parse_patch(iter_lines):
387
343
    iter_lines = iter_lines_handle_nl(iter_lines)
388
344
    try:
389
345
        (orig_name, mod_name) = get_patch_names(iter_lines)
390
 
    except BinaryFiles as e:
 
346
    except BinaryFiles, e:
391
347
        return BinaryPatch(e.orig_name, e.mod_name)
392
348
    else:
393
349
        patch = Patch(orig_name, mod_name)
394
 
        for hunk in iter_hunks(iter_lines, allow_dirty):
 
350
        for hunk in iter_hunks(iter_lines):
395
351
            patch.hunks.append(hunk)
396
352
        return patch
397
353
 
398
354
 
399
 
def iter_file_patch(iter_lines, allow_dirty=False, keep_dirty=False):
400
 
    '''
401
 
    :arg iter_lines: iterable of lines to parse for patches
402
 
    :kwarg allow_dirty: If True, allow comments and other non-patch text
403
 
        before the first patch.  Note that the algorithm here can only find
404
 
        such text before any patches have been found.  Comments after the
405
 
        first patch are stripped away in iter_hunks() if it is also passed
406
 
        allow_dirty=True.  Default False.
407
 
    '''
408
 
    # FIXME: Docstring is not quite true.  We allow certain comments no
409
 
    # matter what, If they startwith '===', '***', or '#' Someone should
410
 
    # reexamine this logic and decide if we should include those in
411
 
    # allow_dirty or restrict those to only being before the patch is found
412
 
    # (as allow_dirty does).
 
355
def iter_file_patch(iter_lines):
413
356
    regex = re.compile(binary_files_re)
414
357
    saved_lines = []
415
 
    dirty_head = []
416
358
    orig_range = 0
417
 
    beginning = True
418
 
 
419
359
    for line in iter_lines:
420
 
        if line.startswith(b'=== '):
421
 
            if len(saved_lines) > 0:
422
 
                if keep_dirty and len(dirty_head) > 0:
423
 
                    yield {'saved_lines': saved_lines,
424
 
                           'dirty_head': dirty_head}
425
 
                    dirty_head = []
426
 
                else:
427
 
                    yield saved_lines
428
 
                saved_lines = []
429
 
            dirty_head.append(line)
430
 
            continue
431
 
        if line.startswith(b'*** '):
432
 
            continue
433
 
        if line.startswith(b'#'):
 
360
        if line.startswith('=== ') or line.startswith('*** '):
 
361
            continue
 
362
        if line.startswith('#'):
434
363
            continue
435
364
        elif orig_range > 0:
436
 
            if line.startswith(b'-') or line.startswith(b' '):
 
365
            if line.startswith('-') or line.startswith(' '):
437
366
                orig_range -= 1
438
 
        elif line.startswith(b'--- ') or regex.match(line):
439
 
            if allow_dirty and beginning:
440
 
                # Patches can have "junk" at the beginning
441
 
                # Stripping junk from the end of patches is handled when we
442
 
                # parse the patch
443
 
                beginning = False
444
 
            elif len(saved_lines) > 0:
445
 
                if keep_dirty and len(dirty_head) > 0:
446
 
                    yield {'saved_lines': saved_lines,
447
 
                           'dirty_head': dirty_head}
448
 
                    dirty_head = []
449
 
                else:
450
 
                    yield saved_lines
 
367
        elif line.startswith('--- ') or regex.match(line):
 
368
            if len(saved_lines) > 0:
 
369
                yield saved_lines
451
370
            saved_lines = []
452
 
        elif line.startswith(b'@@'):
 
371
        elif line.startswith('@@'):
453
372
            hunk = hunk_from_header(line)
454
373
            orig_range = hunk.orig_range
455
374
        saved_lines.append(line)
456
375
    if len(saved_lines) > 0:
457
 
        if keep_dirty and len(dirty_head) > 0:
458
 
            yield {'saved_lines': saved_lines,
459
 
                   'dirty_head': dirty_head}
460
 
        else:
461
 
            yield saved_lines
 
376
        yield saved_lines
462
377
 
463
378
 
464
379
def iter_lines_handle_nl(iter_lines):
471
386
    last_line = None
472
387
    for line in iter_lines:
473
388
        if line == NO_NL:
474
 
            if not last_line.endswith(b'\n'):
 
389
            if not last_line.endswith('\n'):
475
390
                raise AssertionError()
476
391
            last_line = last_line[:-1]
477
392
            line = None
482
397
        yield last_line
483
398
 
484
399
 
485
 
def parse_patches(iter_lines, allow_dirty=False, keep_dirty=False):
486
 
    '''
487
 
    :arg iter_lines: iterable of lines to parse for patches
488
 
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
489
 
        selected places.  This includes comments before and after a patch
490
 
        for instance.  Default False.
491
 
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
492
 
        Default False.
493
 
    '''
494
 
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
495
 
        if 'dirty_head' in patch_lines:
496
 
            yield ({'patch': parse_patch(patch_lines['saved_lines'], allow_dirty),
497
 
                    'dirty_head': patch_lines['dirty_head']})
498
 
        else:
499
 
            yield parse_patch(patch_lines, allow_dirty)
 
400
def parse_patches(iter_lines):
 
401
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
500
402
 
501
403
 
502
404
def difference_index(atext, btext):
514
416
        length = len(btext)
515
417
    for i in range(length):
516
418
        if atext[i] != btext[i]:
517
 
            return i
 
419
            return i;
518
420
    return None
519
421
 
520
422
 
540
442
        orig_lines = iter(orig_lines)
541
443
    for hunk in hunks:
542
444
        while line_no < hunk.orig_pos:
543
 
            orig_line = next(orig_lines)
 
445
            orig_line = orig_lines.next()
544
446
            yield orig_line
545
447
            line_no += 1
546
448
        for hunk_line in hunk.lines:
548
450
            if isinstance(hunk_line, InsertLine):
549
451
                yield hunk_line.contents
550
452
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
551
 
                orig_line = next(orig_lines)
 
453
                orig_line = orig_lines.next()
552
454
                if orig_line != hunk_line.contents:
553
 
                    raise PatchConflict(line_no, orig_line,
554
 
                                        b"".join(seen_patch))
 
455
                    raise PatchConflict(line_no, orig_line, "".join(seen_patch))
555
456
                if isinstance(hunk_line, ContextLine):
556
457
                    yield orig_line
557
458
                else: