~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-09-13 01:37:23 UTC
  • Revision ID: mbp@sourcefrog.net-20050913013723-7e0026b48cbf08ff
- BROKEN: start refactoring fetch code to work well with weaves

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
26
25
     sha_file, appendpath, file_kind
27
26
 
28
27
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
 
                           NoSuchRevision, HistoryMissing, NotBranchError,
30
 
                           LockError)
 
28
                           NoSuchRevision)
31
29
from bzrlib.textui import show_status
32
 
from bzrlib.revision import Revision, validate_revision_id
 
30
from bzrlib.revision import Revision
33
31
from bzrlib.delta import compare_trees
34
32
from bzrlib.tree import EmptyTree, RevisionTree
35
33
from bzrlib.inventory import Inventory
36
34
from bzrlib.weavestore import WeaveStore
37
 
from bzrlib.store import ImmutableStore
38
35
import bzrlib.xml5
39
36
import bzrlib.ui
40
37
 
41
38
 
42
 
INVENTORY_FILEID = '__inventory'
43
 
ANCESTRY_FILEID = '__ancestry'
44
 
 
45
39
 
46
40
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
47
41
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
50
44
 
51
45
# TODO: Some operations like log might retrieve the same revisions
52
46
# repeatedly to calculate deltas.  We could perhaps have a weakref
53
 
# cache in memory to make this faster.  In general anything can be
54
 
# cached in memory between lock and unlock operations.
 
47
# cache in memory to make this faster.
55
48
 
56
49
# TODO: please move the revision-string syntax stuff out of the branch
57
50
# object; it's clutter
104
97
        if tail:
105
98
            s.insert(0, tail)
106
99
    else:
 
100
        from errors import NotBranchError
107
101
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
108
102
 
109
103
    return os.sep.join(s)
137
131
        head, tail = os.path.split(f)
138
132
        if head == f:
139
133
            # reached the root, whatever that may be
140
 
            raise NotBranchError('%s is not in a branch' % orig_f)
 
134
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
141
135
        f = head
142
136
 
143
137
 
173
167
    _lock_mode = None
174
168
    _lock_count = None
175
169
    _lock = None
176
 
    _inventory_weave = None
177
170
    
178
171
    # Map some sort of prefix into a namespace
179
172
    # stuff like "revno:10", "revid:", etc.
180
173
    # This should match a prefix with a function which accepts
181
174
    REVISION_NAMESPACES = {}
182
175
 
183
 
    def __init__(self, base, init=False, find_root=True,
184
 
                 relax_version_check=False):
 
176
    def __init__(self, base, init=False, find_root=True):
185
177
        """Create new branch object at a particular location.
186
178
 
187
179
        base -- Base directory for the branch.
193
185
        find_root -- If true and init is false, find the root of the
194
186
             existing branch containing base.
195
187
 
196
 
        relax_version_check -- If true, the usual check for the branch
197
 
            version is not applied.  This is intended only for
198
 
            upgrade/recovery type use; it's not guaranteed that
199
 
            all operations will work on old format branches.
200
 
 
201
188
        In the test suite, creation of new trees is tested using the
202
189
        `ScratchBranch` class.
203
190
        """
 
191
        from bzrlib.store import ImmutableStore
204
192
        if init:
205
193
            self.base = os.path.realpath(base)
206
194
            self._make_control()
209
197
        else:
210
198
            self.base = os.path.realpath(base)
211
199
            if not isdir(self.controlfilename('.')):
212
 
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
213
 
                                     ['use "bzr init" to initialize a '
214
 
                                      'new working tree'])
215
 
        
216
 
        self._check_format(relax_version_check)
217
 
        if self._branch_format == 4:
218
 
            self.inventory_store = \
219
 
                ImmutableStore(self.controlfilename('inventory-store'))
220
 
            self.text_store = \
221
 
                ImmutableStore(self.controlfilename('text-store'))
 
200
                from errors import NotBranchError
 
201
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
 
