/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2005-2010 Aaron Bentley, Canonical Ltd
0.5.93 by Aaron Bentley
Added patches.py
2
# <aaron.bentley@utoronto.ca>
3
#
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6289.2.1 by Jelmer Vernooij
Move the primary definition of the patches exceptions to bzrlib.errors.
17
6379.6.3 by Jelmer Vernooij
Use absolute_import.
18
from __future__ import absolute_import
19
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
20
from .errors import (
6729.5.1 by Jelmer Vernooij
Move patches errors to breezy.patches.
21
    BzrError,
6289.2.1 by Jelmer Vernooij
Move the primary definition of the patches exceptions to bzrlib.errors.
22
    )
23
4634.80.1 by Aaron Bentley
Parse binary files.
24
import re
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
25
import sys
26
27
28
binary_files_re = b'Binary files (.*) and (.*) differ\n'
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
29
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
30
6729.5.1 by Jelmer Vernooij
Move patches errors to breezy.patches.
31
class PatchSyntax(BzrError):
32
    """Base class for patch syntax errors."""
33
34
35
class BinaryFiles(BzrError):
36
37
    _fmt = 'Binary files section encountered.'
38
39
    def __init__(self, orig_name, mod_name):
40
        self.orig_name = orig_name
41
        self.mod_name = mod_name
42
43
44
class MalformedPatchHeader(PatchSyntax):
45
46
    _fmt = "Malformed patch header.  %(desc)s\n%(line)r"
47
48
    def __init__(self, desc, line):
49
        self.desc = desc
50
        self.line = line
51
52
53
class MalformedLine(PatchSyntax):
54
55
    _fmt = "Malformed line.  %(desc)s\n%(line)r"
56
57
    def __init__(self, desc, line):
58
        self.desc = desc
59
        self.line = line
60
61
62
class PatchConflict(BzrError):
63
64
    _fmt = ('Text contents mismatch at line %(line_no)d.  Original has '
65
            '"%(orig_line)s", but patch says it should be "%(patch_line)s"')
66
67
    def __init__(self, line_no, orig_line, patch_line):
68
        self.line_no = line_no
69
        self.orig_line = orig_line.rstrip('\n')
70
        self.patch_line = patch_line.rstrip('\n')
71
72
73
class MalformedHunkHeader(PatchSyntax):
74
75
    _fmt = "Malformed hunk header.  %(desc)s\n%(line)r"
76
77
    def __init__(self, desc, line):
78
        self.desc = desc
79
        self.line = line
80
81
0.5.93 by Aaron Bentley
Added patches.py
82
def get_patch_names(iter_lines):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
83
    line = next(iter_lines)
0.5.93 by Aaron Bentley
Added patches.py
84
    try:
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
85
        match = re.match(binary_files_re, line)
4634.80.1 by Aaron Bentley
Parse binary files.
86
        if match is not None:
87
            raise BinaryFiles(match.group(1), match.group(2))
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
88
        if not line.startswith(b"--- "):
0.5.93 by Aaron Bentley
Added patches.py
89
            raise MalformedPatchHeader("No orig name", line)
90
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
91
            orig_name = line[4:].rstrip(b"\n")
0.5.93 by Aaron Bentley
Added patches.py
92
    except StopIteration:
93
        raise MalformedPatchHeader("No orig line", "")
94
    try:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
95
        line = next(iter_lines)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
96
        if not line.startswith(b"+++ "):
0.5.93 by Aaron Bentley
Added patches.py
97
            raise PatchSyntax("No mod name")
98
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
99
            mod_name = line[4:].rstrip(b"\n")
0.5.93 by Aaron Bentley
Added patches.py
100
    except StopIteration:
101
        raise MalformedPatchHeader("No mod line", "")
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
102
    return (orig_name, mod_name)
