/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/bundle/read_bundle.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006 by Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
18
18
 
21
21
import os
22
22
import pprint
23
23
 
24
 
from bzrlib import (
25
 
    osutils,
26
 
    timestamp,
27
 
    )
 
24
from bzrlib.bundle.common import get_header, header_str
28
25
import bzrlib.errors
29
 
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError,
 
26
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
27
                           MalformedHeader, MalformedPatches, NotABundle)
32
28
from bzrlib.inventory import (Inventory, InventoryEntry,
33
29
                              InventoryDirectory, InventoryFile,
34
30
                              InventoryLink)
35
 
from bzrlib.osutils import sha_file, sha_string, pathjoin
 
31
from bzrlib.osutils import sha_file, sha_string
36
32
from bzrlib.revision import Revision, NULL_REVISION
37
33
from bzrlib.testament import StrictTestament
38
34
from bzrlib.trace import mutter, warning
77
73
        if self.properties:
78
74
            for property in self.properties:
79
75
                key_end = property.find(': ')
80
 
                if key_end == -1:
81
 
                    if not property.endswith(':'):
82
 
                        raise ValueError(property)
83
 
                    key = str(property[:-1])
84
 
                    value = ''
85
 
                else:
86
 
                    key = str(property[:key_end])
87
 
                    value = property[key_end+2:]
 
76
                assert key_end is not None
 
77
                key = property[:key_end].encode('utf-8')
 
78
                value = property[key_end+2:].encode('utf-8')
88
79
                rev.properties[key] = value
89
80
 
90
81
        return rev
91
82
 
92
 
    @staticmethod
93
 
    def from_revision(revision):
94
 
        revision_info = RevisionInfo(revision.revision_id)
95
 
        date = timestamp.format_highres_date(revision.timestamp,
96
 
                                             revision.timezone)
97
 
        revision_info.date = date
98
 
        revision_info.timezone = revision.timezone
99
 
        revision_info.timestamp = revision.timestamp
100
 
        revision_info.message = revision.message.split('\n')
101
 
        revision_info.properties = [': '.join(p) for p in
102
 
                                    revision.properties.iteritems()]
103
 
        return revision_info
104
 
 
105
83
 
106
84
class BundleInfo(object):
107
85
    """This contains the meta information. Stuff that allows you to
108
86
    recreate the revision or inventory XML.
109
87
    """
110
 
    def __init__(self, bundle_format=None):
111
 
        self.bundle_format = None
 
88
    def __init__(self):
112
89
        self.committer = None
113
90
        self.date = None
114
91
        self.message = None
125
102
        self.timestamp = None
126
103
        self.timezone = None
127
104
 
128
 
        # Have we checked the repository yet?
129
 
        self._validated_revisions_against_repo = False
130
 
 
131
105
    def __str__(self):
132
106
        return pprint.pformat(self.__dict__)
133
107
 
136
110
        split up, based on the assumptions that can be made
137
111
        when information is missing.
