/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
1
#!/usr/bin/env python
2
"""\
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
3
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.
4
"""
5
1185.82.96 by Aaron Bentley
Got first binary test passing
6
import base64
1185.82.78 by Aaron Bentley
Cleanups
7
from cStringIO import StringIO
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
8
import os
9
import pprint
10
1185.82.131 by Aaron Bentley
Move BadBundle error (and subclasses) to errors.py
11
from bzrlib.errors import (TestamentMismatch, BzrError, 
1185.82.139 by Aaron Bentley
Raise NotABundle when a non-bundle is supplied
12
                           MalformedHeader, MalformedPatches, NotABundle)
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
13
from bzrlib.bundle.common import get_header, header_str
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
14
from bzrlib.inventory import (Inventory, InventoryEntry,
15
                              InventoryDirectory, InventoryFile,
16
                              InventoryLink)
1185.82.78 by Aaron Bentley
Cleanups
17
from bzrlib.osutils import sha_file, sha_string
18
from bzrlib.revision import Revision, NULL_REVISION
1185.82.116 by Aaron Bentley
Introduce StrictTestament, get test failing for the right reasons
19
from bzrlib.testament import StrictTestament
1185.82.78 by Aaron Bentley
Cleanups
20
from bzrlib.trace import mutter, warning
21
from bzrlib.tree import Tree
22
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.
23
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
24
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
25
class RevisionInfo(object):
26
    """Gets filled out for each revision object that is read.
27
    """
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
28
    def __init__(self, revision_id):
29
        self.revision_id = revision_id
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
30
        self.sha1 = None
31
        self.committer = None
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
32
        self.date = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
33
        self.timestamp = None
34
        self.timezone = None
35
        self.inventory_sha1 = None
36
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
37
        self.parent_ids = None
1185.82.74 by Aaron Bentley
Allow custom base for any revision
38
        self.base_id = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
39
        self.message = None
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
40
        self.properties = None
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
41
        self.tree_actions = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
42
43
    def __str__(self):
44
        return pprint.pformat(self.__dict__)
45
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
46
    def as_revision(self):
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
47
        rev = Revision(revision_id=self.revision_id,
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
48
            committer=self.committer,
49
            timestamp=float(self.timestamp),
50
            timezone=int(self.timezone),
51
            inventory_sha1=self.inventory_sha1,
52
            message='\n'.join(self.message))
53
1185.82.28 by Aaron Bentley
Got parent_id handling working
54
        if self.parent_ids:
55
            rev.parent_ids.extend(self.parent_ids)
1185.82.35 by Aaron Bentley
Read revision properties
56
1185.82.59 by Aaron Bentley
Behave properly when there are no properties in a revision
57
        if self.properties:
58
            for property in self.properties:
59
                key_end = property.find(': ')
60
                assert key_end is not None
61
                key = property[:key_end].encode('utf-8')
62
                value = property[key_end+2:].encode('utf-8')
63
                rev.properties[key] = value
1185.82.35 by Aaron Bentley
Read revision properties
64
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
65
        return rev
66
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
67
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
68
class BundleInfo(object):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
69
    """This contains the meta information. Stuff that allows you to
70
    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.
71
    """
72
    def __init__(self):
73
        self.committer = None
74
        self.date = None
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
75
        self.message = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
76
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
77
        # A list of RevisionInfo objects
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
78
        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.
79
80
        # The next entries are created during complete_info() and
81
        # other post-read functions.
82
83
        # A list of real Revision objects
84
        self.real_revisions = []
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
85
86
        self.timestamp = None
87
        self.timezone = None
0.5.15 by John Arbash Meinel
Created an apply-changeset function, and modified output for better parsing.
88
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
89
    def __str__(self):
90
        return pprint.pformat(self.__dict__)
91
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
92
    def complete_info(self):
93
        """This makes sure that all information is properly
94
        split up, based on the assumptions that can be made
95
        when information is missing.
96
        """
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
97
        from bzrlib.bundle.common 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.
98
        # Put in all of the guessable information.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
99
        if not self.timestamp and self.date:
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
100
            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.
101
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.
102
        self.real_revisions = []
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
103
        for rev in self.revisions:
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
104
            if rev.timestamp is None:
105
                if rev.date is not None:
106
                    rev.timestamp, rev.timezone = \
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
107
                            unpack_highres_date(rev.date)
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
108
                else:
109
                    rev.timestamp = self.timestamp
110
                    rev.timezone = self.timezone
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
111
            if rev.message is None and self.message:
112
                rev.message = self.message
113
            if rev.committer is None and self.committer:
114
                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.
