~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] robert's integration branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
import shutil
18
19
import sys
19
20
import os
20
21
import errno
33
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
35
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
36
                           DivergedBranches, LockError, UnlistableStore,
36
 
                           UnlistableBranch, NoSuchFile, NotVersionedError)
 
37
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
38
                           NoWorkingTree)
37
39
from bzrlib.textui import show_status
38
 
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
 
40
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
 
41
                             NULL_REVISION)
39
42
 
40
43
from bzrlib.delta import compare_trees
41
44
from bzrlib.tree import EmptyTree, RevisionTree
208
211
        """Create new branch object at a particular location.
209
212
 
210
213
        transport -- A Transport object, defining how to access files.
211
 
                (If a string, transport.transport() will be used to
212
 
                create a Transport object)
213
214
        
214
215
        init -- If True, create new control files in a previously
215
216
             unversioned directory.  If False, the branch must already
273
274
    def __str__(self):
274
275
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
275
276
 
276
 
 
277
277
    __repr__ = __str__
278
278
 
279
 
 
280
279
    def __del__(self):
281
280
        if self._lock_mode or self._lock:
282
281
            # XXX: This should show something every time, and be suitable for
292
291
        # should never expect their __del__ function to run.
293
292
        if hasattr(self, 'cache_root') and self.cache_root is not None:
294
293
            try:
295
 
                import shutil
296
294
                shutil.rmtree(self.cache_root)
297
295
            except:
298
296
                pass
318
316
        """Return the current active transaction.
319
317
 
320
318
        If no transaction is active, this returns a passthrough object
321
 
        for which all data is immedaitely flushed and no caching happens.
 
319
        for which all data is immediately flushed and no caching happens.
322
320
        """
323
321
        if self._transaction is None:
324
322
            return transactions.PassThroughTransaction()
395
393
        """Return location relative to branch."""
396
394
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
397
395
 
398
 
 
399
396
    def controlfile(self, file_or_path, mode='r'):
400
397
        """Open a control file for this branch.
401
398
 
417
414
        elif mode == 'wb':
418
415
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
419
416
        elif mode == 'r':
 
417
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
418
            # an error, or at least reported, if there's incorrectly-encoded
 
419
            # data inside a file.
 
420
            # <https://launchpad.net/products/bzr/+bug/3823>
420
421
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
421
422
        elif mode == 'w':
422
423
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
517
518
 
518
519
    def get_root_id(self):
519
520
        """Return the id of this branches root"""
520
 
        inv = self.read_working_inventory()
 
521
        inv = self.get_inventory(self.last_revision())
521
522
        return inv.root.file_id
522
523
 
523
 
    def set_root_id(self, file_id):
524
 
        inv = self.read_working_inventory()
525
 
        orig_root_id = inv.root.file_id
526
 
        del inv._byid[inv.root.file_id]
527
 
        inv.root.file_id = file_id
528
 
        inv._byid[inv.root.file_id] = inv.root
529
 
        for fid in inv:
530
 
            entry = inv[fid]
531
 
            if entry.parent_id in (None, orig_root_id):
532
 
                entry.parent_id = inv.root.file_id
533
 
        self._write_inventory(inv)
534
 
 
535
 
    @needs_read_lock
536
 
    def read_working_inventory(self):
537
 
        """Read the working inventory."""
538
 
        # ElementTree does its own conversion from UTF-8, so open in
539
 
        # binary.
540
 
        f = self.controlfile('inventory', 'rb')
541
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
542
 
 
543
 
    @needs_write_lock
544
 
    def _write_inventory(self, inv):
545
 
        """Update the working inventory.
546
 
 
547
 
        That is to say, the inventory describing changes underway, that
548
 
        will be committed to the next revision.
549
 
        """
550
 
        from cStringIO import StringIO
551
 
        sio = StringIO()
552
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
553
 
        sio.seek(0)
554
 
        # Transport handles atomicity
555
 
        self.put_controlfile('inventory', sio)
556
 
        
557
 
        mutter('wrote working inventory')
558
 
            
559
 
    inventory = property(read_working_inventory, _write_inventory, None,
560
 
                         """Inventory for the working copy.""")
561
 
 
562
524
    @needs_write_lock
563
525
    def add(self, files, ids=None):
