/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
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
187
NO_NL = b'\\ No newline at end of file\n'
0.5.93 by Aaron Bentley
Added patches.py
188
__pychecker__="no-returnvalues"
189
190
def parse_line(line):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
191
    if line.startswith(b"\n"):
0.5.93 by Aaron Bentley
Added patches.py
192
        return ContextLine(line)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
193
    elif line.startswith(b" "):
0.5.93 by Aaron Bentley
Added patches.py
194
        return ContextLine(line[1:])
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 InsertLine(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 RemoveLine(line[1:])
199
    else:
200
        raise MalformedLine("Unknown line type", line)
201
__pychecker__=""
202
203
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
204
class Hunk(object):
205
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
206
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
0.5.93 by Aaron Bentley
Added patches.py
207
        self.orig_pos = orig_pos
208
        self.orig_range = orig_range
209
        self.mod_pos = mod_pos
210
        self.mod_range = mod_range
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
211
        self.tail = tail
0.5.93 by Aaron Bentley
Added patches.py
212
        self.lines = []
213
214
    def get_header(self):
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
215
        if self.tail is None:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
216
            tail_str = b''
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
217
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
218
            tail_str = b' ' + self.tail
219
        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
220
                                                     self.orig_range),
221
                                      self.range_str(self.mod_pos,
222
                                                     self.mod_range),
223
                                      tail_str)
0.5.93 by Aaron Bentley
Added patches.py
224
225
    def range_str(self, pos, range):
226
        """Return a file range, special-casing for 1-line files.
227
228
        :param pos: The position in the file
229
        :type pos: int
230
        :range: The range in the file
231
        :type range: int
232
        :return: a string in the format 1,4 except when range == pos == 1
233
        """
234
        if range == 1:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
235
            return b"%i" % pos
0.5.93 by Aaron Bentley
Added patches.py
236
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
237
            return b"%i,%i" % (pos, range)
0.5.93 by Aaron Bentley
Added patches.py
238
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
239
    def as_bytes(self):
0.5.93 by Aaron Bentley
Added patches.py
240
        lines = [self.get_header()]
241
        for line in self.lines:
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
242
            lines.append(line.as_bytes())
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
243
        return b"".join(lines)
244
7045.3.1 by Jelmer Vernooij
Fix another ~500 tests.
245
    __bytes__ = as_bytes
246
0.5.93 by Aaron Bentley
Added patches.py
247
    def shift_to_mod(self, pos):
248
        if pos < self.orig_pos-1:
249
            return 0
250
        elif pos > self.orig_pos+self.orig_range:
251
            return self.mod_range - self.orig_range
252
        else:
253
            return self.shift_to_mod_lines(pos)
254
255
    def shift_to_mod_lines(self, pos):
256
        position = self.orig_pos-1
257
        shift = 0
258
        for line in self.lines:
259
            if isinstance(line, InsertLine):
260
                shift += 1
261
            elif isinstance(line, RemoveLine):
262
                if position == pos:
263
                    return None
264
                shift -= 1
265
                position += 1
266
            elif isinstance(line, ContextLine):
267
                position += 1
268
            if position > pos:
269
                break
270
        return shift
271
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
272
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
273
def iter_hunks(iter_lines, allow_dirty=False):
274
    '''
275
    :arg iter_lines: iterable of lines to parse for hunks
276
    :kwarg allow_dirty: If True, when we encounter something that is not
277
        a hunk header when we're looking for one, assume the rest of the lines
278
        are not part of the patch (comments or other junk).  Default False
279
    '''
0.5.93 by Aaron Bentley
Added patches.py
280
    hunk = None
281
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
282
        if line == b"\n":
0.5.93 by Aaron Bentley
Added patches.py
283
            if hunk is not None:
284
                yield hunk
285
                hunk = None
286
            continue
287
        if hunk is not None:
288
            yield hunk
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
289
        try:
290
            hunk = hunk_from_header(line)
291
        except MalformedHunkHeader:
292
            if allow_dirty:
293
                # If the line isn't a hunk header, then we've reached the end
294
                # of this patch and there's "junk" at the end.  Ignore the
295
                # rest of this patch.
296
                return
297
            raise
0.5.93 by Aaron Bentley
Added patches.py
298
        orig_size = 0
299
        mod_size = 0
300
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
301
            hunk_line = parse_line(next(iter_lines))
