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