/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
1
# Copyright (C) 2006 by Canonical Ltd
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
1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
24
from bzrlib.bundle.common import get_header, header_str
25
import bzrlib.errors
1185.82.131 by Aaron Bentley
Move BadBundle error (and subclasses) to errors.py
26
from bzrlib.errors import (TestamentMismatch, BzrError, 
1185.82.139 by Aaron Bentley
Raise NotABundle when a non-bundle is supplied
27
                           MalformedHeader, MalformedPatches, NotABundle)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
28
from bzrlib.inventory import (Inventory, InventoryEntry,
29
                              InventoryDirectory, InventoryFile,
30
                              InventoryLink)
1185.82.78 by Aaron Bentley
Cleanups
31
from bzrlib.osutils import sha_file, sha_string
32
from bzrlib.revision import Revision, NULL_REVISION
1185.82.116 by Aaron Bentley
Introduce StrictTestament, get test failing for the right reasons
33
from bzrlib.testament import StrictTestament
1185.82.78 by Aaron Bentley
Cleanups
34
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
35
import bzrlib.transport
1185.82.78 by Aaron Bentley
Cleanups
36
from bzrlib.tree import Tree
1711.3.2 by John Arbash Meinel
Add the read_bundle_from_url command, which handles lots of exceptions
37
import bzrlib.urlutils
1185.82.78 by Aaron Bentley
Cleanups
38
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.
39
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
40
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
41
class RevisionInfo(object):
42
    """Gets filled out for each revision object that is read.
43
    """
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
44
    def __init__(self, revision_id):
45
        self.revision_id = revision_id
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
46
        self.sha1 = None
47
        self.committer = None
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
48
        self.date = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
49
        self.timestamp = None
50
        self.timezone = None
51
        self.inventory_sha1 = None
52
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
53
        self.parent_ids = None
1185.82.74 by Aaron Bentley
Allow custom base for any revision
54
        self.base_id = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
55
        self.message = None
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
56
        self.properties = None
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
57
        self.tree_actions = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
58
59
    def __str__(self):
60
        return pprint.pformat(self.__dict__)
61
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
62
    def as_revision(self):
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
63
        rev = Revision(revision_id=self.revision_id,
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
64
            committer=self.committer,
65
            timestamp=float(self.timestamp),
66
            timezone=int(self.timezone),
67
            inventory_sha1=self.inventory_sha1,
68
            message='\n'.join(self.message))
69
1185.82.28 by Aaron Bentley
Got parent_id handling working
70
        if self.parent_ids:
71
            rev.parent_ids.extend(self.parent_ids)
1185.82.35 by Aaron Bentley
Read revision properties
72
1185.82.59 by Aaron Bentley
Behave properly when there are no properties in a revision
73
        if self.properties:
74
            for property in self.properties:
75
                key_end = property.find(': ')
76
                assert key_end is not None
77
                key = property[:key_end].encode('utf-8')
78
                value = property[key_end+2:].encode('utf-8')
79
                rev.properties[key] = value
1185.82.35 by Aaron Bentley
Read revision properties
80
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
81
        return rev
82
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
83
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
84
class BundleInfo(object):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
85
    """This contains the meta information. Stuff that allows you to
86
    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.
87
    """
88
    def __init__(self):
89
        self.committer = None
90
        self.date = None
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
91
        self.message = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
92
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
93
        # A list of RevisionInfo objects
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
94
        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.
95
96
        # The next entries are created during complete_info() and
97
        # other post-read functions.
98
99
        # A list of real Revision objects
100
        self.real_revisions = []
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
101
102
        self.timestamp = None
103
        self.timezone = None
0.5.15 by John Arbash Meinel
Created an apply-changeset function, and modified output for better parsing.
104
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
105
    def __str__(self):
106
        return pprint.pformat(self.__dict__)
107
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
108
    def complete_info(self):
109
        """This makes sure that all information is properly
110
        split up, based on the assumptions that can be made
111
        when information is missing.
112
        """
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
113
        from bzrlib.bundle.common 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.
114
        # Put in all of the guessable information.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
115
        if not self.timestamp and self.date:
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
116
            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.
117
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.
118
        self.real_revisions = []
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
119
        for rev in self.revisions:
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
120
            if rev.timestamp is None:
121
                if rev.date is not None:
122
                    rev.timestamp, rev.timezone = \
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
123
                            unpack_highres_date(rev.date)
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
124
                else:
125
                    rev.timestamp = self.timestamp
126
                    rev.timezone = self.timezone
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
127
            if rev.message is None and self.message:
128
                rev.message = self.message
129
            if rev.committer is None and self.committer:
130
                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.
131
            self.real_revisions.append(rev.as_revision())
132
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
133
    def get_base(self, revision):
1185.82.74 by Aaron Bentley
Allow custom base for any revision
134
        revision_info = self.get_revision_info(revision.revision_id)
