/brz/remove-bazaar

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