/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
26
27
binary_files_re = b'Binary files (.*) and (.*) differ\n'
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
28
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
29
6729.5.1 by Jelmer Vernooij
Move patches errors to breezy.patches.
30
class PatchSyntax(BzrError):
31
    """Base class for patch syntax errors."""
32
33
34
class BinaryFiles(BzrError):
35
36
    _fmt = 'Binary files section encountered.'
37
38
    def __init__(self, orig_name, mod_name):
39
        self.orig_name = orig_name
40
        self.mod_name = mod_name
41
42
43
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
50
51
52
class MalformedLine(PatchSyntax):
53
54
    _fmt = "Malformed line.  %(desc)s\n%(line)r"
55
56
    def __init__(self, desc, line):
57
        self.desc = desc
58
        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
66
    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
79
80
0.5.93 by Aaron Bentley
Added patches.py
81
def get_patch_names(iter_lines):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
82
    line = next(iter_lines)
0.5.93 by Aaron Bentley
Added patches.py
83
    try:
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
84
        match = re.match(binary_files_re, line)
4634.80.1 by Aaron Bentley
Parse binary files.
85
        if match is not None:
86
            raise BinaryFiles(match.group(1), match.group(2))
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
87
        if not line.startswith(b"--- "):
0.5.93 by Aaron Bentley
Added patches.py
88
            raise MalformedPatchHeader("No orig name", line)
89
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
90
            orig_name = line[4:].rstrip(b"\n")
0.5.93 by Aaron Bentley
Added patches.py
91
    except StopIteration:
92
        raise MalformedPatchHeader("No orig line", "")
93
    try:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
94
        line = next(iter_lines)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
95
        if not line.startswith(b"+++ "):
0.5.93 by Aaron Bentley
Added patches.py
96
            raise PatchSyntax("No mod name")
97
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
98
            mod_name = line[4:].rstrip(b"\n")
0.5.93 by Aaron Bentley
Added patches.py
99
    except StopIteration:
100
        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
101
    return (orig_name, mod_name)
0.5.93 by Aaron Bentley
Added patches.py
102
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
103
0.5.93 by Aaron Bentley
Added patches.py
104
def parse_range(textrange):
105
    """Parse a patch range, handling the "1" special-case
106
107
    :param textrange: The text to parse
108
    :type textrange: str
109
    :return: the position and range, as a tuple
110
    :rtype: (int, int)
111
    """
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
112
    tmp = textrange.split(b',')
0.5.93 by Aaron Bentley
Added patches.py
113
    if len(tmp) == 1:
114
        pos = tmp[0]
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
115
        range = b"1"
0.5.93 by Aaron Bentley
Added patches.py
116
    else:
117
        (pos, range) = tmp
118
    pos = int(pos)
119
    range = int(range)
120
    return (pos, range)
121
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
122
0.5.93 by Aaron Bentley
Added patches.py
123
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.
124
    import re
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
125
    matches = re.match(br'\@\@ ([^@]*) \@\@( (.*))?\n', line)
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
126
    if matches is None:
127
        raise MalformedHunkHeader("Does not match format.", line)
0.5.93 by Aaron Bentley
Added patches.py
128
    try:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
129
        (orig, mod) = matches.group(1).split(b" ")
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
130
    except (ValueError, IndexError) as e:
0.5.93 by Aaron Bentley
Added patches.py
131
        raise MalformedHunkHeader(str(e), line)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
132
    if not orig.startswith(b'-') or not mod.startswith(b'+'):
0.5.93 by Aaron Bentley
Added patches.py
133
        raise MalformedHunkHeader("Positions don't start with + or -.", line)
134
    try:
135
        (orig_pos, orig_range) = parse_range(orig[1:])
136
        (mod_pos, mod_range) = parse_range(mod[1:])
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
137
    except (ValueError, IndexError) as e:
0.5.93 by Aaron Bentley
Added patches.py
138
        raise MalformedHunkHeader(str(e), line)
139
    if mod_range < 0 or orig_range < 0:
140
        raise MalformedHunkHeader("Hunk range is negative", line)
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
141
    tail = matches.group(3)
142
    return Hunk(orig_pos, orig_range, mod_pos, mod_range, tail)
0.5.93 by Aaron Bentley
Added patches.py
143
144
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
145
class HunkLine(object):
146
0.5.93 by Aaron Bentley
Added patches.py
147
    def __init__(self, contents):
148
        self.contents = contents
149
150
    def get_str(self, leadchar):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
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
0.5.93 by Aaron Bentley
Added patches.py
155
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
156
            terminator = b''
0.5.93 by Aaron Bentley
Added patches.py
157
        return leadchar + self.contents + terminator
158
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
159
    def as_bytes(self):
160
        raise NotImplementedError
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
161
0.5.93 by Aaron Bentley
Added patches.py
162
163
class ContextLine(HunkLine):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
164
0.5.93 by Aaron Bentley
Added patches.py
165
    def __init__(self, contents):
166
        HunkLine.__init__(self, contents)
167
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
168
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
169
        return self.get_str(b" ")
0.5.93 by Aaron Bentley
Added patches.py
170
171
172
class InsertLine(HunkLine):
173
    def __init__(self, contents):
174
        HunkLine.__init__(self, contents)
175
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
176
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
177
        return self.get_str(b"+")
0.5.93 by Aaron Bentley
Added patches.py
178
179
180
class RemoveLine(HunkLine):
181
    def __init__(self, contents):
182
        HunkLine.__init__(self, contents)
183
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
184
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
185
        return self.get_str(b"-")
0.5.93 by Aaron Bentley
Added patches.py
186
7143.15.2 by Jelmer Vernooij
Run autopep8.
187
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
188
NO_NL = b'\\ No newline at end of file\n'
7143.15.2 by Jelmer Vernooij
Run autopep8.
189
__pychecker__ = "no-returnvalues"
190
0.5.93 by Aaron Bentley
Added patches.py
191
192
def parse_line(line):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
193
    if line.startswith(b"\n"):
0.5.93 by Aaron Bentley
Added patches.py
194
        return ContextLine(line)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
195
    elif line.startswith(b" "):
0.5.93 by Aaron Bentley
Added patches.py
196
        return ContextLine(line[1:])
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
197
    elif line.startswith(b"+"):
0.5.93 by Aaron Bentley
Added patches.py
198
        return InsertLine(line[1:])
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
199
    elif line.startswith(b"-"):
0.5.93 by Aaron Bentley
Added patches.py
200
        return RemoveLine(line[1:])
201
    else:
202
        raise MalformedLine("Unknown line type", line)
7143.15.2 by Jelmer Vernooij
Run autopep8.
203
204
205
__pychecker__ = ""
0.5.93 by Aaron Bentley
Added patches.py
206
207
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
208
class Hunk(object):
209
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
210
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
0.5.93 by Aaron Bentley
Added patches.py
211
        self.orig_pos = orig_pos
212
        self.orig_range = orig_range
213
        self.mod_pos = mod_pos
214
        self.mod_range = mod_range
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
215
        self.tail = tail
0.5.93 by Aaron Bentley
Added patches.py
216
        self.lines = []
217
218
    def get_header(self):
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
219
        if self.tail is None:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
220
            tail_str = b''
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
221
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
222
            tail_str = b' ' + self.tail
223
        return b"@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
7143.15.2 by Jelmer Vernooij
Run autopep8.
224
                                                      self.orig_range),
225
                                       self.range_str(self.mod_pos,
226
                                                      self.mod_range),
227
                                       tail_str)
0.5.93 by Aaron Bentley
Added patches.py
228
229
    def range_str(self, pos, range):
230
        """Return a file range, special-casing for 1-line files.
231
232
        :param pos: The position in the file
233
        :type pos: int
234
        :range: The range in the file
235
        :type range: int
236
        :return: a string in the format 1,4 except when range == pos == 1
237
        """
238
        if range == 1:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
239
            return b"%i" % pos
