~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-08-25 05:58:05 UTC
  • mfrom: (974.1.36)
  • Revision ID: mbp@sourcefrog.net-20050825055805-8c892bc3c2d75131
- merge aaron's merge improvements:

  * When merging, pull in all missing revisions from the source
    branch. 

  * Detect common ancestors by looking at the whole ancestry graph, 
    rather than just mainline history.

  Some changes to reconcile this with parallel updates to the test and
  trace code.

aaron.bentley@utoronto.ca-20050823052551-f3401a8b57d9126f

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import sys
19
19
import os
20
 
from cStringIO import StringIO
21
20
 
22
21
import bzrlib
23
22
from bzrlib.trace import mutter, note
25
24
     splitpath, \
26
25
     sha_file, appendpath, file_kind
27
26
 
28
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
 
                           NoSuchRevision)
 
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
 
28
import bzrlib.errors
30
29
from bzrlib.textui import show_status
31
30
from bzrlib.revision import Revision
 
31
from bzrlib.xml import unpack_xml
32
32
from bzrlib.delta import compare_trees
33
33
from bzrlib.tree import EmptyTree, RevisionTree
34
 
from bzrlib.inventory import Inventory
35
 
from bzrlib.weavestore import WeaveStore
36
 
from bzrlib.store import ImmutableStore
37
 
import bzrlib.xml5
38
34
import bzrlib.ui
39
35
 
40
36
 
41
 
INVENTORY_FILEID = '__inventory'
42
 
ANCESTRY_FILEID = '__ancestry'
43
 
 
44
 
 
45
 
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
46
 
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
37
 
 
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
47
39
## TODO: Maybe include checks for common corruption of newlines, etc?
48
40
 
49
41
 
50
42
# TODO: Some operations like log might retrieve the same revisions
51
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
52
 
# cache in memory to make this faster.  In general anything can be
53
 
# cached in memory between lock and unlock operations.
 
44
# cache in memory to make this faster.
54
45
 
55
46
# TODO: please move the revision-string syntax stuff out of the branch
56
47
# object; it's clutter
173
164
    _lock_mode = None
174
165
    _lock_count = None
175
166
    _lock = None
176
 
    _inventory_weave = None
177
167
    
178
168
    # Map some sort of prefix into a namespace
179
169
    # stuff like "revno:10", "revid:", etc.
195
185
        In the test suite, creation of new trees is tested using the
196
186
        `ScratchBranch` class.
197
187
        """
 
188
        from bzrlib.store import ImmutableStore
198
189
        if init:
199
190
            self.base = os.path.realpath(base)
200
191
            self._make_control()
209
200
                                      'current bzr can only operate from top-of-tree'])
210
201
        self._check_format()
211
202
 
212
 
        self.weave_store = WeaveStore(self.controlfilename('weaves'))
 
203
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
213
204
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
 
205
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
214
206
 
215
207
 
216
208
    def __str__(self):
227
219
            self._lock.unlock()
228
220
 
229
221
 
 
222
 
230
223
    def lock_write(self):
231
224
        if self._lock_mode:
232
225
            if self._lock_mode != 'w':
242
235
            self._lock_count = 1
243
236
 
244
237
 
 
238
 
245
239
    def lock_read(self):
246
240
        if self._lock_mode:
247
241
            assert self._lock_mode in ('r', 'w'), \
254
248
            self._lock_mode = 'r'
255
249
            self._lock_count = 1
256
250
                        
 
251
 
 
252
            
257
253
    def unlock(self):
258
254
        if not self._lock_mode:
259
255
            from errors import LockError
266
262
            self._lock = None
267
263
            self._lock_mode = self._lock_count = None
268
264
 
 
265
 
269
266
    def abspath(self, name):
270
267
        """Return absolute filename for something in the branch"""
271
268
        return os.path.join(self.base, name)
272
269
 
 
270
 
273
271
    def relpath(self, path):
274
272
        """Return path relative to this branch of something inside it.
275
273
 