0.5.93 by Aaron Bentley
Added patches.py
103
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
104
0.5.93 by Aaron Bentley
Added patches.py
105
def parse_range(textrange):
106
    """Parse a patch range, handling the "1" special-case
107
108
    :param textrange: The text to parse
109
    :type textrange: str
110
    :return: the position and range, as a tuple
111
    :rtype: (int, int)
112
    """
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
113
    tmp = textrange.split(b',')
0.5.93 by Aaron Bentley
Added patches.py
114
    if len(tmp) == 1:
115
        pos = tmp[0]
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
116
        range = b"1"
0.5.93 by Aaron Bentley
Added patches.py
117
    else:
118
        (pos, range) = tmp
119
    pos = int(pos)
120
    range = int(range)
121
    return (pos, range)
122
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
123
0.5.93 by Aaron Bentley
Added patches.py
124
def hunk_from_header(line):
3224.5.1 by Andrew Bennetts
Lots of assorted hackery to reduce the number of imports for common operations. Improves 'rocks', 'st' and 'help' times by ~50ms on my laptop.
125
    import re
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
126
    matches = re.match(br'\@\@ ([^@]*) \@\@( (.*))?\n', line)
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
127
    if matches is None:
128
        raise MalformedHunkHeader("Does not match format.", line)
0.5.93 by Aaron Bentley
Added patches.py
129
    try:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
130
        (orig, mod) = matches.group(1).split(b" ")
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
131
    except (ValueError, IndexError) as e:
0.5.93 by Aaron Bentley
Added patches.py
132
        raise MalformedHunkHeader(str(e), line)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
133
    if not orig.startswith(b'-') or not mod.startswith(b'+'):
0.5.93 by Aaron Bentley
Added patches.py
134
        raise MalformedHunkHeader("Positions don't start with + or -.", line)
135
    try:
136
        (orig_pos, orig_range) = parse_range(orig[1:])
137
        (mod_pos, mod_range) = parse_range(mod[1:])
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
138
    except (ValueError, IndexError) as e:
0.5.93 by Aaron Bentley
Added patches.py
139
        raise MalformedHunkHeader(str(e), line)
140
    if mod_range < 0 or orig_range < 0:
141
        raise MalformedHunkHeader("Hunk range is negative", line)
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
142
    tail = matches.group(3)
143
    return Hunk(orig_pos, orig_range, mod_pos, mod_range, tail)
0.5.93 by Aaron Bentley
Added patches.py
144
145
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
146
class HunkLine(object):
147
0.5.93 by Aaron Bentley
Added patches.py
148
    def __init__(self, contents):
149
        self.contents = contents
150
151
    def get_str(self, leadchar):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
152
        if self.contents == b"\n" and leadchar == b" " and False:
153
            return b"\n"
154
        if not self.contents.endswith(b'\n'):
155
            terminator = b'\n' + NO_NL
0.5.93 by Aaron Bentley
Added patches.py
156
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
157
            terminator = b''
0.5.93 by Aaron Bentley
Added patches.py
158
        return leadchar + self.contents + terminator
159
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
160
    def as_bytes(self):
161
        raise NotImplementedError
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
162
0.5.93 by Aaron Bentley
Added patches.py
163
164
class ContextLine(HunkLine):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
165
0.5.93 by Aaron Bentley
Added patches.py
166
    def __init__(self, contents):
167
        HunkLine.__init__(self, contents)
168
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
169
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
170
        return self.get_str(b" ")
0.5.93 by Aaron Bentley
Added patches.py
171
172
173
class InsertLine(HunkLine):
174
    def __init__(self, contents):
175
        HunkLine.__init__(self, contents)
176
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
177
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
178
        return self.get_str(b"+")
0.5.93 by Aaron Bentley
Added patches.py
179
180
181
class RemoveLine(HunkLine):
182
    def __init__(self, contents):
183
        HunkLine.__init__(self, contents)
184
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
185
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
186
        return self.get_str(b"-")
0.5.93 by Aaron Bentley
Added patches.py
187
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
188
NO_NL = b'\\ No newline at end of file\n'
0.5.93 by Aaron Bentley
Added patches.py
189
__pychecker__="no-returnvalues"
190
191
def parse_line(line):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
192
    if line.startswith(b"\n"):