115
            self.real_revisions.append(rev.as_revision())
116
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
117
    def get_base(self, revision):
1185.82.74 by Aaron Bentley
Allow custom base for any revision
118
        revision_info = self.get_revision_info(revision.revision_id)
119
        if revision_info.base_id is not None:
120
            if revision_info.base_id == NULL_REVISION:
121
                return None
122
            else:
123
                return revision_info.base_id
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
124
        if len(revision.parent_ids) == 0:
125
            # There is no base listed, and
126
            # the lowest revision doesn't have a parent
127
            # so this is probably against the empty tree
128
            # and thus base truly is None
129
            return None
130
        else:
1185.82.73 by Aaron Bentley
Use rightmost parent always
131
            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.
132
0.5.67 by John Arbash Meinel
Working on apply_changeset
133
    def _get_target(self):
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
134
        """Return the target revision."""
0.5.67 by John Arbash Meinel
Working on apply_changeset
135
        if len(self.real_revisions) > 0:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
136
            return self.real_revisions[0].revision_id
0.5.67 by John Arbash Meinel
Working on apply_changeset
137
        elif len(self.revisions) > 0:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
138
            return self.revisions[0].revision_id
0.5.67 by John Arbash Meinel
Working on apply_changeset
139
        return None
140
141
    target = property(_get_target, doc='The target revision id')
142
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
143
    def get_revision(self, revision_id):
144
        for r in self.real_revisions:
145
            if r.revision_id == revision_id:
146
                return r
147
        raise KeyError(revision_id)
148
149
    def get_revision_info(self, revision_id):
150
        for r in self.revisions:
151
            if r.revision_id == revision_id:
152
                return r
153
        raise KeyError(revision_id)
154
155
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
156
class BundleReader(object):
157
    """This class reads in a bundle from a file, and returns
158
    a Bundle object, which can then be applied against a tree.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
159
    """
160
    def __init__(self, from_file):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
161
        """Read in the bundle from the file.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
162
163
        :param from_file: A file-like object (must have iterator support).
164
        """
165
        object.__init__(self)
1185.82.50 by Aaron Bentley
Avoid overwriting action lines
166
        self.from_file = iter(from_file)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
167
        self._next_line = None
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
168
        
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
169
        self.info = BundleInfo()
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
170
        # We put the actual inventory ids in the footer, so that the patch
171
        # is easier to read for humans.
172
        # Unfortunately, that means we need to read everything before we
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
173
        # can create a proper bundle.
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
174
        self._read()
175
        self._validate()
176
177
    def _read(self):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
178
        self._read_header()
1185.82.47 by Aaron Bentley
Ensure all intended rev_ids end up in the revision
179
        while self._next_line is not None:
180
            self._read_revision_header()
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
181
            if self._next_line is None:
182
                break
1185.82.47 by Aaron Bentley
Ensure all intended rev_ids end up in the revision
183
            self._read_patches()
184
            self._read_footer()
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
185
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
186
    def _validate(self):
187
        """Make sure that the information read in makes sense
188
        and passes appropriate checksums.
189
        """
190
        # Fill in all the missing blanks for the revisions
191
        # and generate the real_revisions list.
192
        self.info.complete_info()
193
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
194
    def _validate_revision(self, inventory, revision_id):
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
195
        """Make sure all revision entries match their checksum."""
196
197
        # This is a mapping from each revision id to it's sha hash
198
        rev_to_sha1 = {}
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
199
        
200
        rev = self.info.get_revision(revision_id)
201
        rev_info = self.info.get_revision_info(revision_id)
202
        assert rev.revision_id == rev_info.revision_id
1185.82.50 by Aaron Bentley
Avoid overwriting action lines
203
        assert rev.revision_id == revision_id
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
204
        sha1 = StrictTestament(rev, inventory).as_sha1()
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
205
        if sha1 != rev_info.sha1:
1185.82.118 by Aaron Bentley
Ensure that StrictTestament handles execute bit differences
206
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
207
        if rev_to_sha1.has_key(rev.revision_id):
208
            raise BzrError('Revision {%s} given twice in the list'
209
                    % (rev.revision_id))
210
        rev_to_sha1[rev.revision_id] = sha1
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
211
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
212
    def _validate_references_from_repository(self, repository):
213
        """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.
214
        revisions we care about, go through and validate all of them
215
        that we can.
216
        """
217
        rev_to_sha = {}
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
218
        inv_to_sha = {}
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
219
        def add_sha(d, revision_id, sha1):
220
            if revision_id is None:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
221
                if sha1 is not None:
222
                    raise BzrError('A Null revision should always'
223
                        'have a null sha1 hash')
224
                return
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
225
            if revision_id in d:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
226
                # This really should have been validated as part
227
                # 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.
228
                if sha1 != d[revision_id]:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
229
                    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.
230
                            ' sha hashes %s != %s' % (revision_id,
231
                                sha1, d[revision_id]))
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
232
            else:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
233
                d[revision_id] = sha1
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
234
235
        # All of the contained revisions were checked
236
        # in _validate_revisions
237
        checked = {}
238
        for rev_info in self.info.revisions:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
239
            checked[rev_info.revision_id] = True
240
            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.
241
                
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
242
        for (rev, rev_info) in zip(self.info.real_revisions, self.info.revisions):
243
            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.
244
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
245
        count = 0
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
246
        missing = {}
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
247
        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
248
            if repository.has_revision(revision_id):
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
249
                testament = StrictTestament.from_revision(repository, 
250
                                                          revision_id)
251
                local_sha1 = testament.as_sha1()
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
252
                if sha1 != local_sha1:
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
253
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
254
                            '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.
255
                else:
256
                    count += 1
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
257
            elif revision_id not in checked:
258
                missing[revision_id] = sha1
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
259
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
260
        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
261
            if repository.has_revision(inv_id):
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
262
                # Note: branch.get_inventory_sha1() just returns the value that
263
                # is stored in the revision text, and that value may be out
264
                # of date. This is bogus, because that means we aren't
265
                # validating the actual text, just that we wrote and read the
266
                # 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
267
                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.
268
                if sha1 != local_sha1:
269
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
270
                                   'local: %s, bundle: %s' % 