276
274
        Raises an error if path is not in this branch."""
277
275
        return _relpath(self.base, path)
278
276
 
 
277
 
279
278
    def controlfilename(self, file_or_path):
280
279
        """Return location relative to branch."""
281
280
        if isinstance(file_or_path, basestring):
308
307
        else:
309
308
            raise BzrError("invalid controlfile mode %r" % mode)
310
309
 
 
310
 
 
311
 
311
312
    def _make_control(self):
 
313
        from bzrlib.inventory import Inventory
 
314
        from bzrlib.xml import pack_xml
 
315
        
312
316
        os.mkdir(self.controlfilename([]))
313
317
        self.controlfile('README', 'w').write(
314
318
            "This is a Bazaar-NG control directory.\n"
315
319
            "Do not change any files in this directory.\n")
316
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
317
 
        for d in ('text-store', 'revision-store',
318
 
                  'weaves'):
 
320
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
 
321
        for d in ('text-store', 'inventory-store', 'revision-store'):
319
322
            os.mkdir(self.controlfilename(d))
320
323
        for f in ('revision-history', 'merged-patches',
321
324
                  'pending-merged-patches', 'branch-name',
327
330
        # if we want per-tree root ids then this is the place to set
328
331
        # them; they're not needed for now and so ommitted for
329
332
        # simplicity.
330
 
        f = self.controlfile('inventory','w')
331
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
332
 
        
 
333
        pack_xml(Inventory(), self.controlfile('inventory','w'))
333
334
 
334
335
 
335
336
    def _check_format(self):
336
337
        """Check this branch format is supported.
337
338
 
338
 
        The format level is stored, as an integer, in
339
 
        self._branch_format for code that needs to check it later.
 
339
        The current tool only supports the current unstable format.
340
340
 
341
341
        In the future, we might need different in-memory Branch
342
342
        classes to support downlevel branches.  But not yet.
343
343
        """
 
344
        # This ignores newlines so that we can open branches created
 
345
        # on Windows from Linux and so on.  I think it might be better
 
346
        # to always make all internal files in unix format.
344
347
        fmt = self.controlfile('branch-format', 'r').read()
345
 
        if fmt == BZR_BRANCH_FORMAT_5:
346
 
            self._branch_format = 5
347
 
        else:
348
 
            raise BzrError('sorry, branch format "%s" not supported; ' 
349
 
                           'use a different bzr version, '
350
 
                           'or run "bzr upgrade", '
351
 
                           'or remove the .bzr directory and "bzr init" again'
352
 
                           % fmt.rstrip('\n\r'))
 
348
        fmt.replace('\r\n', '')
 
349
        if fmt != BZR_BRANCH_FORMAT:
 
350
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
351
                           ['use a different bzr version',
 
352
                            'or remove the .bzr directory and "bzr init" again'])
353
353
 
354
354
    def get_root_id(self):
355
355
        """Return the id of this branches root"""
370
370
 
371
371
    def read_working_inventory(self):
372
372
        """Read the working inventory."""
 
373
        from bzrlib.inventory import Inventory
 
374
        from bzrlib.xml import unpack_xml
 
375
        from time import time
 
376
        before = time()
373
377
        self.lock_read()
374
378
        try:
375
379
            # ElementTree does its own conversion from UTF-8, so open in
376
380
            # binary.
377
 
            f = self.controlfile('inventory', 'rb')
378
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
 
381
            inv = unpack_xml(Inventory,
 
382
                             self.controlfile('inventory', 'rb'))
 
383
            mutter("loaded inventory of %d items in %f"
 
384
                   % (len(inv), time() - before))
 
385
            return inv
379
386
        finally:
380
387
            self.unlock()
381
388
            
387
394
        will be committed to the next revision.
388
395
        """
389
396
        from bzrlib.atomicfile import AtomicFile
 
397
        from bzrlib.xml import pack_xml
390
398
        
391
399
        self.lock_write()
392
400
        try:
393
401
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
394
402
            try:
395
 
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
 
403
                pack_xml(inv, f)
396
404
                f.commit()
397
405
            finally:
398
406
                f.close()
