/brz/remove-bazaar

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