564
526
        """Make files versioned.
595
557
        else:
596
558
            assert(len(ids) == len(files))
597
559
 
598
 
        inv = self.read_working_inventory()
 
560
        inv = self.working_tree().read_working_inventory()
599
561
        for f,file_id in zip(files, ids):
600
562
            if is_control_file(f):
601
563
                raise BzrError("cannot add control file %s" % quotefn(f))
623
585
 
624
586
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
625
587
 
626
 
        self._write_inventory(inv)
 
588
        self.working_tree()._write_inventory(inv)
627
589
 
628
590
    @needs_read_lock
629
591
    def print_file(self, file, revno):
635
597
            raise BzrError("%r is not present in revision %s" % (file, revno))
636
598
        tree.print_file(file_id)
637
599
 
638
 
    # FIXME: this doesn't need to be a branch method
639
 
    def set_inventory(self, new_inventory_list):
640
 
        from bzrlib.inventory import Inventory, InventoryEntry
641
 
        inv = Inventory(self.get_root_id())
642
 
        for path, file_id, parent, kind in new_inventory_list:
643
 
            name = os.path.basename(path)
644
 
            if name == "":
645
 
                continue
646
 
            # fixme, there should be a factory function inv,add_?? 
647
 
            if kind == 'directory':
648
 
                inv.add(inventory.InventoryDirectory(file_id, name, parent))
649
 
            elif kind == 'file':
650
 
                inv.add(inventory.InventoryFile(file_id, name, parent))
651
 
            elif kind == 'symlink':
652
 
                inv.add(inventory.InventoryLink(file_id, name, parent))
653
 
            else:
654
 
                raise BzrError("unknown kind %r" % kind)
655
 
        self._write_inventory(inv)
656
 
 
657
600
    def unknowns(self):
658
601
        """Return all unknown files.
659
602
 
697
640
    def get_revision_xml_file(self, revision_id):
698
641
        """Return XML file object for revision object."""
699
642
        if not revision_id or not isinstance(revision_id, basestring):
700
 
            raise InvalidRevisionId(revision_id=revision_id)
 
643
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
701
644
        try:
702
645
            return self.revision_store.get(revision_id)
703
646
        except (IndexError, KeyError):
796
739
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
797
740
        # must be the same as its revision, so this is trivial.
798
741
        if revision_id == None:
799
 
            return Inventory(self.get_root_id())
 
742
            # This does not make sense: if there is no revision,
 
743
            # then it is the current tree inventory surely ?!
 
744
            # and thus get_root_id() is something that looks at the last
 
745
            # commit on the branch, and the get_root_id is an inventory check.
 
746
            raise NotImplementedError
 
747
            # return Inventory(self.get_root_id())
800
748
        else:
801
749
            return self.get_inventory(revision_id)
802
750
 
880
828
 
881
829
    def update_revisions(self, other, stop_revision=None):
882
830
        """Pull in new perfect-fit revisions."""
883
 
        # FIXME: If the branches have diverged, but the latest
884
 
        # revision in this branch is completely merged into the other,
885
 
        # then we should still be able to pull.
886
831
        from bzrlib.fetch import greedy_fetch
887
832
        if stop_revision is None:
888
833
            stop_revision = other.last_revision()
912
857
                else:
913
858
                    raise e
914
859
        
915
 
    def commit(self, *args, **kw):
916
 
        from bzrlib.commit import Commit
917
 
        Commit().commit(self, *args, **kw)
918
 
    
919
860
    def revision_id_to_revno(self, revision_id):
920
861
        """Given a revision id, return its revno"""
921
862
        if revision_id is None:
943
884
        an `EmptyTree` is returned."""
944
885
        # TODO: refactor this to use an existing revision object
945
886
        # so we don't need to read it in twice.
946
 
        if revision_id == None:
 
887
        if revision_id == None or revision_id == NULL_REVISION:
947
888
            return EmptyTree()
948
889
        else:
949
890
            inv = self.get_revision_inventory(revision_id)
957
898
        # much more complex to keep consistent than our careful .bzr subset.
958
899
        # instead, we should say that working trees are local only, and optimise
959
900
        # for that.
 
901
        if self._transport.base.find('://') != -1:
 
902
            raise NoWorkingTree(self.base)
960
903
        return WorkingTree(self.base, branch=self)
961
904
 
 
905
    @needs_write_lock
 
906
    def pull(self, source, overwrite=False):
 
907
        source.lock_read()
 
908
        try:
 
909
            try:
 
910
                self.update_revisions(source)
 
911
            except DivergedBranches:
 
912
                if not overwrite:
 
913
                    raise
 
914
                self.set_revision_history(source.revision_history())
 
915
        finally:
 
916
            source.unlock()
962
917
 
963
918
    def basis_tree(self):
964
919
        """Return `Tree` object for last revision.