138
112
        """
139
 
        from bzrlib.timestamp import unpack_highres_date
 
113
        from bzrlib.bundle.common import unpack_highres_date
140
114
        # Put in all of the guessable information.
141
115
        if not self.timestamp and self.date:
142
116
            self.timestamp, self.timezone = unpack_highres_date(self.date)
159
133
    def get_base(self, revision):
160
134
        revision_info = self.get_revision_info(revision.revision_id)
161
135
        if revision_info.base_id is not None:
162
 
            return revision_info.base_id
 
136
            if revision_info.base_id == NULL_REVISION:
 
137
                return None
 
138
            else:
 
139
                return revision_info.base_id
163
140
        if len(revision.parent_ids) == 0:
164
141
            # There is no base listed, and
165
142
            # the lowest revision doesn't have a parent
166
143
            # so this is probably against the empty tree
167
 
            # and thus base truly is NULL_REVISION
168
 
            return NULL_REVISION
 
144
            # and thus base truly is None
 
145
            return None
169
146
        else:
170
147
            return revision.parent_ids[-1]
171
148
 
191
168
                return r
192
169
        raise KeyError(revision_id)
193
170
 
194
 
    def revision_tree(self, repository, revision_id, base=None):
195
 
        revision = self.get_revision(revision_id)
196
 
        base = self.get_base(revision)
197
 
        if base == revision_id:
198
 
            raise AssertionError()
199
 
        if not self._validated_revisions_against_repo:
200
 
            self._validate_references_from_repository(repository)
201
 
        revision_info = self.get_revision_info(revision_id)
202
 
        inventory_revision_id = revision_id
203
 
        bundle_tree = BundleTree(repository.revision_tree(base),
204
 
                                  inventory_revision_id)
205
 
        self._update_tree(bundle_tree, revision_id)
206
 
 
207
 
        inv = bundle_tree.inventory
208
 
        self._validate_inventory(inv, revision_id)
209
 
        self._validate_revision(inv, revision_id)
210
 
 
211
 
        return bundle_tree
 
171
 
 
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.
 
175
    """
 
176
    def __init__(self, from_file):
 
177
        """Read in the bundle from the file.
 
178
 
 
179
        :param from_file: A file-like object (must have iterator support).
 
180
        """
 
181
        object.__init__(self)
 
182
        self.from_file = iter(from_file)
 
183
        self._next_line = None
 
184
        
 
185
        self.info = BundleInfo()
 
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
 
189
        # can create a proper bundle.
 
190
        self._read()
 
191
        self._validate()
 
192
 
 
193
    def _read(self):
 
194
        self._read_header()
 
195
        while self._next_line is not None:
 
196
            self._read_revision_header()
 
197
            if self._next_line is None:
 
198
                break
 
199
            self._read_patches()
 
200
            self._read_footer()
 
201
 
 
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
 
 
210
    def _validate_revision(self, inventory, revision_id):
 
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 = {}
 
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
 
219
        assert rev.revision_id == revision_id
 
220
        sha1 = StrictTestament(rev, inventory).as_sha1()
 
221
        if sha1 != rev_info.sha1:
 
222
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
 
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
212
227
 
213
228
    def _validate_references_from_repository(self, repository):
214
229
        """Now that we have a repository which should have some of the
236
251
        # All of the contained revisions were checked
237
252
        # in _validate_revisions
238
253
        checked = {}
239
 
        for rev_info in self.revisions:
 
254
        for rev_info in self.info.revisions:
240
255
            checked[rev_info.revision_id] = True
241
256
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
242
 
 
243
 
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
 
257
                
 
258
        for (rev, rev_info) in zip(self.info.real_revisions, self.info.revisions):
244
259
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
245
260
 
246
261
        count = 0
247
262
        missing = {}
248
263
        for revision_id, sha1 in rev_to_sha.iteritems():
249
264
            if repository.has_revision(revision_id):
250
 
                testament = StrictTestament.from_revision(repository,
 
265
                testament = StrictTestament.from_revision(repository, 
251
266
                                                          revision_id)
252
 
                local_sha1 = self._testament_sha1_from_revision(repository,
253
 
                                                                revision_id)
 
267
                local_sha1 = testament.as_sha1()
254
268
                if sha1 != local_sha1:
255
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
269
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
256
270
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
257
271
                else:
258
272
                    count += 1
259
273
            elif revision_id not in checked:
260
274
                missing[revision_id] = sha1
261
275
 
 
276
        for inv_id, sha1 in inv_to_sha.iteritems():
 
277
            if repository.has_revision(inv_id):
 
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.
 
283
                local_sha1 = repository.get_inventory_sha1(inv_id)
 
284
                if sha1 != local_sha1:
 
285
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
 
286
                                   'local: %s, bundle: %s' % 
 
287
                                   (inv_id, local_sha1, sha1))
 
288
                else:
 
289
                    count += 1
 
290
 
262
291
        if len(missing) > 0:
263
292
            # I don't know if this is an error yet
264
293
            warning('Not all revision hashes could be validated.'
265
294
                    ' Unable validate %d hashes' % len(missing))
266
295
        mutter('Verified %d sha hashes for the bundle.' % count)
267
 
        self._validated_revisions_against_repo = True
268
296
 
269
297
    def _validate_inventory(self, inv, revision_id):
270
298
        """At this point we should have generated the BundleTree,
