~bzr-pqm/bzr/bzr.dev

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