/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/patches.py

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Aaron Bentley, Canonical Ltd
 
1
# Copyright (C) 2004 - 2006, 2008 Aaron Bentley, Canonical Ltd
2
2
# <aaron.bentley@utoronto.ca>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
 
from .errors import (
19
 
    BzrError,
20
 
    )
21
 
 
22
 
import os
23
 
import re
24
 
 
25
 
 
26
 
binary_files_re = b'Binary files (.*) and (.*) differ\n'
27
 
 
28
 
 
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
 
18
 
 
19
class PatchSyntax(Exception):
 
20
    def __init__(self, msg):
 
21
        Exception.__init__(self, msg)
40
22
 
41
23
 
42
24
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
 
25
    def __init__(self, desc, line):
 
26
        self.desc = desc
 
27
        self.line = line
 
28
        msg = "Malformed patch header.  %s\n%r" % (self.desc, self.line)
 
29
        PatchSyntax.__init__(self, msg)
 
30
 
 
31
 
 
32
class MalformedHunkHeader(PatchSyntax):
 
33
    def __init__(self, desc, line):
 
34
        self.desc = desc
 
35
        self.line = line
 
36
        msg = "Malformed hunk header.  %s\n%r" % (self.desc, self.line)
 
37
        PatchSyntax.__init__(self, msg)
49
38
 
50
39
 
51
40
class MalformedLine(PatchSyntax):
52
 
 
53
 
    _fmt = "Malformed line.  %(desc)s\n%(line)r"
54
 
 
55
41
    def __init__(self, desc, line):
56
42
        self.desc = desc
57
43
        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
 
 
 
44
        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
 
45
        PatchSyntax.__init__(self, msg)
 
46
 
 
47
 
 
48
class PatchConflict(Exception):
65
49
    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
 
50
        orig = orig_line.rstrip('\n')
 
51
        patch = str(patch_line).rstrip('\n')
 
52
        msg = 'Text contents mismatch at line %d.  Original has "%s",'\
 
53
            ' but patch says it should be "%s"' % (line_no, orig, patch)
 
54
        Exception.__init__(self, msg)
78
55
 
79
56
 
80
57
def get_patch_names(iter_lines):
81
 
    line = next(iter_lines)
82
58
    try:
83
 
        match = re.match(binary_files_re, line)
84
 
        if match is not None:
85
 
            raise BinaryFiles(match.group(1), match.group(2))
86
 
        if not line.startswith(b"--- "):
 
59
        line = iter_lines.next()
 
60
        if not line.startswith("--- "):
87
61
            raise MalformedPatchHeader("No orig name", line)
88
62
        else:
89
 
            orig_name = line[4:].rstrip(b"\n")
 
63
            orig_name = line[4:].rstrip("\n")
90
64
    except StopIteration:
91
65
        raise MalformedPatchHeader("No orig line", "")
92
66
    try:
93
 
        line = next(iter_lines)
94
 
        if not line.startswith(b"+++ "):
 
67
        line = iter_lines.next()
 
68
        if not line.startswith("+++ "):
95
69
            raise PatchSyntax("No mod name")
96
70
        else:
97
 
            mod_name = line[4:].rstrip(b"\n")
 
71
            mod_name = line[4:].rstrip("\n")
98
72
    except StopIteration:
99
73
        raise MalformedPatchHeader("No mod line", "")
100
74
    return (orig_name, mod_name)
108
82
    :return: the position and range, as a tuple
109
83
    :rtype: (int, int)
