~bzr-pqm/bzr/bzr.dev

1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
1
# Copyright (C) 2006 by Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Read in a bundle stream, and process it into a BundleReader object."""
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
18
1185.82.96 by Aaron Bentley
Got first binary test passing
19
import base64
1185.82.78 by Aaron Bentley
Cleanups
20
from cStringIO import StringIO
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
21
import os
22
import pprint
23
1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
24
import bzrlib.errors
1185.82.131 by Aaron Bentley
Move BadBundle error (and subclasses) to errors.py
25
from bzrlib.errors import (TestamentMismatch, BzrError, 
1185.82.139 by Aaron Bentley
Raise NotABundle when a non-bundle is supplied
26
                           MalformedHeader, MalformedPatches, NotABundle)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
27
from bzrlib.inventory import (Inventory, InventoryEntry,
28
                              InventoryDirectory, InventoryFile,
29
                              InventoryLink)
1711.4.19 by John Arbash Meinel
Bundles were still using os.path.join to compute paths rather than osutils.pathjoin
30
from bzrlib.osutils import sha_file, sha_string, pathjoin
1185.82.78 by Aaron Bentley
Cleanups
31
from bzrlib.revision import Revision, NULL_REVISION
1185.82.116 by Aaron Bentley
Introduce StrictTestament, get test failing for the right reasons
32
from bzrlib.testament import StrictTestament
1185.82.78 by Aaron Bentley
Cleanups
33
from bzrlib.trace import mutter, warning
1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
34
import bzrlib.transport
1185.82.78 by Aaron Bentley
Cleanups
35
from bzrlib.tree import Tree
1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
36
import bzrlib.urlutils
1185.82.78 by Aaron Bentley
Cleanups
37
from bzrlib.xml5 import serializer_v5
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
38
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
39
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
40
class RevisionInfo(object):
41
    """Gets filled out for each revision object that is read.
42
    """
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
43
    def __init__(self, revision_id):
44
        self.revision_id = revision_id
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
45
        self.sha1 = None
46
        self.committer = None
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
47
        self.date = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
48
        self.timestamp = None
49
        self.timezone = None
50
        self.inventory_sha1 = None
51
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
52
        self.parent_ids = None
1185.82.74 by Aaron Bentley
Allow custom base for any revision
53
        self.base_id = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
54
        self.message = None
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
55
        self.properties = None
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
56
        self.tree_actions = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
57
58
    def __str__(self):
59
        return pprint.pformat(self.__dict__)
60
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
61
    def as_revision(self):
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
62
        rev = Revision(revision_id=self.revision_id,
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
63
            committer=self.committer,
64
            timestamp=float(self.timestamp),
65
            timezone=int(self.timezone),
66
            inventory_sha1=self.inventory_sha1,
67
            message='\n'.join(self.message))
68
1185.82.28 by Aaron Bentley
Got parent_id handling working
69
        if self.parent_ids:
70
            rev.parent_ids.extend(self.parent_ids)
1185.82.35 by Aaron Bentley
Read revision properties
71
1185.82.59 by Aaron Bentley
Behave properly when there are no properties in a revision
72
        if self.properties:
73
            for property in self.properties:
74
                key_end = property.find(': ')
75
                assert key_end is not None
76
                key = property[:key_end].encode('utf-8')
77
                value = property[key_end+2:].encode('utf-8')
78
                rev.properties[key] = value
1185.82.35 by Aaron Bentley
Read revision properties
79
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
80
        return rev
81
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
82
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
83
class BundleInfo(object):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
84
    """This contains the meta information. Stuff that allows you to
85
    recreate the revision or inventory XML.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
86
    """
87
    def __init__(self):
88
        self.committer = None
89
        self.date = None
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
90
        self.message = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
91
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
92
        # A list of RevisionInfo objects
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
93
        self.revisions = []
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
94
95
        # The next entries are created during complete_info() and
96
        # other post-read functions.
97
98
        # A list of real Revision objects
99
        self.real_revisions = []
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
100
101
        self.timestamp = None
102
        self.timezone = None
0.5.15 by John Arbash Meinel
Created an apply-changeset function, and modified output for better parsing.
103
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
104
    def __str__(self):
105
        return pprint.pformat(self.__dict__)
106
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
107
    def complete_info(self):
