~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-10-17 21:57:32 UTC
  • mto: This revision was merged to the branch mainline in revision 1462.
  • Revision ID: robertc@robertcollins.net-20051017215732-08f487800e726748
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
                           DivergedBranches, LockError, UnlistableStore,
36
36
                           UnlistableBranch, NoSuchFile)
37
37
from bzrlib.textui import show_status
38
 
from bzrlib.revision import Revision
 
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
 
39
 
39
40
from bzrlib.delta import compare_trees
40
41
from bzrlib.tree import EmptyTree, RevisionTree
41
42
from bzrlib.inventory import Inventory
43
44
from bzrlib.store.compressed_text import CompressedTextStore
44
45
from bzrlib.store.text import TextStore
45
46
from bzrlib.store.weave import WeaveStore
 
47
from bzrlib.testament import Testament
46
48
import bzrlib.transactions as transactions
47
49
from bzrlib.transport import Transport, get_transport
48
50
import bzrlib.xml5
64
66
    # XXX: leave this here for about one release, then remove it
65
67
    raise NotImplementedError('find_branch() is not supported anymore, '
66
68
                              'please use one of the new branch constructors')
67
 
def _relpath(base, path):
68
 
    """Return path relative to base, or raise exception.
69
 
 
70
 
    The path may be either an absolute path or a path relative to the
71
 
    current working directory.
72
 
 
73
 
    Lifted out of Branch.relpath for ease of testing.
74
 
 
75
 
    os.path.commonprefix (python2.4) has a bad bug that it works just
76
 
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
77
 
    avoids that problem."""
78
 
    rp = os.path.abspath(path)
79
 
 
80
 
    s = []
81
 
    head = rp
82
 
    while len(head) >= len(base):
83
 
        if head == base:
84
 
            break
85
 
        head, tail = os.path.split(head)
86
 
        if tail:
87
 
            s.insert(0, tail)
88
 
    else:
89
 
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
90
 
 
91
 
    return os.sep.join(s)
92
 
        
93
69
 
94
70
######################################################################
95
71
# branch objects
268
244
            self.weave_store = get_weave('weaves', prefixed=True)
269
245
            self.revision_store = get_store('revision-store', compressed=False,
270
246
                                            prefixed=True)
 
247
        self.revision_store.register_suffix('sig')
271
248
        self._transaction = None
272
249
 
273
250
    def __str__(self):
303
280
            return self._transport.base
304
281
        return None
305
282
 
306
 
    base = property(_get_base)
 
283
    base = property(_get_base, doc="The URL for the root of this branch.")
307
284
 
308
285
    def _finish_transaction(self):
309
286
        """Exit the current transaction."""
377
354
            self._lock_mode = self._lock_count = None
378
355
 
379
356
    def abspath(self, name):
380
 
        """Return absolute filename for something in the branch"""
 
357
        """Return absolute filename for something in the branch
 
358
        
 
359
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
360
        method and not a tree method.
 
361
        """
381
362
        return self._transport.abspath(name)
382
363
 
383
 
    def relpath(self, path):
384
 
        """Return path relative to this branch of something inside it.
385
 
 
386
 
        Raises an error if path is not in this branch."""
387
 
        return self._transport.relpath(path)
388
 
 
389
 
 
390
364
    def _rel_controlfilename(self, file_or_path):
391
365
        if isinstance(file_or_path, basestring):
392
366
            file_or_path = [file_or_path]
750
724
        This does not necessarily imply the revision is merge
751
725
        or on the mainline."""
752
726
        return (revision_id is None
753
 
                or revision_id in self.revision_store)
 
727
                or self.revision_store.has_id(revision_id))
754
728
 
755
729
    def get_revision_xml_file(self, revision_id):
756
730
        """Return XML file object for revision object."""
760
734
        self.lock_read()
761
735
        try:
762
736
            try:
763
 
                return self.revision_store[revision_id]
 
737
                return self.revision_store.get(revision_id)
764
738
            except (IndexError, KeyError):
765
739
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
766
740
        finally:
882
856
        finally:
883
857
            self.unlock()
884
858
 
885
 
    def common_ancestor(self, other, self_revno=None, other_revno=None):
886
 
        """