0.5.93 by Aaron Bentley
Added patches.py
240
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
241
            return b"%i,%i" % (pos, range)
0.5.93 by Aaron Bentley
Added patches.py
242
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
243
    def as_bytes(self):
0.5.93 by Aaron Bentley
Added patches.py
244
        lines = [self.get_header()]
245
        for line in self.lines:
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
246
            lines.append(line.as_bytes())
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
247
        return b"".join(lines)
248
7045.3.1 by Jelmer Vernooij
Fix another ~500 tests.
249
    __bytes__ = as_bytes
250
0.5.93 by Aaron Bentley
Added patches.py
251
    def shift_to_mod(self, pos):
7143.15.2 by Jelmer Vernooij
Run autopep8.
252
        if pos < self.orig_pos - 1:
0.5.93 by Aaron Bentley
Added patches.py
253
            return 0
7143.15.2 by Jelmer Vernooij
Run autopep8.
254
        elif pos > self.orig_pos + self.orig_range:
0.5.93 by Aaron Bentley
Added patches.py
255
            return self.mod_range - self.orig_range
256
        else:
257
            return self.shift_to_mod_lines(pos)
258
259
    def shift_to_mod_lines(self, pos):
7143.15.2 by Jelmer Vernooij
Run autopep8.
260
        position = self.orig_pos - 1
0.5.93 by Aaron Bentley
Added patches.py
261
        shift = 0
262
        for line in self.lines:
263
            if isinstance(line, InsertLine):
264
                shift += 1
265
            elif isinstance(line, RemoveLine):
266
                if position == pos:
267
                    return None
268
                shift -= 1
269
                position += 1
270
            elif isinstance(line, ContextLine):
271
                position += 1
272
            if position > pos:
273
                break
274
        return shift
275
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
276
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
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
    '''
0.5.93 by Aaron Bentley
Added patches.py
284
    hunk = None
285
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
286
        if line == b"\n":
0.5.93 by Aaron Bentley
Added patches.py
287
            if hunk is not None:
288
                yield hunk
289
                hunk = None
290
            continue
291
        if hunk is not None:
292
            yield hunk
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
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
0.5.93 by Aaron Bentley
Added patches.py
302
        orig_size = 0
303
        mod_size = 0
304
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
305
            hunk_line = parse_line(next(iter_lines))
0.5.96 by Aaron Bentley
Cleaned up handling of files with no terminating \n
306
            hunk.lines.append(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
307
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
308
                orig_size += 1
309
            if isinstance(hunk_line, (InsertLine, ContextLine)):
310
                mod_size += 1
311
    if hunk is not None:
312
        yield hunk
313
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
314
4634.80.1 by Aaron Bentley
Parse binary files.
315
class BinaryPatch(object):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
316
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
317
    def __init__(self, oldname, newname):
0.5.93 by Aaron Bentley
Added patches.py
318
        self.oldname = oldname
319
        self.newname = newname
4634.80.1 by Aaron Bentley
Parse binary files.
320
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
321
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
322
        return b'Binary files %s and %s differ\n' % (self.oldname, self.newname)
323
4634.80.1 by Aaron Bentley
Parse binary files.
324
325
class Patch(BinaryPatch):
326
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
327
    def __init__(self, oldname, newname):
328
        BinaryPatch.__init__(self, oldname, newname)
0.5.93 by Aaron Bentley
Added patches.py
329
        self.hunks = []
330
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
331
    def as_bytes(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
332
        ret = self.get_header()
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
333
        ret += b"".join([h.as_bytes() for h in self.hunks])
0.5.93 by Aaron Bentley
Added patches.py
334
        return ret
335
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
336
    def get_header(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
337
        return b"--- %s\n+++ %s\n" % (self.oldname, self.newname)
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
338
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
339
    def stats_values(self):
340
        """Calculate the number of inserts and removes."""
0.5.93 by Aaron Bentley
Added patches.py
341
        removes = 0
342
        inserts = 0
343
        for hunk in self.hunks:
344
            for line in hunk.lines:
345
                if isinstance(line, InsertLine):
7143.15.2 by Jelmer Vernooij
Run autopep8.
346
                    inserts += 1
0.5.93 by Aaron Bentley
Added patches.py
347
                elif isinstance(line, RemoveLine):
7143.15.2 by Jelmer Vernooij
Run autopep8.
348
                    removes += 1
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
349
        return (inserts, removes, len(self.hunks))
350
351
    def stats_str(self):
352
        """Return a string of patch statistics"""
0.5.93 by Aaron Bentley
Added patches.py
353
        return "%i inserts, %i removes in %i hunks" % \
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
354
            self.stats_values()
0.5.93 by Aaron Bentley
Added patches.py
355
356
    def pos_in_mod(self, position):
357
        newpos = position
358
        for hunk in self.hunks:
359
            shift = hunk.shift_to_mod(position)
360
            if shift is None:
361
                return None
362
            newpos += shift
363
        return newpos
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
364
0.5.93 by Aaron Bentley
Added patches.py
365
    def iter_inserted(self):
366
        """Iteraties through inserted lines
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
367
0.5.93 by Aaron Bentley
Added patches.py
368
        :return: Pair of line number, line
369
        :rtype: iterator of (int, InsertLine)
370
        """
371
        for hunk in self.hunks:
7143.15.2 by Jelmer Vernooij
Run autopep8.
372
            pos = hunk.mod_pos - 1
0.5.93 by Aaron Bentley
Added patches.py
373
            for line in hunk.lines:
374
                if isinstance(line, InsertLine):
375
                    yield (pos, line)
376
                    pos += 1
377
                if isinstance(line, ContextLine):
378
                    pos += 1
379
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
380
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
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
    '''
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
387
    iter_lines = iter_lines_handle_nl(iter_lines)