271
                                   (inv_id, local_sha1, sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
272
                else:
273
                    count += 1
274
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
275
        if len(missing) > 0:
276
            # I don't know if this is an error yet
277
            warning('Not all revision hashes could be validated.'
278
                    ' Unable validate %d hashes' % len(missing))
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
279
        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.
280
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
281
    def _validate_inventory(self, inv, revision_id):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
282
        """At this point we should have generated the BundleTree,
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
283
        so build up an inventory, and make sure the hashes match.
284
        """
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
285
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
286
        assert inv is not None
287
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
288
        # 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.
289
        s = serializer_v5.write_inventory_to_string(inv)
290
        sha1 = sha_string(s)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
291
        # Target revision is the last entry in the real_revisions list
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
292
        rev = self.info.get_revision(revision_id)
293
        assert rev.revision_id == revision_id
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
294
        if sha1 != rev.inventory_sha1:
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
295
            open(',,bogus-inv', 'wb').write(s)
1185.82.61 by Aaron Bentley
Downgrade inventory mismatch to warning (source can be inaccurate)
296
            warning('Inventory sha hash mismatch for revision %s. %s'
297
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
298
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
299
    def get_bundle(self, repository):
300
        """Return the meta information, and a Bundle tree which can
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
301
        be used to populate the local stores and working tree, respectively.
302
        """
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
303
        return self.info, self.revision_tree(repository, self.info.target)
304
305
    def revision_tree(self, repository, revision_id, base=None):
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
306
        revision = self.info.get_revision(revision_id)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
307
        base = self.info.get_base(revision)
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
308
        assert base != revision_id
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
309
        self._validate_references_from_repository(repository)
1185.82.60 by Aaron Bentley
Only include revision ids in inventories that originally had them
310
        revision_info = self.info.get_revision_info(revision_id)
1185.82.67 by Aaron Bentley
Stop differentiating whether inventories have revision_ids
311
        inventory_revision_id = revision_id
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
312
        bundle_tree = BundleTree(repository.revision_tree(base), 
1185.82.60 by Aaron Bentley
Only include revision ids in inventories that originally had them
313
                                  inventory_revision_id)
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
314
        self._update_tree(bundle_tree, revision_id)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
315
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
316
        inv = bundle_tree.inventory
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
317
        self._validate_inventory(inv, revision_id)
318
        self._validate_revision(inv, revision_id)
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
319
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
320
        return bundle_tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
321
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
322
    def _next(self):
323
        """yield the next line, but secretly
324
        keep 1 extra line for peeking.
325
        """
326
        for line in self.from_file:
327
            last = self._next_line
328
            self._next_line = line
329
            if last is not None:
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
330
                #mutter('yielding line: %r' % last)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
331
                yield last
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.
332
        last = self._next_line
333
        self._next_line = None
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
334
        #mutter('yielding line: %r' % last)
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.
335
        yield last
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
336
337
    def _read_header(self):
338
        """Read the bzr header"""
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
339
        header = get_header()
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
340
        found = False
341
        for line in self._next():
342
            if found:
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
343
                # not all mailers will keep trailing whitespace
344
                if line == '#\n':
345
                    line = '# \n'
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
346
                if (not line.startswith('# ') or not line.endswith('\n')
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
347
                        or line[2:-1].decode('utf-8') != header[0]):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
348
                    raise MalformedHeader('Found a header, but it'
349
                        ' was improperly formatted')
350
                header.pop(0) # We read this line.
351
                if not header:
352
                    break # We found everything.
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
353
            elif (line.startswith('#') and line.endswith('\n')):
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
354
                line = line[1:-1].strip().decode('utf-8')
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
355
                if line[:len(header_str)] == header_str:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
356
                    if line == header[0]:
357
                        found = True
358
                    else:
359
                        raise MalformedHeader('Found what looks like'
360
                                ' a header, but did not match')
361
                    header.pop(0)
362
        else:
1185.82.139 by Aaron Bentley
Raise NotABundle when a non-bundle is supplied
363
            raise NotABundle('Did not find an opening header')
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
364
1185.82.47 by Aaron Bentley
Ensure all intended rev_ids end up in the revision
365
    def _read_revision_header(self):
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
366
        self.info.revisions.append(RevisionInfo(None))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
367
        for line in self._next():
368
            # The bzr header is terminated with a blank line
369
            # which does not start with '#'
1185.82.40 by Aaron Bentley
Started work on testing install_revisions/handling empty changesets
370
            if line is None or line == '\n':
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
371
                break
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
372
            self._handle_next(line)
373
374
    def _read_next_entry(self, line, indent=1):
375
        """Read in a key-value pair
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
376
        """
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
377
        if not line.startswith('#'):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
378
            raise MalformedHeader('Bzr header did not start with #')
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
379
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
380
        if line[:indent] == ' '*indent:
381
            line = line[indent:]
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
382
        if not line:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
383
            return None, None# Ignore blank lines
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
384
385
        loc = line.find(': ')
386
        if loc != -1:
387
            key = line[:loc]
388
            value = line[loc+2:]
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
389
            if not value:
1185.82.28 by Aaron Bentley
Got parent_id handling working
390
                value = self._read_many(indent=indent+2)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
391
        elif line[-1:] == ':':
392
            key = line[:-1]
1185.82.28 by Aaron Bentley
Got parent_id handling working
393
            value = self._read_many(indent=indent+2)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
394
        else:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
395
            raise MalformedHeader('While looking for key: value pairs,'
396
                    ' did not find the colon %r' % (line))
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
397
398
        key = key.replace(' ', '_')
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
399
        #mutter('found %s: %s' % (key, value))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
400
        return key, value
401
402
    def _handle_next(self, line):
1185.82.40 by Aaron Bentley
Started work on testing install_revisions/handling empty changesets
403
        if line is None:
404
            return
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
405
        key, value = self._read_next_entry(line, indent=1)
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
406
        mutter('_handle_next %r => %r' % (key, value))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
407
        if key is None:
408
            return
409
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
410
        revision_info = self.info.revisions[-1]
411
        if hasattr(revision_info, key):
412
            if getattr(revision_info, key) is None:
413
                setattr(revision_info, key, value)
414
            else:
415
                raise MalformedHeader('Duplicated Key: %s' % key)
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
416
        else:
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
417
            # What do we do with a key we don't recognize
418
            raise MalformedHeader('Unknown Key: "%s"' % key)
419
    
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
420
    def _read_many(self, indent):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
421
        """If a line ends with no entry, that means that it should be
422
        followed with multiple lines of values.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
423
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
424
        This detects the end of the list, because it will be a line that
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
425
        does not start properly indented.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
426
        """
427
        values = []
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
428
        start = '#' + (' '*indent)
429
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.
430
        if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
431
            return values
432
433
        for line in self._next():
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
434
            values.append(line[len(start):-1].decode('utf-8'))
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.
435
            if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
436
                break
437
        return values
438
439
    def _read_one_patch(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
440
        """Read in one patch, return the complete patch, along with
441
        the next line.
442
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
443
        :return: action, lines, do_continue
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
444
        """
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
445
        #mutter('_read_one_patch: %r' % self._next_line)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
446
        # Peek and see if there are no patches
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
447
        if self._next_line is None or self._next_line.startswith('#'):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
448
            return None, [], False
449
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
450
        first = True
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
451
        lines = []
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
452
        for line in self._next():
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
453
            if first:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
454
                if not line.startswith('==='):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
455
                    raise MalformedPatches('The first line of all patches'
0.5.100 by John Arbash Meinel
Switching from *** to ===
456
                        ' should be a bzr meta line "==="'
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
457
                        ': %r' % line)
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
458
                action = line[4:-1].decode('utf-8')
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
459
            elif line.startswith('... '):
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
460
                action += line[len('... '):-1].decode('utf-8')
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
461
462
            if (self._next_line is not None and 
463
                self._next_line.startswith('===')):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
464
                return action, lines, True
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
465
            elif self._next_line is None or self._next_line.startswith('#'):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
466
                return action, lines, False
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
467
468
            if first:
469
                first = False
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
470
            elif not line.startswith('... '):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
471
                lines.append(line)
472
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
473
        return action, lines, False
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
474
            
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
475
    def _read_patches(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
476
        do_continue = True
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
477
        revision_actions = []
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
478
        while do_continue:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
479
            action, lines, do_continue = self._read_one_patch()
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
480
            if action is not None:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
481
                revision_actions.append((action, lines))
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
482
        assert self.info.revisions[-1].tree_actions is None
483
        self.info.revisions[-1].tree_actions = revision_actions
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
484
485
    def _read_footer(self):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
486
        """Read the rest of the meta information.
487
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
488
        :param first_line:  The previous step iterates past what it
489
                            can handle. That extra line is given here.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
490
        """
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
491
        for line in self._next():
492
            self._handle_next(line)
1185.82.76 by Aaron Bentley
Avoid double-reads of each patch
493
            if not self._next_line.startswith('#'):
494
                self._next().next()
495
                break
496
            if self._next_line is None:
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
497
                break
498
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
499
    def _update_tree(self, bundle_tree, revision_id):
500
        """This fills out a BundleTree based on the information
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
501
        that was read in.
502
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
503
        :param bundle_tree: A BundleTree to update with the new information.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
504
        """
505
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
506
        def get_rev_id(last_changed, path, kind):
1185.82.93 by Aaron Bentley
Code cleanup
507
            if last_changed is not None:
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
508
                changed_revision_id = last_changed.decode('utf-8')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
509
            else:
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
510
                changed_revision_id = revision_id
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
511
            bundle_tree.note_last_changed(path, changed_revision_id)
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
512
            return changed_revision_id
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
513
1185.82.93 by Aaron Bentley
Code cleanup
514
        def extra_info(info, new_path):
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
515
            last_changed = None
1185.82.96 by Aaron Bentley
Got first binary test passing
516
            encoding = None
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
517
            for info_item in info:
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
518
                try:
519
                    name, value = info_item.split(':', 1)
520
                except ValueError:
521
                    raise 'Value %r has no colon' % info_item
1185.82.86 by Aaron Bentley
Regularized extra_info a bit
522
                if name == 'last-changed':
1185.82.93 by Aaron Bentley
Code cleanup
523
                    last_changed = value
1185.82.86 by Aaron Bentley
Regularized extra_info a bit
524
                elif name == 'executable':
525
                    assert value in ('yes', 'no'), value
526
                    val = (value == 'yes')
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
527
                    bundle_tree.note_executable(new_path, val)
1185.82.86 by Aaron Bentley
Regularized extra_info a bit
528
                elif name == 'target':
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
529
                    bundle_tree.note_target(new_path, value)
1185.82.96 by Aaron Bentley
Got first binary test passing
530
                elif name == 'encoding':
531
                    encoding = value
532
            return last_changed, encoding
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
533
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
534
        def do_patch(path, lines, encoding):
535
            if encoding is not None:
536
                assert encoding == 'base64'
537
                patch = base64.decodestring(''.join(lines))
538
            else:
539
                patch =  ''.join(lines)
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
540
            bundle_tree.note_patch(path, patch)
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
541
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
542
        def renamed(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
543
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
544
            if len(info) < 2:
545
                raise BzrError('renamed action lines need both a from and to'
546
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
547
            old_path = info[0]
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
548
            if info[1].startswith('=> '):
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
549
                new_path = info[1][3:]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
550
            else:
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
551
                new_path = info[1]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
552
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
553
            bundle_tree.note_rename(old_path, new_path)
1185.82.96 by Aaron Bentley
Got first binary test passing
554
            last_modified, encoding = extra_info(info[2:], new_path)
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
555
            revision = get_rev_id(last_modified, new_path, kind)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
556
            if lines:
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
557
                do_patch(new_path, lines, encoding)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
558
559
        def removed(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
560
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
561
            if len(info) > 1:
562
                # TODO: in the future we might allow file ids to be
563
                # given for removed entries
564
                raise BzrError('removed action lines should only have the path'
565
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
566
            path = info[0]
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
567
            bundle_tree.note_deletion(path)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
568
569
        def added(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
570
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
571
            if len(info) <= 1:
572
                raise BzrError('add action lines require the path and file id'
573
                        ': %r' % extra)
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
574
            elif len(info) > 5:
575
                raise BzrError('add action lines have fewer than 5 entries.'
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
576
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
577
            path = info[0]
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
578
            if not info[1].startswith('file-id:'):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
579
                raise BzrError('The file-id should follow the path for an add'
580
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
581
            file_id = info[1][8:]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
582
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
583
            bundle_tree.note_id(file_id, path, kind)
1185.82.119 by Aaron Bentley
Default execute bit to no for new files, directories, symlinks
584
            # this will be overridden in extra_info if executable is specified.
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
585
            bundle_tree.note_executable(path, False)
1185.82.96 by Aaron Bentley
Got first binary test passing
586
            last_changed, encoding = extra_info(info[2:], path)
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
587
            revision = get_rev_id(last_changed, path, kind)
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
588
            if kind == 'directory':
589
                return
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
590
            do_patch(path, lines, encoding)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
591
592
        def modified(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
593
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
594
            if len(info) < 1:
595
                raise BzrError('modified action lines have at least'
596
                        'the path in them: %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
597
            path = info[0]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
598
1185.82.96 by Aaron Bentley
Got first binary test passing
599
            last_modified, encoding = extra_info(info[1:], path)
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
600
            revision = get_rev_id(last_modified, path, kind)
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
601
            if lines:
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
602
                do_patch(path, lines, encoding)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
603
            
604
        valid_actions = {
605
            'renamed':renamed,
606
            'removed':removed,
607
            'added':added,
608
            'modified':modified
609
        }
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
610
        for action_line, lines in \
611
            self.info.get_revision_info(revision_id).tree_actions:
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
612
            first = action_line.find(' ')
613
            if first == -1:
614
                raise BzrError('Bogus action line'
615
                        ' (no opening space): %r' % action_line)
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.
616
            second = action_line.find(' ', first+1)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
617
            if second == -1:
618
                raise BzrError('Bogus action line'
619
                        ' (missing second space): %r' % action_line)
620
            action = action_line[:first]
621
            kind = action_line[first+1:second]
1185.82.87 by Aaron Bentley
Got symlink adding working
622
            if kind not in ('file', 'directory', 'symlink'):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
623
                raise BzrError('Bogus action line'
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.
624
                        ' (invalid object kind %r): %r' % (kind, action_line))
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
625
            extra = action_line[second+1:]
626
627
            if action not in valid_actions:
628
                raise BzrError('Bogus action line'
629
                        ' (unrecognized action): %r' % action_line)
630
            valid_actions[action](kind, extra, lines)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
631
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
632
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
633
class BundleTree(Tree):
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
634
    def __init__(self, base_tree, revision_id):
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
635
        self.base_tree = base_tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
636
        self._renamed = {} # Mapping from old_path => new_path
637
        self._renamed_r = {} # new_path => old_path
638
        self._new_id = {} # new_path => new_id
639
        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.
640
        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.
641
        self._last_changed = {} # new_id => revision_id
1185.82.66 by Aaron Bentley
Handle new executable files
642
        self._executable = {} # new_id => executable value
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
643
        self.patches = {}
1185.82.87 by Aaron Bentley
Got symlink adding working
644
        self._targets = {} # new path => new symlink target
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
645
        self.deleted = []
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
646
        self.contents_by_id = True
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
647
        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
648
        self._inventory = None
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
649
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
650
    def __str__(self):
651
        return pprint.pformat(self.__dict__)
652
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
653
    def note_rename(self, old_path, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
654
        """A file/directory has been renamed from old_path => new_path"""
1185.82.70 by Aaron Bentley
Handle renamed files better
655
        assert not self._renamed.has_key(new_path)
656
        assert not self._renamed_r.has_key(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
657
        self._renamed[new_path] = old_path
658
        self._renamed_r[old_path] = new_path
659
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
660
    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.
661
        """Files that don't exist in base need a new id."""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
662
        self._new_id[new_path] = new_id
663
        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.
664
        self._kinds[new_id] = kind
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
665
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
666
    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.
667
        if (self._last_changed.has_key(file_id)
668
                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.
669
            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
670
                    ': %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.
671
                                    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.
672
                                    revision_id))
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
673
        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
674
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
675
    def note_patch(self, new_path, patch):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
676
        """There is a patch for a given filename."""
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
677
        self.patches[new_path] = patch
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
678
1185.82.87 by Aaron Bentley
Got symlink adding working
679
    def note_target(self, new_path, target):
680
        """The symlink at the new path has the given target"""
681
        self._targets[new_path] = target
682
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
683
    def note_deletion(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
684
        """The file at old_path has been deleted."""
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
685
        self.deleted.append(old_path)
686
1185.82.66 by Aaron Bentley
Handle new executable files
687
    def note_executable(self, new_path, executable):
688
        self._executable[new_path] = executable
689
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
690
    def old_path(self, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
691
        """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
692
        assert new_path[:1] not in ('\\', '/')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
693
        old_path = self._renamed.get(new_path)
694
        if old_path is not None:
695
            return old_path
696
        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.
697
        # dirname is not '' doesn't work, because
698
        # dirname may be a unicode entry, and is
699
        # requires the objects to be identical
700
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
701
            old_dir = self.old_path(dirname)
702
            if old_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
703
                old_path = None
704
            else:
705
                old_path = os.path.join(old_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
706
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
707
            old_path = new_path
708
        #If the new path wasn't in renamed, the old one shouldn't be in
709
        #renamed_r
710
        if self._renamed_r.has_key(old_path):
711
            return None
712
        return old_path 
713
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
714
    def new_path(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
715
        """Get the new_path (path in the target_tree) for the file at old_path
716
        in the base tree.
717
        """
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
718
        assert old_path[:1] not in ('\\', '/')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
719
        new_path = self._renamed_r.get(old_path)
720
        if new_path is not None:
721
            return new_path
722
        if self._renamed.has_key(new_path):
723
            return None
724
        dirname,basename = os.path.split(old_path)
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
725
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
726
            new_dir = self.new_path(dirname)
727
            if new_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
728
                new_path = None
729
            else:
730
                new_path = os.path.join(new_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
731
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
732
            new_path = old_path
733
        #If the old path wasn't in renamed, the new one shouldn't be in
734
        #renamed_r
735
        if self._renamed.has_key(new_path):
736
            return None
737
        return new_path 
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
738
739
    def path2id(self, path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
740
        """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
741
        file_id = self._new_id.get(path)
742
        if file_id is not None:
743
            return file_id
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
744
        old_path = self.old_path(path)
745
        if old_path is None:
746
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
747
        if old_path in self.deleted:
748
            return None
0.5.66 by John Arbash Meinel
Refactoring, moving test code into test (switching back to assert is None)
749
        if hasattr(self.base_tree, 'path2id'):
750
            return self.base_tree.path2id(old_path)
751
        else:
752
            return self.base_tree.inventory.path2id(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
753
754
    def id2path(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
755
        """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
756
        path = self._new_id_r.get(file_id)
757
        if path is not None:
758
            return path
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
759
        old_path = self.base_tree.id2path(file_id)
760
        if old_path is None:
761
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
762
        if old_path in self.deleted:
763
            return None
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
764
        return self.new_path(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
765
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
766
    def old_contents_id(self, file_id):
1185.82.94 by Aaron Bentley
Remove old chatter
767
        """Return the id in the base_tree for the given file_id.
768
        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.
769
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
770
        if self.contents_by_id:
771
            if self.base_tree.has_id(file_id):
772
                return file_id
773
            else:
774
                return None
775
        new_path = self.id2path(file_id)
776
        return self.base_tree.path2id(new_path)
777
        
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
778
    def get_file(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
779
        """Return a file-like object containing the new contents of the
780
        file given by file_id.
781
782
        TODO:   It might be nice if this actually generated an entry
783
                in the text-store, so that the file contents would
784
                then be cached.
785
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
786
        base_id = self.old_contents_id(file_id)
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
787
        if base_id is not None:
788
            patch_original = self.base_tree.get_file(base_id)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
789
        else:
790
            patch_original = None
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
791
        file_patch = self.patches.get(self.id2path(file_id))
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
792
        if file_patch is None:
1185.82.42 by Aaron Bentley
Handle new directories properly
793
            if (patch_original is None and 
794
                self.get_kind(file_id) == 'directory'):
795
                return StringIO()
796
            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
797
            return patch_original
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
798
799
        assert not file_patch.startswith('\\'), \
800
            'Malformed patch for %s, %r' % (file_id, file_patch)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
801
        return patched_file(file_patch, patch_original)
802
1185.82.87 by Aaron Bentley
Got symlink adding working
803
    def get_symlink_target(self, file_id):
804
        new_path = self.id2path(file_id)
805
        try:
806
            return self._targets[new_path]
807
        except KeyError:
808
            return self.base_tree.get_symlink_target(file_id)
809
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
810
    def get_kind(self, file_id):
811
        if file_id in self._kinds:
812
            return self._kinds[file_id]
813
        return self.base_tree.inventory[file_id].kind
814
1185.82.66 by Aaron Bentley
Handle new executable files
815
    def is_executable(self, file_id):
1185.82.93 by Aaron Bentley
Code cleanup
816
        path = self.id2path(file_id)
817
        if path in self._executable:
818
            return self._executable[path]
1185.82.66 by Aaron Bentley
Handle new executable files
819
        else:
820
            return self.base_tree.inventory[file_id].executable
821
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
822
    def get_last_changed(self, file_id):
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
823
        path = self.id2path(file_id)
824
        if path in self._last_changed:
825
            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.
826
        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
827
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
828
    def get_size_and_sha1(self, file_id):
829
        """Return the size and sha1 hash of the given file id.
830
        If the file was not locally modified, this is extracted
831
        from the base_tree. Rather than re-reading the file.
832
        """
833
        new_path = self.id2path(file_id)
834
        if new_path is None:
835
            return None, None
836
        if new_path not in self.patches:
837
            # If the entry does not have a patch, then the
838
            # contents must be the same as in the base_tree
839
            ie = self.base_tree.inventory[file_id]
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
840
            if ie.text_size is None:
841
                return ie.text_size, ie.text_sha1
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
842
            return int(ie.text_size), ie.text_sha1
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
843
        fileobj = self.get_file(file_id)
844
        content = fileobj.read()
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
845
        return len(content), sha_string(content)
846
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
847
    def _get_inventory(self):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
848
        """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
849
850
        This need to be called before ever accessing self.inventory
851
        """
852
        from os.path import dirname, basename
853
854
        assert self.base_tree is not None
855
        base_inv = self.base_tree.inventory
856
        root_id = base_inv.root.file_id
857
        try:
858
            # New inventories have a unique root_id
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
859
            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
860
        except TypeError:
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
861
            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
862
863
        def add_entry(file_id):
864
            path = self.id2path(file_id)
865
            if path is None:
866
                return
867
            parent_path = dirname(path)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
868
            if parent_path == u'':
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
869
                parent_id = root_id
870
            else:
871
                parent_id = self.path2id(parent_path)
872
873
            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.
874
            revision_id = self.get_last_changed(file_id)
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
875
876
            name = basename(path)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
877
            if kind == 'directory':
878
                ie = InventoryDirectory(file_id, name, parent_id)
879
            elif kind == 'file':
880
                ie = InventoryFile(file_id, name, parent_id)
1185.82.66 by Aaron Bentley
Handle new executable files
881
                ie.executable = self.is_executable(file_id)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
882
            elif kind == 'symlink':
883
                ie = InventoryLink(file_id, name, parent_id)
1185.82.87 by Aaron Bentley
Got symlink adding working
884
                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.
885
            ie.revision = revision_id
886
1185.82.88 by Aaron Bentley
Get symlink modification, renames and deletion under test
887
            if kind in ('directory', 'symlink'):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
888
                ie.text_size, ie.text_sha1 = None, None
889
            else:
890
                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
891
            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
892
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
893
            inv.add(ie)
894
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
895
        sorted_entries = self.sorted_path_id()
896
        for path, file_id in sorted_entries:
897
            if file_id == inv.root.file_id:
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
898
                continue
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
899
            add_entry(file_id)
900
901
        return inv
902
903
    # Have to overload the inherited inventory property
904
    # because _get_inventory is only called in the parent.
905
    # Reading the docs, property() objects do not use
906
    # overloading, they use the function as it was defined
907
    # at that instant
908
    inventory = property(_get_inventory)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
909
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
910
    def __iter__(self):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
911
        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.
912
            yield entry.file_id
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
913
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
914
    def sorted_path_id(self):
915
        paths = []
916
        for result in self._new_id.iteritems():
917
            paths.append(result)
918
        for id in self.base_tree:
919
            path = self.id2path(id)
920
            if path is None:
921
                continue
922
            paths.append((path, id))
923
        paths.sort()
924
        return paths
925
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
926
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
927
def patched_file(file_patch, original):
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
928
    """Produce a file-like object with the patched version of a text"""
1185.82.13 by Aaron Bentley
Got old changeset tests running
929
    from bzrlib.patches import iter_patched
930
    from bzrlib.iterablefile import IterableFile
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
931
    if file_patch == "":
932
        return IterableFile(())
933
    return IterableFile(iter_patched(original, file_patch.splitlines(True)))