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