271
299
        so build up an inventory, and make sure the hashes match.
272
300
        """
 
301
 
 
302
        assert inv is not None
 
303
 
273
304
        # Now we should have a complete inventory entry.
274
305
        s = serializer_v5.write_inventory_to_string(inv)
275
306
        sha1 = sha_string(s)
276
307
        # Target revision is the last entry in the real_revisions list
277
 
        rev = self.get_revision(revision_id)
278
 
        if rev.revision_id != revision_id:
279
 
            raise AssertionError()
 
308
        rev = self.info.get_revision(revision_id)
 
309
        assert rev.revision_id == revision_id
280
310
        if sha1 != rev.inventory_sha1:
281
311
            open(',,bogus-inv', 'wb').write(s)
282
312
            warning('Inventory sha hash mismatch for revision %s. %s'
283
313
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
284
314
 
285
 
    def _validate_revision(self, inventory, revision_id):
286
 
        """Make sure all revision entries match their checksum."""
287
 
 
288
 
        # This is a mapping from each revision id to it's sha hash
289
 
        rev_to_sha1 = {}
290
 
 
291
 
        rev = self.get_revision(revision_id)
292
 
        rev_info = self.get_revision_info(revision_id)
293
 
        if not (rev.revision_id == rev_info.revision_id):
294
 
            raise AssertionError()
295
 
        if not (rev.revision_id == revision_id):
296
 
            raise AssertionError()
297
 
        sha1 = self._testament_sha1(rev, inventory)
298
 
        if sha1 != rev_info.sha1:
299
 
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
300
 
        if rev.revision_id in rev_to_sha1:
301
 
            raise BzrError('Revision {%s} given twice in the list'
302
 
                    % (rev.revision_id))
303
 
        rev_to_sha1[rev.revision_id] = sha1
 
315
    def get_bundle(self, repository):
 
316
        """Return the meta information, and a Bundle tree which can
 
317
        be used to populate the local stores and working tree, respectively.
 
318
        """
 
319
        return self.info, self.revision_tree(repository, self.info.target)
 
320
 
 
321
    def revision_tree(self, repository, revision_id, base=None):
 
322
        revision = self.info.get_revision(revision_id)
 
323
        base = self.info.get_base(revision)
 
324
        assert base != revision_id
 
325
        self._validate_references_from_repository(repository)
 
326
        revision_info = self.info.get_revision_info(revision_id)
 
327
        inventory_revision_id = revision_id
 
328
        bundle_tree = BundleTree(repository.revision_tree(base), 
 
329
                                  inventory_revision_id)
 
330
        self._update_tree(bundle_tree, revision_id)
 
331
 
 
332
        inv = bundle_tree.inventory
 
333
        self._validate_inventory(inv, revision_id)
 
334
        self._validate_revision(inv, revision_id)
 
335
 
 
336
        return bundle_tree
 
337
 
 
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:
 
346
                #mutter('yielding line: %r' % last)
 
347
                yield last
 
348
        last = self._next_line
 
349
        self._next_line = None
 
350
        #mutter('yielding line: %r' % last)
 
351
        yield last
 
352
 
 
353
    def _read_header(self):
 
354
        """Read the bzr header"""
 
355
        header = get_header()
 
356
        found = False
 
357
        for line in self._next():
 
358
            if found:
 
359
                # not all mailers will keep trailing whitespace
 
360
                if line == '#\n':
 
361
                    line = '# \n'
 
362
                if (not line.startswith('# ') or not line.endswith('\n')
 
363
                        or line[2:-1].decode('utf-8') != header[0]):
 
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.
 
369
            elif (line.startswith('#') and line.endswith('\n')):
 
370
                line = line[1:-1].strip().decode('utf-8')
 
371
                if line[:len(header_str)] == header_str:
 
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:
 
379
            raise NotABundle('Did not find an opening header')
 
380
 
 
381
    def _read_revision_header(self):
 
382
        self.info.revisions.append(RevisionInfo(None))
 
383
        for line in self._next():
 
384
            # The bzr header is terminated with a blank line
 
385
            # which does not start with '#'
 
386
            if line is None or line == '\n':
 
387
                break
 
388
            self._handle_next(line)
 
389
 
 
390
    def _read_next_entry(self, line, indent=1):
 
391
        """Read in a key-value pair
 
