/brz/remove-bazaar

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