/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
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
18
from .errors import (
6729.5.1 by Jelmer Vernooij
Move patches errors to breezy.patches.
19
    BzrError,
6289.2.1 by Jelmer Vernooij
Move the primary definition of the patches exceptions to bzrlib.errors.
20
    )
21
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
22
import os
4634.80.1 by Aaron Bentley
Parse binary files.
23
import re
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
24
25
26
binary_files_re = b'Binary files (.*) and (.*) differ\n'
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
27
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
28
6729.5.1 by Jelmer Vernooij
Move patches errors to breezy.patches.
29
class PatchSyntax(BzrError):
30
    """Base class for patch syntax errors."""
31
32
33
class BinaryFiles(BzrError):
34
35
    _fmt = 'Binary files section encountered.'
36
37
    def __init__(self, orig_name, mod_name):
38
        self.orig_name = orig_name
39
        self.mod_name = mod_name
40
41
42
class MalformedPatchHeader(PatchSyntax):
43
44
    _fmt = "Malformed patch header.  %(desc)s\n%(line)r"
45
46
    def __init__(self, desc, line):
47
        self.desc = desc
48
        self.line = line
49
50
51
class MalformedLine(PatchSyntax):
52
53
    _fmt = "Malformed line.  %(desc)s\n%(line)r"
54
55
    def __init__(self, desc, line):
56
        self.desc = desc
57
        self.line = line
58
59
60
class PatchConflict(BzrError):
61
62
    _fmt = ('Text contents mismatch at line %(line_no)d.  Original has '
63
            '"%(orig_line)s", but patch says it should be "%(patch_line)s"')
64
65
    def __init__(self, line_no, orig_line, patch_line):
66
        self.line_no = line_no
67
        self.orig_line = orig_line.rstrip('\n')
68
        self.patch_line = patch_line.rstrip('\n')
69
70
71
class MalformedHunkHeader(PatchSyntax):
72
73
    _fmt = "Malformed hunk header.  %(desc)s\n%(line)r"
74
75
    def __init__(self, desc, line):
76
        self.desc = desc
77
        self.line = line
78
79
0.5.93 by Aaron Bentley
Added patches.py
80
def get_patch_names(iter_lines):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
81
    line = next(iter_lines)
0.5.93 by Aaron Bentley
Added patches.py
82
    try:
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
83
        match = re.match(binary_files_re, line)
4634.80.1 by Aaron Bentley
Parse binary files.
84
        if match is not None:
85
            raise BinaryFiles(match.group(1), match.group(2))
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
86
        if not line.startswith(b"--- "):
0.5.93 by Aaron Bentley
Added patches.py
87
            raise MalformedPatchHeader("No orig name", line)
88
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
89
            orig_name = line[4:].rstrip(b"\n")
0.5.93 by Aaron Bentley
Added patches.py
90
    except StopIteration:
91
        raise MalformedPatchHeader("No orig line", "")
92
    try:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
93
        line = next(iter_lines)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
94
        if not line.startswith(b"+++ "):
0.5.93 by Aaron Bentley
Added patches.py
95
            raise PatchSyntax("No mod name")
96
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
97
            mod_name = line[4:].rstrip(b"\n")
0.5.93 by Aaron Bentley
Added patches.py
98
    except StopIteration:
99
        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
100
    return (orig_name, mod_name)
0.5.93 by Aaron Bentley
Added patches.py
101
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
102
0.5.93 by Aaron Bentley
Added patches.py
103
def parse_range(textrange):
104
    """Parse a patch range, handling the "1" special-case
105
106
    :param textrange: The text to parse
107
    :type textrange: str
108
    :return: the position and range, as a tuple
109
    :rtype: (int, int)
110
    """
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
111
    tmp = textrange.split(b',')
0.5.93 by Aaron Bentley
Added patches.py
112
    if len(tmp) == 1:
113
        pos = tmp[0]
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
114
        range = b"1"
0.5.93 by Aaron Bentley
Added patches.py
115
    else:
