~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: aaron.bentley at utoronto
  • Date: 2005-09-05 07:10:59 UTC
  • mto: (1185.3.4)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: aaron.bentley@utoronto.ca-20050905071059-63693b49338a914c
Added merge test

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):
309
301
            raise BzrError("invalid controlfile mode %r" % mode)
310
302
 
311
303
    def _make_control(self):
 
304
        from bzrlib.inventory import Inventory
 
305
        from bzrlib.xml import pack_xml
 
306
        
312
307
        os.mkdir(self.controlfilename([]))
313
308
        self.controlfile('README', 'w').write(
314
309
            "This is a Bazaar-NG control directory.\n"
315
310
            "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'):
 
311
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
 
312
        for d in ('text-store', 'inventory-store', 'revision-store'):
319
313
            os.mkdir(self.controlfilename(d))
320
314
        for f in ('revision-history', 'merged-patches',
321
315
                  'pending-merged-patches', 'branch-name',
327
321
        # if we want per-tree root ids then this is the place to set
328
322
        # them; they're not needed for now and so ommitted for
329
323
        # simplicity.
330
 
        f = self.controlfile('inventory','w')
331
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
332
 
        
333
 
 
 
324
        pack_xml(Inventory(), self.controlfile('inventory','w'))
334
325
 
335
326
    def _check_format(self):
336
327
        """Check this branch format is supported.
337
328
 
338
 
        The format level is stored, as an integer, in
339
 
        self._branch_format for code that needs to check it later.
 
329
        The current tool only supports the current unstable format.
340
330
 
341
331
        In the future, we might need different in-memory Branch
342
332
        classes to support downlevel branches.  But not yet.
343
333
        """
 
334
        # This ignores newlines so that we can open branches created
 
335
        # on Windows from Linux and so on.  I think it might be better
 
336
        # to always make all internal files in unix format.
344
337
        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'))
 
338
        fmt.replace('\r\n', '')
 
339
        if fmt != BZR_BRANCH_FORMAT:
 
340
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
341
                           ['use a different bzr version',
 
342
                            'or remove the .bzr directory and "bzr init" again'])
353
343
 
354
344
    def get_root_id(self):
355
345
        """Return the id of this branches root"""
370
360
 
371
361
    def read_working_inventory(self):
372
362
        """Read the working inventory."""
 
363
        from bzrlib.inventory import Inventory
 
364
        from bzrlib.xml import unpack_xml
 
365
        from time import time
 
366
        before = time()
373
367
        self.lock_read()
374
368
        try:
375
369
            # ElementTree does its own conversion from UTF-8, so open in
376
370
            # binary.
377
 
            f = self.controlfile('inventory', 'rb')
378
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
 
371
            inv = unpack_xml(Inventory,
 
372
                             self.controlfile('inventory', 'rb'))
 
373
            mutter("loaded inventory of %d items in %f"
 
374
                   % (len(inv), time() - before))
 
375
            return inv
379
376
        finally:
380
377
            self.unlock()
381
378
            
387
384
        will be committed to the next revision.
388
385
        """
389
386
        from bzrlib.atomicfile import AtomicFile
 
387
        from bzrlib.xml import pack_xml
390
388
        
391
389
        self.lock_write()
392
390
        try:
393
391
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
394
392
            try:
395
 
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
 
393
                pack_xml(inv, f)
396
394
                f.commit()
397
395
            finally:
398
396
                f.close()
583
581
            f.close()
584
582
 
585
583
 
586
 
    def get_revision_xml_file(self, revision_id):
 
584
    def get_revision_xml(self, revision_id):
587
585
        """Return XML file object for revision object."""
588
586
        if not revision_id or not isinstance(revision_id, basestring):
589
587
            raise InvalidRevisionId(revision_id)
598
596
            self.unlock()
599
597
 
600
598
 
601
 
    #deprecated
602
 
    get_revision_xml = get_revision_xml_file
603
 
 
604
 
 
605
599
    def get_revision(self, revision_id):
606
600
        """Return the Revision object for a named revision"""
607
 
        xml_file = self.get_revision_xml_file(revision_id)
 
601
        xml_file = self.get_revision_xml(revision_id)
608
602
 
609
603
        try:
610
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
604
            r = unpack_xml(Revision, xml_file)
611
605
        except SyntaxError, e:
612
606
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
613
607
                                         [revision_id,
651
645
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
652
646
 
653
647
 
654
 
    def get_ancestry(self, revision_id):
655
 
        """Return a list of revision-ids integrated by a revision.
656
 
        """
657
 
        w = self.weave_store.get_weave(ANCESTRY_FILEID)
658
 
        # strip newlines
659
 
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
660
 
 
661
 
 
662
 
    def get_inventory_weave(self):
663
 
        return self.weave_store.get_weave(INVENTORY_FILEID)
664
 
 
665
 
 
666
 
    def get_inventory(self, revision_id):
667
 
        """Get Inventory object by hash."""
668
 
        # FIXME: The text gets passed around a lot coming from the weave.
669
 
        f = StringIO(self.get_inventory_xml(revision_id))
670
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
671
 
 
672
 
 
673
 
    def get_inventory_xml(self, revision_id):
 
648
    def get_inventory(self, inventory_id):
 
649
        """Get Inventory object by hash.
 
650
 
 
651
        TODO: Perhaps for this and similar methods, take a revision
 
652
               parameter which can be either an integer revno or a
 
653
               string hash."""
 
654
        from bzrlib.inventory import Inventory
 
655
        from bzrlib.xml import unpack_xml
 
656
 
 
657
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
 
658
 
 
659
 
 
660
    def get_inventory_xml(self, inventory_id):
674
661
        """Get inventory XML as a file object."""
675
 
        try:
676
 
            assert isinstance(revision_id, basestring), type(revision_id)
677
 
            iw = self.get_inventory_weave()
678
 
            return iw.get_text(iw.lookup(revision_id))
679
 
        except IndexError:
680
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
681
 
 
682
 
 
683
 
    def get_inventory_sha1(self, revision_id):
 
662
        return self.inventory_store[inventory_id]
 
663
            
 
664
 
 
665
    def get_inventory_sha1(self, inventory_id):
684
666
        """Return the sha1 hash of the inventory entry
685
667
        """
686
 
        return self.get_revision(revision_id).inventory_sha1
 
668
        return sha_file(self.get_inventory_xml(inventory_id))
687
669
 
688
670
 
689
671
    def get_revision_inventory(self, revision_id):
690
672
        """Return inventory of a past revision."""
691
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
673
        # bzr 0.0.6 imposes the constraint that the inventory_id
692
674
        # must be the same as its revision, so this is trivial.
693
675
        if revision_id == None:
 
676
            from bzrlib.inventory import Inventory
694
677
            return Inventory(self.get_root_id())
695
678
        else:
696
679
            return self.get_inventory(revision_id)
716
699
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
717
700
        >>> sb.common_ancestor(sb) == (None, None)
718
701
        True
719
 
        >>> commit.commit(sb, "Committing first revision")
 
702
        >>> commit.commit(sb, "Committing first revision", verbose=False)
720
703
        >>> sb.common_ancestor(sb)[0]
721
704
        1
722
705
        >>> clone = sb.clone()
723
 
        >>> commit.commit(sb, "Committing second revision")
 
706
        >>> commit.commit(sb, "Committing second revision", verbose=False)
724
707
        >>> sb.common_ancestor(sb)[0]
725
708
        2
726
709
        >>> sb.common_ancestor(clone)[0]
727
710
        1
728
 
        >>> commit.commit(clone, "Committing divergent second revision")
 
711
        >>> commit.commit(clone, "Committing divergent second revision", 
 
712
        ...               verbose=False)
729
713
        >>> sb.common_ancestor(clone)[0]
730
714
        1
731
715
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
835
819
        ## note("Added %d revisions." % count)
836
820
        pb.clear()
837
821
 
 
822
    def install_revisions(self, other, revision_ids, pb):
 
823
        if hasattr(other.revision_store, "prefetch"):
 
824
            other.revision_store.prefetch(revision_ids)
 
825
        if hasattr(other.inventory_store, "prefetch"):
 
826
            inventory_ids = [other.get_revision(r).inventory_id
 
827
                             for r in revision_ids]
 
828
            other.inventory_store.prefetch(inventory_ids)
 
829
 
 
830
        if pb is None:
 
831
            pb = bzrlib.ui.ui_factory.progress_bar()
 
832
                
 
833
        revisions = []
 
834
        needed_texts = set()
 
835
        i = 0
 
836
 
 
837
        failures = set()
 
838
        for i, rev_id in enumerate(revision_ids):
 
839
            pb.update('fetching revision', i+1, len(revision_ids))
 
840
            try:
 
841
                rev = other.get_revision(rev_id)
 
842
            except bzrlib.errors.NoSuchRevision:
 
843
                failures.add(rev_id)
 
844
                continue
 
845
 
 
846
            revisions.append(rev)
 
847
            inv = other.get_inventory(str(rev.inventory_id))
 
848
            for key, entry in inv.iter_entries():
 
849
                if entry.text_id is None:
 
850
                    continue
 
851
                if entry.text_id not in self.text_store:
 
852
                    needed_texts.add(entry.text_id)
 
853
 
 
854
        pb.clear()
 
855
                    
 
856
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
857
                                                    needed_texts)
 
858
        #print "Added %d texts." % count 
 
859
        inventory_ids = [ f.inventory_id for f in revisions ]
 
860
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
861
                                                         inventory_ids)
 
862
        #print "Added %d inventories." % count 
 
863
        revision_ids = [ f.revision_id for f in revisions]
 
864
 
 
865
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
866
                                                          revision_ids,
 
867
                                                          permit_failure=True)
 
868
        assert len(cp_fail) == 0 
 
869
        return count, failures
 
870
       
838
871
 
839
872
    def commit(self, *args, **kw):
840
 
        from bzrlib.commit import Commit
841
 
        Commit().commit(self, *args, **kw)
 
873
        from bzrlib.commit import commit
 
874
        commit(self, *args, **kw)
842
875
        
843
876
 
844
877
    def lookup_revision(self, revision):
1056
1089
            return EmptyTree()
1057
1090
        else:
1058
1091
            inv = self.get_revision_inventory(revision_id)
1059
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1092
            return RevisionTree(self.text_store, inv)
1060
1093
 
1061
1094
 
1062
1095
    def working_tree(self):
1070
1103
 
1071
1104
        If there are no revisions yet, return an `EmptyTree`.
1072
1105
        """
1073
 
        return self.revision_tree(self.last_patch())
 
1106
        r = self.last_patch()
 
1107
        if r == None:
 
1108
            return EmptyTree()
 
1109
        else:
 
1110
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
 
1111
 
1074
1112
 
1075
1113
 
1076
1114
    def rename_one(self, from_rel, to_rel):