110
84
    """
111
 
    tmp = textrange.split(b',')
 
85
    tmp = textrange.split(',')
112
86
    if len(tmp) == 1:
113
87
        pos = tmp[0]
114
 
        range = b"1"
 
88
        range = "1"
115
89
    else:
116
90
        (pos, range) = tmp
117
91
    pos = int(pos)
121
95
 
122
96
def hunk_from_header(line):
123
97
    import re
124
 
    matches = re.match(br'\@\@ ([^@]*) \@\@( (.*))?\n', line)
 
98
    matches = re.match(r'\@\@ ([^@]*) \@\@( (.*))?\n', line)
125
99
    if matches is None:
126
100
        raise MalformedHunkHeader("Does not match format.", line)
127
101
    try:
128
 
        (orig, mod) = matches.group(1).split(b" ")
129
 
    except (ValueError, IndexError) as e:
 
102
        (orig, mod) = matches.group(1).split(" ")
 
103
    except (ValueError, IndexError), e:
130
104
        raise MalformedHunkHeader(str(e), line)
131
 
    if not orig.startswith(b'-') or not mod.startswith(b'+'):
 
105
    if not orig.startswith('-') or not mod.startswith('+'):
132
106
        raise MalformedHunkHeader("Positions don't start with + or -.", line)
133
107
    try:
134
108
        (orig_pos, orig_range) = parse_range(orig[1:])
135
109
        (mod_pos, mod_range) = parse_range(mod[1:])
136
 
    except (ValueError, IndexError) as e:
 
110
    except (ValueError, IndexError), e:
137
111
        raise MalformedHunkHeader(str(e), line)
138
112
    if mod_range < 0 or orig_range < 0:
139
113
        raise MalformedHunkHeader("Hunk range is negative", line)
141
115
    return Hunk(orig_pos, orig_range, mod_pos, mod_range, tail)
142
116
 
143
117
 
144
 
class HunkLine(object):
145
 
 
 
118
class HunkLine:
146
119
    def __init__(self, contents):
147
120
        self.contents = contents
148
121
 
149
122
    def get_str(self, leadchar):
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
 
123
        if self.contents == "\n" and leadchar == " " and False:
 
124
            return "\n"
 
125
        if not self.contents.endswith('\n'):
 
126
            terminator = '\n' + NO_NL
154
127
        else:
155
 
            terminator = b''
 
128
            terminator = ''
156
129
        return leadchar + self.contents + terminator
157
130
 
158
 
    def as_bytes(self):
159
 
        raise NotImplementedError
160
 
 
161
131
 
162
132
class ContextLine(HunkLine):
163
 
 
164
133
    def __init__(self, contents):
165
134
        HunkLine.__init__(self, contents)
166
135
 
167
 
    def as_bytes(self):
168
 
        return self.get_str(b" ")
 
136
    def __str__(self):
 
137
        return self.get_str(" ")
169
138
 
170
139
 
171
140
class InsertLine(HunkLine):
172
141
    def __init__(self, contents):
173
142
        HunkLine.__init__(self, contents)
174
143
 
175
 
    def as_bytes(self):
176
 
        return self.get_str(b"+")
 
144
    def __str__(self):
 
145
        return self.get_str("+")
177
146
 
178
147
 
179
148
class RemoveLine(HunkLine):
180
149
    def __init__(self, contents):
181
150
        HunkLine.__init__(self, contents)
182
151
 
183
 
    def as_bytes(self):
184
 
        return self.get_str(b"-")
185
 
 
186
 
 
187
 
NO_NL = b'\\ No newline at end of file\n'
188
 
__pychecker__ = "no-returnvalues"
189
 
 
 
152
    def __str__(self):
 
153
        return self.get_str("-")
 
154
 
 
155
NO_NL = '\\ No newline at end of file\n'
 
156
__pychecker__="no-returnvalues"
190
157
 
191
158
def parse_line(line):
192
 
    if line.startswith(b"\n"):
 
159
    if line.startswith("\n"):
193
160
        return ContextLine(line)
194
 
    elif line.startswith(b" "):
 
161
    elif line.startswith(" "):
195
162
        return ContextLine(line[1:])
196
 
    elif line.startswith(b"+"):
 
163
    elif line.startswith("+"):
197
164
        return InsertLine(line[1:])
198
 
    elif line.startswith(b"-"):
 
165
    elif line.startswith("-"):
199
166
        return RemoveLine(line[1:])
200
167
    else:
201
168
        raise MalformedLine("Unknown line type", line)
202
 
 
203
 
 
204
 
__pychecker__ = ""
205
 
 
206
 
 
207
 
class Hunk(object):
208
 
 
 
169
__pychecker__=""
 
170
 
 
171
 
 
172
class Hunk:
209
173
    def __init__(self, orig_pos, orig_range, mod_pos, mod_range, tail=None):
210
174
        self.orig_pos = orig_pos
211
175
        self.orig_range = orig_range
216
180
 
217
181
    def get_header(self):
218
182
        if self.tail is None:
219
 
            tail_str = b''
 
183
            tail_str = ''
220
184
        else:
221
 
            tail_str = b' ' + self.tail
222
 
        return b"@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
223
 
                                                      self.orig_range),
224
 
                                       self.range_str(self.mod_pos,
225
 
                                                      self.mod_range),
226
 
                                       tail_str)
 
185
            tail_str = ' ' + self.tail
 
186
        return "@@ -%s +%s @@%s\n" % (self.range_str(self.orig_pos,
 
187
                                                     self.orig_range),
 
188
                                      self.range_str(self.mod_pos,
 
189
                                                     self.mod_range),
 
190
                                      tail_str)
227
191
 
228
192
    def range_str(self, pos, range):
229
193
        """Return a file range, special-casing for 1-line files.