108
        """This makes sure that all information is properly
109
        split up, based on the assumptions that can be made
110
        when information is missing.
111
        """
1793.3.8 by John Arbash Meinel
Removed duplicated highres date code.
112
        from bzrlib.bundle.serializer import unpack_highres_date
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
113
        # Put in all of the guessable information.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
114
        if not self.timestamp and self.date:
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
115
            self.timestamp, self.timezone = unpack_highres_date(self.date)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
116
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
117
        self.real_revisions = []
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
118
        for rev in self.revisions:
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
119
            if rev.timestamp is None:
120
                if rev.date is not None:
121
                    rev.timestamp, rev.timezone = \
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
122
                            unpack_highres_date(rev.date)
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
123
                else:
124
                    rev.timestamp = self.timestamp
125
                    rev.timezone = self.timezone
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
126
            if rev.message is None and self.message:
127
                rev.message = self.message
128
            if rev.committer is None and self.committer:
129
                rev.committer = self.committer
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
130
            self.real_revisions.append(rev.as_revision())
131
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
132
    def get_base(self, revision):
1185.82.74 by Aaron Bentley
Allow custom base for any revision
133
        revision_info = self.get_revision_info(revision.revision_id)
134
        if revision_info.base_id is not None:
135
            if revision_info.base_id == NULL_REVISION:
136
                return None
137
            else:
138
                return revision_info.base_id
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
139
        if len(revision.parent_ids) == 0:
140
            # There is no base listed, and
141
            # the lowest revision doesn't have a parent
142
            # so this is probably against the empty tree
143
            # and thus base truly is None
144
            return None
145
        else:
1185.82.73 by Aaron Bentley
Use rightmost parent always
146
            return revision.parent_ids[-1]
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
147
0.5.67 by John Arbash Meinel
Working on apply_changeset
148
    def _get_target(self):
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
149
        """Return the target revision."""
0.5.67 by John Arbash Meinel
Working on apply_changeset
150
        if len(self.real_revisions) > 0:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
151
            return self.real_revisions[0].revision_id
0.5.67 by John Arbash Meinel
Working on apply_changeset
152
        elif len(self.revisions) > 0:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
153
            return self.revisions[0].revision_id
0.5.67 by John Arbash Meinel
Working on apply_changeset
154
        return None
155
156
    target = property(_get_target, doc='The target revision id')
157
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
158
    def get_revision(self, revision_id):
159
        for r in self.real_revisions:
160
            if r.revision_id == revision_id:
161
                return r
162
        raise KeyError(revision_id)
163
164
    def get_revision_info(self, revision_id):
165
        for r in self.revisions:
166
            if r.revision_id == revision_id:
167
                return r
168
        raise KeyError(revision_id)
169
1793.2.1 by Aaron Bentley
Move revision_tree into BundleInfo
170
    def revision_tree(self, repository, revision_id, base=None):
171
        revision = self.get_revision(revision_id)
172
        base = self.get_base(revision)
173
        assert base != revision_id
174
        self._validate_references_from_repository(repository)
175
        revision_info = self.get_revision_info(revision_id)
176
        inventory_revision_id = revision_id
177
        bundle_tree = BundleTree(repository.revision_tree(base), 
178
                                  inventory_revision_id)
179
        self._update_tree(bundle_tree, revision_id)
180
181
        inv = bundle_tree.inventory
182
        self._validate_inventory(inv, revision_id)
183
        self._validate_revision(inv, revision_id)
184
185
        return bundle_tree
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
186
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
187
    def _validate_references_from_repository(self, repository):
188
        """Now that we have a repository which should have some of the
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
189
        revisions we care about, go through and validate all of them
190
        that we can.
191
        """
192
        rev_to_sha = {}
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
193
        inv_to_sha = {}
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
194
        def add_sha(d, revision_id, sha1):
195
            if revision_id is None:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
196
                if sha1 is not None:
197
                    raise BzrError('A Null revision should always'
198
                        'have a null sha1 hash')
199
                return
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
200
            if revision_id in d:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
201
                # This really should have been validated as part
202
                # of _validate_revisions but lets do it again
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
203
                if sha1 != d[revision_id]:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
204
                    raise BzrError('** Revision %r referenced with 2 different'
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
205
                            ' sha hashes %s != %s' % (revision_id,
206
                                sha1, d[revision_id]))
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
207
            else:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