116
        (pos, range) = tmp
117
    pos = int(pos)
118
    range = int(range)
119
    return (pos, range)
120
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
121
0.5.93 by Aaron Bentley
Added patches.py
122
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.
123
    import re
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
124
    matches = re.match(br'\@\@ ([^@]*) \@\@( (.*))?\n', line)
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
125
    if matches is None:
126
        raise MalformedHunkHeader("Does not match format.", line)
0.5.93 by Aaron Bentley
Added patches.py
127
    try:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
128
        (orig, mod) = matches.group(1).split(b" ")
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
129
    except (ValueError, IndexError) as e:
0.5.93 by Aaron Bentley
Added patches.py
130
        raise MalformedHunkHeader(str(e), line)
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
131
    if not orig.startswith(b'-') or not mod.startswith(b'+'):
0.5.93 by Aaron Bentley
Added patches.py
132
        raise MalformedHunkHeader("Positions don't start with + or -.", line)
133
    try:
134
        (orig_pos, orig_range) = parse_range(orig[1:])
135
        (mod_pos, mod_range) = parse_range(mod[1:])
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
136
    except (ValueError, IndexError) as e:
0.5.93 by Aaron Bentley
Added patches.py
137
        raise MalformedHunkHeader(str(e), line)
138
    if mod_range < 0 or orig_range < 0:
139
        raise MalformedHunkHeader("Hunk range is negative", line)
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
140
    tail = matches.group(3)
141
    return Hunk(orig_pos, orig_range, mod_pos, mod_range, tail)
0.5.93 by Aaron Bentley
Added patches.py
142
143
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
144
class HunkLine(object):
145
0.5.93 by Aaron Bentley
Added patches.py
146
    def __init__(self, contents):
147
        self.contents = contents
148
149
    def get_str(self, leadchar):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
150
        if self.contents == b"\n" and leadchar == b" " and False:
151
            return b"\n"
152
        if not self.contents.endswith(b'\n'):
153
            terminator = b'\n' + NO_NL
0.5.93 by Aaron Bentley
Added patches.py
154
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
155
            terminator = b''
0.5.93 by Aaron Bentley
Added patches.py
156
        return leadchar + self.contents + terminator
157
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
158
    def as_bytes(self):
159
        raise NotImplementedError
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
160
0.5.93 by Aaron Bentley
Added patches.py
161
162
class ContextLine(HunkLine):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
163
0.5.93 by Aaron Bentley
Added patches.py
164
    def __init__(self, contents):
165
        HunkLine.__init__(self, contents)
166
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
167
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
168
        return self.get_str(b" ")
0.5.93 by Aaron Bentley
Added patches.py
169
170
171
class InsertLine(HunkLine):
172
    def __init__(self, contents):
173
        HunkLine.__init__(self, contents)
174
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
175
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
176
        return self.get_str(b"+")
0.5.93 by Aaron Bentley
Added patches.py
177
178
179
class RemoveLine(HunkLine):
180
    def __init__(self, contents):
181
        HunkLine.__init__(self, contents)
182
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
183
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
184
        return self.get_str(b"-")
0.5.93 by Aaron Bentley
Added patches.py
185
7143.15.2 by Jelmer Vernooij
Run autopep8.
186
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
187
NO_NL = b'\\ No newline at end of file\n'
7143.15.2 by Jelmer Vernooij
Run autopep8.
188
__pychecker__ = "no-returnvalues"
189
0.5.93 by Aaron Bentley
Added patches.py
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)
7143.15.2 by Jelmer Vernooij
Run autopep8.
202
203
204
__pychecker__ = ""
0.5.93 by Aaron Bentley
Added patches.py
205
206
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
207
class Hunk(object):
208
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
209
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
0.5.93 by Aaron Bentley
Added patches.py
210
        self.orig_pos = orig_pos
211
        self.orig_range = orig_range
212
        self.mod_pos = mod_pos
213
        self.mod_range = mod_range
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
214
        self.tail = tail