235
199
        :return: a string in the format 1,4 except when range == pos == 1
236
200
        """
237
201
        if range == 1:
238
 
            return b"%i" % pos
 
202
            return "%i" % pos
239
203
        else:
240
 
            return b"%i,%i" % (pos, range)
 
204
            return "%i,%i" % (pos, range)
241
205
 
242
 
    def as_bytes(self):
 
206
    def __str__(self):
243
207
        lines = [self.get_header()]
244
208
        for line in self.lines:
245
 
            lines.append(line.as_bytes())
246
 
        return b"".join(lines)
247
 
 
248
 
    __bytes__ = as_bytes
 
209
            lines.append(str(line))
 
210
        return "".join(lines)
249
211
 
250
212
    def shift_to_mod(self, pos):
251
 
        if pos < self.orig_pos - 1:
 
213
        if pos < self.orig_pos-1:
252
214
            return 0
253
 
        elif pos > self.orig_pos + self.orig_range:
 
215
        elif pos > self.orig_pos+self.orig_range:
254
216
            return self.mod_range - self.orig_range
255
217
        else:
256
218
            return self.shift_to_mod_lines(pos)
257
219
 
258
220
    def shift_to_mod_lines(self, pos):
259
 
        position = self.orig_pos - 1
 
221
        position = self.orig_pos-1
260
222
        shift = 0
261
223
        for line in self.lines:
262
224
            if isinstance(line, InsertLine):
273
235
        return shift
274
236
 
275
237
 
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
 
    '''
 
238
def iter_hunks(iter_lines):
283
239
    hunk = None
284
240
    for line in iter_lines:
285
 
        if line == b"\n":
 
241
        if line == "\n":
286
242
            if hunk is not None:
287
243
                yield hunk
288
244
                hunk = None
289
245
            continue
290
246
        if hunk is not None:
291
247
            yield hunk
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
 
248
        hunk = hunk_from_header(line)
301
249
        orig_size = 0
302
250
        mod_size = 0
303
251
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
304
 
            hunk_line = parse_line(next(iter_lines))
 
252
            hunk_line = parse_line(iter_lines.next())
305
253
            hunk.lines.append(hunk_line)
306
254
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
307
255
                orig_size += 1
311
259
        yield hunk
312
260
 
313
261
 
314
 
class BinaryPatch(object):
315
 
 
 
262
class Patch:
316
263
    def __init__(self, oldname, newname):
317
264
        self.oldname = oldname
318
265
        self.newname = newname
319
 
 
320
 
    def as_bytes(self):
321
 
        return b'Binary files %s and %s differ\n' % (self.oldname, self.newname)
322
 
 
323
 
 
324
 
class Patch(BinaryPatch):
325
 
 
326
 
    def __init__(self, oldname, newname):
327
 
        BinaryPatch.__init__(self, oldname, newname)
328
266
        self.hunks = []
329
267
 
330
 
    def as_bytes(self):
 
268
    def __str__(self):
331
269
        ret = self.get_header()
332
 
        ret += b"".join([h.as_bytes() for h in self.hunks])
 
270
        ret += "".join([str(h) for h in self.hunks])
333
271
        return ret
334
272
 
335
273
    def get_header(self):
336
 
        return b"--- %s\n+++ %s\n" % (self.oldname, self.newname)
 
274
        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
337
275
 
338
276
    def stats_values(self):
339
277
        """Calculate the number of inserts and removes."""
342
280
        for hunk in self.hunks:
343
281
            for line in hunk.lines:
344
282
                if isinstance(line, InsertLine):
345
 
                    inserts += 1
 
283
                     inserts+=1;
346
284
                elif isinstance(line, RemoveLine):
347
 
                    removes += 1
 
285
                     removes+=1;
348
286
        return (inserts, removes, len(self.hunks))
349
287
 
350
288
    def stats_str(self):
368
306
        :rtype: iterator of (int, InsertLine)
369
307
        """