135
        if revision_info.base_id is not None:
136
            if revision_info.base_id == NULL_REVISION:
137
                return None
138
            else:
139
                return revision_info.base_id
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
140
        if len(revision.parent_ids) == 0:
141
            # There is no base listed, and
142
            # the lowest revision doesn't have a parent
143
            # so this is probably against the empty tree
144
            # and thus base truly is None
145
            return None
146
        else:
1185.82.73 by Aaron Bentley
Use rightmost parent always
147
            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.
148
0.5.67 by John Arbash Meinel
Working on apply_changeset
149
    def _get_target(self):
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
150
        """Return the target revision."""
0.5.67 by John Arbash Meinel
Working on apply_changeset
151
        if len(self.real_revisions) > 0:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
152
            return self.real_revisions[0].revision_id
0.5.67 by John Arbash Meinel
Working on apply_changeset
153
        elif len(self.revisions) > 0:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
154
            return self.revisions[0].revision_id
0.5.67 by John Arbash Meinel
Working on apply_changeset
155
        return None
156
157
    target = property(_get_target, doc='The target revision id')
158
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
159
    def get_revision(self, revision_id):
160
        for r in self.real_revisions:
161
            if r.revision_id == revision_id:
162
                return r
163
        raise KeyError(revision_id)
164
165
    def get_revision_info(self, revision_id):
166
        for r in self.revisions:
167
            if r.revision_id == revision_id:
168
                return r
169
        raise KeyError(revision_id)
170
171
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
172
class BundleReader(object):
173
    """This class reads in a bundle from a file, and returns
174
    a Bundle object, which can then be applied against a tree.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
175
    """
176
    def __init__(self, from_file):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
177
        """Read in the bundle from the file.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
178
179
        :param from_file: A file-like object (must have iterator support).
180
        """
181
        object.__init__(self)
1185.82.50 by Aaron Bentley
Avoid overwriting action lines
182
        self.from_file = iter(from_file)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
183
        self._next_line = 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.
184
        
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
185
        self.info = BundleInfo()
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
186
        # We put the actual inventory ids in the footer, so that the patch
187
        # is easier to read for humans.
188
        # Unfortunately, that means we need to read everything before we
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
189
        # can create a proper bundle.
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
190
        self._read()
191
        self._validate()
192
193
    def _read(self):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
194
        self._read_header()
1185.82.47 by Aaron Bentley
Ensure all intended rev_ids end up in the revision
195
        while self._next_line is not None:
196
            self._read_revision_header()
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
197
            if self._next_line is None:
198
                break
1185.82.47 by Aaron Bentley
Ensure all intended rev_ids end up in the revision
199
            self._read_patches()
200
            self._read_footer()
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
201
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
202
    def _validate(self):
203
        """Make sure that the information read in makes sense
204
        and passes appropriate checksums.
205
        """
206
        # Fill in all the missing blanks for the revisions
207
        # and generate the real_revisions list.
208
        self.info.complete_info()
209
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
210
    def _validate_revision(self, inventory, revision_id):
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
211
        """Make sure all revision entries match their checksum."""
212
213
        # This is a mapping from each revision id to it's sha hash
214
        rev_to_sha1 = {}
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
215
        
216
        rev = self.info.get_revision(revision_id)
217
        rev_info = self.info.get_revision_info(revision_id)
218
        assert rev.revision_id == rev_info.revision_id
1185.82.50 by Aaron Bentley
Avoid overwriting action lines
219
        assert rev.revision_id == revision_id
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
220
        sha1 = StrictTestament(rev, inventory).as_sha1()
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
221
        if sha1 != rev_info.sha1:
1185.82.118 by Aaron Bentley
Ensure that StrictTestament handles execute bit differences
222
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
223
        if rev_to_sha1.has_key(rev.revision_id):
224
            raise BzrError('Revision {%s} given twice in the list'
225
                    % (rev.revision_id))
226
        rev_to_sha1[rev.revision_id] = sha1
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
227
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
228
    def _validate_references_from_repository(self, repository):
229
        """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.
230
        revisions we care about, go through and validate all of them
231
        that we can.
232
        """
233
        rev_to_sha = {}
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
234
        inv_to_sha = {}
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
235
        def add_sha(d, revision_id, sha1):
236
            if revision_id is None:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
237
                if sha1 is not None:
238
                    raise BzrError('A Null revision should always'
239
                        'have a null sha1 hash')
240
                return
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
241
            if revision_id in d:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
242
                # This really should have been validated as part
243
                # 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.
244
                if sha1 != d[revision_id]:
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
245
                    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.
246
                            ' sha hashes %s != %s' % (revision_id,
247
                                sha1, d[revision_id]))
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
248
            else:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
249
                d[revision_id] = sha1
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
250
251
        # All of the contained revisions were checked
252
        # in _validate_revisions
253
        checked = {}
254
        for rev_info in self.info.revisions:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