4634.80.1 by Aaron Bentley
Parse binary files.
388
    try:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
389
        (orig_name, mod_name) = get_patch_names(iter_lines)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
390
    except BinaryFiles as e:
4634.80.1 by Aaron Bentley
Parse binary files.
391
        return BinaryPatch(e.orig_name, e.mod_name)
392
    else:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
393
        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.
394
        for hunk in iter_hunks(iter_lines, allow_dirty):
4634.80.1 by Aaron Bentley
Parse binary files.
395
            patch.hunks.append(hunk)
396
        return patch
0.5.93 by Aaron Bentley
Added patches.py
397
398
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
399
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.
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
    '''
7143.15.2 by Jelmer Vernooij
Run autopep8.
408
    # FIXME: Docstring is not quite true.  We allow certain comments no
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
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).
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
413
    regex = re.compile(binary_files_re)
0.5.93 by Aaron Bentley
Added patches.py
414
    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
415
    dirty_head = []
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
416
    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.
417
    beginning = True
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
418
0.5.93 by Aaron Bentley
Added patches.py
419
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
420
        if line.startswith(b'=== '):
6603.2.1 by Colin Watson
Avoid associating dirty patch headers with the previous file in the patch.
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 = []
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
429
            dirty_head.append(line)
430
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
431
        if line.startswith(b'*** '):
0.5.93 by Aaron Bentley
Added patches.py
432
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
433
        if line.startswith(b'#'):
1770.1.1 by Aaron Bentley
Ignore lines that start with '#' in patch parser
434
            continue
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
435
        elif orig_range > 0:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
436
            if line.startswith(b'-') or line.startswith(b' '):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
437
                orig_range -= 1
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
438
        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.
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:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
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
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
451
            saved_lines = []
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
452
        elif line.startswith(b'@@'):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
453
            hunk = hunk_from_header(line)
454
            orig_range = hunk.orig_range
0.5.93 by Aaron Bentley
Added patches.py
455
        saved_lines.append(line)
456
    if len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
457
        if keep_dirty and len(dirty_head) > 0:
458
            yield {'saved_lines': saved_lines,
459
                   '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
460
        else:
461
            yield saved_lines
0.5.93 by Aaron Bentley
Added patches.py
462
463
3873.1.6 by Benoît Pierre
OK, so now patches should handle '\ No newline at end of file' in both
464
def iter_lines_handle_nl(iter_lines):
465
    """
466
    Iterates through lines, ensuring that lines that originally had no
467
    terminating \n are produced without one.  This transformation may be
468
    applied at any point up until hunk line parsing, and is safe to apply
469
    repeatedly.
470
    """
471
    last_line = None
472
    for line in iter_lines:
473
        if line == NO_NL:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
474
            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
475
                raise AssertionError()
476
            last_line = last_line[:-1]
477
            line = None
478
        if last_line is not None:
479
            yield last_line
480
        last_line = line
481
    if last_line is not None:
482
        yield last_line
483
484
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
485
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.
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.
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
491
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
492
        Default False.
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
493
    '''
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
494
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
495
        if 'dirty_head' in patch_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