887
 
        >>> from bzrlib.commit import commit
888
 
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
889
 
        >>> sb.common_ancestor(sb) == (None, None)
890
 
        True
891
 
        >>> commit(sb, "Committing first revision", verbose=False)
892
 
        >>> sb.common_ancestor(sb)[0]
893
 
        1
894
 
        >>> clone = sb.clone()
895
 
        >>> commit(sb, "Committing second revision", verbose=False)
896
 
        >>> sb.common_ancestor(sb)[0]
897
 
        2
898
 
        >>> sb.common_ancestor(clone)[0]
899
 
        1
900
 
        >>> commit(clone, "Committing divergent second revision", 
901
 
        ...               verbose=False)
902
 
        >>> sb.common_ancestor(clone)[0]
903
 
        1
904
 
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
905
 
        True
906
 
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
907
 
        True
908
 
        >>> clone2 = sb.clone()
909
 
        >>> sb.common_ancestor(clone2)[0]
910
 
        2
911
 
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
912
 
        1
913
 
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
914
 
        1
915
 
        """
916
 
        my_history = self.revision_history()
917
 
        other_history = other.revision_history()
918
 
        if self_revno is None:
919
 
            self_revno = len(my_history)
920
 
        if other_revno is None:
921
 
            other_revno = len(other_history)
922
 
        indices = range(min((self_revno, other_revno)))
923
 
        indices.reverse()
924
 
        for r in indices:
925
 
            if my_history[r] == other_history[r]:
926
 
                return r+1, my_history[r]
927
 
        return None, None
928
 
 
929
 
 
930
859
    def revno(self):
931
860
        """Return current revision number for this branch.
932
861
 
974
903
        Traceback (most recent call last):
975
904
        DivergedBranches: These branches have diverged.
976
905
        """
977
 
        # FIXME: If the branches have diverged, but the latest
978
 
        # revision in this branch is completely merged into the other,
979
 
        # then we should still be able to pull.
980
906
        self_history = self.revision_history()
981
907
        self_len = len(self_history)
982
908
        other_history = other.revision_history()
996
922
 
997
923
    def update_revisions(self, other, stop_revision=None):
998
924
        """Pull in new perfect-fit revisions."""
 
925
        # FIXME: If the branches have diverged, but the latest
 
926
        # revision in this branch is completely merged into the other,
 
927
        # then we should still be able to pull.
999
928
        from bzrlib.fetch import greedy_fetch
1000
 
        from bzrlib.revision import get_intervening_revisions
1001
929
        if stop_revision is None:
1002
930
            stop_revision = other.last_revision()
 
931
        ### Should this be checking is_ancestor instead of revision_history?
1003
932
        if (stop_revision is not None and 
1004
933
            stop_revision in self.revision_history()):
1005
934
            return
1006
935
        greedy_fetch(to_branch=self, from_branch=other,
1007
936
                     revision=stop_revision)
1008
 
        pullable_revs = self.missing_revisions(
1009
 
            other, other.revision_id_to_revno(stop_revision))
1010
 
        if pullable_revs:
1011
 
            greedy_fetch(to_branch=self,
1012
 
                         from_branch=other,
1013
 
                         revision=pullable_revs[-1])
 
937
        pullable_revs = self.pullable_revisions(other, stop_revision)
 
938
        if len(pullable_revs) > 0:
1014
939
            self.append_revision(*pullable_revs)
1015
 
    
1016
940
 
 
941
    def pullable_revisions(self, other, stop_revision):
 
942
        other_revno = other.revision_id_to_revno(stop_revision)
 
943
        try:
 
944
            return self.missing_revisions(other, other_revno)
 
945
        except DivergedBranches, e:
 
946
            try:
 
947
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
948
                                                          stop_revision, self)
 
949
                assert self.last_revision() not in pullable_revs
 
950
                return pullable_revs
 
951
            except bzrlib.errors.NotAncestor:
 
952
                if is_ancestor(self.last_revision(), stop_revision, self):
 
953
                    return []
 
954
                else:
 
955
                    raise e
 
956
        
1017
957
    def commit(self, *args, **kw):
1018
958
        from bzrlib.commit import Commit
1019
959
        Commit().commit(self, *args, **kw)
1051
991
            inv = self.get_revision_inventory(revision_id)
1052
992
            return RevisionTree(self.weave_store, inv, revision_id)
1053
993
 
1054
 
 
1055
994
    def working_tree(self):
1056
995
        """Return a `Tree` for the working copy."""
1057
996
        from bzrlib.workingtree import WorkingTree
1060
999
        # much more complex to keep consistent than our careful .bzr subset.
1061
1000
        # instead, we should say that working trees are local only, and optimise
1062
1001
        # for that.
1063
 
        return WorkingTree(self._transport.base, self.read_working_inventory())
 
1002
        return WorkingTree(self.base, branch=self)
1064
1003
 
1065
1004
 
1066
1005
    def basis_tree(self):
1318
1257
        if revno < 1 or revno > self.revno():
1319
1258
            raise InvalidRevisionNumber(revno)
1320
1259
        
1321
 
        
1322
 
        
 
1260
    def sign_revision(self, revision_id, gpg_strategy):
 
1261
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
1262
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1263
 
 
1264
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1265
        self.lock_write()
 
1266
        try:
 
1267
            self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
1268
                                    revision_id, "sig")
 
1269
        finally:
 
1270
            self.unlock()
1323
1271
 
1324
1272
 
1325
1273
class ScratchBranch(_Branch):
1329
1277
    >>> isdir(b.base)
1330
1278
    True
1331
1279
    >>> bd = b.base
1332
 
    >>> b.destroy()
 
1280
    >>> b._transport.__del__()
1333
1281
    >>> isdir(bd)
1334
1282
    False
1335
1283
    """