406
414
                         """Inventory for the working copy.""")
407
415
 
408
416
 
409
 
    def add(self, files, ids=None):
 
417
    def add(self, files, verbose=False, ids=None):
410
418
        """Make files versioned.
411
419
 
412
 
        Note that the command line normally calls smart_add instead,
413
 
        which can automatically recurse.
 
420
        Note that the command line normally calls smart_add instead.
414
421
 
415
422
        This puts the files in the Added state, so that they will be
416
423
        recorded by the next commit.
426
433
        TODO: Perhaps have an option to add the ids even if the files do
427
434
              not (yet) exist.
428
435
 
429
 
        TODO: Perhaps yield the ids and paths as they're added.
 
436
        TODO: Perhaps return the ids of the files?  But then again it
 
437
              is easy to retrieve them if they're needed.
 
438
 
 
439
        TODO: Adding a directory should optionally recurse down and
 
440
              add all non-ignored children.  Perhaps do that in a
 
441
              higher-level method.
430
442
        """
431
443
        # TODO: Re-adding a file that is removed in the working copy
432
444
        # should probably put it back with the previous ID.
468
480
                    file_id = gen_file_id(f)
469
481
                inv.add_path(f, kind=kind, file_id=file_id)
470
482
 
 
483
                if verbose:
 
484
                    print 'added', quotefn(f)
 
485
 
471
486
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
472
487
 
473
488
            self._write_inventory(inv)
583
598
            f.close()
584
599
 
585
600
 
586
 
    def get_revision_xml_file(self, revision_id):
 
601
    def get_revision_xml(self, revision_id):
587
602
        """Return XML file object for revision object."""
588
603
        if not revision_id or not isinstance(revision_id, basestring):
589
604
            raise InvalidRevisionId(revision_id)
598
613
            self.unlock()
599
614
 
600
615
 
601
 
    def get_revision_xml(self, revision_id):
602
 
        return self.get_revision_xml_file(revision_id).read()
603
 
 
604
 
 
605
616
    def get_revision(self, revision_id):
606
617
        """Return the Revision object for a named revision"""
607
 
        xml_file = self.get_revision_xml_file(revision_id)
 
618
        xml_file = self.get_revision_xml(revision_id)
608
619
 
609
620
        try:
610
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
621
            r = unpack_xml(Revision, xml_file)
611
622
        except SyntaxError, e:
612
623
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
613
624
                                         [revision_id,
642
653
 
643
654
    def get_revision_sha1(self, revision_id):
644
655
        """Hash the stored value of a revision, and return it."""
645
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
646
 
 
647
 
 
648
 
    def get_ancestry(self, revision_id):
649
 
        """Return a list of revision-ids integrated by a revision.
650
 
        """
651
 
        w = self.weave_store.get_weave(ANCESTRY_FILEID)
652
 
        # strip newlines
653
 
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
654
 
 
655
 
 
656
 
    def get_inventory_weave(self):
657
 
        return self.weave_store.get_weave(INVENTORY_FILEID)
658
 
 
659
 
 
660
 
    def get_inventory(self, revision_id):
661
 
        """Get Inventory object by hash."""
662
 
        # FIXME: The text gets passed around a lot coming from the weave.
663
 
        f = StringIO(self.get_inventory_xml(revision_id))
664
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
665
 
 
666
 
 
667
 
    def get_inventory_xml(self, revision_id):
 
656
        # In the future, revision entries will be signed. At that
 
657
        # point, it is probably best *not* to include the signature
 
658
        # in the revision hash. Because that lets you re-sign
 
659
        # the revision, (add signatures/remove signatures) and still
 
660
        # have all hash pointers stay consistent.
 
661
        # But for now, just hash the contents.
 
662
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
 
663
 
 
664
 
 
665
    def get_inventory(self, inventory_id):
 
666
        """Get Inventory object by hash.
 
667
 
 
668
        TODO: Perhaps for this and similar methods, take a revision
 
669
               parameter which can be either an integer revno or a
 
670
               string hash."""
 
671
        from bzrlib.inventory import Inventory
 
672
        from bzrlib.xml import unpack_xml
 
673
 
 
674
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
 
675
 
 
676
 
 
677
    def get_inventory_xml(self, inventory_id):
668
678
        """Get inventory XML as a file object."""
669
 
        try:
670
 
            assert isinstance(revision_id, basestring), type(revision_id)
671
 
            iw = self.get_inventory_weave()
672
 
            return iw.get_text(iw.lookup(revision_id))
673
 
        except IndexError:
674
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
675
 
 
676
 
 
677
 
    def get_inventory_sha1(self, revision_id):
 
679
        return self.inventory_store[inventory_id]
 
680
            
 
681
 
 
682
    def get_inventory_sha1(self, inventory_id):
678
683
        """Return the sha1 hash of the inventory entry
679
684
        """
680
 
        return self.get_revision(revision_id).inventory_sha1
 
685
        return sha_file(self.get_inventory_xml(inventory_id))
681
686
 
682
687
 
683
688
    def get_revision_inventory(self, revision_id):
684
689
        """Return inventory of a past revision."""
685
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
690
        # bzr 0.0.6 imposes the constraint that the inventory_id
686
691
        # must be the same as its revision, so this is trivial.
687
692
        if revision_id == None:
 
693
            from bzrlib.inventory import Inventory
688
694
            return Inventory(self.get_root_id())
689
695
        else:
690
696
            return self.get_inventory(revision_id)
710
716
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
711
717
        >>> sb.common_ancestor(sb) == (None, None)
712
718
        True
713
 
        >>> commit.commit(sb, "Committing first revision")
 
719
        >>> commit.commit(sb, "Committing first revision", verbose=False)
714
720
        >>> sb.common_ancestor(sb)[0]
715
721
        1
716
722
        >>> clone = sb.clone()
717
 
        >>> commit.commit(sb, "Committing second revision")
 
723
        >>> commit.commit(sb, "Committing second revision", verbose=False)
718
724
        >>> sb.common_ancestor(sb)[0]
719
725
        2
720
726
        >>> sb.common_ancestor(clone)[0]
721
727
        1
722
 
        >>> commit.commit(clone, "Committing divergent second revision")
 
728
        >>> commit.commit(clone, "Committing divergent second revision", 
 
729
        ...               verbose=False)
723
730
        >>> sb.common_ancestor(clone)[0]
724
731
        1
725
732
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
827
834
            count = 0
828
835
        self.append_revision(*revision_ids)
829
836
        ## note("Added %d revisions." % count)
 
837
 
 
838
        
 
839
    def install_revisions(self, other, revision_ids, pb):
 
840
        if hasattr(other.revision_store, "prefetch"):
 
841
            other.revision_store.prefetch(revision_ids)
 
842
        if hasattr(other.inventory_store, "prefetch"):
 
843
            inventory_ids = [other.get_revision(r).inventory_id
 
844
                             for r in revision_ids]
 
845
            other.inventory_store.prefetch(inventory_ids)
 
846
 
 
847
        if pb is None:
 
848
            pb = bzrlib.ui.ui_factory.progress_bar()
 
849
                
 
850
        revisions = []
 
851
        needed_texts = set()
 
852
        i = 0
 
853
 
 
854
        failures = set()
 
855
        for i, rev_id in enumerate(revision_ids):
 
856
            pb.update('fetching revision', i+1, len(revision_ids))
 
857
            try:
 
858
                rev = other.get_revision(rev_id)
 
859
            except bzrlib.errors.NoSuchRevision:
 
860
                failures.add(rev_id)
 
861
                continue
 
862
 
 
863
            revisions.append(rev)
 
864
            inv = other.get_inventory(str(rev.inventory_id))
 
865
            for key, entry in inv.iter_entries():
 
866
                if entry.text_id is None:
 
867
                    continue
 
868
                if entry.text_id not in self.text_store:
 
869
                    needed_texts.add(entry.text_id)
 
870
 
830
871
        pb.clear()
 
872
                    
 
873
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
874
                                                    needed_texts)
 