255
            checked[rev_info.revision_id] = True
256
            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.
257
                
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
258
        for (rev, rev_info) in zip(self.info.real_revisions, self.info.revisions):
259
            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.
260
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
261
        count = 0
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
262
        missing = {}
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
263
        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
264
            if repository.has_revision(revision_id):
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
265
                testament = StrictTestament.from_revision(repository, 
266
                                                          revision_id)
267
                local_sha1 = testament.as_sha1()
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
268
                if sha1 != local_sha1:
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
269
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
270
                            '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.
271
                else:
272
                    count += 1
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
273
            elif revision_id not in checked:
274
                missing[revision_id] = sha1
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
275
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
276
        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
277
            if repository.has_revision(inv_id):
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
278
                # Note: branch.get_inventory_sha1() just returns the value that
279
                # is stored in the revision text, and that value may be out
280
                # of date. This is bogus, because that means we aren't
281
                # validating the actual text, just that we wrote and read the
282
                # 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
283
                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.
284
                if sha1 != local_sha1:
285
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
286
                                   'local: %s, bundle: %s' % 
287
                                   (inv_id, local_sha1, sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
288
                else:
289
                    count += 1
290
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
291
        if len(missing) > 0:
292
            # I don't know if this is an error yet
293
            warning('Not all revision hashes could be validated.'
294
                    ' Unable validate %d hashes' % len(missing))
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
295
        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.
296
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
297
    def _validate_inventory(self, inv, revision_id):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
298
        """At this point we should have generated the BundleTree,
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
299
        so build up an inventory, and make sure the hashes match.
300
        """
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
301
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
302
        assert inv is not None
303
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
304
        # 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.
305
        s = serializer_v5.write_inventory_to_string(inv)
306
        sha1 = sha_string(s)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
307
        # Target revision is the last entry in the real_revisions list
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
308
        rev = self.info.get_revision(revision_id)
309
        assert rev.revision_id == revision_id
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
310
        if sha1 != rev.inventory_sha1:
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
311
            open(',,bogus-inv', 'wb').write(s)
1185.82.61 by Aaron Bentley
Downgrade inventory mismatch to warning (source can be inaccurate)
312
            warning('Inventory sha hash mismatch for revision %s. %s'
313
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
314
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
315
    def get_bundle(self, repository):
316
        """Return the meta information, and a Bundle tree which can
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
317
        be used to populate the local stores and working tree, respectively.
318
        """
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
319
        return self.info, self.revision_tree(repository, self.info.target)
320
321
    def revision_tree(self, repository, revision_id, base=None):
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
322
        revision = self.info.get_revision(revision_id)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
323
        base = self.info.get_base(revision)
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
324
        assert base != revision_id
1185.82.15 by Aaron Bentley
Disabled validate_revisions (needs info it doesn't have), updated API to repos
325
        self._validate_references_from_repository(repository)
1185.82.60 by Aaron Bentley
Only include revision ids in inventories that originally had them
326
        revision_info = self.info.get_revision_info(revision_id)
1185.82.67 by Aaron Bentley
Stop differentiating whether inventories have revision_ids
327
        inventory_revision_id = revision_id
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
328
        bundle_tree = BundleTree(repository.revision_tree(base), 
1185.82.60 by Aaron Bentley
Only include revision ids in inventories that originally had them
329
                                  inventory_revision_id)
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
330
        self._update_tree(bundle_tree, revision_id)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
331
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
332
        inv = bundle_tree.inventory
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
333
        self._validate_inventory(inv, revision_id)
334
        self._validate_revision(inv, revision_id)
0.5.63 by John Arbash Meinel
Moving the validation into part of the reading.
335
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
336
        return bundle_tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
337
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
338
    def _next(self):
339
        """yield the next line, but secretly
340
        keep 1 extra line for peeking.
341
        """
342
        for line in self.from_file:
343
            last = self._next_line
344
            self._next_line = line
345
            if last is not None:
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
346
                #mutter('yielding line: %r' % last)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
347
                yield last
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.
348
        last = self._next_line
349
        self._next_line = None
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
350
        #mutter('yielding line: %r' % last)
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.
351
        yield last
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
352
353
    def _read_header(self):
354
        """Read the bzr header"""
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
355
        header = get_header()
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
356
        found = False
357
        for line in self._next():
358
            if found:
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
359
                # not all mailers will keep trailing whitespace
360
                if line == '#\n':
361
                    line = '# \n'
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
362
                if (not line.startswith('# ') or not line.endswith('\n')
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
363
                        or line[2:-1].decode('utf-8') != header[0]):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
364
                    raise MalformedHeader('Found a header, but it'
365
                        ' was improperly formatted')
366
                header.pop(0) # We read this line.
367
                if not header:
368
                    break # We found everything.
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
369
            elif (line.startswith('#') and line.endswith('\n')):
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
370
                line = line[1:-1].strip().decode('utf-8')
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
371
                if line[:len(header_str)] == header_str:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
372
                    if line == header[0]:
373
                        found = True
374
                    else:
375
                        raise MalformedHeader('Found what looks like'
376
                                ' a header, but did not match')
377
                    header.pop(0)
378
        else:
1185.82.139 by Aaron Bentley
Raise NotABundle when a non-bundle is supplied
379
            raise NotABundle('Did not find an opening header')
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
380
1185.82.47 by Aaron Bentley
Ensure all intended rev_ids end up in the revision
381
    def _read_revision_header(self):
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
382
        self.info.revisions.append(RevisionInfo(None))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
383
        for line in self._next():
384
            # The bzr header is terminated with a blank line
385
            # which does not start with '#'
1185.82.40 by Aaron Bentley
Started work on testing install_revisions/handling empty changesets
386
            if line is None or line == '\n':
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
387
                break
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
388
            self._handle_next(line)
389
390
    def _read_next_entry(self, line, indent=1):
391
        """Read in a key-value pair
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
392
        """
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
393
        if not line.startswith('#'):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
394
            raise MalformedHeader('Bzr header did not start with #')
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
395
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
396
        if line[:indent] == ' '*indent:
397
            line = line[indent:]
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
398
        if not line:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
399
            return None, None# Ignore blank lines
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
400
401
        loc = line.find(': ')
402
        if loc != -1:
403
            key = line[:loc]
404
            value = line[loc+2:]
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
405
            if not value:
1185.82.28 by Aaron Bentley
Got parent_id handling working
406
                value = self._read_many(indent=indent+2)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
407
        elif line[-1:] == ':':
408
            key = line[:-1]
1185.82.28 by Aaron Bentley
Got parent_id handling working
409
            value = self._read_many(indent=indent+2)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
410
        else:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
411
            raise MalformedHeader('While looking for key: value pairs,'
412
                    ' did not find the colon %r' % (line))
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
413
414
        key = key.replace(' ', '_')
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
415
        #mutter('found %s: %s' % (key, value))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
416
        return key, value
417
418
    def _handle_next(self, line):
1185.82.40 by Aaron Bentley
Started work on testing install_revisions/handling empty changesets
419
        if line is None:
420
            return
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
421
        key, value = self._read_next_entry(line, indent=1)
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
422
        mutter('_handle_next %r => %r' % (key, value))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
423
        if key is None:
424
            return
425
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
426
        revision_info = self.info.revisions[-1]
427
        if hasattr(revision_info, key):
428
            if getattr(revision_info, key) is None:
429
                setattr(revision_info, key, value)
430
            else:
431
                raise MalformedHeader('Duplicated Key: %s' % key)
1185.82.27 by Aaron Bentley
Fixed most revision attribute handling
432
        else:
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
433
            # What do we do with a key we don't recognize
434
            raise MalformedHeader('Unknown Key: "%s"' % key)
435
    
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
436
    def _read_many(self, indent):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
437
        """If a line ends with no entry, that means that it should be
438
        followed with multiple lines of values.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
439
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
440
        This detects the end of the list, because it will be a line that
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
441
        does not start properly indented.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
442
        """
443
        values = []
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
444
        start = '#' + (' '*indent)
445
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.
446
        if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
447
            return values
448
449
        for line in self._next():
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
450
            values.append(line[len(start):-1].decode('utf-8'))
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.
451
            if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
452
                break
453
        return values
454
455
    def _read_one_patch(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
456
        """Read in one patch, return the complete patch, along with
457
        the next line.
458
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
459
        :return: action, lines, do_continue
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
460
        """
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
461
        #mutter('_read_one_patch: %r' % self._next_line)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
462
        # Peek and see if there are no patches
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
463
        if self._next_line is None or self._next_line.startswith('#'):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
464
            return None, [], False
465
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
466
        first = True
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
467
        lines = []
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
468
        for line in self._next():
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
469
            if first:
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
470
                if not line.startswith('==='):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
471
                    raise MalformedPatches('The first line of all patches'
0.5.100 by John Arbash Meinel
Switching from *** to ===
472
                        ' should be a bzr meta line "==="'
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
473
                        ': %r' % line)
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
474
                action = line[4:-1].decode('utf-8')
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
475
            elif line.startswith('... '):
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
476
                action += line[len('... '):-1].decode('utf-8')
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
477
478
            if (self._next_line is not None and 
479
                self._next_line.startswith('===')):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
480
                return action, lines, True
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
481
            elif self._next_line is None or self._next_line.startswith('#'):
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
482
                return action, lines, False
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
483
484
            if first:
485
                first = False
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
486
            elif not line.startswith('... '):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
487
                lines.append(line)
488
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
489
        return action, lines, False
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
490
            
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
491
    def _read_patches(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
492
        do_continue = True
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
493
        revision_actions = []
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
494
        while do_continue:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
495
            action, lines, do_continue = self._read_one_patch()
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
496
            if action is not None:
1185.82.48 by Aaron Bentley
Inching closer to supporting multiple revisions per changeset
497
                revision_actions.append((action, lines))
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
498
        assert self.info.revisions[-1].tree_actions is None
499
        self.info.revisions[-1].tree_actions = revision_actions
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
500
501
    def _read_footer(self):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
502
        """Read the rest of the meta information.
503
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
504
        :param first_line:  The previous step iterates past what it
505
                            can handle. That extra line is given here.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
506
        """
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
507
        for line in self._next():
508
            self._handle_next(line)
1185.82.76 by Aaron Bentley
Avoid double-reads of each patch
509
            if not self._next_line.startswith('#'):
510
                self._next().next()
511
                break
512
            if self._next_line is None:
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
513
                break
514
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
515
    def _update_tree(self, bundle_tree, revision_id):
516
        """This fills out a BundleTree based on the information
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
517
        that was read in.
518
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
519
        :param bundle_tree: A BundleTree to update with the new information.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
520
        """
521
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
522
        def get_rev_id(last_changed, path, kind):
1185.82.93 by Aaron Bentley
Code cleanup
523
            if last_changed is not None:
1185.82.124 by Aaron Bentley
Clean-up, remove utf-8 pass-through calls
524
                changed_revision_id = last_changed.decode('utf-8')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
525
            else:
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
526
                changed_revision_id = revision_id
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
527
            bundle_tree.note_last_changed(path, changed_revision_id)
1185.82.49 by Aaron Bentley
SPOT fixes, fix inventory validation
528
            return changed_revision_id
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
529
1185.82.93 by Aaron Bentley
Code cleanup
530
        def extra_info(info, new_path):
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
531
            last_changed = None
1185.82.96 by Aaron Bentley
Got first binary test passing
532
            encoding = None
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
533
            for info_item in info:
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
534
                try:
535
                    name, value = info_item.split(':', 1)
536
                except ValueError:
537
                    raise 'Value %r has no colon' % info_item
1185.82.86 by Aaron Bentley
Regularized extra_info a bit
538
                if name == 'last-changed':
1185.82.93 by Aaron Bentley
Code cleanup
539
                    last_changed = value
1185.82.86 by Aaron Bentley
Regularized extra_info a bit
540
                elif name == 'executable':
541
                    assert value in ('yes', 'no'), value
542
                    val = (value == 'yes')
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
543
                    bundle_tree.note_executable(new_path, val)
1185.82.86 by Aaron Bentley
Regularized extra_info a bit
544
                elif name == 'target':
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
545
                    bundle_tree.note_target(new_path, value)
1185.82.96 by Aaron Bentley
Got first binary test passing
546
                elif name == 'encoding':
547
                    encoding = value
548
            return last_changed, encoding
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
549
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
550
        def do_patch(path, lines, encoding):
551
            if encoding is not None:
552
                assert encoding == 'base64'
553
                patch = base64.decodestring(''.join(lines))
554
            else:
555
                patch =  ''.join(lines)
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
556
            bundle_tree.note_patch(path, patch)
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
557
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
558
        def renamed(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
559
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
560
            if len(info) < 2:
561
                raise BzrError('renamed action lines need both a from and to'
562
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
563
            old_path = info[0]
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
564
            if info[1].startswith('=> '):
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
565
                new_path = info[1][3:]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
566
            else:
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
567
                new_path = info[1]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
568
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
569
            bundle_tree.note_rename(old_path, new_path)
1185.82.96 by Aaron Bentley
Got first binary test passing
570
            last_modified, encoding = extra_info(info[2:], new_path)
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
571
            revision = get_rev_id(last_modified, new_path, kind)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
572
            if lines:
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
573
                do_patch(new_path, lines, encoding)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
574
575
        def removed(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
576
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
577
            if len(info) > 1:
578
                # TODO: in the future we might allow file ids to be
579
                # given for removed entries
580
                raise BzrError('removed action lines should only have the path'
581
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
582
            path = info[0]
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
583
            bundle_tree.note_deletion(path)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
584
585
        def added(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
586
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
587
            if len(info) <= 1:
588
                raise BzrError('add action lines require the path and file id'
589
                        ': %r' % extra)
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
590
            elif len(info) > 5:
591
                raise BzrError('add action lines have fewer than 5 entries.'
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
592
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
593
            path = info[0]
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
594
            if not info[1].startswith('file-id:'):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
595
                raise BzrError('The file-id should follow the path for an add'
596
                        ': %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
597
            file_id = info[1][8:]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
598
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
599
            bundle_tree.note_id(file_id, path, kind)
1185.82.119 by Aaron Bentley
Default execute bit to no for new files, directories, symlinks
600
            # this will be overridden in extra_info if executable is specified.
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
601
            bundle_tree.note_executable(path, False)
1185.82.96 by Aaron Bentley
Got first binary test passing
602
            last_changed, encoding = extra_info(info[2:], path)
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
603
            revision = get_rev_id(last_changed, path, kind)
0.5.117 by John Arbash Meinel
Almost there. Just need to track down a few remaining bugs.
604
            if kind == 'directory':
605
                return
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
606
            do_patch(path, lines, encoding)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
607
608
        def modified(kind, extra, lines):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
609
            info = extra.split(' // ')
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
610
            if len(info) < 1:
611
                raise BzrError('modified action lines have at least'
612
                        'the path in them: %r' % extra)
0.5.87 by John Arbash Meinel
Handling international characters, added more test cases.
613
            path = info[0]
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
614
1185.82.96 by Aaron Bentley
Got first binary test passing
615
            last_modified, encoding = extra_info(info[1:], path)
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
616
            revision = get_rev_id(last_modified, path, kind)
1185.82.68 by Aaron Bentley
Handle execute bit on modified files
617
            if lines:
1185.82.97 by Aaron Bentley
Got binary files working for adds, renames, modifications
618
                do_patch(path, lines, encoding)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
619
            
620
        valid_actions = {
621
            'renamed':renamed,
622
            'removed':removed,
623
            'added':added,
624
            'modified':modified
625
        }
1185.82.77 by Aaron Bentley
Move tree actions to RevisionInfo
626
        for action_line, lines in \
627
            self.info.get_revision_info(revision_id).tree_actions:
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
628
            first = action_line.find(' ')
629
            if first == -1:
630
                raise BzrError('Bogus action line'
631
                        ' (no opening space): %r' % action_line)
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.
632
            second = action_line.find(' ', first+1)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
633
            if second == -1:
634
                raise BzrError('Bogus action line'
635
                        ' (missing second space): %r' % action_line)
636
            action = action_line[:first]
637
            kind = action_line[first+1:second]
1185.82.87 by Aaron Bentley
Got symlink adding working
638
            if kind not in ('file', 'directory', 'symlink'):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
639
                raise BzrError('Bogus action line'
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.
640
                        ' (invalid object kind %r): %r' % (kind, action_line))
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
641
            extra = action_line[second+1:]
642
643
            if action not in valid_actions:
644
                raise BzrError('Bogus action line'
645
                        ' (unrecognized action): %r' % action_line)
646
            valid_actions[action](kind, extra, lines)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
647
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
648
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
649
class BundleTree(Tree):
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
650
    def __init__(self, base_tree, revision_id):
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
651
        self.base_tree = base_tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
652
        self._renamed = {} # Mapping from old_path => new_path
653
        self._renamed_r = {} # new_path => old_path
654
        self._new_id = {} # new_path => new_id
655
        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.
656
        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.
657
        self._last_changed = {} # new_id => revision_id
1185.82.66 by Aaron Bentley
Handle new executable files
658
        self._executable = {} # new_id => executable value
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
659
        self.patches = {}
1185.82.87 by Aaron Bentley
Got symlink adding working
660
        self._targets = {} # new path => new symlink target
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
661
        self.deleted = []
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
662
        self.contents_by_id = True
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
663
        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
664
        self._inventory = None
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
665
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
666
    def __str__(self):
667
        return pprint.pformat(self.__dict__)
668
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
669
    def note_rename(self, old_path, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
670
        """A file/directory has been renamed from old_path => new_path"""
1185.82.70 by Aaron Bentley
Handle renamed files better
671
        assert not self._renamed.has_key(new_path)
672
        assert not self._renamed_r.has_key(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
673
        self._renamed[new_path] = old_path
674
        self._renamed_r[old_path] = new_path
675
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
676
    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.
677
        """Files that don't exist in base need a new id."""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
678
        self._new_id[new_path] = new_id
679
        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.
680
        self._kinds[new_id] = kind
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
681
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
682
    def note_last_changed(self, file_id, revision_id):
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
683
        if (self._last_changed.has_key(file_id)
684
                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.
685
            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
686
                    ': %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.
687
                                    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.
688
                                    revision_id))
0.5.118 by John Arbash Meinel
Got most of test_changeset to work. Still needs work for Aaron's test code.
689
        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
690
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
691
    def note_patch(self, new_path, patch):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
692
        """There is a patch for a given filename."""
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
693
        self.patches[new_path] = patch
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
694
1185.82.87 by Aaron Bentley
Got symlink adding working
695
    def note_target(self, new_path, target):
696
        """The symlink at the new path has the given target"""
697
        self._targets[new_path] = target
698
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
699
    def note_deletion(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
700
        """The file at old_path has been deleted."""
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
701
        self.deleted.append(old_path)
702
1185.82.66 by Aaron Bentley
Handle new executable files
703
    def note_executable(self, new_path, executable):
704
        self._executable[new_path] = executable
705
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
706
    def old_path(self, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
707
        """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
708
        assert new_path[:1] not in ('\\', '/')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
709
        old_path = self._renamed.get(new_path)
710
        if old_path is not None:
711
            return old_path
712
        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.
713
        # dirname is not '' doesn't work, because
714
        # dirname may be a unicode entry, and is
715
        # requires the objects to be identical
716
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
717
            old_dir = self.old_path(dirname)
718
            if old_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
719
                old_path = None
720
            else:
721
                old_path = os.path.join(old_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
722
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
723
            old_path = new_path
724
        #If the new path wasn't in renamed, the old one shouldn't be in
725
        #renamed_r
726
        if self._renamed_r.has_key(old_path):
727
            return None
728
        return old_path 
729
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
730
    def new_path(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
731
        """Get the new_path (path in the target_tree) for the file at old_path
732
        in the base tree.
733
        """
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
734
        assert old_path[:1] not in ('\\', '/')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
735
        new_path = self._renamed_r.get(old_path)
736
        if new_path is not None:
737
            return new_path
738
        if self._renamed.has_key(new_path):
739
            return None
740
        dirname,basename = os.path.split(old_path)
0.5.81 by John Arbash Meinel
Cleaning up from pychecker.
741
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
742
            new_dir = self.new_path(dirname)
743
            if new_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
744
                new_path = None
745
            else:
746
                new_path = os.path.join(new_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
747
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
748
            new_path = old_path
749
        #If the old path wasn't in renamed, the new one shouldn't be in
750
        #renamed_r
751
        if self._renamed.has_key(new_path):
752
            return None
753
        return new_path 
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
754
755
    def path2id(self, path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
756
        """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
757
        file_id = self._new_id.get(path)
758
        if file_id is not None:
759
            return file_id
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
760
        old_path = self.old_path(path)
761
        if old_path is None:
762
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
763
        if old_path in self.deleted:
764
            return None
0.5.66 by John Arbash Meinel
Refactoring, moving test code into test (switching back to assert is None)
765
        if hasattr(self.base_tree, 'path2id'):
766
            return self.base_tree.path2id(old_path)
767
        else:
768
            return self.base_tree.inventory.path2id(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
769
770
    def id2path(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
771
        """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
772
        path = self._new_id_r.get(file_id)
773
        if path is not None:
774
            return path
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
775
        old_path = self.base_tree.id2path(file_id)
776
        if old_path is None:
777
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
778
        if old_path in self.deleted:
779
            return None
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
780
        return self.new_path(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
781
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
782
    def old_contents_id(self, file_id):
1185.82.94 by Aaron Bentley
Remove old chatter
783
        """Return the id in the base_tree for the given file_id.
784
        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.
785
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
786
        if self.contents_by_id:
787
            if self.base_tree.has_id(file_id):
788
                return file_id
789
            else:
790
                return None
791
        new_path = self.id2path(file_id)
792
        return self.base_tree.path2id(new_path)
793
        
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
794
    def get_file(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
795
        """Return a file-like object containing the new contents of the
796
        file given by file_id.
797
798
        TODO:   It might be nice if this actually generated an entry
799
                in the text-store, so that the file contents would
800
                then be cached.
801
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
802
        base_id = self.old_contents_id(file_id)
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
803
        if base_id is not None:
804
            patch_original = self.base_tree.get_file(base_id)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
805
        else:
806
            patch_original = None
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
807
        file_patch = self.patches.get(self.id2path(file_id))
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
808
        if file_patch is None:
1185.82.42 by Aaron Bentley
Handle new directories properly
809
            if (patch_original is None and 
810
                self.get_kind(file_id) == 'directory'):
811
                return StringIO()
812
            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
813
            return patch_original
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
814
815
        assert not file_patch.startswith('\\'), \
816
            'Malformed patch for %s, %r' % (file_id, file_patch)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
817
        return patched_file(file_patch, patch_original)
818
1185.82.87 by Aaron Bentley
Got symlink adding working
819
    def get_symlink_target(self, file_id):
820
        new_path = self.id2path(file_id)
821
        try:
822
            return self._targets[new_path]
823
        except KeyError:
824
            return self.base_tree.get_symlink_target(file_id)
825
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
826
    def get_kind(self, file_id):
827
        if file_id in self._kinds:
828
            return self._kinds[file_id]
829
        return self.base_tree.inventory[file_id].kind
830
1185.82.66 by Aaron Bentley
Handle new executable files
831
    def is_executable(self, file_id):
1185.82.93 by Aaron Bentley
Code cleanup
832
        path = self.id2path(file_id)
833
        if path in self._executable:
834
            return self._executable[path]
1185.82.66 by Aaron Bentley
Handle new executable files
835
        else:
836
            return self.base_tree.inventory[file_id].executable
837
0.5.115 by John Arbash Meinel
Getting closer to being able to read back the changesets, still broken, though.
838
    def get_last_changed(self, file_id):
1185.82.95 by Aaron Bentley
Restore path-orientation of ChangesetTree
839
        path = self.id2path(file_id)
840
        if path in self._last_changed:
841
            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.
842
        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
843
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
844
    def get_size_and_sha1(self, file_id):
845
        """Return the size and sha1 hash of the given file id.
846
        If the file was not locally modified, this is extracted
847
        from the base_tree. Rather than re-reading the file.
848
        """
849
        new_path = self.id2path(file_id)
850
        if new_path is None:
851
            return None, None
852
        if new_path not in self.patches:
853
            # If the entry does not have a patch, then the
854
            # contents must be the same as in the base_tree
855
            ie = self.base_tree.inventory[file_id]
0.5.69 by John Arbash Meinel
Applying patch from Robey Pointer to clean up apply_changeset.
856
            if ie.text_size is None:
857
                return ie.text_size, ie.text_sha1
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
858
            return int(ie.text_size), ie.text_sha1
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
859
        fileobj = self.get_file(file_id)
860
        content = fileobj.read()
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
861
        return len(content), sha_string(content)
862
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
863
    def _get_inventory(self):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
864
        """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
865
866
        This need to be called before ever accessing self.inventory
867
        """
868
        from os.path import dirname, basename
869
870
        assert self.base_tree is not None
871
        base_inv = self.base_tree.inventory
872
        root_id = base_inv.root.file_id
873
        try:
874
            # New inventories have a unique root_id
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
875
            inv = Inventory(root_id, self.revision_id)
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
876
        except TypeError:
1185.82.16 by Aaron Bentley
Ensured revision ID is stored in ChangesetTree inventories
877
            inv = Inventory(revision_id=self.revision_id)
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
878
879
        def add_entry(file_id):
880
            path = self.id2path(file_id)
881
            if path is None:
882
                return
883
            parent_path = dirname(path)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
884
            if parent_path == u'':
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
885
                parent_id = root_id
886
            else:
887
                parent_id = self.path2id(parent_path)
888
889
            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.
890
            revision_id = self.get_last_changed(file_id)
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
891
892
            name = basename(path)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
893
            if kind == 'directory':
894
                ie = InventoryDirectory(file_id, name, parent_id)
895
            elif kind == 'file':
896
                ie = InventoryFile(file_id, name, parent_id)
1185.82.66 by Aaron Bentley
Handle new executable files
897
                ie.executable = self.is_executable(file_id)
0.5.116 by John Arbash Meinel
Fixed a bug based on the new InventoryEntry separation.
898
            elif kind == 'symlink':
899
                ie = InventoryLink(file_id, name, parent_id)
1185.82.87 by Aaron Bentley
Got symlink adding working
900
                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.
901
            ie.revision = revision_id
902
1185.82.88 by Aaron Bentley
Get symlink modification, renames and deletion under test
903
            if kind in ('directory', 'symlink'):
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
904
                ie.text_size, ie.text_sha1 = None, None
905
            else:
906
                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
907
            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
908
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
909
            inv.add(ie)
910
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
911
        sorted_entries = self.sorted_path_id()
912
        for path, file_id in sorted_entries:
913
            if file_id == inv.root.file_id:
0.5.83 by John Arbash Meinel
Tests pass. Now ChangesetTree has it's own inventory.
914
                continue
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
915
            add_entry(file_id)
916
917
        return inv
918
919
    # Have to overload the inherited inventory property
920
    # because _get_inventory is only called in the parent.
921
    # Reading the docs, property() objects do not use
922
    # overloading, they use the function as it was defined
923
    # at that instant
924
    inventory = property(_get_inventory)
0.5.64 by John Arbash Meinel
SUCCESS, we now are able to validate the inventory XML.
925
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
926
    def __iter__(self):
0.5.82 by John Arbash Meinel
Lots of changes, changing separators, updating tests, updated ChangesetTree to include text_ids
927
        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.
928
            yield entry.file_id
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
929
0.6.1 by Aaron Bentley
Fleshed out MockTree, fixed all test failures
930
    def sorted_path_id(self):
931
        paths = []
932
        for result in self._new_id.iteritems():
933
            paths.append(result)
934
        for id in self.base_tree:
935
            path = self.id2path(id)
936
            if path is None:
937
                continue
938
            paths.append((path, id))
939
        paths.sort()
940
        return paths
941
1185.82.123 by Aaron Bentley
Cleanups to prepare for review
942
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
943
def patched_file(file_patch, original):
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
944
    """Produce a file-like object with the patched version of a text"""
1185.82.13 by Aaron Bentley
Got old changeset tests running
945
    from bzrlib.patches import iter_patched
946
    from bzrlib.iterablefile import IterableFile
0.5.94 by Aaron Bentley
Switched to native patch application, added tests for terminating newlines
947
    if file_patch == "":
948
        return IterableFile(())
949
    return IterableFile(iter_patched(original, file_patch.splitlines(True)))