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