370
308
        for hunk in self.hunks:
371
 
            pos = hunk.mod_pos - 1
 
309
            pos = hunk.mod_pos - 1;
372
310
            for line in hunk.lines:
373
311
                if isinstance(line, InsertLine):
374
312
                    yield (pos, line)
377
315
                    pos += 1
378
316
 
379
317
 
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
 
    '''
 
318
def parse_patch(iter_lines):
386
319
    iter_lines = iter_lines_handle_nl(iter_lines)
387
 
    try:
388
 
        (orig_name, mod_name) = get_patch_names(iter_lines)
389
 
    except BinaryFiles as e:
390
 
        return BinaryPatch(e.orig_name, e.mod_name)
391
 
    else:
392
 
        patch = Patch(orig_name, mod_name)
393
 
        for hunk in iter_hunks(iter_lines, allow_dirty):
394
 
            patch.hunks.append(hunk)
395
 
        return patch
396
 
 
397
 
 
398
 
def iter_file_patch(iter_lines, allow_dirty=False, keep_dirty=False):
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
 
    '''
407
 
    # FIXME: Docstring is not quite true.  We allow certain comments no
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).
412
 
    regex = re.compile(binary_files_re)
 
320
    (orig_name, mod_name) = get_patch_names(iter_lines)
 
321
    patch = Patch(orig_name, mod_name)
 
322
    for hunk in iter_hunks(iter_lines):
 
323
        patch.hunks.append(hunk)
 
324
    return patch
 
325
 
 
326
 
 
327
def iter_file_patch(iter_lines):
413
328
    saved_lines = []
414
 
    dirty_head = []
415
329
    orig_range = 0
416
 
    beginning = True
417
 
 
418
330
    for line in iter_lines:
419
 
        if line.startswith(b'=== '):
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 = []
428
 
            dirty_head.append(line)
429
 
            continue
430
 
        if line.startswith(b'*** '):
431
 
            continue
432
 
        if line.startswith(b'#'):
 
331
        if line.startswith('=== ') or line.startswith('*** '):
 
332
            continue
 
333
        if line.startswith('#'):
433
334
            continue
434
335
        elif orig_range > 0:
435
 
            if line.startswith(b'-') or line.startswith(b' '):
 
336
            if line.startswith('-') or line.startswith(' '):
436
337
                orig_range -= 1
437
 
        elif line.startswith(b'--- ') or regex.match(line):
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:
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
 
338
        elif line.startswith('--- '):
 
339
            if len(saved_lines) > 0:
 
340
                yield saved_lines
450
341
            saved_lines = []
451
 
        elif line.startswith(b'@@'):
 
342
        elif line.startswith('@@'):
452
343
            hunk = hunk_from_header(line)
453
344
            orig_range = hunk.orig_range
454
345
        saved_lines.append(line)
455
346
    if len(saved_lines) > 0:
456
 
        if keep_dirty and len(dirty_head) > 0:
457
 
            yield {'saved_lines': saved_lines,
458
 
                   'dirty_head': dirty_head}
459
 
        else:
460
 
            yield saved_lines
 
347
        yield saved_lines
461
348
 
462
349
 
463
350
def iter_lines_handle_nl(iter_lines):
470
357
    last_line = None
471
358
    for line in iter_lines:
472
359
        if line == NO_NL:
473
 
            if not last_line.endswith(b'\n'):
 
360
            if not last_line.endswith('\n'):
474
361
                raise AssertionError()
475
362
            last_line = last_line[:-1]
476
363
            line = None
481
368
        yield last_line
482
369
 
483
370
 
484
 
def parse_patches(iter_lines, allow_dirty=False, keep_dirty=False):
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.
490
 
    :kwarg keep_dirty: If True, returns a dict of patches with dirty headers.
491
 
        Default False.
492
 
    '''
493
 
    for patch_lines in iter_file_patch(iter_lines, allow_dirty, keep_dirty):
494
 
        if 'dirty_head' in patch_lines:
495
 
            yield ({'patch': parse_patch(patch_lines['saved_lines'], allow_dirty),
496
 
                    'dirty_head': patch_lines['dirty_head']})
497
 
        else:
498
 
            yield parse_patch(patch_lines, allow_dirty)
 
371
def parse_patches(iter_lines):
 
372
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
499
373
 
500
374
 
501
375
def difference_index(atext, btext):
513
387
        length = len(btext)
514
388
    for i in range(length):
515
389
        if atext[i] != btext[i]:
516
 
            return i
 
390
            return i;
517
391
    return None
518
392
 
519
393
 
539
413
        orig_lines = iter(orig_lines)
540
414
    for hunk in hunks:
541
415
        while line_no < hunk.orig_pos:
542
 
            orig_line = next(orig_lines)
 
416
            orig_line = orig_lines.next()
543
417
            yield orig_line
544
418
            line_no += 1
545
419
        for hunk_line in hunk.lines:
546
 
            seen_patch.append(hunk_line.contents)
 
420
            seen_patch.append(str(hunk_line))
547
421
            if isinstance(hunk_line, InsertLine):
548
422
                yield hunk_line.contents
549
423
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
550
 
                orig_line = next(orig_lines)
 
424
                orig_line = orig_lines.next()
551
425
                if orig_line != hunk_line.contents:
552
 
                    raise PatchConflict(line_no, orig_line,
553
 
                                        b''.join(seen_patch))
 
426
                    raise PatchConflict(line_no, orig_line, "".join(seen_patch))
554
427
                if isinstance(hunk_line, ContextLine):
555
428
                    yield orig_line
556
429
                else:
560
433
    if orig_lines is not None:
561
434
        for line in orig_lines:
562
435
            yield line
563
 
 
564
 
 
565
 
def apply_patches(tt, patches, prefix=1):
566
 
    """Apply patches to a TreeTransform.
567
 
 
568
 
    :param tt: TreeTransform instance
569
 
    :param patches: List of patches
570
 
    :param prefix: Number leading path segments to strip
571
 
    """
572
 
    def strip_prefix(p):
573
 
        return '/'.join(p.split('/')[1:])
574
 
 
575
 
    from breezy.bzr.generate_ids import gen_file_id
576
 
    # TODO(jelmer): Extract and set mode
577
 
    for patch in patches:
578
 
        if patch.oldname == b'/dev/null':
579
 
            trans_id = None
580
 
            orig_contents = b''
581
 
        else:
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)
585
 
            tt.delete_contents(trans_id)
586
 
 
587
 
        if patch.newname != b'/dev/null':
588
 
            newname = strip_prefix(patch.newname.decode())
589
 
            new_contents = iter_patched_from_hunks(
590
 
                orig_contents.splitlines(True), patch.hunks)
591
 
            if trans_id is None:
592
 
                parts = os.path.split(newname)
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,
598
 
                    file_id=gen_file_id(newname))
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
 
 
607
 
    def __init__(self, tree, patches, prefix=1):
608
 
        self.tree = tree
609
 
        self.patches = patches
610
 
        self.prefix = prefix
611
 
 
612
 
    def __enter__(self):
613
 
        self._tt = self.tree.preview_transform()
614
 
        apply_patches(self._tt, self.patches, prefix=self.prefix)
615
 
        return self._tt.get_preview_tree()
616
 
 
617
 
    def __exit__(self, exc_type, exc_value, exc_tb):
618
 
        self._tt.finalize()
619
 
        return False