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