202
                                     ['use "bzr init" to initialize a new working tree',
 
203
                                      'current bzr can only operate from top-of-tree'])
 
204
        self._check_format()
 
205
 
222
206
        self.weave_store = WeaveStore(self.controlfilename('weaves'))
223
 
        self.revision_store = \
224
 
            ImmutableStore(self.controlfilename('revision-store'))
 
207
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
 
208
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
225
209
 
226
210
 
227
211
    def __str__(self):
241
225
    def lock_write(self):
242
226
        if self._lock_mode:
243
227
            if self._lock_mode != 'w':
 
228
                from errors import LockError
244
229
                raise LockError("can't upgrade to a write lock from %r" %
245
230
                                self._lock_mode)
246
231
            self._lock_count += 1
266
251
                        
267
252
    def unlock(self):
268
253
        if not self._lock_mode:
 
254
            from errors import LockError
269
255
            raise LockError('branch %r is not locked' % (self))
270
256
 
271
257
        if self._lock_count > 1:
323
309
            "This is a Bazaar-NG control directory.\n"
324
310
            "Do not change any files in this directory.\n")
325
311
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
326
 
        for d in ('text-store', 'revision-store',
 
312
        for d in ('text-store', 'inventory-store', 'revision-store',
327
313
                  'weaves'):
328
314
            os.mkdir(self.controlfilename(d))
329
315
        for f in ('revision-history', 'merged-patches',
338
324
        # simplicity.
339
325
        f = self.controlfile('inventory','w')
340
326
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
341
 
        
342
 
 
343
 
 
344
 
    def _check_format(self, relax_version_check):
 
327
 
 
328
 
 
329
    def _check_format(self):
345
330
        """Check this branch format is supported.
346
331
 
347
332
        The format level is stored, as an integer, in
353
338
        fmt = self.controlfile('branch-format', 'r').read()
354
339
        if fmt == BZR_BRANCH_FORMAT_5:
355
340
            self._branch_format = 5
356
 
        elif fmt == BZR_BRANCH_FORMAT_4:
357
 
            self._branch_format = 4
358
 
 
359
 
        if (not relax_version_check
360
 
            and self._branch_format != 5):
 
341
        else:
361
342
            raise BzrError('sorry, branch format "%s" not supported; ' 
362
343
                           'use a different bzr version, '
363
 
                           'or run "bzr upgrade"'
 
344
                           'or run "bzr upgrade", '
 
345
                           'or remove the .bzr directory and "bzr init" again'
364
346
                           % fmt.rstrip('\n\r'))
365
 
        
366
347
 
367
348
    def get_root_id(self):
368
349
        """Return the id of this branches root"""
596
577
            f.close()
597
578
 
598
579
 
599
 
    def has_revision(self, revision_id):
600
 
        """True if this branch has a copy of the revision.
601
 
 
602
 
        This does not necessarily imply the revision is merge
603
 
        or on the mainline."""
604
 
        return revision_id in self.revision_store
605
 
 
606
 
 
607
580
    def get_revision_xml_file(self, revision_id):
608
581
        """Return XML file object for revision object."""
609
582
        if not revision_id or not isinstance(revision_id, basestring):
619
592
            self.unlock()
620
593
 
621
594
 
622
 
    def get_revision_xml(self, revision_id):
623
 
        return self.get_revision_xml_file(revision_id).read()
 
595
    #deprecated
 
596
    get_revision_xml = get_revision_xml_file
624
597
 
625
598
 
626
599
    def get_revision(self, revision_id):
663
636
 
664
637
    def get_revision_sha1(self, revision_id):
665
638
        """Hash the stored value of a revision, and return it."""
666
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
667
 
 
668
 
 
669
 
    def get_ancestry(self, revision_id):
670
 
        """Return a list of revision-ids integrated by a revision.
671
 
        """
672
 
        w = self.weave_store.get_weave(ANCESTRY_FILEID)
673
 
        # strip newlines
674
 
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
675
 
 
676
 
 
677
 
    def get_inventory_weave(self):
678
 
        return self.weave_store.get_weave(INVENTORY_FILEID)
 
639
        # In the future, revision entries will be signed. At that
 
640
        # point, it is probably best *not* to include the signature
 
641
        # in the revision hash. Because that lets you re-sign
 
642
        # the revision, (add signatures/remove signatures) and still
 
643
        # have all hash pointers stay consistent.
 
644
        # But for now, just hash the contents.
 
645
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
679
646
 
680
647
 
681
648
    def get_inventory(self, revision_id):
682
 
        """Get Inventory object by hash."""
683
 
        # FIXME: The text gets passed around a lot coming from the weave.
684
 
        f = StringIO(self.get_inventory_xml(revision_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
        f = self.get_inventory_xml_file(revision_id)
685
655
        return bzrlib.xml5.serializer_v5.read_inventory(f)
686
656
 
687
657
 
689
659
        """Get inventory XML as a file object."""
690
660
        try:
691
661
            assert isinstance(revision_id, basestring), type(revision_id)
692
 
            iw = self.get_inventory_weave()
693
 
            return iw.get_text(iw.lookup(revision_id))
 
662
            return self.inventory_store[revision_id]
694
663
        except IndexError:
695
664
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
696
665
 
 
666
    get_inventory_xml_file = get_inventory_xml
 
667
            
697
668
 
698
669
    def get_inventory_sha1(self, revision_id):
699
670
        """Return the sha1 hash of the inventory entry
700
671
        """
701
 
        return self.get_revision(revision_id).inventory_sha1
 
672
        return sha_file(self.get_inventory_xml_file(revision_id))
702
673
 
703
674
 
704
675
    def get_revision_inventory(self, revision_id):
712
683
 
713
684
 
714
685
    def revision_history(self):
715
 
        """Return sequence of revision hashes on to this branch."""
 
686
        """Return sequence of revision hashes on to this branch.
 
687
 
 
688
        >>> ScratchBranch().revision_history()
 
689
        []
 
690
        """
716
691
        self.lock_read()
717
692
        try:
718
693
            return [l.rstrip('\r\n') for l in
727
702
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
728
703
        >>> sb.common_ancestor(sb) == (None, None)
729
704
        True
730
 
        >>> commit.commit(sb, "Committing first revision")
 
705
        >>> commit.commit(sb, "Committing first revision", verbose=False)
731
706
        >>> sb.common_ancestor(sb)[0]
732
707
        1
733
708
        >>> clone = sb.clone()
734
 
        >>> commit.commit(sb, "Committing second revision")
 
709
        >>> commit.commit(sb, "Committing second revision", verbose=False)
735
710
        >>> sb.common_ancestor(sb)[0]
736
711
        2
737
712
        >>> sb.common_ancestor(clone)[0]
738
713
        1
739
 
        >>> commit.commit(clone, "Committing divergent second revision")
 
714
        >>> commit.commit(clone, "Committing divergent second revision", 
 
715
        ...               verbose=False)
740
716
        >>> sb.common_ancestor(clone)[0]
741
717
        1
742
718
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
774
750
        return len(self.revision_history())
775
751
 
776
752
 
777
 
    def last_revision(self):
 
753
    def last_patch(self):
778
754
        """Return last patch hash, or None if no history.
779
755
        """
780
756
        ph = self.revision_history()
785
761
 
786
762
 
787
763
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
788
 
        """Return a list of new revisions that would perfectly fit.
789
 
        
 
764
        """
790
765
        If self and other have not diverged, return a list of the revisions
791
766
        present in other, but missing from self.
792
767
 
812
787
        Traceback (most recent call last):
813
788
        DivergedBranches: These branches have diverged.
814
789
        """
815
 
        # FIXME: If the branches have diverged, but the latest
816
 
        # revision in this branch is completely merged into the other,
817
 
        # then we should still be able to pull.
818
790
        self_history = self.revision_history()
819
791
        self_len = len(self_history)
820
792
        other_history = other.revision_history()
826
798
 
827
799
        if stop_revision is None:
828
800
            stop_revision = other_len
829
 
        else:
830
 
            assert isinstance(stop_revision, int)
831
 
            if stop_revision > other_len:
832
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
801
        elif stop_revision > other_len:
 
802
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
833
803
        
834
804
        return other_history[self_len:stop_revision]
835
805
 
836
806
 
837
 
    def update_revisions(self, other, stop_revno=None):
838
 
        """Pull in new perfect-fit revisions.
 
807
    def update_revisions(self, other, stop_revision=None):
 
808
        """Pull in all new revisions from other branch.
839
809
        """
840
810
        from bzrlib.fetch import greedy_fetch
841
811
 
842
 
        if stop_revno:
843
 
            stop_revision = other.lookup_revision(stop_revno)
 
812
        pb = bzrlib.ui.ui_factory.progress_bar()
 
813
        pb.update('comparing histories')
 
814
 
 
815
        revision_ids = self.missing_revisions(other, stop_revision)
 
816
 
 
817
        if len(revision_ids) > 0:
 
818
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
844
819
        else:
845
 
            stop_revision = None
846
 
        greedy_fetch(to_branch=self, from_branch=other,
847
 
                     revision=stop_revision)
848
 
 
849
 
        pullable_revs = self.missing_revisions(other, stop_revision)
850
 
 
851
 
        if pullable_revs:
852
 
            greedy_fetch(to_branch=self,
853
 
                         from_branch=other,
854
 
                         revision=pullable_revs[-1])
855
 
            self.append_revision(*pullable_revs)
 
820
            count = 0
 
821
        self.append_revision(*revision_ids)
 
822
        ## note("Added %d revisions." % count)
 
823
        pb.clear()
856
824
 
857
825
 
858
826
    def commit(self, *args, **kw):
1089
1057
 
1090
1058
        If there are no revisions yet, return an `EmptyTree`.
1091
1059
        """
1092
 
        return self.revision_tree(self.last_revision())
 
1060
        return self.revision_tree(self.last_patch())
1093
1061
 
1094
1062
 
1095
1063
    def rename_one(self, from_rel, to_rel):
1271
1239
 
1272
1240
 
1273
1241
    def add_pending_merge(self, revision_id):
 
1242
        from bzrlib.revision import validate_revision_id
 
1243
 
1274
1244
        validate_revision_id(revision_id)
1275
 
        # TODO: Perhaps should check at this point that the
1276
 
        # history of the revision is actually present?
 
1245
 
1277
1246
        p = self.pending_merges()
1278
1247
        if revision_id in p:
1279
1248
            return
1485
1454
 
1486
1455
    revision
1487
1456
        If not None, only revisions up to this point will be copied.
1488
 
        The head of the new branch will be that revision.  Can be a
1489
 
        revno or revid.
 
1457
        The head of the new branch will be that revision.
1490
1458
 
1491
1459
    to_location
1492
1460
        The name of a local directory that exists but is empty.
1493
1461
    """
1494
 
    # TODO: This could be done *much* more efficiently by just copying
1495
 
    # all the whole weaves and revisions, rather than getting one
1496
 
    # revision at a time.
1497
1462
    from bzrlib.merge import merge
1498
1463
    from bzrlib.branch import Branch
1499
1464
 
1503
1468
    br_to = Branch(to_location, init=True)
1504
1469
    br_to.set_root_id(branch_from.get_root_id())
1505
1470
    if revision is None:
1506
 
        revno = None
 
1471
        revno = branch_from.revno()
1507
1472
    else:
1508
1473
        revno, rev_id = branch_from.get_revision_info(revision)
1509
 
    br_to.update_revisions(branch_from, stop_revno=revno)
 
1474
    br_to.update_revisions(branch_from, stop_revision=revno)
1510
1475
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1511
1476
          check_clean=False, ignore_zero=True)
1512
1477