208
                d[revision_id] = sha1
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
209
210
        # All of the contained revisions were checked
211
        # in _validate_revisions
212
        checked = {}
1793.2.1 by Aaron Bentley
Move revision_tree into BundleInfo
213
        for rev_info in self.revisions:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
214
            checked[rev_info.revision_id] = True
215
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
216
                
1793.2.1 by Aaron Bentley
Move revision_tree into BundleInfo
217
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
218
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
219
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
220
        count = 0
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
221
        missing = {}
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
222
        for revision_id, sha1 in rev_to_sha.iteritems():
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
223
            if repository.has_revision(revision_id):
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
224
                testament = StrictTestament.from_revision(repository, 
225
                                                          revision_id)
226
                local_sha1 = testament.as_sha1()
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
227
                if sha1 != local_sha1:
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
228
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
229
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
230
                else:
231
                    count += 1
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
232
            elif revision_id not in checked:
233
                missing[revision_id] = sha1
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
234
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
235
        for inv_id, sha1 in inv_to_sha.iteritems():
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
236
            if repository.has_revision(inv_id):
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
237
                # Note: branch.get_inventory_sha1() just returns the value that
238
                # is stored in the revision text, and that value may be out
239
                # of date. This is bogus, because that means we aren't
240
                # validating the actual text, just that we wrote and read the
241
                # string. But for now, what the hell.
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
242
                local_sha1 = repository.get_inventory_sha1(inv_id)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
243
                if sha1 != local_sha1:
244
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
245
                                   'local: %s, bundle: %s' % 