875
        print "Added %d texts." % count 
 
876
        inventory_ids = [ f.inventory_id for f in revisions ]
 
877
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
878
                                                         inventory_ids)
 
879
        print "Added %d inventories." % count 
 
880
        revision_ids = [ f.revision_id for f in revisions]
831
881
 
 
882
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
883
                                                          revision_ids,
 
884
                                                          permit_failure=True)
 
885
        assert len(cp_fail) == 0 
 
886
        return count, failures
 
887
       
832
888
 
833
889
    def commit(self, *args, **kw):
834
 
        from bzrlib.commit import Commit
835
 
        Commit().commit(self, *args, **kw)
 
890
        from bzrlib.commit import commit
 
891
        commit(self, *args, **kw)
836
892
        
837
893
 
838
894
    def lookup_revision(self, revision):
839
895
        """Return the revision identifier for a given revision information."""
840
 
        revno, info = self._get_revision_info(revision)
 
896
        revno, info = self.get_revision_info(revision)
841
897
        return info
842
898
 
843
899
 
858
914
        revision can also be a string, in which case it is parsed for something like
859
915
            'date:' or 'revid:' etc.
860
916
        """
861
 
        revno, rev_id = self._get_revision_info(revision)
862
 
        if revno is None:
863
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
864
 
        return revno, rev_id
865
 
 
866
 
    def get_rev_id(self, revno, history=None):
867
 
        """Find the revision id of the specified revno."""
868
 
        if revno == 0:
869
 
            return None
870
 
        if history is None:
871
 
            history = self.revision_history()
872
 
        elif revno <= 0 or revno > len(history):
873
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
874
 
        return history[revno - 1]
875
 
 
876
 
    def _get_revision_info(self, revision):
877
 
        """Return (revno, revision id) for revision specifier.
878
 
 
879
 
        revision can be an integer, in which case it is assumed to be revno
880
 
        (though this will translate negative values into positive ones)
881
 
        revision can also be a string, in which case it is parsed for something
882
 
        like 'date:' or 'revid:' etc.
883
 
 
884
 
        A revid is always returned.  If it is None, the specifier referred to
885
 
        the null revision.  If the revid does not occur in the revision
886
 
        history, revno will be None.