1010
965
                    % (from_abs, to_abs, e[1]),
1011
966
                    ["rename rolled back"])
1012
967
 
1013
 
        self._write_inventory(inv)
 
968
        self.working_tree()._write_inventory(inv)
1014
969
 
1015
970
    @needs_write_lock
1016
971
    def move(self, from_paths, to_name):
1074
1029
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1075
1030
                        ["rename rolled back"])
1076
1031
 
1077
 
        self._write_inventory(inv)
 
1032
        self.working_tree()._write_inventory(inv)
1078
1033
        return result
1079
1034
 
1080
 
 
1081
 
    def revert(self, filenames, old_tree=None, backups=True):
1082
 
        """Restore selected files to the versions from a previous tree.
1083
 
 
1084
 
        backups
1085
 
            If true (default) backups are made of files before
1086
 
            they're renamed.
1087
 
        """
1088
 
        from bzrlib.atomicfile import AtomicFile
1089
 
        from bzrlib.osutils import backup_file
1090
 
        
1091
 
        inv = self.read_working_inventory()
1092
 
        if old_tree is None:
1093
 
            old_tree = self.basis_tree()
1094
 
        old_inv = old_tree.inventory
1095
 
 
1096
 
        nids = []
1097
 
        for fn in filenames:
1098
 
            file_id = inv.path2id(fn)
1099
 
            if not file_id:
1100
 
                raise NotVersionedError(path=fn)
1101
 
            if not old_inv.has_id(file_id):
1102
 
                raise BzrError("file not present in old tree", fn, file_id)
1103
 
            nids.append((fn, file_id))
1104
 
            
1105
 
        # TODO: Rename back if it was previously at a different location
1106
 
 
1107
 
        # TODO: If given a directory, restore the entire contents from
1108
 
        # the previous version.
1109
 
 
1110
 
        # TODO: Make a backup to a temporary file.
1111
 
 
1112
 
        # TODO: If the file previously didn't exist, delete it?
1113
 
        for fn, file_id in nids:
1114
 
            backup_file(fn)
1115
 
            
1116
 
            f = AtomicFile(fn, 'wb')
1117
 
            try:
1118
 
                f.write(old_tree.get_file(file_id).read())
1119
 
                f.commit()
1120
 
            finally:
1121
 
                f.close()
1122
 
 
1123
 
 
1124
 
    def pending_merges(self):
1125
 
        """Return a list of pending merges.
1126
 
 
1127
 
        These are revisions that have been merged into the working
1128
 
        directory but not yet committed.
1129
 
        """
1130
 
        cfn = self._rel_controlfilename('pending-merges')
1131
 
        if not self._transport.has(cfn):
1132
 
            return []
1133
 
        p = []
1134
 
        for l in self.controlfile('pending-merges', 'r').readlines():
1135
 
            p.append(l.rstrip('\n'))
1136
 
        return p
1137
 
 
1138
 
 
1139
 
    def add_pending_merge(self, *revision_ids):
1140
 
        # TODO: Perhaps should check at this point that the
1141
 
        # history of the revision is actually present?
1142
 
        p = self.pending_merges()
1143
 
        updated = False
1144
 
        for rev_id in revision_ids:
1145
 
            if rev_id in p:
1146
 
                continue
1147
 
            p.append(rev_id)
1148
 
            updated = True
1149
 
        if updated:
1150
 
            self.set_pending_merges(p)
1151
 
 
1152
 
    @needs_write_lock
1153
 
    def set_pending_merges(self, rev_list):
1154
 
        self.put_controlfile('pending-merges', '\n'.join(rev_list))
1155
 
 
1156
1035
    def get_parent(self):
1157
1036
        """Return the parent location of the branch.
1158
1037
 
1170
1049
                    raise
1171
1050
        return None
1172
1051
 
 
1052
    def get_push_location(self):
 
1053
        """Return the None or the location to push this branch to."""
 
1054
        config = bzrlib.config.BranchConfig(self)
 
1055
        push_loc = config.get_user_option('push_location')
 
1056
        return push_loc
 
1057
 
 
1058
    def set_push_location(self, location):
 
1059
        """Set a new push location for this branch."""
 
1060
        config = bzrlib.config.LocationConfig(self.base)
 
1061
        config.set_user_option('push_location', location)
 
1062
 
1173
1063
    @needs_write_lock
1174
1064
    def set_parent(self, url):
1175
1065
        # TODO: Maybe delete old location files?