246
                                   (inv_id, local_sha1, sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
247
                else:
248
                    count += 1
249
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
250
        if len(missing) > 0:
251
            # I don't know if this is an error yet
252
            warning('Not all revision hashes could be validated.'
253
                    ' Unable validate %d hashes' % len(missing))
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
254
        mutter('Verified %d sha hashes for the bundle.' % count)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
255
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
256
    def _validate_inventory(self, inv, revision_id):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
257
        """At this point we should have generated the BundleTree,
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
258
        so build up an inventory, and make sure the hashes match.
259
        """
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
260
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
261
        assert inv is not None
262
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
263
        # Now we should have a complete inventory entry.
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
264
        s = serializer_v5.write_inventory_to_string(inv)
265
        sha1 = sha_string(s)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
266
        # Target revision is the last entry in the real_revisions list
1793.2.1 by Aaron Bentley
Move revision_tree into BundleInfo
267
        rev = self.get_revision(revision_id)
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
268
        assert rev.revision_id == revision_id
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
269
        if sha1 != rev.inventory_sha1:
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
270
            open(',,bogus-inv', 'wb').write(s)
1185.82.61 by Aaron Bentley
Downgrade inventory mismatch to warning (source can be inaccurate)
271
            warning('Inventory sha hash mismatch for revision %s. %s'
272
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
273
1793.2.1 by Aaron Bentley
Move revision_tree into BundleInfo
274
    def _validate_revision(self, inventory, revision_id):
275
        """Make sure all revision entries match their checksum."""
276
277
        # This is a mapping from each revision id to it's sha hash
278
        rev_to_sha1 = {}
279
        
280
        rev = self.get_revision(revision_id)
281
        rev_info = self.get_revision_info(revision_id)
282
        assert rev.revision_id == rev_info.revision_id
283
        assert rev.revision_id == revision_id
284
        sha1 = StrictTestament(rev, inventory).as_sha1()
285
        if sha1 != rev_info.sha1:
286
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
287
        if rev_to_sha1.has_key(rev.revision_id):
288
            raise BzrError('Revision {%s} given twice in the list'
289
                    % (rev.revision_id))
290
        rev_to_sha1[rev.revision_id] = sha1
291
292
    def _update_tree(self, bundle_tree, revision_id):
293
        """This fills out a BundleTree based on the information
294
        that was read in.
295
296
        :param bundle_tree: A BundleTree to update with the new information.
297
        """
298
299
        def get_rev_id(last_changed, path, kind):
300
            if last_changed is not None:
301
                changed_revision_id = last_changed.decode('utf-8')
302
            else:
303
                changed_revision_id = revision_id
304
            bundle_tree.note_last_changed(path, changed_revision_id)
305
            return changed_revision_id
306
307
        def extra_info(info, new_path):
308
            last_changed = None
309
            encoding = None
310
            for info_item in info:
311
                try:
312
                    name, value = info_item.split(':', 1)
313
                except ValueError:
314
                    raise 'Value %r has no colon' % info_item
315
                if name == 'last-changed':
316
                    last_changed = value
317
                elif name == 'executable':
318
                    assert value in ('yes', 'no'), value
319
                    val = (value == 'yes')
320
                    bundle_tree.note_executable(new_path, val)
321
                elif name == 'target':
322
                    bundle_tree.note_target(new_path, value)
323
                elif name == 'encoding':
324
                    encoding = value
325
            return last_changed, encoding
326
327
        def do_patch(path, lines, encoding):
328
            if encoding is not None:
329
                assert encoding == 'base64'
330
                patch = base64.decodestring(''.join(lines))
331
            else:
332
                patch =  ''.join(lines)
333
            bundle_tree.note_patch(path, patch)
334
335
        def renamed(kind, extra, lines):
336
            info = extra.split(' // ')
337
            if len(info) < 2:
338
                raise BzrError('renamed action lines need both a from and to'
339
                        ': %r' % extra)
340
            old_path = info[0]
341
            if info[1].startswith('=> '):
342
                new_path = info[1][3:]
343
            else:
344
                new_path = info[1]
345
346
            bundle_tree.note_rename(old_path, new_path)
347
            last_modified, encoding = extra_info(info[2:], new_path)
348
            revision = get_rev_id(last_modified, new_path, kind)
349
            if lines:
350
                do_patch(new_path, lines, encoding)
351
352
        def removed(kind, extra, lines):
353
            info = extra.split(' // ')
354
            if len(info) > 1:
355
                # TODO: in the future we might allow file ids to be
356
                # given for removed entries
357
                raise BzrError('removed action lines should only have the path'
358
                        ': %r' % extra)
359
            path = info[0]
360
            bundle_tree.note_deletion(path)
361
362
        def added(kind, extra, lines):
363
            info = extra.split(' // ')
364
            if len(info) <= 1:
365
                raise BzrError('add action lines require the path and file id'
366
                        ': %r' % extra)
367
            elif len(info) > 5:
368
                raise BzrError('add action lines have fewer than 5 entries.'
369
                        ': %r' % extra)
370
            path = info[0]
371
            if not info[1].startswith('file-id:'):
372
                raise BzrError('The file-id should follow the path for an add'
373
                        ': %r' % extra)
374
            file_id = info[1][8:]
375
376
            bundle_tree.note_id(file_id, path, kind)
377
            # this will be overridden in extra_info if executable is specified.
378
            bundle_tree.note_executable(path, False)
379
            last_changed, encoding = extra_info(info[2:], path)
380
            revision = get_rev_id(last_changed, path, kind)
381
            if kind == 'directory':
382
                return
383
            do_patch(path, lines, encoding)
384
385
        def modified(kind, extra, lines):
386
            info = extra.split(' // ')
387
            if len(info) < 1:
388
                raise BzrError('modified action lines have at least'
389
                        'the path in them: %r' % extra)
390
            path = info[0]
391
392
            last_modified, encoding = extra_info(info[1:], path)
393
            revision = get_rev_id(last_modified, path, kind)
394
            if lines:
395
                do_patch(path, lines, encoding)
396
            
397
        valid_actions = {
398
            'renamed':renamed,
399
            'removed':removed,
400
            'added':added,
401
            'modified':modified
402
        }
403
        for action_line, lines in \
404
            self.get_revision_info(revision_id).tree_actions:
405
            first = action_line.find(' ')
406
            if first == -1:
407
                raise BzrError('Bogus action line'
408
                        ' (no opening space): %r' % action_line)
409
            second = action_line.find(' ', first+1)
410
            if second == -1:
411
                raise BzrError('Bogus action line'
412
                        ' (missing second space): %r' % action_line)
413
            action = action_line[:first]
414
            kind = action_line[first+1:second]
415
            if kind not in ('file', 'directory', 'symlink'):
416
                raise BzrError('Bogus action line'
417
                        ' (invalid object kind %r): %r' % (kind, action_line))
418
            extra = action_line[second+1:]
419
420
            if action not in valid_actions:
421
                raise BzrError('Bogus action line'
422
                        ' (unrecognized action): %r' % action_line)
423
            valid_actions[action](kind, extra, lines)
424
425
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
426
class BundleTree(Tree):
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
427
    def __init__(self, base_tree, revision_id):
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
428
        self.base_tree = base_tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
429
        self._renamed = {} # Mapping from old_path => new_path
430
        self._renamed_r = {} # new_path => old_path
431
        self._new_id = {} # new_path => new_id
432
        self._new_id_r = {} # new_id => new_path
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
433
        self._kinds = {} # new_id => kind
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
434
        self._last_changed = {} # new_id => revision_id
1185.82.66 by Aaron Bentley
Handle new executable files
435
        self._executable = {} # new_id => executable value
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
436
        self.patches = {}
1185.82.87 by Aaron Bentley
Got symlink adding working
437
        self._targets = {} # new path => new symlink target
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
438
        self.deleted = []
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
439
        self.contents_by_id = True
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
440
        self.revision_id = revision_id
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
441
        self._inventory = None
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
442
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
443
    def __str__(self):
444
        return pprint.pformat(self.__dict__)
445
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
446
    def note_rename(self, old_path, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
447
        """A file/directory has been renamed from old_path => new_path"""
1185.82.70 by Aaron Bentley
Handle renamed files better
448
        assert not self._renamed.has_key(new_path)
449
        assert not self._renamed_r.has_key(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
450
        self._renamed[new_path] = old_path
451
        self._renamed_r[old_path] = new_path
452
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
453
    def note_id(self, new_id, new_path, kind='file'):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
454
        """Files that don't exist in base need a new id."""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
455
        self._new_id[new_path] = new_id
456
        self._new_id_r[new_id] = new_path
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
457
        self._kinds[new_id] = kind
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
458
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
459
    def note_last_changed(self, file_id, revision_id):
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
460
        if (self._last_changed.has_key(file_id)
461
                and self._last_changed[file_id] != revision_id):
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
462
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
463
                    ': %s != %s' % (file_id,
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
464
                                    self._last_changed[file_id],
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
465
                                    revision_id))
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
466
        self._last_changed[file_id] = revision_id
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
467
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
468
    def note_patch(self, new_path, patch):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
469
        """There is a patch for a given filename."""
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
470
        self.patches[new_path] = patch
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
471
1185.82.87 by Aaron Bentley
Got symlink adding working
472
    def note_target(self, new_path, target):
473
        """The symlink at the new path has the given target"""
474
        self._targets[new_path] = target
475
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
476
    def note_deletion(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
477
        """The file at old_path has been deleted."""
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
478
        self.deleted.append(old_path)
479
1185.82.66 by Aaron Bentley
Handle new executable files
480
    def note_executable(self, new_path, executable):
481
        self._executable[new_path] = executable
482
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
483
    def old_path(self, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
484
        """Get the old_path (path in the base_tree) for the file at new_path"""
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
485
        assert new_path[:1] not in ('\\', '/')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
486
        old_path = self._renamed.get(new_path)
487
        if old_path is not None:
488
            return old_path
489
        dirname,basename = os.path.split(new_path)
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
490
        # dirname is not '' doesn't work, because
491
        # dirname may be a unicode entry, and is
492
        # requires the objects to be identical
493
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
494
            old_dir = self.old_path(dirname)
495
            if old_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
496
                old_path = None
497
            else:
1711.4.19 by John Arbash Meinel
Bundles were still using os.path.join to compute paths rather than osutils.pathjoin
498
                old_path = pathjoin(old_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
499
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
500
            old_path = new_path
501
        #If the new path wasn't in renamed, the old one shouldn't be in
502
        #renamed_r
503
        if self._renamed_r.has_key(old_path):
504
            return None
505
        return old_path 
506
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
507
    def new_path(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
508
        """Get the new_path (path in the target_tree) for the file at old_path
509
        in the base tree.
510
        """
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
511
        assert old_path[:1] not in ('\\', '/')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
512
        new_path = self._renamed_r.get(old_path)
513
        if new_path is not None:
514
            return new_path
515
        if self._renamed.has_key(new_path):
516
            return None
517
        dirname,basename = os.path.split(old_path)
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
518
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
519
            new_dir = self.new_path(dirname)
520
            if new_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
521
                new_path = None
522
            else:
1711.4.19 by John Arbash Meinel
Bundles were still using os.path.join to compute paths rather than osutils.pathjoin
523
                new_path = pathjoin(new_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
524
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
525
            new_path = old_path
526
        #If the old path wasn't in renamed, the new one shouldn't be in
527
        #renamed_r
528
        if self._renamed.has_key(new_path):
529
            return None
530
        return new_path 
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
531
532
    def path2id(self, path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
533
        """Return the id of the file present at path in the target tree."""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
534
        file_id = self._new_id.get(path)
535
        if file_id is not None:
536
            return file_id
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
537
        old_path = self.old_path(path)
538
        if old_path is None:
539
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
540
        if old_path in self.deleted:
541
            return None
0.5.66 by John Arbash Meinel
Refactoring, moving test code into test (switching back to assert is None)
542
        if hasattr(self.base_tree, 'path2id'):
543
            return self.base_tree.path2id(old_path)
544
        else:
545
            return self.base_tree.inventory.path2id(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
546
547
    def id2path(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
548
        """Return the new path in the target tree of the file with id file_id"""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
549
        path = self._new_id_r.get(file_id)
550
        if path is not None:
551
            return path
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
552
        old_path = self.base_tree.id2path(file_id)
553
        if old_path is None:
554
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
555
        if old_path in self.deleted:
556
            return None
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
557
        return self.new_path(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
558
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
559
    def old_contents_id(self, file_id):
1185.82.94 by Aaron Bentley
Remove old chatter
560
        """Return the id in the base_tree for the given file_id.
561
        Return None if the file did not exist in base.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
562
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
563
        if self.contents_by_id:
564
            if self.base_tree.has_id(file_id):
565
                return file_id
566
            else:
567
                return None
568
        new_path = self.id2path(file_id)
569
        return self.base_tree.path2id(new_path)
570
        
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
571
    def get_file(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
572
        """Return a file-like object containing the new contents of the
573
        file given by file_id.
574
575
        TODO:   It might be nice if this actually generated an entry
576
                in the text-store, so that the file contents would
577
                then be cached.
578
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
579
        base_id = self.old_contents_id(file_id)
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
580
        if base_id is not None:
581
            patch_original = self.base_tree.get_file(base_id)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
582
        else:
583
            patch_original = None
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
584
        file_patch = self.patches.get(self.id2path(file_id))
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
585
        if file_patch is None:
1185.82.42 by Aaron Bentley
Handle new directories properly
586
            if (patch_original is None and 
587
                self.get_kind(file_id) == 'directory'):
588
                return StringIO()
589
            assert patch_original is not None, "None: %s" % file_id
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
590
            return patch_original
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
591
592
        assert not file_patch.startswith('\\'), \
593
            'Malformed patch for %s, %r' % (file_id, file_patch)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
594
        return patched_file(file_patch, patch_original)
595
1185.82.87 by Aaron Bentley
Got symlink adding working
596
    def get_symlink_target(self, file_id):
597
        new_path = self.id2path(file_id)
598
        try:
599
            return self._targets[new_path]
600
        except KeyError:
601
            return self.base_tree.get_symlink_target(file_id)
602
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
603
    def get_kind(self, file_id):
604
        if file_id in self._kinds:
605
            return self._kinds[file_id]
606
        return self.base_tree.inventory[file_id].kind
607
1185.82.66 by Aaron Bentley
Handle new executable files
608
    def is_executable(self, file_id):
1185.82.93 by Aaron Bentley
Code cleanup
609
        path = self.id2path(file_id)
610
        if path in self._executable:
611
            return self._executable[path]
1185.82.66 by Aaron Bentley
Handle new executable files
612
        else:
613
            return self.base_tree.inventory[file_id].executable
614
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
615
    def get_last_changed(self, file_id):
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
616
        path = self.id2path(file_id)
617
        if path in self._last_changed:
618
            return self._last_changed[path]
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
619
        return self.base_tree.inventory[file_id].revision
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
620
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
621
    def get_size_and_sha1(self, file_id):
622
        """Return the size and sha1 hash of the given file id.
623
        If the file was not locally modified, this is extracted
624
        from the base_tree. Rather than re-reading the file.
625
        """
626
        new_path = self.id2path(file_id)
627
        if new_path is None:
628
            return None, None
629
        if new_path not in self.patches:
630
            # If the entry does not have a patch, then the
631
            # contents must be the same as in the base_tree
632
            ie = self.base_tree.inventory[file_id]
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
633
            if ie.text_size is None:
634
                return ie.text_size, ie.text_sha1
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
635
            return int(ie.text_size), ie.text_sha1
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
636
        fileobj = self.get_file(file_id)
637
        content = fileobj.read()
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
638
        return len(content), sha_string(content)
639
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
640
    def _get_inventory(self):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
641
        """Build up the inventory entry for the BundleTree.
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
642
643
        This need to be called before ever accessing self.inventory
644
        """
645
        from os.path import dirname, basename
646
647
        assert self.base_tree is not None
648
        base_inv = self.base_tree.inventory
649
        root_id = base_inv.root.file_id
650
        try:
651
            # New inventories have a unique root_id
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
652
            inv = Inventory(root_id, self.revision_id)
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
653
        except TypeError:
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
654
            inv = Inventory(revision_id=self.revision_id)
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
655
656
        def add_entry(file_id):
657
            path = self.id2path(file_id)
658
            if path is None:
659
                return
660
            parent_path = dirname(path)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
661
            if parent_path == u'':
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
662
                parent_id = root_id
663
            else:
664
                parent_id = self.path2id(parent_path)
665
666
            kind = self.get_kind(file_id)
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
667
            revision_id = self.get_last_changed(file_id)
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
668
669
            name = basename(path)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
670
            if kind == 'directory':
671
                ie = InventoryDirectory(file_id, name, parent_id)
672
            elif kind == 'file':
673
                ie = InventoryFile(file_id, name, parent_id)
1185.82.66 by Aaron Bentley
Handle new executable files
674
                ie.executable = self.is_executable(file_id)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
675
            elif kind == 'symlink':
676
                ie = InventoryLink(file_id, name, parent_id)
1185.82.87 by Aaron Bentley
Got symlink adding working
677
                ie.symlink_target = self.get_symlink_target(file_id)
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
678
            ie.revision = revision_id
679
1185.82.88 by Aaron Bentley
Get symlink modification, renames and deletion under test
680
            if kind in ('directory', 'symlink'):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
681
                ie.text_size, ie.text_sha1 = None, None
682
            else:
683
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
1185.82.88 by Aaron Bentley
Get symlink modification, renames and deletion under test
684
            if (ie.text_size is None) and (kind == 'file'):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
685
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
686
            inv.add(ie)
687
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
688
        sorted_entries = self.sorted_path_id()
689
        for path, file_id in sorted_entries:
690
            if file_id == inv.root.file_id:
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
691
                continue
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
692
            add_entry(file_id)
693
694
        return inv
695
696
    # Have to overload the inherited inventory property
697
    # because _get_inventory is only called in the parent.
698
    # Reading the docs, property() objects do not use
699
    # overloading, they use the function as it was defined
700
    # at that instant
701
    inventory = property(_get_inventory)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
702
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
703
    def __iter__(self):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
704
        for path, entry in self.inventory.iter_entries():
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
705
            yield entry.file_id
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
706
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
707
    def sorted_path_id(self):
708
        paths = []
709
        for result in self._new_id.iteritems():
710
            paths.append(result)
711
        for id in self.base_tree:
712
            path = self.id2path(id)
713
            if path is None:
714
                continue
715
            paths.append((path, id))
716
        paths.sort()
717
        return paths
718
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
719
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
720
def patched_file(file_patch, original):
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
721
    """Produce a file-like object with the patched version of a text"""
1185.82.13 by Aaron Bentley
Got old changeset tests running
722
    from bzrlib.patches import iter_patched
723
    from bzrlib.iterablefile import IterableFile
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
724
    if file_patch == "":
725
        return IterableFile(())
1848.1.1 by John Arbash Meinel
fix bug in bundle handling of binary files with just '\r' in them.
726
    # string.splitlines(True) also splits on '\r', but the iter_patched code
727
    # only expects to iterate over '\n' style lines
728
    return IterableFile(iter_patched(original,
729
                StringIO(file_patch).readlines()))