887
 
        """
888
 
        
889
917
        if revision is None:
890
918
            return 0, None
891
919
        revno = None
895
923
            pass
896
924
        revs = self.revision_history()
897
925
        if isinstance(revision, int):
 
926
            if revision == 0:
 
927
                return 0, None
 
928
            # Mabye we should do this first, but we don't need it if revision == 0
898
929
            if revision < 0:
899
930
                revno = len(revs) + revision + 1
900
931
            else:
901
932
                revno = revision
902
 
            rev_id = self.get_rev_id(revno, revs)
903
933
        elif isinstance(revision, basestring):
904
934
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
905
935
                if revision.startswith(prefix):
906
 
                    result = func(self, revs, revision)
907
 
                    if len(result) > 1:
908
 
                        revno, rev_id = result
909
 
                    else:
910
 
                        revno = result[0]
911
 
                        rev_id = self.get_rev_id(revno, revs)
 
936
                    revno = func(self, revs, revision)
912
937
                    break
913
938
            else:
914
 
                raise BzrError('No namespace registered for string: %r' %
915
 
                               revision)
916
 
        else:
917
 
            raise TypeError('Unhandled revision type %s' % revision)
 
939
                raise BzrError('No namespace registered for string: %r' % revision)
918
940
 
919
 
        if revno is None:
920
 
            if rev_id is None:
921
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
922
 
        return revno, rev_id
 
941
        if revno is None or revno <= 0 or revno > len(revs):
 
942
            raise BzrError("no such revision %s" % revision)
 
943
        return revno, revs[revno-1]
923
944
 
924
945
    def _namespace_revno(self, revs, revision):
925
946
        """Lookup a revision by revision number"""
926
947
        assert revision.startswith('revno:')
927
948
        try:
928
 
            return (int(revision[6:]),)
 
949
            return int(revision[6:])
929
950
        except ValueError:
930
951
            return None
931
952
    REVISION_NAMESPACES['revno:'] = _namespace_revno
932
953
 
933
954
    def _namespace_revid(self, revs, revision):
934
955
        assert revision.startswith('revid:')
935
 
        rev_id = revision[len('revid:'):]
936
956
        try:
937
 
            return revs.index(rev_id) + 1, rev_id
 
957
            return revs.index(revision[6:]) + 1
938
958
        except ValueError:
939
 
            return None, rev_id
 
959
            return None
940
960
    REVISION_NAMESPACES['revid:'] = _namespace_revid
941
961
 
942
962
    def _namespace_last(self, revs, revision):
944
964
        try:
945
965
            offset = int(revision[5:])
946
966
        except ValueError:
947
 
            return (None,)
 
967
            return None
948
968
        else:
949
969
            if offset <= 0:
950
970
                raise BzrError('You must supply a positive value for --revision last:XXX')
951
 
            return (len(revs) - offset + 1,)
 
971
            return len(revs) - offset + 1
952
972
    REVISION_NAMESPACES['last:'] = _namespace_last
953
973
 
954
974
    def _namespace_tag(self, revs, revision):
1029
1049
                # TODO: Handle timezone.
1030
1050
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1031
1051
                if first >= dt and (last is None or dt >= last):
1032
 
                    return (i+1,)
 
1052
                    return i+1
1033
1053
        else:
1034
1054
            for i in range(len(revs)):
1035
1055
                r = self.get_revision(revs[i])
1036
1056
                # TODO: Handle timezone.
1037
1057
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1038
1058
                if first <= dt and (last is None or dt <= last):
1039
 
                    return (i+1,)
 
1059
                    return i+1
1040
1060
    REVISION_NAMESPACES['date:'] = _namespace_date
1041
1061
 
1042
1062
    def revision_tree(self, revision_id):
1050
1070
            return EmptyTree()
1051
1071
        else:
1052
1072
            inv = self.get_revision_inventory(revision_id)
1053
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1073
            return RevisionTree(self.text_store, inv)
1054
1074
 
1055
1075
 
1056
1076
    def working_tree(self):
1064
1084
 
1065
1085
        If there are no revisions yet, return an `EmptyTree`.
1066
1086
        """
1067
 
        return self.revision_tree(self.last_patch())
 
1087
        r = self.last_patch()
 
1088
        if r == None:
 
1089
            return EmptyTree()
 
1090
        else:
 
1091
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
 
1092
 
1068
1093
 
1069
1094
 
1070
1095
    def rename_one(self, from_rel, to_rel):
1102
1127
 
1103
1128
            inv.rename(file_id, to_dir_id, to_tail)
1104
1129
 
 
1130
            print "%s => %s" % (from_rel, to_rel)
 
1131
 
1105
1132
            from_abs = self.abspath(from_rel)
1106
1133
            to_abs = self.abspath(to_rel)
1107
1134
            try:
1126
1153
 
1127
1154
        Note that to_name is only the last component of the new name;
1128
1155
        this doesn't change the directory.
1129
 
 
1130
 
        This returns a list of (from_path, to_path) pairs for each
1131
 
        entry that is moved.
1132
1156
        """
1133
 
        result = []
1134
1157
        self.lock_write()
1135
1158
        try:
1136
1159
            ## TODO: Option to move IDs only
1171
1194
            for f in from_paths:
1172
1195
                name_tail = splitpath(f)[-1]
1173
1196
                dest_path = appendpath(to_name, name_tail)
1174
 
                result.append((f, dest_path))
 
1197
                print "%s => %s" % (f, dest_path)
1175
1198
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1176
1199
                try:
1177
1200
                    os.rename(self.abspath(f), self.abspath(dest_path))
1183
1206
        finally:
1184
1207
            self.unlock()
1185
1208
 
1186
 
        return result
1187
 
 
1188
1209
 
1189
1210
    def revert(self, filenames, old_tree=None, backups=True):
1190
1211
        """Restore selected files to the versions from a previous tree.