0.5.96 by Aaron Bentley
Cleaned up handling of files with no terminating \n
302
            hunk.lines.append(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
303
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
304
                orig_size += 1
305
            if isinstance(hunk_line, (InsertLine, ContextLine)):
306
                mod_size += 1
307
    if hunk is not None:
308
        yield hunk
309
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
310
4634.80.1 by Aaron Bentley
Parse binary files.
311
class BinaryPatch(object):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
312
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
313
    def __init__(self, oldname, newname):
0.5.93 by Aaron Bentley
Added patches.py
314
        self.oldname = oldname
315
        self.newname = newname
4634.80.1 by Aaron Bentley
Parse binary files.
316
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
317
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
318
        return b'Binary files %s and %s differ\n' % (self.oldname, self.newname)
319
4634.80.1 by Aaron Bentley
Parse binary files.
320
321
class Patch(BinaryPatch):
322
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
323
    def __init__(self, oldname, newname):
324
        BinaryPatch.__init__(self, oldname, newname)
0.5.93 by Aaron Bentley
Added patches.py
325
        self.hunks = []
326
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
327
    def as_bytes(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
328
        ret = self.get_header()
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
329
        ret += b"".join([h.as_bytes() for h in self.hunks])
0.5.93 by Aaron Bentley
Added patches.py
330
        return ret
331
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
332
    def get_header(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
333
        return b"--- %s\n+++ %s\n" % (self.oldname, self.newname)
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
334
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
335
    def stats_values(self):
336
        """Calculate the number of inserts and removes."""
0.5.93 by Aaron Bentley
Added patches.py
337
        removes = 0
338
        inserts = 0
339
        for hunk in self.hunks:
340
            for line in hunk.lines:
341
                if isinstance(line, InsertLine):
342
                     inserts+=1;
343
                elif isinstance(line, RemoveLine):
344
                     removes+=1;
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
345
        return (inserts, removes, len(self.hunks))
346
347
    def stats_str(self):
348
        """Return a string of patch statistics"""
0.5.93 by Aaron Bentley
Added patches.py
349
        return "%i inserts, %i removes in %i hunks" % \
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
350
            self.stats_values()
0.5.93 by Aaron Bentley
Added patches.py
351
352
    def pos_in_mod(self, position):
353
        newpos = position
354
        for hunk in self.hunks:
355
            shift = hunk.shift_to_mod(position)
356
            if shift is None:
357
                return None
358
            newpos += shift
359
        return newpos
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
360
0.5.93 by Aaron Bentley
Added patches.py
361
    def iter_inserted(self):
362
        """Iteraties through inserted lines
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
363
0.5.93 by Aaron Bentley
Added patches.py
364
        :return: Pair of line number, line
365
        :rtype: iterator of (int, InsertLine)
366
        """
367
        for hunk in self.hunks:
368
            pos = hunk.mod_pos - 1;
369
            for line in hunk.lines:
370
                if isinstance(line, InsertLine):
371
                    yield (pos, line)
372
                    pos += 1
373
                if isinstance(line, ContextLine):
374
                    pos += 1
375
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
376
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
377
def parse_patch(iter_lines, allow_dirty=False):
378
    '''
379
    :arg iter_lines: iterable of lines to parse
380
    :kwarg allow_dirty: If True, allow the patch to have trailing junk.
381
        Default False
382
    '''
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
383
    iter_lines = iter_lines_handle_nl(iter_lines)
4634.80.1 by Aaron Bentley
Parse binary files.
384
    try:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
385
        (orig_name, mod_name) = get_patch_names(iter_lines)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
386
    except BinaryFiles as e:
4634.80.1 by Aaron Bentley
Parse binary files.
387
        return BinaryPatch(e.orig_name, e.mod_name)
388
    else:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
389
        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.
390
        for hunk in iter_hunks(iter_lines, allow_dirty):
4634.80.1 by Aaron Bentley
Parse binary files.
391
            patch.hunks.append(hunk)
392
        return patch
0.5.93 by Aaron Bentley
Added patches.py
393
394
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
395
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.
396
    '''
397
    :arg iter_lines: iterable of lines to parse for patches
398
    :kwarg allow_dirty: If True, allow comments and other non-patch text
399
        before the first patch.  Note that the algorithm here can only find
400
        such text before any patches have been found.  Comments after the
401
        first patch are stripped away in iter_hunks() if it is also passed
402
        allow_dirty=True.  Default False.
403
    '''
404
    ### FIXME: Docstring is not quite true.  We allow certain comments no
405
    # matter what, If they startwith '===', '***', or '#' Someone should
406
    # reexamine this logic and decide if we should include those in
407
    # allow_dirty or restrict those to only being before the patch is found
408
    # (as allow_dirty does).
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
409
    regex = re.compile(binary_files_re)
0.5.93 by Aaron Bentley
Added patches.py
410
    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
411
    dirty_head = []
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
412
    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.
413
    beginning = True
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
414
0.5.93 by Aaron Bentley
Added patches.py
415
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
416
        if line.startswith(b'=== '):
6603.2.1 by Colin Watson
Avoid associating dirty patch headers with the previous file in the patch.
417
            if len(saved_lines) > 0:
418
                if keep_dirty and len(dirty_head) > 0:
419
                    yield {'saved_lines': saved_lines,
420
                           'dirty_head': dirty_head}
421
                    dirty_head = []
422
                else:
423
                    yield saved_lines
424
                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
425
            dirty_head.append(line)
426
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
427
        if line.startswith(b'*** '):
0.5.93 by Aaron Bentley
Added patches.py
428
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
429
        if line.startswith(b'#'):
1770.1.1 by Aaron Bentley
Ignore lines that start with '#' in patch parser
430
            continue
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
431
        elif orig_range > 0:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
432
            if line.startswith(b'-') or line.startswith(b' '):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
433
                orig_range -= 1
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
434
        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.
435
            if allow_dirty and beginning:
436
                # Patches can have "junk" at the beginning
437
                # Stripping junk from the end of patches is handled when we
438
                # parse the patch
439
                beginning = False
440
            elif len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
441
                if keep_dirty and len(dirty_head) > 0:
442
                    yield {'saved_lines': saved_lines,
443
                           'dirty_head': dirty_head}
444
                    dirty_head = []
445
                else:
446
                    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
447
            saved_lines = []
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
448
        elif line.startswith(b'@@'):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
449
            hunk = hunk_from_header(line)
450
            orig_range = hunk.orig_range
0.5.93 by Aaron Bentley
Added patches.py
451
        saved_lines.append(line)
452
    if len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
453
        if keep_dirty and len(dirty_head) > 0:
454
            yield {'saved_lines': saved_lines,
455
                   '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
456
        else:
457
            yield saved_lines
0.5.93 by Aaron Bentley
Added patches.py
458
459
3873.1.6 by Benoît Pierre
OK, so now patches should handle '\ No newline at end of file' in both
460
def iter_lines_handle_nl(iter_lines):
461
    """
462
    Iterates through lines, ensuring that lines that originally had no
463
    terminating \n are produced without one.  This transformation may be
464
    applied at any point up until hunk line parsing, and is safe to apply
465
    repeatedly.
466
    """
467
    last_line = None
468
    for line in iter_lines:
469
        if line == NO_NL:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
470
            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
471
                raise AssertionError()
472
            last_line = last_line[:-1]
473
            line = None
474
        if last_line is not None:
475
            yield last_line
476
        last_line = line
477
    if last_line is not None:
478
        yield last_line
479
480
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
481
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.
482
    '''
483
    :arg iter_lines: iterable of lines to parse for patches
484
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
485
        selected places.  This includes comments before and after a patch
486
        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
487
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
488
        Default False.
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
489
    '''
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
490
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
491
        if 'dirty_head' in patch_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
492
            yield ({'patch': parse_patch(patch_lines['saved_lines'], allow_dirty),
493
                    'dirty_head': patch_lines['dirty_head']})
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
494
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
495
            yield parse_patch(patch_lines, allow_dirty)
0.5.93 by Aaron Bentley
Added patches.py
496
497
498
def difference_index(atext, btext):
1759.2.1 by Jelmer Vernooij
Fix some types (found using aspell).
499
    """Find the indext of the first character that differs between two texts
0.5.93 by Aaron Bentley
Added patches.py
500
501
    :param atext: The first text
502
    :type atext: str
503
    :param btext: The second text
504
    :type str: str
505
    :return: The index, or None if there are no differences within the range
506
    :rtype: int or NoneType
507
    """
508
    length = len(atext)
509
    if len(btext) < length:
510
        length = len(btext)
511
    for i in range(length):
512
        if atext[i] != btext[i]:
513
            return i;
514
    return None
515
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
516
0.5.93 by Aaron Bentley
Added patches.py
517
def iter_patched(orig_lines, patch_lines):
518
    """Iterate through a series of lines with a patch applied.
519
    This handles a single file, and does exact, not fuzzy patching.
520
    """
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
521
    patch_lines = iter_lines_handle_nl(iter(patch_lines))
0.5.93 by Aaron Bentley
Added patches.py
522
    get_patch_names(patch_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
523
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
524
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
525
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
526
def iter_patched_from_hunks(orig_lines, hunks):
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
527
    """Iterate through a series of lines with a patch applied.
528
    This handles a single file, and does exact, not fuzzy patching.
529
530
    :param orig_lines: The unpatched lines.
531
    :param hunks: An iterable of Hunk instances.
532
    """
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
533
    seen_patch = []
0.5.93 by Aaron Bentley
Added patches.py
534
    line_no = 1
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
535
    if orig_lines is not None:
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
536
        orig_lines = iter(orig_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
537
    for hunk in hunks:
0.5.93 by Aaron Bentley
Added patches.py
538
        while line_no < hunk.orig_pos:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
539
            orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
540
            yield orig_line
541
            line_no += 1
542
        for hunk_line in hunk.lines:
543
            seen_patch.append(str(hunk_line))
544
            if isinstance(hunk_line, InsertLine):
545
                yield hunk_line.contents
546
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
547
                orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
548
                if orig_line != hunk_line.contents:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
549
                    raise PatchConflict(line_no, orig_line, b"".join(seen_patch))
0.5.93 by Aaron Bentley
Added patches.py
550
                if isinstance(hunk_line, ContextLine):
551
                    yield orig_line
552
                else:
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
553
                    if not isinstance(hunk_line, RemoveLine):
554
                        raise AssertionError(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
555
                line_no += 1
0.5.105 by John Arbash Meinel
Adding more test patches to the test suite.
556
    if orig_lines is not None:
557
        for line in orig_lines:
558
            yield line