0.5.93 by Aaron Bentley
Added patches.py
193
        return ContextLine(line)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
194
    elif line.startswith(b" "):
0.5.93 by Aaron Bentley
Added patches.py
195
        return ContextLine(line[1:])
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
196
    elif line.startswith(b"+"):
0.5.93 by Aaron Bentley
Added patches.py
197
        return InsertLine(line[1:])
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
198
    elif line.startswith(b"-"):
0.5.93 by Aaron Bentley
Added patches.py
199
        return RemoveLine(line[1:])
200
    else:
201
        raise MalformedLine("Unknown line type", line)
202
__pychecker__=""
203
204
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
205
class Hunk(object):
206
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
207
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
0.5.93 by Aaron Bentley
Added patches.py
208
        self.orig_pos = orig_pos
209
        self.orig_range = orig_range
210
        self.mod_pos = mod_pos
211
        self.mod_range = mod_range
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
212
        self.tail = tail
0.5.93 by Aaron Bentley
Added patches.py
213
        self.lines = []
214
215
    def get_header(self):
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
216
        if self.tail is None:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
217
            tail_str = b''
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
218
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
219
            tail_str = b' ' + self.tail
220
        return b"@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
221
                                                     self.orig_range),
222
                                      self.range_str(self.mod_pos,
223
                                                     self.mod_range),
224
                                      tail_str)
0.5.93 by Aaron Bentley
Added patches.py
225
226
    def range_str(self, pos, range):
227
        """Return a file range, special-casing for 1-line files.
228
229
        :param pos: The position in the file
230
        :type pos: int
231
        :range: The range in the file
232
        :type range: int
233
        :return: a string in the format 1,4 except when range == pos == 1
234
        """
235
        if range == 1:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
236
            return b"%i" % pos
0.5.93 by Aaron Bentley
Added patches.py
237
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
238
            return b"%i,%i" % (pos, range)
0.5.93 by Aaron Bentley
Added patches.py
239
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
240
    def as_bytes(self):
0.5.93 by Aaron Bentley
Added patches.py
241
        lines = [self.get_header()]
242
        for line in self.lines:
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
243
            lines.append(line.as_bytes())
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
244
        return b"".join(lines)
245
7045.3.1 by Jelmer Vernooij
Fix another ~500 tests.
246
    __bytes__ = as_bytes
247
0.5.93 by Aaron Bentley
Added patches.py
248
    def shift_to_mod(self, pos):
249
        if pos < self.orig_pos-1:
250
            return 0
251
        elif pos > self.orig_pos+self.orig_range:
252
            return self.mod_range - self.orig_range
253
        else:
254
            return self.shift_to_mod_lines(pos)
255
256
    def shift_to_mod_lines(self, pos):
257
        position = self.orig_pos-1
258
        shift = 0
259
        for line in self.lines:
260
            if isinstance(line, InsertLine):
261
                shift += 1
262
            elif isinstance(line, RemoveLine):
263
                if position == pos:
264
                    return None
265
                shift -= 1
266
                position += 1
267
            elif isinstance(line, ContextLine):
268
                position += 1
269
            if position > pos:
270
                break
271
        return shift
272
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
273
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
274
def iter_hunks(iter_lines, allow_dirty=False):
275
    '''
276
    :arg iter_lines: iterable of lines to parse for hunks
277
    :kwarg allow_dirty: If True, when we encounter something that is not
278
        a hunk header when we're looking for one, assume the rest of the lines
279
        are not part of the patch (comments or other junk).  Default False
280
    '''
0.5.93 by Aaron Bentley
Added patches.py
281
    hunk = None
282
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
283
        if line == b"\n":
0.5.93 by Aaron Bentley
Added patches.py
284
            if hunk is not None:
285
                yield hunk
286
                hunk = None
287
            continue
288
        if hunk is not None:
289
            yield hunk
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
290
        try:
291
            hunk = hunk_from_header(line)
292
        except MalformedHunkHeader:
293
            if allow_dirty:
294
                # If the line isn't a hunk header, then we've reached the end
295
                # of this patch and there's "junk" at the end.  Ignore the
296
                # rest of this patch.
297
                return
298
            raise
0.5.93 by Aaron Bentley
Added patches.py
299
        orig_size = 0
300
        mod_size = 0
301
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
302
            hunk_line = parse_line(next(iter_lines))
0.5.96 by Aaron Bentley
Cleaned up handling of files with no terminating \n
303
            hunk.lines.append(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
304
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
305
                orig_size += 1
306
            if isinstance(hunk_line, (InsertLine, ContextLine)):
307
                mod_size += 1
308
    if hunk is not None:
309
        yield hunk
310
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
311
4634.80.1 by Aaron Bentley
Parse binary files.
312
class BinaryPatch(object):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
313
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
314
    def __init__(self, oldname, newname):
0.5.93 by Aaron Bentley
Added patches.py
315
        self.oldname = oldname
316
        self.newname = newname
4634.80.1 by Aaron Bentley
Parse binary files.
317
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
318
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
319
        return b'Binary files %s and %s differ\n' % (self.oldname, self.newname)
320
4634.80.1 by Aaron Bentley
Parse binary files.
321
322
class Patch(BinaryPatch):
323
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
324
    def __init__(self, oldname, newname):
325
        BinaryPatch.__init__(self, oldname, newname)
0.5.93 by Aaron Bentley
Added patches.py
326
        self.hunks = []
327
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
328
    def as_bytes(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
329
        ret = self.get_header()
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
330
        ret += b"".join([h.as_bytes() for h in self.hunks])
0.5.93 by Aaron Bentley
Added patches.py
331
        return ret
332
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
333
    def get_header(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
334
        return b"--- %s\n+++ %s\n" % (self.oldname, self.newname)
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
335
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
336
    def stats_values(self):
337
        """Calculate the number of inserts and removes."""
0.5.93 by Aaron Bentley
Added patches.py
338
        removes = 0
339
        inserts = 0
340
        for hunk in self.hunks:
341
            for line in hunk.lines:
342
                if isinstance(line, InsertLine):
343
                     inserts+=1;
344
                elif isinstance(line, RemoveLine):
345
                     removes+=1;
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
346
        return (inserts, removes, len(self.hunks))
347
348
    def stats_str(self):
349
        """Return a string of patch statistics"""
0.5.93 by Aaron Bentley
Added patches.py
350
        return "%i inserts, %i removes in %i hunks" % \
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
351
            self.stats_values()
0.5.93 by Aaron Bentley
Added patches.py
352
353
    def pos_in_mod(self, position):
354
        newpos = position
355
        for hunk in self.hunks:
356
            shift = hunk.shift_to_mod(position)
357
            if shift is None:
358
                return None
359
            newpos += shift
360
        return newpos
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
361
0.5.93 by Aaron Bentley
Added patches.py
362
    def iter_inserted(self):
363
        """Iteraties through inserted lines
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
364
0.5.93 by Aaron Bentley
Added patches.py
365
        :return: Pair of line number, line
366
        :rtype: iterator of (int, InsertLine)
367
        """
368
        for hunk in self.hunks:
369
            pos = hunk.mod_pos - 1;
370
            for line in hunk.lines:
371
                if isinstance(line, InsertLine):
372
                    yield (pos, line)
373
                    pos += 1
374
                if isinstance(line, ContextLine):
375
                    pos += 1
376
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
377
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
378
def parse_patch(iter_lines, allow_dirty=False):
379
    '''
380
    :arg iter_lines: iterable of lines to parse
381
    :kwarg allow_dirty: If True, allow the patch to have trailing junk.
382
        Default False
383
    '''
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
384
    iter_lines = iter_lines_handle_nl(iter_lines)
4634.80.1 by Aaron Bentley
Parse binary files.
385
    try:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
386
        (orig_name, mod_name) = get_patch_names(iter_lines)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
387
    except BinaryFiles as e:
4634.80.1 by Aaron Bentley
Parse binary files.
388
        return BinaryPatch(e.orig_name, e.mod_name)
389
    else:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
390
        patch = Patch(orig_name, mod_name)
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
391
        for hunk in iter_hunks(iter_lines, allow_dirty):
4634.80.1 by Aaron Bentley
Parse binary files.
392
            patch.hunks.append(hunk)
393
        return patch
0.5.93 by Aaron Bentley
Added patches.py
394
395
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
396
def iter_file_patch(iter_lines, allow_dirty=False, keep_dirty=False):
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
397
    '''
398
    :arg iter_lines: iterable of lines to parse for patches
399
    :kwarg allow_dirty: If True, allow comments and other non-patch text
400
        before the first patch.  Note that the algorithm here can only find
401
        such text before any patches have been found.  Comments after the
402
        first patch are stripped away in iter_hunks() if it is also passed
403
        allow_dirty=True.  Default False.
404
    '''
405
    ### FIXME: Docstring is not quite true.  We allow certain comments no
406
    # matter what, If they startwith '===', '***', or '#' Someone should
407
    # reexamine this logic and decide if we should include those in
408
    # allow_dirty or restrict those to only being before the patch is found
409
    # (as allow_dirty does).
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
410
    regex = re.compile(binary_files_re)
0.5.93 by Aaron Bentley
Added patches.py
411
    saved_lines = []
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
412
    dirty_head = []
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
413
    orig_range = 0
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
414
    beginning = True
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
415
0.5.93 by Aaron Bentley
Added patches.py
416
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
417
        if line.startswith(b'=== '):
6603.2.1 by Colin Watson
Avoid associating dirty patch headers with the previous file in the patch.
418
            if len(saved_lines) > 0:
419
                if keep_dirty and len(dirty_head) > 0:
420
                    yield {'saved_lines': saved_lines,
421
                           'dirty_head': dirty_head}
422
                    dirty_head = []
423
                else:
424
                    yield saved_lines
425
                saved_lines = []
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
426
            dirty_head.append(line)
427
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
428
        if line.startswith(b'*** '):
0.5.93 by Aaron Bentley
Added patches.py
429
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
430
        if line.startswith(b'#'):
1770.1.1 by Aaron Bentley
Ignore lines that start with '#' in patch parser
431
            continue
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
432
        elif orig_range > 0:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
433
            if line.startswith(b'-') or line.startswith(b' '):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
434
                orig_range -= 1
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
435
        elif line.startswith(b'--- ') or regex.match(line):
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
436
            if allow_dirty and beginning:
437
                # Patches can have "junk" at the beginning
438
                # Stripping junk from the end of patches is handled when we
439
                # parse the patch
440
                beginning = False
441
            elif len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
442
                if keep_dirty and len(dirty_head) > 0:
443
                    yield {'saved_lines': saved_lines,
444
                           'dirty_head': dirty_head}
445
                    dirty_head = []
446
                else:
447
                    yield saved_lines
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
448
            saved_lines = []
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
449
        elif line.startswith(b'@@'):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
450
            hunk = hunk_from_header(line)
451
            orig_range = hunk.orig_range
0.5.93 by Aaron Bentley
Added patches.py
452
        saved_lines.append(line)
453
    if len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
454
        if keep_dirty and len(dirty_head) > 0:
455
            yield {'saved_lines': saved_lines,
456
                   'dirty_head': dirty_head}
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
457
        else:
458
            yield saved_lines
0.5.93 by Aaron Bentley
Added patches.py
459
460
3873.1.6 by Benoît Pierre
OK, so now patches should handle '\ No newline at end of file' in both
461
def iter_lines_handle_nl(iter_lines):
462
    """
463
    Iterates through lines, ensuring that lines that originally had no
464
    terminating \n are produced without one.  This transformation may be
465
    applied at any point up until hunk line parsing, and is safe to apply
466
    repeatedly.
467
    """
468
    last_line = None
469
    for line in iter_lines:
470
        if line == NO_NL:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
471
            if not last_line.endswith(b'\n'):
3873.1.6 by Benoît Pierre
OK, so now patches should handle '\ No newline at end of file' in both
472
                raise AssertionError()
473
            last_line = last_line[:-1]
474
            line = None
475
        if last_line is not None:
476
            yield last_line
477
        last_line = line
478
    if last_line is not None:
479
        yield last_line
480
481
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
482
def parse_patches(iter_lines, allow_dirty=False, keep_dirty=False):
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
483
    '''
484
    :arg iter_lines: iterable of lines to parse for patches
485
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
486
        selected places.  This includes comments before and after a patch
487
        for instance.  Default False.
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
488
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
489
        Default False.
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
490
    '''
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
491
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
492
        if 'dirty_head' in patch_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
493
            yield ({'patch': parse_patch(patch_lines['saved_lines'], allow_dirty),
494
                    'dirty_head': patch_lines['dirty_head']})
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
495
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
496
            yield parse_patch(patch_lines, allow_dirty)
0.5.93 by Aaron Bentley
Added patches.py
497
498
499
def difference_index(atext, btext):
1759.2.1 by Jelmer Vernooij
Fix some types (found using aspell).
500
    """Find the indext of the first character that differs between two texts
0.5.93 by Aaron Bentley
Added patches.py
501
502
    :param atext: The first text
503
    :type atext: str
504
    :param btext: The second text
505
    :type str: str
506
    :return: The index, or None if there are no differences within the range
507
    :rtype: int or NoneType
508
    """
509
    length = len(atext)
510
    if len(btext) < length:
511
        length = len(btext)
512
    for i in range(length):
513
        if atext[i] != btext[i]:
514
            return i;
515
    return None
516
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
517
0.5.93 by Aaron Bentley
Added patches.py
518
def iter_patched(orig_lines, patch_lines):
519
    """Iterate through a series of lines with a patch applied.
520
    This handles a single file, and does exact, not fuzzy patching.
521
    """
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
522
    patch_lines = iter_lines_handle_nl(iter(patch_lines))
0.5.93 by Aaron Bentley
Added patches.py
523
    get_patch_names(patch_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
524
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
525
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
526
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
527
def iter_patched_from_hunks(orig_lines, hunks):
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
528
    """Iterate through a series of lines with a patch applied.
529
    This handles a single file, and does exact, not fuzzy patching.
530
531
    :param orig_lines: The unpatched lines.
532
    :param hunks: An iterable of Hunk instances.
533
    """
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
534
    seen_patch = []
0.5.93 by Aaron Bentley
Added patches.py
535
    line_no = 1
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
536
    if orig_lines is not None:
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
537
        orig_lines = iter(orig_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
538
    for hunk in hunks:
0.5.93 by Aaron Bentley
Added patches.py
539
        while line_no < hunk.orig_pos:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
540
            orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
541
            yield orig_line
542
            line_no += 1
543
        for hunk_line in hunk.lines:
544
            seen_patch.append(str(hunk_line))
545
            if isinstance(hunk_line, InsertLine):
546
                yield hunk_line.contents
547
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
548
                orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
549
                if orig_line != hunk_line.contents:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
550
                    raise PatchConflict(line_no, orig_line, b"".join(seen_patch))
0.5.93 by Aaron Bentley
Added patches.py
551
                if isinstance(hunk_line, ContextLine):
552
                    yield orig_line
553
                else:
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
554
                    if not isinstance(hunk_line, RemoveLine):
555
                        raise AssertionError(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
556
                line_no += 1
0.5.105 by John Arbash Meinel
Adding more test patches to the test suite.
557
    if orig_lines is not None:
558
        for line in orig_lines:
559
            yield line