1336
 
    def __init__(self, files=[], dirs=[], base=None):
 
1284
 
 
1285
    def __init__(self, files=[], dirs=[], transport=None):
1337
1286
        """Make a test branch.
1338
1287
 
1339
1288
        This creates a temporary directory and runs init-tree in it.
1340
1289
 
1341
1290
        If any files are listed, they are created in the working copy.
1342
1291
        """
1343
 
        from tempfile import mkdtemp
1344
 
        init = False
1345
 
        if base is None:
1346
 
            base = mkdtemp()
1347
 
            init = True
1348
 
        if isinstance(base, basestring):
1349
 
            base = get_transport(base)
1350
 
        _Branch.__init__(self, base, init=init)
 
1292
        if transport is None:
 
1293
            transport = bzrlib.transport.local.ScratchTransport()
 
1294
            super(ScratchBranch, self).__init__(transport, init=True)
 
1295
        else:
 
1296
            super(ScratchBranch, self).__init__(transport)
 
1297
 
1351
1298
        for d in dirs:
1352
1299
            self._transport.mkdir(d)
1353
1300
            
1373
1320
        base = mkdtemp()
1374
1321
        os.rmdir(base)
1375
1322
        copytree(self.base, base, symlinks=True)
1376
 
        return ScratchBranch(base=base)
1377
 
 
1378
 
    def __del__(self):
1379
 
        self.destroy()
1380
 
 
1381
 
    def destroy(self):
1382
 
        """Destroy the test branch, removing the scratch directory."""
1383
 
        from shutil import rmtree
1384
 
        try:
1385
 
            if self.base:
1386
 
                mutter("delete ScratchBranch %s" % self.base)
1387
 
                rmtree(self.base)
1388
 
        except OSError, e:
1389
 
            # Work around for shutil.rmtree failing on Windows when
1390
 
            # readonly files are encountered
1391
 
            mutter("hit exception in destroying ScratchBranch: %s" % e)
1392
 
            for root, dirs, files in os.walk(self.base, topdown=False):
1393
 
                for name in files:
1394
 
                    os.chmod(os.path.join(root, name), 0700)
1395
 
            rmtree(self.base)
1396
 
        self._transport = None
1397
 
 
 
1323
        return ScratchBranch(
 
1324
            transport=bzrlib.transport.local.ScratchTransport(base))
1398
1325
    
1399
1326
 
1400
1327
######################################################################