0.5.93 by Aaron Bentley
Added patches.py
215
        self.lines = []
216
217
    def get_header(self):
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
218
        if self.tail is None:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
219
            tail_str = b''
1551.18.6 by Aaron Bentley
Add support for diff -p-style diffs to patch parser
220
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
221
            tail_str = b' ' + self.tail
222
        return b"@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
7143.15.2 by Jelmer Vernooij
Run autopep8.
223
                                                      self.orig_range),
224
                                       self.range_str(self.mod_pos,
225
                                                      self.mod_range),
226
                                       tail_str)
0.5.93 by Aaron Bentley
Added patches.py
227
228
    def range_str(self, pos, range):
229
        """Return a file range, special-casing for 1-line files.
230
231
        :param pos: The position in the file
232
        :type pos: int
233
        :range: The range in the file
234
        :type range: int
235
        :return: a string in the format 1,4 except when range == pos == 1
236
        """
237
        if range == 1:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
238
            return b"%i" % pos
0.5.93 by Aaron Bentley
Added patches.py
239
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
240
            return b"%i,%i" % (pos, range)
0.5.93 by Aaron Bentley
Added patches.py
241
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
242
    def as_bytes(self):
0.5.93 by Aaron Bentley
Added patches.py
243
        lines = [self.get_header()]
244
        for line in self.lines:
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
245
            lines.append(line.as_bytes())
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
246
        return b"".join(lines)
247
7045.3.1 by Jelmer Vernooij
Fix another ~500 tests.
248
    __bytes__ = as_bytes
249
0.5.93 by Aaron Bentley
Added patches.py
250
    def shift_to_mod(self, pos):
7143.15.2 by Jelmer Vernooij
Run autopep8.
251
        if pos < self.orig_pos - 1:
0.5.93 by Aaron Bentley
Added patches.py
252
            return 0
7143.15.2 by Jelmer Vernooij
Run autopep8.
253
        elif pos > self.orig_pos + self.orig_range:
0.5.93 by Aaron Bentley
Added patches.py
254
            return self.mod_range - self.orig_range
255
        else:
256
            return self.shift_to_mod_lines(pos)
257
258
    def shift_to_mod_lines(self, pos):
7143.15.2 by Jelmer Vernooij
Run autopep8.
259
        position = self.orig_pos - 1
0.5.93 by Aaron Bentley
Added patches.py
260
        shift = 0
261
        for line in self.lines:
262
            if isinstance(line, InsertLine):
263
                shift += 1
264
            elif isinstance(line, RemoveLine):
265
                if position == pos:
266
                    return None
267
                shift -= 1
268
                position += 1
269
            elif isinstance(line, ContextLine):
270
                position += 1
271
            if position > pos:
272
                break
273
        return shift
274
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
275
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
276
def iter_hunks(iter_lines, allow_dirty=False):
277
    '''
278
    :arg iter_lines: iterable of lines to parse for hunks
279
    :kwarg allow_dirty: If True, when we encounter something that is not
280
        a hunk header when we're looking for one, assume the rest of the lines
281
        are not part of the patch (comments or other junk).  Default False
282
    '''
0.5.93 by Aaron Bentley
Added patches.py
283
    hunk = None
284
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
285
        if line == b"\n":
0.5.93 by Aaron Bentley
Added patches.py
286
            if hunk is not None:
287
                yield hunk
288
                hunk = None
289
            continue
290
        if hunk is not None:
291
            yield hunk
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
292
        try:
293
            hunk = hunk_from_header(line)
294
        except MalformedHunkHeader:
295
            if allow_dirty:
296
                # If the line isn't a hunk header, then we've reached the end
297
                # of this patch and there's "junk" at the end.  Ignore the
298
                # rest of this patch.
299
                return
300
            raise
0.5.93 by Aaron Bentley
Added patches.py
301
        orig_size = 0
302
        mod_size = 0
303
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
304
            hunk_line = parse_line(next(iter_lines))