392
        """
 
393
        if not line.startswith('#'):
 
394
            raise MalformedHeader('Bzr header did not start with #')
 
395
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
 
396
        if line[:indent] == ' '*indent:
 
397
            line = line[indent:]
 
398
        if not line:
 
399
            return None, None# Ignore blank lines
 
400
 
 
401
        loc = line.find(': ')
 
402
        if loc != -1:
 
403
            key = line[:loc]
 
404
            value = line[loc+2:]
 
405
            if not value:
 
406
                value = self._read_many(indent=indent+2)
 
407
        elif line[-1:] == ':':
 
408
            key = line[:-1]
 
409
            value = self._read_many(indent=indent+2)
 
410
        else:
 
411
            raise MalformedHeader('While looking for key: value pairs,'
 
412
                    ' did not find the colon %r' % (line))
 
413
 
 
414
        key = key.replace(' ', '_')
 
415
        #mutter('found %s: %s' % (key, value))
 
416
        return key, value
 
417
 
 
418
    def _handle_next(self, line):
 
419
        if line is None:
 
420
            return
 
421
        key, value = self._read_next_entry(line, indent=1)
 
422
        mutter('_handle_next %r => %r' % (key, value))
 
423
        if key is None:
 
424
            return
 
425
 
 
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)
 
432
        else:
 
433
            # What do we do with a key we don't recognize
 
434
            raise MalformedHeader('Unknown Key: "%s"' % key)
 
435
    
 
436
    def _read_many(self, indent):
 
437
        """If a line ends with no entry, that means that it should be
 
438
        followed with multiple lines of values.
 
439
 
 
440
        This detects the end of the list, because it will be a line that
 
441
        does not start properly indented.
 
442
        """
 
443
        values = []
 
444
        start = '#' + (' '*indent)
 
445
 
 
446
        if self._next_line is None or self._next_line[:len(start)] != start:
 
447
            return values
 
448
 
 
449
        for line in self._next():
 
450
            values.append(line[len(start):-1].decode('utf-8'))
 
451
            if self._next_line is None or self._next_line[:len(start)] != start:
 
452
                break
 
453
        return values
 
454
 
 
455
    def _read_one_patch(self):
 
456
        """Read in one patch, return the complete patch, along with
 
457
        the next line.
 
458
 
 
459
        :return: action, lines, do_continue
 
