~bzr-pqm/bzr/bzr.dev

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