1272
1293
            self.unlock()
1273
1294
 
1274
1295
 
1275
 
    def get_parent(self):
1276
 
        """Return the parent location of the branch.
1277
 
 
1278
 
        This is the default location for push/pull/missing.  The usual
1279
 
        pattern is that the user can override it by specifying a
1280
 
        location.
1281
 
        """
1282
 
        import errno
1283
 
        _locs = ['parent', 'pull', 'x-pull']
1284
 
        for l in _locs:
1285
 
            try:
1286
 
                return self.controlfile(l, 'r').read().strip('\n')
1287
 
            except IOError, e:
1288
 
                if e.errno != errno.ENOENT:
1289
 
                    raise
1290
 
        return None
1291
 
 
1292
 
 
1293
 
    def set_parent(self, url):
1294
 
        # TODO: Maybe delete old location files?
1295
 
        from bzrlib.atomicfile import AtomicFile
1296
 
        self.lock_write()
1297
 
        try:
1298
 
            f = AtomicFile(self.controlfilename('parent'))
1299
 
            try:
1300
 
                f.write(url + '\n')
1301
 
                f.commit()
1302
 
            finally:
1303
 
                f.close()
1304
 
        finally:
1305
 
            self.unlock()
1306
 
 
1307
 
    def check_revno(self, revno):
1308
 
        """\
1309
 
        Check whether a revno corresponds to any revision.
1310
 
        Zero (the NULL revision) is considered valid.
1311
 
        """
1312
 
        if revno != 0:
1313
 
            self.check_real_revno(revno)
1314
 
            
1315
 
    def check_real_revno(self, revno):
1316
 
        """\
1317
 
        Check whether a revno corresponds to a real revision.
1318
 
        Zero (the NULL revision) is considered invalid
1319
 
        """
1320
 
        if revno < 1 or revno > self.revno():
1321
 
            raise InvalidRevisionNumber(revno)
1322
 
        
1323
 
        
1324
 
 
1325
1296
 
1326
1297
class ScratchBranch(Branch):
1327
1298
    """Special test class: a branch that cleans up after itself.
1369
1340
        os.rmdir(base)
1370
1341
        copytree(self.base, base, symlinks=True)
1371
1342
        return ScratchBranch(base=base)
1372
 
 
1373
 
 
1374
1343
        
1375
1344
    def __del__(self):
1376
1345
        self.destroy()
1446
1415
    """Return a new tree-root file id."""
1447
1416
    return gen_file_id('TREE_ROOT')
1448
1417
 
1449
 
 
1450
 
def pull_loc(branch):
1451
 
    # TODO: Should perhaps just make attribute be 'base' in
1452
 
    # RemoteBranch and Branch?
1453
 
    if hasattr(branch, "baseurl"):
1454
 
        return branch.baseurl
1455
 
    else:
1456
 
        return branch.base
1457
 
 
1458
 
 
1459
 
def copy_branch(branch_from, to_location, revision=None):
1460
 
    """Copy branch_from into the existing directory to_location.
1461
 
 
1462
 
    revision
1463
 
        If not None, only revisions up to this point will be copied.
1464
 
        The head of the new branch will be that revision.
1465
 
 
1466
 
    to_location
1467
 
        The name of a local directory that exists but is empty.
1468
 
    """
1469
 
    from bzrlib.merge import merge
1470
 
    from bzrlib.branch import Branch
1471
 
 
1472
 
    assert isinstance(branch_from, Branch)
1473
 
    assert isinstance(to_location, basestring)
1474
 
    
1475
 
    br_to = Branch(to_location, init=True)
1476
 
    br_to.set_root_id(branch_from.get_root_id())
1477
 
    if revision is None:
1478
 
        revno = branch_from.revno()
1479
 
    else:
1480
 
        revno, rev_id = branch_from.get_revision_info(revision)
1481
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1482
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1483
 
          check_clean=False, ignore_zero=True)
1484
 
    
1485
 
    from_location = pull_loc(branch_from)
1486
 
    br_to.set_parent(pull_loc(branch_from))
1487