460
        """
 
461
        #mutter('_read_one_patch: %r' % self._next_line)
 
462
        # Peek and see if there are no patches
 
463
        if self._next_line is None or self._next_line.startswith('#'):
 
464
            return None, [], False
 
465
 
 
466
        first = True
 
467
        lines = []
 
468
        for line in self._next():
 
469
            if first:
 
470
                if not line.startswith('==='):
 
471
                    raise MalformedPatches('The first line of all patches'
 
472
                        ' should be a bzr meta line "==="'
 
473
                        ': %r' % line)
 
474
                action = line[4:-1].decode('utf-8')
 
475
            elif line.startswith('... '):
 
476
                action += line[len('... '):-1].decode('utf-8')
 
477
 
 
478
            if (self._next_line is not None and 
 
479
                self._next_line.startswith('===')):
 
480
                return action, lines, True
 
481
            elif self._next_line is None or self._next_line.startswith('#'):
 
482
                return action, lines, False
 
483
 
 
484
            if first:
 
485
                first = False
 
486
            elif not line.startswith('... '):
 
487
                lines.append(line)
 
488
 
 
489
        return action, lines, False
 
490
            
 
491
    def _read_patches(self):
 
492
        do_continue = True
 
493
        revision_actions = []
 
494
        while do_continue:
 
495
            action, lines, do_continue = self._read_one_patch()
 
496
            if action is not None:
 
497
                revision_actions.append((action, lines))
 
498
        assert self.info.revisions[-1].tree_actions is None
 
499
        self.info.revisions[-1].tree_actions = revision_actions
 
500
 
 
501
    def _read_footer(self):
 
502
        """Read the rest of the meta information.
 
503
 
 
504
        :param first_line:  The previous step iterates past what it
 
505
                            can handle. That extra line is given here.
 
506
        """
 
507
        for line in self._next():
 
508
            self._handle_next(line)
 
509
            if not self._next_line.startswith('#'):
 
510
                self._next().next()
 
511
                break
 
512
            if self._next_line is None:
 
513
                break
304
514
 
305
515
    def _update_tree(self, bundle_tree, revision_id):
306
516
        """This fills out a BundleTree based on the information
311
521
 
312
522
        def get_rev_id(last_changed, path, kind):
313
523
            if last_changed is not None:
314
 
                # last_changed will be a Unicode string because of how it was
315
 
                # read. Convert it back to utf8.
316
 
                changed_revision_id = osutils.safe_revision_id(last_changed,
317
 
                                                               warn=False)
 
524
                changed_revision_id = last_changed.decode('utf-8')
318
525
            else:
319
526
                changed_revision_id = revision_id
320
527
            bundle_tree.note_last_changed(path, changed_revision_id)
331
538
                if name == 'last-changed':
332
539
                    last_changed = value
333
540
                elif name == 'executable':
 
541
                    assert value in ('yes', 'no'), value
334
542
                    val = (value == 'yes')
335
543
                    bundle_tree.note_executable(new_path, val)
336
544
                elif name == 'target':
340
548
            return last_changed, encoding
341
549
 
342
550
        def do_patch(path, lines, encoding):
343
 
            if encoding == 'base64':
 
551
            if encoding is not None:
 
552
                assert encoding == 'base64'
344
553
                patch = base64.decodestring(''.join(lines))
345
 
            elif encoding is None:
 
554
            else:
346
555
                patch =  ''.join(lines)
347
 
            else:
348
 
                raise ValueError(encoding)
349
556
            bundle_tree.note_patch(path, patch)
350
557
 
351
558
        def renamed(kind, extra, lines):
387
594
            if not info[1].startswith('file-id:'):
388
595
                raise BzrError('The file-id should follow the path for an add'
389
596
                        ': %r' % extra)
390
 
            # This will be Unicode because of how the stream is read. Turn it
391
 
            # back into a utf8 file_id
392
 
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
 
597
            file_id = info[1][8:]
393
598
 
394
599
            bundle_tree.note_id(file_id, path, kind)
395
600
            # this will be overridden in extra_info if executable is specified.
411
616
            revision = get_rev_id(last_modified, path, kind)
412
617
            if lines:
413
618
                do_patch(path, lines, encoding)
414
 
 
 
619
            
415
620
        valid_actions = {
416
621
            'renamed':renamed,
417
622
            'removed':removed,
419
624
            'modified':modified
420
625
        }
421
626
        for action_line, lines in \
422
 
            self.get_revision_info(revision_id).tree_actions:
 
627
            self.info.get_revision_info(revision_id).tree_actions:
