~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

- constraints on revprops
- tests for this

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, is_ancestor, get_intervening_revisions
39
 
 
 
38
from bzrlib.revision import Revision
40
39
from bzrlib.delta import compare_trees
41
40
from bzrlib.tree import EmptyTree, RevisionTree
42
41
from bzrlib.inventory import Inventory
44
43
from bzrlib.store.compressed_text import CompressedTextStore
45
44
from bzrlib.store.text import TextStore
46
45
from bzrlib.store.weave import WeaveStore
47
 
from bzrlib.testament import Testament
48
46
import bzrlib.transactions as transactions
49
47
from bzrlib.transport import Transport, get_transport
50
48
import bzrlib.xml5
66
64
    # XXX: leave this here for about one release, then remove it
67
65
    raise NotImplementedError('find_branch() is not supported anymore, '
68
66
                              '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
        
69
93
 
70
94
######################################################################
71
95
# branch objects
244
268
            self.weave_store = get_weave('weaves', prefixed=True)
245
269
            self.revision_store = get_store('revision-store', compressed=False,
246
270
                                            prefixed=True)
247
 
        self.revision_store.register_suffix('sig')
248
271
        self._transaction = None
249
272
 
250
273
    def __str__(self):
280
303
            return self._transport.base
281
304
        return None
282
305
 
283
 
    base = property(_get_base, doc="The URL for the root of this branch.")
 
306
    base = property(_get_base)
284
307
 
285
308
    def _finish_transaction(self):
286
309
        """Exit the current transaction."""
354
377
            self._lock_mode = self._lock_count = None
355
378
 
356
379
    def abspath(self, name):
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
 
        """
 
380
        """Return absolute filename for something in the branch"""
362
381
        return self._transport.abspath(name)
363
382
 
 
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
 
364
390
    def _rel_controlfilename(self, file_or_path):
365
391
        if isinstance(file_or_path, basestring):
366
392
            file_or_path = [file_or_path]
724
750
        This does not necessarily imply the revision is merge
725
751
        or on the mainline."""
726
752
        return (revision_id is None
727
 
                or self.revision_store.has_id(revision_id))
 
753
                or revision_id in self.revision_store)
728
754
 
729
755
    def get_revision_xml_file(self, revision_id):
730
756
        """Return XML file object for revision object."""
734
760
        self.lock_read()
735
761
        try:
736
762
            try:
737
 
                return self.revision_store.get(revision_id)
 
763
                return self.revision_store[revision_id]
738
764
            except (IndexError, KeyError):
739
765
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
740
766
        finally:
856
882
        finally:
857
883
            self.unlock()
858
884
 
 
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
 
859
930
    def revno(self):
860
931
        """Return current revision number for this branch.
861
932
 
903
974
        Traceback (most recent call last):
904
975
        DivergedBranches: These branches have diverged.
905
976
        """
 
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.
906
980
        self_history = self.revision_history()
907
981
        self_len = len(self_history)
908
982
        other_history = other.revision_history()
922
996
 
923
997
    def update_revisions(self, other, stop_revision=None):
924
998
        """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.
928
999
        from bzrlib.fetch import greedy_fetch
 
1000
        from bzrlib.revision import get_intervening_revisions
929
1001
        if stop_revision is None:
930
1002
            stop_revision = other.last_revision()
931
 
        ### Should this be checking is_ancestor instead of revision_history?
932
1003
        if (stop_revision is not None and 
933
1004
            stop_revision in self.revision_history()):
934
1005
            return
935
1006
        greedy_fetch(to_branch=self, from_branch=other,
936
1007
                     revision=stop_revision)
937
 
        pullable_revs = self.pullable_revisions(other, stop_revision)
938
 
        if len(pullable_revs) > 0:
 
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])
939
1014
            self.append_revision(*pullable_revs)
 
1015
    
940
1016
 
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
 
        
957
1017
    def commit(self, *args, **kw):
958
1018
        from bzrlib.commit import Commit
959
1019
        Commit().commit(self, *args, **kw)
991
1051
            inv = self.get_revision_inventory(revision_id)
992
1052
            return RevisionTree(self.weave_store, inv, revision_id)
993
1053
 
 
1054
 
994
1055
    def working_tree(self):
995
1056
        """Return a `Tree` for the working copy."""
996
1057
        from bzrlib.workingtree import WorkingTree
999
1060
        # much more complex to keep consistent than our careful .bzr subset.
1000
1061
        # instead, we should say that working trees are local only, and optimise
1001
1062
        # for that.
1002
 
        return WorkingTree(self.base, branch=self)
 
1063
        return WorkingTree(self._transport.base, self.read_working_inventory())
1003
1064
 
1004
1065
 
1005
1066
    def basis_tree(self):
1257
1318
        if revno < 1 or revno > self.revno():
1258
1319
            raise InvalidRevisionNumber(revno)
1259
1320
        
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()
 
1321
        
 
1322
        
1271
1323
 
1272
1324
 
1273
1325
class ScratchBranch(_Branch):
1277
1329
    >>> isdir(b.base)
1278
1330
    True
1279
1331
    >>> bd = b.base
1280
 
    >>> b._transport.__del__()
 
1332
    >>> b.destroy()
1281
1333
    >>> isdir(bd)
1282
1334
    False
1283
1335
    """
1284
 
 
1285
 
    def __init__(self, files=[], dirs=[], transport=None):
 
1336
    def __init__(self, files=[], dirs=[], base=None):
1286
1337
        """Make a test branch.
1287
1338
 
1288
1339
        This creates a temporary directory and runs init-tree in it.
1289
1340
 
1290
1341
        If any files are listed, they are created in the working copy.
1291
1342
        """
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
 
 
 
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)
1298
1351
        for d in dirs:
1299
1352
            self._transport.mkdir(d)
1300
1353
            
1320
1373
        base = mkdtemp()
1321
1374
        os.rmdir(base)
1322
1375
        copytree(self.base, base, symlinks=True)
1323
 
        return ScratchBranch(
1324
 
            transport=bzrlib.transport.local.ScratchTransport(base))
 
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
 
1325
1398
    
1326
1399
 
1327
1400
######################################################################