496
            yield ({'patch': parse_patch(patch_lines['saved_lines'], allow_dirty),
497
                    'dirty_head': patch_lines['dirty_head']})
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
498
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
499
            yield parse_patch(patch_lines, allow_dirty)
0.5.93 by Aaron Bentley
Added patches.py
500
501
502
def difference_index(atext, btext):
1759.2.1 by Jelmer Vernooij
Fix some types (found using aspell).
503
    """Find the indext of the first character that differs between two texts
0.5.93 by Aaron Bentley
Added patches.py
504
505
    :param atext: The first text
506
    :type atext: str
507
    :param btext: The second text
508
    :type str: str
509
    :return: The index, or None if there are no differences within the range
510
    :rtype: int or NoneType
511
    """
512
    length = len(atext)
513
    if len(btext) < length:
514
        length = len(btext)
515
    for i in range(length):
516
        if atext[i] != btext[i]:
7143.15.2 by Jelmer Vernooij
Run autopep8.
517
            return i
0.5.93 by Aaron Bentley
Added patches.py
518
    return None
519
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
520
0.5.93 by Aaron Bentley
Added patches.py
521
def iter_patched(orig_lines, patch_lines):
522
    """Iterate through a series of lines with a patch applied.
523
    This handles a single file, and does exact, not fuzzy patching.
524
    """
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
525
    patch_lines = iter_lines_handle_nl(iter(patch_lines))
0.5.93 by Aaron Bentley
Added patches.py
526
    get_patch_names(patch_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
527
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
528
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
529
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
530
def iter_patched_from_hunks(orig_lines, hunks):
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
531
    """Iterate through a series of lines with a patch applied.
532
    This handles a single file, and does exact, not fuzzy patching.
533
534
    :param orig_lines: The unpatched lines.
535
    :param hunks: An iterable of Hunk instances.
536
    """
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
537
    seen_patch = []
0.5.93 by Aaron Bentley
Added patches.py
538
    line_no = 1
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
539
    if orig_lines is not None:
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
540
        orig_lines = iter(orig_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
541
    for hunk in hunks:
0.5.93 by Aaron Bentley
Added patches.py
542
        while line_no < hunk.orig_pos:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
543
            orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
544
            yield orig_line
545
            line_no += 1
546
        for hunk_line in hunk.lines:
547
            seen_patch.append(str(hunk_line))
548
            if isinstance(hunk_line, InsertLine):
549
                yield hunk_line.contents
550
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
551
                orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
552
                if orig_line != hunk_line.contents:
7143.15.2 by Jelmer Vernooij
Run autopep8.
553
                    raise PatchConflict(line_no, orig_line,
554
                                        b"".join(seen_patch))
0.5.93 by Aaron Bentley
Added patches.py
555
                if isinstance(hunk_line, ContextLine):
556
                    yield orig_line
557
                else:
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
558
                    if not isinstance(hunk_line, RemoveLine):
559
                        raise AssertionError(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
560
                line_no += 1
0.5.105 by John Arbash Meinel
Adding more test patches to the test suite.
561
    if orig_lines is not None:
562
        for line in orig_lines:
563
            yield line