423
628
            first = action_line.find(' ')
424
629
            if first == -1:
425
630
                raise BzrError('Bogus action line'
440
645
                        ' (unrecognized action): %r' % action_line)
441
646
            valid_actions[action](kind, extra, lines)
442
647
 
443
 
    def install_revisions(self, target_repo, stream_input=True):
444
 
        """Install revisions and return the target revision
445
 
 
446
 
        :param target_repo: The repository to install into
447
 
        :param stream_input: Ignored by this implementation.
448
 
        """
449
 
        apply_bundle.install_bundle(target_repo, self)
450
 
        return self.target
451
 
 
452
 
    def get_merge_request(self, target_repo):
453
 
        """Provide data for performing a merge
454
 
 
455
 
        Returns suggested base, suggested target, and patch verification status
456
 
        """
457
 
        return None, self.target, 'inapplicable'
458
 
 
459
648
 
460
649
class BundleTree(Tree):
461
650
    def __init__(self, base_tree, revision_id):
479
668
 
480
669
    def note_rename(self, old_path, new_path):
481
670
        """A file/directory has been renamed from old_path => new_path"""
482
 
        if new_path in self._renamed:
483
 
            raise AssertionError(new_path)
484
 
        if old_path in self._renamed_r:
485
 
            raise AssertionError(old_path)
 
671
        assert not self._renamed.has_key(new_path)
 
672
        assert not self._renamed_r.has_key(old_path)
486
673
        self._renamed[new_path] = old_path
487
674
        self._renamed_r[old_path] = new_path
488
675
 
493
680
        self._kinds[new_id] = kind
494
681
 
495
682
    def note_last_changed(self, file_id, revision_id):