0.5.96 by Aaron Bentley
Cleaned up handling of files with no terminating \n
305
            hunk.lines.append(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
306
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
307
                orig_size += 1
308
            if isinstance(hunk_line, (InsertLine, ContextLine)):
309
                mod_size += 1
310
    if hunk is not None:
311
        yield hunk
312
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
313
4634.80.1 by Aaron Bentley
Parse binary files.
314
class BinaryPatch(object):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
315
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
316
    def __init__(self, oldname, newname):
0.5.93 by Aaron Bentley
Added patches.py
317
        self.oldname = oldname
318
        self.newname = newname
4634.80.1 by Aaron Bentley
Parse binary files.
319
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
320
    def as_bytes(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
321
        return b'Binary files %s and %s differ\n' % (self.oldname, self.newname)
322
4634.80.1 by Aaron Bentley
Parse binary files.
323
324
class Patch(BinaryPatch):
325
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
326
    def __init__(self, oldname, newname):
327
        BinaryPatch.__init__(self, oldname, newname)
0.5.93 by Aaron Bentley
Added patches.py
328
        self.hunks = []
329
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
330
    def as_bytes(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
331
        ret = self.get_header()
7029.1.3 by Jelmer Vernooij
Use separate .as_bytes method rather than __bytes__.
332
        ret += b"".join([h.as_bytes() for h in self.hunks])
0.5.93 by Aaron Bentley
Added patches.py
333
        return ret
334
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
335
    def get_header(self):
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
336
        return b"--- %s\n+++ %s\n" % (self.oldname, self.newname)
0.5.95 by Aaron Bentley
Updated patch to match bzrtools
337
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
338
    def stats_values(self):
339
        """Calculate the number of inserts and removes."""
0.5.93 by Aaron Bentley
Added patches.py
340
        removes = 0
341
        inserts = 0
342
        for hunk in self.hunks:
343
            for line in hunk.lines:
344
                if isinstance(line, InsertLine):
7143.15.2 by Jelmer Vernooij
Run autopep8.
345
                    inserts += 1
0.5.93 by Aaron Bentley
Added patches.py
346
                elif isinstance(line, RemoveLine):
7143.15.2 by Jelmer Vernooij
Run autopep8.
347
                    removes += 1
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
348
        return (inserts, removes, len(self.hunks))
349
350
    def stats_str(self):
351
        """Return a string of patch statistics"""
0.5.93 by Aaron Bentley
Added patches.py
352
        return "%i inserts, %i removes in %i hunks" % \
3946.4.1 by Tim Penhey
Extract out the counting of the stats values.
353
            self.stats_values()
0.5.93 by Aaron Bentley
Added patches.py
354
355
    def pos_in_mod(self, position):
356
        newpos = position
357
        for hunk in self.hunks:
358
            shift = hunk.shift_to_mod(position)
359
            if shift is None:
360
                return None
361
            newpos += shift
362
        return newpos
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
363
0.5.93 by Aaron Bentley
Added patches.py
364
    def iter_inserted(self):
365
        """Iteraties through inserted lines
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
366
0.5.93 by Aaron Bentley
Added patches.py
367
        :return: Pair of line number, line
368
        :rtype: iterator of (int, InsertLine)
369
        """
370
        for hunk in self.hunks:
7143.15.2 by Jelmer Vernooij
Run autopep8.
371
            pos = hunk.mod_pos - 1
0.5.93 by Aaron Bentley
Added patches.py
372
            for line in hunk.lines:
373
                if isinstance(line, InsertLine):
374
                    yield (pos, line)
375
                    pos += 1
376
                if isinstance(line, ContextLine):
377
                    pos += 1
378
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
379
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
380
def parse_patch(iter_lines, allow_dirty=False):
381
    '''
382
    :arg iter_lines: iterable of lines to parse
383
    :kwarg allow_dirty: If True, allow the patch to have trailing junk.
384
        Default False
385
    '''
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
386
    iter_lines = iter_lines_handle_nl(iter_lines)
4634.80.1 by Aaron Bentley
Parse binary files.
387
    try:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
388
        (orig_name, mod_name) = get_patch_names(iter_lines)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
389
    except BinaryFiles as e:
4634.80.1 by Aaron Bentley
Parse binary files.
390
        return BinaryPatch(e.orig_name, e.mod_name)
391
    else:
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
392
        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.
393
        for hunk in iter_hunks(iter_lines, allow_dirty):
4634.80.1 by Aaron Bentley
Parse binary files.
394
            patch.hunks.append(hunk)
395
        return patch
0.5.93 by Aaron Bentley
Added patches.py
396
397
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
398
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.
399
    '''
400
    :arg iter_lines: iterable of lines to parse for patches
401
    :kwarg allow_dirty: If True, allow comments and other non-patch text
402
        before the first patch.  Note that the algorithm here can only find
403
        such text before any patches have been found.  Comments after the
404
        first patch are stripped away in iter_hunks() if it is also passed
405
        allow_dirty=True.  Default False.
406
    '''
7143.15.2 by Jelmer Vernooij
Run autopep8.
407
    # 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.
408
    # matter what, If they startwith '===', '***', or '#' Someone should
409
    # reexamine this logic and decide if we should include those in
410
    # allow_dirty or restrict those to only being before the patch is found
411
    # (as allow_dirty does).
4634.98.1 by Aaron Bentley
Improve patch binary section handling.
412
    regex = re.compile(binary_files_re)
0.5.93 by Aaron Bentley
Added patches.py
413
    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
414
    dirty_head = []
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
415
    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.
416
    beginning = True
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
417
0.5.93 by Aaron Bentley
Added patches.py
418
    for line in iter_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
419
        if line.startswith(b'=== '):
6603.2.1 by Colin Watson
Avoid associating dirty patch headers with the previous file in the patch.
420
            if len(saved_lines) > 0:
421
                if keep_dirty and len(dirty_head) > 0:
422
                    yield {'saved_lines': saved_lines,
423
                           'dirty_head': dirty_head}
424
                    dirty_head = []
425
                else:
426
                    yield saved_lines
427
                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
428
            dirty_head.append(line)
429
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
430
        if line.startswith(b'*** '):
0.5.93 by Aaron Bentley
Added patches.py
431
            continue
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
432
        if line.startswith(b'#'):
1770.1.1 by Aaron Bentley
Ignore lines that start with '#' in patch parser
433
            continue
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
434
        elif orig_range > 0:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
435
            if line.startswith(b'-') or line.startswith(b' '):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
436
                orig_range -= 1
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
437
        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.
438
            if allow_dirty and beginning:
439
                # Patches can have "junk" at the beginning
440
                # Stripping junk from the end of patches is handled when we
441
                # parse the patch
442
                beginning = False
443
            elif len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
444
                if keep_dirty and len(dirty_head) > 0:
445
                    yield {'saved_lines': saved_lines,
446
                           'dirty_head': dirty_head}
447
                    dirty_head = []
448
                else:
449
                    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
450
            saved_lines = []
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
451
        elif line.startswith(b'@@'):
2298.6.1 by Johan Dahlberg
Fix bzrtools shelve command for removed lines beginning with "--"
452
            hunk = hunk_from_header(line)
453
            orig_range = hunk.orig_range
0.5.93 by Aaron Bentley
Added patches.py
454
        saved_lines.append(line)
455
    if len(saved_lines) > 0:
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
456
        if keep_dirty and len(dirty_head) > 0:
457
            yield {'saved_lines': saved_lines,
458
                   '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
459
        else:
460
            yield saved_lines
0.5.93 by Aaron Bentley
Added patches.py
461
462
3873.1.6 by Benoît Pierre
OK, so now patches should handle '\ No newline at end of file' in both
463
def iter_lines_handle_nl(iter_lines):
464
    """
465
    Iterates through lines, ensuring that lines that originally had no
466
    terminating \n are produced without one.  This transformation may be
467
    applied at any point up until hunk line parsing, and is safe to apply
468
    repeatedly.
469
    """
470
    last_line = None
471
    for line in iter_lines:
472
        if line == NO_NL:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
473
            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
474
                raise AssertionError()
475
            last_line = last_line[:-1]
476
            line = None
477
        if last_line is not None:
478
            yield last_line
479
        last_line = line
480
    if last_line is not None:
481
        yield last_line
482
483
6601.1.6 by Kit Randel
change of plan, don't track modified state, just preserve dirty_heads if requested in parse_patches
484
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.
485
    '''
486
    :arg iter_lines: iterable of lines to parse for patches
487
    :kwarg allow_dirty: If True, allow text that's not part of the patch at
488
        selected places.  This includes comments before and after a patch
489
        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
490
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
491
        Default False.
5016.3.1 by Toshio Kuratomi
iAdd an allow_dirty parameter that allows patch files with non-patch data to be used.
492
    '''
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
493
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
494
        if 'dirty_head' in patch_lines:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
495
            yield ({'patch': parse_patch(patch_lines['saved_lines'], allow_dirty),
496
                    'dirty_head': patch_lines['dirty_head']})
6601.1.7 by Kit Randel
fixed dirty_head logic in iter_file_patch
497
        else:
7029.1.1 by Jelmer Vernooij
Port breezy.patches to Python3.
498
            yield parse_patch(patch_lines, allow_dirty)
0.5.93 by Aaron Bentley
Added patches.py
499
500
501
def difference_index(atext, btext):
1759.2.1 by Jelmer Vernooij
Fix some types (found using aspell).
502
    """Find the indext of the first character that differs between two texts
0.5.93 by Aaron Bentley
Added patches.py
503
504
    :param atext: The first text
505
    :type atext: str
506
    :param btext: The second text
507
    :type str: str
508
    :return: The index, or None if there are no differences within the range
509
    :rtype: int or NoneType
510
    """
511
    length = len(atext)
512
    if len(btext) < length:
513
        length = len(btext)
514
    for i in range(length):
515
        if atext[i] != btext[i]:
7143.15.2 by Jelmer Vernooij
Run autopep8.
516
            return i
0.5.93 by Aaron Bentley
Added patches.py
517
    return None
518
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
519
0.5.93 by Aaron Bentley
Added patches.py
520
def iter_patched(orig_lines, patch_lines):
521
    """Iterate through a series of lines with a patch applied.
522
    This handles a single file, and does exact, not fuzzy patching.
523
    """
3873.1.8 by Benoît Pierre
Fix regressions in other parts of the testsuite.
524
    patch_lines = iter_lines_handle_nl(iter(patch_lines))
0.5.93 by Aaron Bentley
Added patches.py
525
    get_patch_names(patch_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
526
    return iter_patched_from_hunks(orig_lines, iter_hunks(patch_lines))
527
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
528
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
529
def iter_patched_from_hunks(orig_lines, hunks):
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
530
    """Iterate through a series of lines with a patch applied.
531
    This handles a single file, and does exact, not fuzzy patching.
532
533
    :param orig_lines: The unpatched lines.
534
    :param hunks: An iterable of Hunk instances.
535
    """
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
536
    seen_patch = []
0.5.93 by Aaron Bentley
Added patches.py
537
    line_no = 1
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
538
    if orig_lines is not None:
3363.18.4 by Aaron Bentley
Updates from review (and a doc update)
539
        orig_lines = iter(orig_lines)
3363.18.1 by Aaron Bentley
Allow patching directly from parsed hunks
540
    for hunk in hunks:
0.5.93 by Aaron Bentley
Added patches.py
541
        while line_no < hunk.orig_pos:
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
542
            orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
543
            yield orig_line
544
            line_no += 1
545
        for hunk_line in hunk.lines:
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
546
            seen_patch.append(hunk_line.contents)
0.5.93 by Aaron Bentley
Added patches.py
547
            if isinstance(hunk_line, InsertLine):
548
                yield hunk_line.contents
549
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
550
                orig_line = next(orig_lines)
0.5.93 by Aaron Bentley
Added patches.py
551
                if orig_line != hunk_line.contents:
7143.15.2 by Jelmer Vernooij
Run autopep8.
552
                    raise PatchConflict(line_no, orig_line,
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
553
                                        b''.join(seen_patch))
0.5.93 by Aaron Bentley
Added patches.py
554
                if isinstance(hunk_line, ContextLine):
555
                    yield orig_line
556
                else:
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
557
                    if not isinstance(hunk_line, RemoveLine):
558
                        raise AssertionError(hunk_line)
0.5.93 by Aaron Bentley
Added patches.py
559
                line_no += 1
0.5.105 by John Arbash Meinel
Adding more test patches to the test suite.
560
    if orig_lines is not None:
561
        for line in orig_lines:
562
            yield line
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
563
564
7413.2.5 by Jelmer Vernooij
Add prefix argument.
565
def apply_patches(tt, patches, prefix=1):
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
566
    """Apply patches to a TreeTransform.
567
568
    :param tt: TreeTransform instance
569
    :param patches: List of patches
7413.2.5 by Jelmer Vernooij
Add prefix argument.
570
    :param prefix: Number leading path segments to strip
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
571
    """
7413.2.5 by Jelmer Vernooij
Add prefix argument.
572
    def strip_prefix(p):
573
        return '/'.join(p.split('/')[1:])
574
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
575
    from breezy.bzr.generate_ids import gen_file_id
576
    # TODO(jelmer): Extract and set mode
577
    for patch in patches:
7413.2.5 by Jelmer Vernooij
Add prefix argument.
578
        if patch.oldname == b'/dev/null':
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
579
            trans_id = None
580
            orig_contents = b''
581
        else:
7413.2.5 by Jelmer Vernooij
Add prefix argument.
582
            oldname = strip_prefix(patch.oldname.decode())
583
            trans_id = tt.trans_id_tree_path(oldname)
584
            orig_contents = tt._tree.get_file_text(oldname)
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
585
            tt.delete_contents(trans_id)
586
7413.2.6 by Jelmer Vernooij
Python3 compatibility.
587
        if patch.newname != b'/dev/null':
7413.2.5 by Jelmer Vernooij
Add prefix argument.
588
            newname = strip_prefix(patch.newname.decode())
7413.2.6 by Jelmer Vernooij
Python3 compatibility.
589
            new_contents = iter_patched_from_hunks(
590
                orig_contents.splitlines(True), patch.hunks)
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
591
            if trans_id is None:
7413.2.5 by Jelmer Vernooij
Add prefix argument.
592
                parts = os.path.split(newname)
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
593
                trans_id = tt.root
594
                for part in parts[1:-1]:
595
                    trans_id = tt.new_directory(part, trans_id)
596
                tt.new_file(
597
                    parts[-1], trans_id, new_contents,
7413.2.5 by Jelmer Vernooij
Add prefix argument.
598
                    file_id=gen_file_id(newname))
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
599
            else:
600
                tt.create_file(new_contents, trans_id)
601
602
603
class AppliedPatches(object):
604
    """Context that provides access to a tree with patches applied.
605
    """
606
7413.2.5 by Jelmer Vernooij
Add prefix argument.
607
    def __init__(self, tree, patches, prefix=1):
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
608
        self.tree = tree
609
        self.patches = patches
7413.2.5 by Jelmer Vernooij
Add prefix argument.
610
        self.prefix = prefix
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
611
612
    def __enter__(self):
613
        from .transform import TransformPreview
614
        self._tt = TransformPreview(self.tree)
7413.2.5 by Jelmer Vernooij
Add prefix argument.
615
        apply_patches(self._tt, self.patches, prefix=self.prefix)
7413.2.4 by Jelmer Vernooij
Add AppliedPatches context manager.
616
        return self._tt.get_preview_tree()
617
618
    def __exit__(self, exc_type, exc_value, exc_tb):
619
        self._tt.finalize()
620
        return False