496
 
        if (file_id in self._last_changed
 
683
        if (self._last_changed.has_key(file_id)
497
684
                and self._last_changed[file_id] != revision_id):
498
685
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
499
686
                    ': %s != %s' % (file_id,
518
705
 
519
706
    def old_path(self, new_path):
520
707
        """Get the old_path (path in the base_tree) for the file at new_path"""
521
 
        if new_path[:1] in ('\\', '/'):
522
 
            raise ValueError(new_path)
 
708
        assert new_path[:1] not in ('\\', '/')
523
709
        old_path = self._renamed.get(new_path)
524
710
        if old_path is not None:
525
711
            return old_path
532
718
            if old_dir is None:
533
719
                old_path = None
534
720
            else:
535
 
                old_path = pathjoin(old_dir, basename)
 
721
                old_path = os.path.join(old_dir, basename)
536
722
        else:
537
723
            old_path = new_path
538
724
        #If the new path wasn't in renamed, the old one shouldn't be in
539
725
        #renamed_r
540
 
        if old_path in self._renamed_r:
 
726
        if self._renamed_r.has_key(old_path):
541
727
            return None
542
 
        return old_path
 
728
        return old_path 
543
729
 
544
730
    def new_path(self, old_path):
545
731
        """Get the new_path (path in the target_tree) for the file at old_path
546
732
        in the base tree.
547
733
        """
548
 
        if old_path[:1] in ('\\', '/'):
549
 
            raise ValueError(old_path)
 
734
        assert old_path[:1] not in ('\\', '/')
550
735
        new_path = self._renamed_r.get(old_path)
551
736
        if new_path is not None:
552
737
            return new_path
553
 
        if new_path in self._renamed:
 
738
        if self._renamed.has_key(new_path):
554
739
            return None
555
740
        dirname,basename = os.path.split(old_path)
556
741
        if dirname != '':
558
743
            if new_dir is None:
559
744
                new_path = None
560
745
            else:
561
 
                new_path = pathjoin(new_dir, basename)
 
746
                new_path = os.path.join(new_dir, basename)
562
747
        else:
563
748
            new_path = old_path
564
749
        #If the old path wasn't in renamed, the new one shouldn't be in
565
750
        #renamed_r
566
 
        if new_path in self._renamed:
 
751
        if self._renamed.has_key(new_path):
567
752
            return None
568
 
        return new_path
 
753
        return new_path 
569
754
 
570
755
    def path2id(self, path):
571
756
        """Return the id of the file present at path in the target tree."""
577
762
            return None
578
763
        if old_path in self.deleted:
579
764
            return None
580
 
        if getattr(self.base_tree, 'path2id', None) is not None:
 
765
        if hasattr(self.base_tree, 'path2id'):
581
766
            return self.base_tree.path2id(old_path)
582
767
        else:
583
768
            return self.base_tree.inventory.path2id(old_path)
605
790
                return None
606
791
        new_path = self.id2path(file_id)
607
792
        return self.base_tree.path2id(new_path)
608
 
 
 
793
        
609
794
    def get_file(self, file_id):
610
795
        """Return a file-like object containing the new contents of the
611
796
        file given by file_id.
615
800
                then be cached.
616
801
        """
617
802
        base_id = self.old_contents_id(file_id)
618
 
        if (base_id is not None and
619
 
            base_id != self.base_tree.inventory.root.file_id):
 
803
        if base_id is not None:
620
804
            patch_original = self.base_tree.get_file(base_id)
621
805
        else:
622
806
            patch_original = None
623
807
        file_patch = self.patches.get(self.id2path(file_id))
624
808
        if file_patch is None:
625
 
            if (patch_original is None and
 
809
            if (patch_original is None and 
626
810
                self.get_kind(file_id) == 'directory'):
627
811
                return StringIO()
628
 
            if patch_original is None:
629
 
                raise AssertionError("None: %s" % file_id)
 
812
            assert patch_original is not None, "None: %s" % file_id
630
813
            return patch_original
631
814
 
632
 
        if file_patch.startswith('\\'):
633
 
            raise ValueError(
634
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
815
        assert not file_patch.startswith('\\'), \
 
816
            'Malformed patch for %s, %r' % (file_id, file_patch)
635
817
        return patched_file(file_patch, patch_original)
636
818
 
637
819
    def get_symlink_target(self, file_id):
684
866
        This need to be called before ever accessing self.inventory
685
867
        """
686
868
        from os.path import dirname, basename
 
869
 
 
870
        assert self.base_tree is not None
687
871
        base_inv = self.base_tree.inventory
688
 
        inv = Inventory(None, self.revision_id)
 
872
        root_id = base_inv.root.file_id
 
873
        try:
 
874
            # New inventories have a unique root_id
 
875
            inv = Inventory(root_id, self.revision_id)
 
876
        except TypeError:
 
877
            inv = Inventory(revision_id=self.revision_id)
689
878
 
690
879
        def add_entry(file_id):
691
880
            path = self.id2path(file_id)
692
881
            if path is None:
693
882
                return
694
 
            if path == '':
695
 
                parent_id = None
 
883
            parent_path = dirname(path)
 
884
            if parent_path == u'':
 
885
                parent_id = root_id
696
886
            else:
697
 
                parent_path = dirname(path)
698
887
                parent_id = self.path2id(parent_path)
699
888
 
700
889
            kind = self.get_kind(file_id)
721
910
 
722
911
        sorted_entries = self.sorted_path_id()
723
912
        for path, file_id in sorted_entries:
 
913
            if file_id == inv.root.file_id:
 
914
                continue
724
915
            add_entry(file_id)
725
916
 
726
917
        return inv
755
946
    from bzrlib.iterablefile import IterableFile
756
947
    if file_patch == "":
757
948
        return IterableFile(())
758
 
    # string.splitlines(True) also splits on '\r', but the iter_patched code
759
 
    # only expects to iterate over '\n' style lines
760
 
    return IterableFile(iter_patched(original,
761
 
                StringIO(file_patch).readlines()))
 
949
    return IterableFile(iter_patched(original, file_patch.splitlines(True)))