/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] (Goffredo) faster merge/fetch by peeking into weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 
25
25
 
26
26
import bzrlib
27
 
from bzrlib.inventory import InventoryEntry
28
27
import bzrlib.inventory as inventory
29
28
from bzrlib.trace import mutter, note
30
 
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
31
 
                            rename, splitpath, sha_file, appendpath, 
32
 
                            file_kind, abspath)
 
29
from bzrlib.osutils import (isdir, quotefn,
 
30
                            rename, splitpath, sha_file,
 
31
                            file_kind, abspath, normpath, pathjoin)
33
32
import bzrlib.errors as errors
34
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
35
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
134
133
        while True:
135
134
            try:
136
135
                return BzrBranch(t), t.relpath(url)
137
 
            except NotBranchError:
138
 
                pass
 
136
            except NotBranchError, e:
 
137
                mutter('not a branch in: %r %s', t.base, e)
139
138
            new_t = t.clone('..')
140
139
            if new_t.base == t.base:
141
140
                # reached the root, whatever that may be
237
236
    def set_root_id(self, file_id):
238
237
        raise NotImplementedError('set_root_id is abstract')
239
238
 
240
 
    def add(self, files, ids=None):
241
 
        """Make files versioned.
242
 
 
243
 
        Note that the command line normally calls smart_add instead,
244
 
        which can automatically recurse.
245
 
 
246
 
        This puts the files in the Added state, so that they will be
247
 
        recorded by the next commit.
248
 
 
249
 
        files
250
 
            List of paths to add, relative to the base of the tree.
251
 
 
252
 
        ids
253
 
            If set, use these instead of automatically generated ids.
254
 
            Must be the same length as the list of files, but may
255
 
            contain None for ids that are to be autogenerated.
256
 
 
257
 
        TODO: Perhaps have an option to add the ids even if the files do
258
 
              not (yet) exist.
259
 
 
260
 
        TODO: Perhaps yield the ids and paths as they're added.
261
 
        """
262
 
        raise NotImplementedError('add is abstract')
263
 
 
264
 
    def print_file(self, file, revno):
 
239
    def print_file(self, file, revision_id):
265
240
        """Print `file` to stdout."""
266
241
        raise NotImplementedError('print_file is abstract')
267
242
 
268
 
    def unknowns(self):
269
 
        """Return all unknown files.
270
 
 
271
 
        These are files in the working directory that are not versioned or
272
 
        control files or ignored.
273
 
        
274
 
        >>> from bzrlib.workingtree import WorkingTree
275
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
276
 
        >>> map(str, b.unknowns())
277
 
        ['foo']
278
 
        >>> b.add('foo')
279
 
        >>> list(b.unknowns())
280
 
        []
281
 
        >>> WorkingTree(b.base, b).remove('foo')
282
 
        >>> list(b.unknowns())
283
 
        [u'foo']
284
 
        """
285
 
        raise NotImplementedError('unknowns is abstract')
286
 
 
287
243
    def append_revision(self, *revision_ids):
288
244
        raise NotImplementedError('append_revision is abstract')
289
245
 
297
253
        or on the mainline."""
298
254
        raise NotImplementedError('has_revision is abstract')
299
255
 
300
 
    def get_revision_xml_file(self, revision_id):
301
 
        """Return XML file object for revision object."""
302
 
        raise NotImplementedError('get_revision_xml_file is abstract')
303
 
 
304
256
    def get_revision_xml(self, revision_id):
305
257
        raise NotImplementedError('get_revision_xml is abstract')
306
258
 
403
355
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
404
356
        >>> br1.missing_revisions(br2)
405
357
        Traceback (most recent call last):
406
 
        DivergedBranches: These branches have diverged.
 
358
        DivergedBranches: These branches have diverged.  Try merge.
407
359
        """
408
360
        self_history = self.revision_history()
409
361
        self_len = len(self_history)
457
409
        raise NotImplementedError('revision_tree is abstract')
458
410
 
459
411
    def working_tree(self):
460
 
        """Return a `Tree` for the working copy."""
 
412
        """Return a `Tree` for the working copy if this is a local branch."""
461
413
        raise NotImplementedError('working_tree is abstract')
462
414
 
463
415
    def pull(self, source, overwrite=False):
493
445
        """
494
446
        raise NotImplementedError('move is abstract')
495
447
 
496
 
    def revert(self, filenames, old_tree=None, backups=True):
497
 
        """Restore selected files to the versions from a previous tree.
498
 
 
499
 
        backups
500
 
            If true (default) backups are made of files before
501
 
            they're renamed.
502
 
        """
503
 
        raise NotImplementedError('revert is abstract')
504
 
 
505
 
    def pending_merges(self):
506
 
        """Return a list of pending merges.
507
 
 
508
 
        These are revisions that have been merged into the working
509
 
        directory but not yet committed.
510
 
        """
511
 
        raise NotImplementedError('pending_merges is abstract')
512
 
 
513
 
    def add_pending_merge(self, *revision_ids):
514
 
        # TODO: Perhaps should check at this point that the
515
 
        # history of the revision is actually present?
516
 
        raise NotImplementedError('add_pending_merge is abstract')
517
 
 
518
 
    def set_pending_merges(self, rev_list):
519
 
        raise NotImplementedError('set_pending_merges is abstract')
520
 
 
521
448
    def get_parent(self):
522
449
        """Return the parent location of the branch.
523
450
 
560
487
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
561
488
        raise NotImplementedError('store_revision_signature is abstract')
562
489
 
 
490
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
491
        """ This function returns the file_id(s) involved in the
 
492
            changes between the from_revid revision and the to_revid
 
493
            revision
 
494
        """
 
495
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
496
 
 
497
    def fileid_involved(self, last_revid=None):
 
498
        """ This function returns the file_id(s) involved in the
 
499
            changes up to the revision last_revid
 
500
            If no parametr is passed, then all file_id[s] present in the
 
501
            repository are returned
 
502
        """
 
503
        raise NotImplementedError('fileid_involved is abstract')
 
504
 
 
505
    def fileid_involved_by_set(self, changes):
 
506
        """ This function returns the file_id(s) involved in the
 
507
            changes present in the set 'changes'
 
508
        """
 
509
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
510
 
563
511
class BzrBranch(Branch):
564
512
    """A branch stored in the actual filesystem.
565
513
 
585
533
    _lock_count = None
586
534
    _lock = None
587
535
    _inventory_weave = None
 
536
    # If set to False (by a plugin, etc) BzrBranch will not set the
 
537
    # mode on created files or directories
 
538
    _set_file_mode = True
 
539
    _set_dir_mode = True
588
540
    
589
541
    # Map some sort of prefix into a namespace
590
542
    # stuff like "revno:10", "revid:", etc.
635
587
        if init:
636
588
            self._make_control()
637
589
        self._check_format(relax_version_check)
 
590
        self._find_modes()
638
591
 
639
592
        def get_store(name, compressed=True, prefixed=False):
640
 
            # FIXME: This approach of assuming stores are all entirely compressed
641
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
642
 
            # some existing branches where there's a mixture; we probably 
643
 
            # still want the option to look for both.
644
 
            relpath = self._rel_controlfilename(name)
 
593
            relpath = self._rel_controlfilename(unicode(name))
645
594
            store = TextStore(self._transport.clone(relpath),
 
595
                              dir_mode=self._dir_mode,
 
596
                              file_mode=self._file_mode,
646
597
                              prefixed=prefixed,
647
598
                              compressed=compressed)
648
 
            #if self._transport.should_cache():
649
 
            #    cache_path = os.path.join(self.cache_root, name)
650
 
            #    os.mkdir(cache_path)
651
 
            #    store = bzrlib.store.CachedStore(store, cache_path)
652
599
            return store
 
600
 
653
601
        def get_weave(name, prefixed=False):
654
 
            relpath = self._rel_controlfilename(name)
655
 
            ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
 
602
            relpath = self._rel_controlfilename(unicode(name))
 
603
            ws = WeaveStore(self._transport.clone(relpath),
 
604
                            prefixed=prefixed,
 
605
                            dir_mode=self._dir_mode,
 
606
                            file_mode=self._file_mode)
656
607
            if self._transport.should_cache():
657
608
                ws.enable_cache = True
658
609
            return ws
662
613
            self.text_store = get_store('text-store')
663
614
            self.revision_store = get_store('revision-store')
664
615
        elif self._branch_format == 5:
665
 
            self.control_weaves = get_weave('')
666
 
            self.weave_store = get_weave('weaves')
667
 
            self.revision_store = get_store('revision-store', compressed=False)
 
616
            self.control_weaves = get_weave(u'')
 
617
            self.weave_store = get_weave(u'weaves')
 
618
            self.revision_store = get_store(u'revision-store', compressed=False)
668
619
        elif self._branch_format == 6:
669
 
            self.control_weaves = get_weave('')
670
 
            self.weave_store = get_weave('weaves', prefixed=True)
671
 
            self.revision_store = get_store('revision-store', compressed=False,
 
620
            self.control_weaves = get_weave(u'')
 
621
            self.weave_store = get_weave(u'weaves', prefixed=True)
 
622
            self.revision_store = get_store(u'revision-store', compressed=False,
672
623
                                            prefixed=True)
673
624
        self.revision_store.register_suffix('sig')
674
625
        self._transaction = None
729
680
        self._transaction = new_transaction
730
681
 
731
682
    def lock_write(self):
732
 
        mutter("lock write: %s (%s)", self, self._lock_count)
 
683
        #mutter("lock write: %s (%s)", self, self._lock_count)
733
684
        # TODO: Upgrade locking to support using a Transport,
734
685
        # and potentially a remote locking protocol
735
686
        if self._lock_mode:
745
696
            self._set_transaction(transactions.PassThroughTransaction())
746
697
 
747
698
    def lock_read(self):
748
 
        mutter("lock read: %s (%s)", self, self._lock_count)
 
699
        #mutter("lock read: %s (%s)", self, self._lock_count)
749
700
        if self._lock_mode:
750
701
            assert self._lock_mode in ('r', 'w'), \
751
702
                   "invalid lock mode %r" % self._lock_mode
760
711
            self.get_transaction().set_cache_size(5000)
761
712
                        
762
713
    def unlock(self):
763
 
        mutter("unlock: %s (%s)", self, self._lock_count)
 
714
        #mutter("unlock: %s (%s)", self, self._lock_count)
764
715
        if not self._lock_mode:
765
716
            raise LockError('branch %r is not locked' % (self))
766
717
 
778
729
 
779
730
    def _rel_controlfilename(self, file_or_path):
780
731
        if not isinstance(file_or_path, basestring):
781
 
            file_or_path = '/'.join(file_or_path)
 
732
            file_or_path = u'/'.join(file_or_path)
782
733
        if file_or_path == '':
783
734
            return bzrlib.BZRDIR
784
 
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
 
735
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
785
736
 
786
737
    def controlfilename(self, file_or_path):
787
738
        """See Branch.controlfilename."""
825
776
                    f = codecs.getwriter('utf-8')(f, errors='replace')
826
777
            path = self._rel_controlfilename(path)
827
778
            ctrl_files.append((path, f))
828
 
        self._transport.put_multi(ctrl_files)
 
779
        self._transport.put_multi(ctrl_files, mode=self._file_mode)
 
780
 
 
781
    def _find_modes(self, path=None):
 
782
        """Determine the appropriate modes for files and directories."""
 
783
        try:
 
784
            if path is None:
 
785
                path = self._rel_controlfilename('')
 
786
            st = self._transport.stat(path)
 
787
        except errors.TransportNotPossible:
 
788
            self._dir_mode = 0755
 
789
            self._file_mode = 0644
 
790
        else:
 
791
            self._dir_mode = st.st_mode & 07777
 
792
            # Remove the sticky and execute bits for files
 
793
            self._file_mode = self._dir_mode & ~07111
 
794
        if not self._set_dir_mode:
 
795
            self._dir_mode = None
 
796
        if not self._set_file_mode:
 
797
            self._file_mode = None
829
798
 
830
799
    def _make_control(self):
831
800
        from bzrlib.inventory import Inventory
843
812
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
844
813
        empty_weave = sio.getvalue()
845
814
 
846
 
        dirs = [[], 'revision-store', 'weaves']
 
815
        cfn = self._rel_controlfilename
 
816
        # Since we don't have a .bzr directory, inherit the
 
817
        # mode from the root directory
 
818
        self._find_modes(u'.')
 
819
 
 
820
        dirs = ['', 'revision-store', 'weaves']
847
821
        files = [('README', 
848
822
            "This is a Bazaar-NG control directory.\n"
849
823
            "Do not change any files in this directory.\n"),
856
830
            ('inventory.weave', empty_weave),
857
831
            ('ancestry.weave', empty_weave)
858
832
        ]
859
 
        cfn = self._rel_controlfilename
860
 
        self._transport.mkdir_multi([cfn(d) for d in dirs])
 
833
        self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
861
834
        self.put_controlfiles(files)
862
835
        mutter('created control directory in ' + self._transport.base)
863
836
 
890
863
                            'or remove the .bzr directory'
891
864
                            ' and "bzr init" again'])
892
865
 
 
866
    @needs_read_lock
893
867
    def get_root_id(self):
894
868
        """See Branch.get_root_id."""
895
869
        inv = self.get_inventory(self.last_revision())
896
870
        return inv.root.file_id
897
871
 
898
 
    @needs_write_lock
899
 
    def set_root_id(self, file_id):
900
 
        """See Branch.set_root_id."""
901
 
        inv = self.working_tree().read_working_inventory()
902
 
        orig_root_id = inv.root.file_id
903
 
        del inv._byid[inv.root.file_id]
904
 
        inv.root.file_id = file_id
905
 
        inv._byid[inv.root.file_id] = inv.root
906
 
        for fid in inv:
907
 
            entry = inv[fid]
908
 
            if entry.parent_id in (None, orig_root_id):
909
 
                entry.parent_id = inv.root.file_id
910
 
        self._write_inventory(inv)
911
 
 
912
 
    @needs_write_lock
913
 
    def add(self, files, ids=None):
914
 
        """See Branch.add."""
915
 
        # TODO: Re-adding a file that is removed in the working copy
916
 
        # should probably put it back with the previous ID.
917
 
        if isinstance(files, basestring):
918
 
            assert(ids is None or isinstance(ids, basestring))
919
 
            files = [files]
920
 
            if ids is not None:
921
 
                ids = [ids]
922
 
 
923
 
        if ids is None:
924
 
            ids = [None] * len(files)
925
 
        else:
926
 
            assert(len(ids) == len(files))
927
 
 
928
 
        inv = self.working_tree().read_working_inventory()
929
 
        for f,file_id in zip(files, ids):
930
 
            if is_control_file(f):
931
 
                raise BzrError("cannot add control file %s" % quotefn(f))
932
 
 
933
 
            fp = splitpath(f)
934
 
 
935
 
            if len(fp) == 0:
936
 
                raise BzrError("cannot add top-level %r" % f)
937
 
 
938
 
            fullpath = os.path.normpath(self.abspath(f))
939
 
 
940
 
            try:
941
 
                kind = file_kind(fullpath)
942
 
            except OSError:
943
 
                # maybe something better?
944
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
945
 
 
946
 
            if not InventoryEntry.versionable_kind(kind):
947
 
                raise BzrError('cannot add: not a versionable file ('
948
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
949
 
 
950
 
            if file_id is None:
951
 
                file_id = gen_file_id(f)
952
 
            inv.add_path(f, kind=kind, file_id=file_id)
953
 
 
954
 
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
955
 
 
956
 
        self.working_tree()._write_inventory(inv)
957
 
 
958
872
    @needs_read_lock
959
 
    def print_file(self, file, revno):
 
873
    def print_file(self, file, revision_id):
960
874
        """See Branch.print_file."""
961
 
        tree = self.revision_tree(self.get_rev_id(revno))
 
875
        tree = self.revision_tree(revision_id)
962
876
        # use inventory as it was in that revision
963
877
        file_id = tree.inventory.path2id(file)
964
878
        if not file_id:
965
 
            raise BzrError("%r is not present in revision %s" % (file, revno))
 
879
            try:
 
880
                revno = self.revision_id_to_revno(revision_id)
 
881
            except errors.NoSuchRevision:
 
882
                # TODO: This should not be BzrError,
 
883
                # but NoSuchFile doesn't fit either
 
884
                raise BzrError('%r is not present in revision %s' 
 
885
                                % (file, revision_id))
 
886
            else:
 
887
                raise BzrError('%r is not present in revision %s'
 
888
                                % (file, revno))
966
889
        tree.print_file(file_id)
967
890
 
968
 
    def unknowns(self):
969
 
        """See Branch.unknowns."""
970
 
        return self.working_tree().unknowns()
971
 
 
972
891
    @needs_write_lock
973
892
    def append_revision(self, *revision_ids):
974
893
        """See Branch.append_revision."""
981
900
    @needs_write_lock
982
901
    def set_revision_history(self, rev_history):
983
902
        """See Branch.set_revision_history."""
 
903
        old_revision = self.last_revision()
 
904
        new_revision = rev_history[-1]
984
905
        self.put_controlfile('revision-history', '\n'.join(rev_history))
 
906
        try:
 
907
            self.working_tree().set_last_revision(new_revision, old_revision)
 
908
        except NoWorkingTree:
 
909
            mutter('Unable to set_last_revision without a working tree.')
985
910
 
986
911
    def has_revision(self, revision_id):
987
912
        """See Branch.has_revision."""
989
914
                or self.revision_store.has_id(revision_id))
990
915
 
991
916
    @needs_read_lock
992
 
    def get_revision_xml_file(self, revision_id):
993
 
        """See Branch.get_revision_xml_file."""
 
917
    def _get_revision_xml_file(self, revision_id):
994
918
        if not revision_id or not isinstance(revision_id, basestring):
995
919
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
996
920
        try:
998
922
        except (IndexError, KeyError):
999
923
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
1000
924
 
1001
 
    #deprecated
1002
 
    get_revision_xml = get_revision_xml_file
1003
 
 
1004
925
    def get_revision_xml(self, revision_id):
1005
926
        """See Branch.get_revision_xml."""
1006
 
        return self.get_revision_xml_file(revision_id).read()
1007
 
 
 
927
        return self._get_revision_xml_file(revision_id).read()
1008
928
 
1009
929
    def get_revision(self, revision_id):
1010
930
        """See Branch.get_revision."""
1011
 
        xml_file = self.get_revision_xml_file(revision_id)
 
931
        xml_file = self._get_revision_xml_file(revision_id)
1012
932
 
1013
933
        try:
1014
934
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1123
1043
                else:
1124
1044
                    raise e
1125
1045
        
1126
 
    def revision_id_to_revno(self, revision_id):
1127
 
        """Given a revision id, return its revno"""
1128
 
        if revision_id is None:
1129
 
            return 0
1130
 
        history = self.revision_history()
1131
 
        try:
1132
 
            return history.index(revision_id) + 1
1133
 
        except ValueError:
1134
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
1135
 
 
1136
 
    def get_rev_id(self, revno, history=None):
1137
 
        """Find the revision id of the specified revno."""
1138
 
        if revno == 0:
1139
 
            return None
1140
 
        if history is None:
1141
 
            history = self.revision_history()
1142
 
        elif revno <= 0 or revno > len(history):
1143
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
1144
 
        return history[revno - 1]
1145
 
 
1146
1046
    def revision_tree(self, revision_id):
1147
1047
        """See Branch.revision_tree."""
1148
1048
        # TODO: refactor this to use an existing revision object
1151
1051
            return EmptyTree()
1152
1052
        else:
1153
1053
            inv = self.get_revision_inventory(revision_id)
1154
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1054
            return RevisionTree(self, inv, revision_id)
 
1055
 
 
1056
    def basis_tree(self):
 
1057
        """See Branch.basis_tree."""
 
1058
        try:
 
1059
            revision_id = self.revision_history()[-1]
 
1060
            xml = self.working_tree().read_basis_inventory(revision_id)
 
1061
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
1062
            return RevisionTree(self, inv, revision_id)
 
1063
        except (IndexError, NoSuchFile, NoWorkingTree), e:
 
1064
            return self.revision_tree(self.last_revision())
1155
1065
 
1156
1066
    def working_tree(self):
1157
1067
        """See Branch.working_tree."""
1158
1068
        from bzrlib.workingtree import WorkingTree
1159
 
        # TODO: In the future, perhaps WorkingTree should utilize Transport
1160
 
        # RobertCollins 20051003 - I don't think it should - working trees are
1161
 
        # much more complex to keep consistent than our careful .bzr subset.
1162
 
        # instead, we should say that working trees are local only, and optimise
1163
 
        # for that.
1164
1069
        if self._transport.base.find('://') != -1:
1165
1070
            raise NoWorkingTree(self.base)
1166
1071
        return WorkingTree(self.base, branch=self)
1170
1075
        """See Branch.pull."""
1171
1076
        source.lock_read()
1172
1077
        try:
 
1078
            old_count = len(self.revision_history())
1173
1079
            try:
1174
1080
                self.update_revisions(source)
1175
1081
            except DivergedBranches:
1176
1082
                if not overwrite:
1177
1083
                    raise
 
1084
            if overwrite:
1178
1085
                self.set_revision_history(source.revision_history())
 
1086
            new_count = len(self.revision_history())
 
1087
            return new_count - old_count
1179
1088
        finally:
1180
1089
            source.unlock()
1181
1090
 
1182
 
    @needs_write_lock
1183
 
    def rename_one(self, from_rel, to_rel):
1184
 
        """See Branch.rename_one."""
1185
 
        tree = self.working_tree()
1186
 
        inv = tree.inventory
1187
 
        if not tree.has_filename(from_rel):
1188
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1189
 
        if tree.has_filename(to_rel):
1190
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
1191
 
 
1192
 
        file_id = inv.path2id(from_rel)
1193
 
        if file_id == None:
1194
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1195
 
 
1196
 
        if inv.path2id(to_rel):
1197
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1198
 
 
1199
 
        to_dir, to_tail = os.path.split(to_rel)
1200
 
        to_dir_id = inv.path2id(to_dir)
1201
 
        if to_dir_id == None and to_dir != '':
1202
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
1203
 
 
1204
 
        mutter("rename_one:")
1205
 
        mutter("  file_id    {%s}" % file_id)
1206
 
        mutter("  from_rel   %r" % from_rel)
1207
 
        mutter("  to_rel     %r" % to_rel)
1208
 
        mutter("  to_dir     %r" % to_dir)
1209
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
1210
 
 
1211
 
        inv.rename(file_id, to_dir_id, to_tail)
1212
 
 
1213
 
        from_abs = self.abspath(from_rel)
1214
 
        to_abs = self.abspath(to_rel)
1215
 
        try:
1216
 
            rename(from_abs, to_abs)
1217
 
        except OSError, e:
1218
 
            raise BzrError("failed to rename %r to %r: %s"
1219
 
                    % (from_abs, to_abs, e[1]),
1220
 
                    ["rename rolled back"])
1221
 
 
1222
 
        self.working_tree()._write_inventory(inv)
1223
 
 
1224
 
    @needs_write_lock
1225
 
    def move(self, from_paths, to_name):
1226
 
        """See Branch.move."""
1227
 
        result = []
1228
 
        ## TODO: Option to move IDs only
1229
 
        assert not isinstance(from_paths, basestring)
1230
 
        tree = self.working_tree()
1231
 
        inv = tree.inventory
1232
 
        to_abs = self.abspath(to_name)
1233
 
        if not isdir(to_abs):
1234
 
            raise BzrError("destination %r is not a directory" % to_abs)
1235
 
        if not tree.has_filename(to_name):
1236
 
            raise BzrError("destination %r not in working directory" % to_abs)
1237
 
        to_dir_id = inv.path2id(to_name)
1238
 
        if to_dir_id == None and to_name != '':
1239
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
1240
 
        to_dir_ie = inv[to_dir_id]
1241
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
1242
 
            raise BzrError("destination %r is not a directory" % to_abs)
1243
 
 
1244
 
        to_idpath = inv.get_idpath(to_dir_id)
1245
 
 
1246
 
        for f in from_paths:
1247
 
            if not tree.has_filename(f):
1248
 
                raise BzrError("%r does not exist in working tree" % f)
1249
 
            f_id = inv.path2id(f)
1250
 
            if f_id == None:
1251
 
                raise BzrError("%r is not versioned" % f)
1252
 
            name_tail = splitpath(f)[-1]
1253
 
            dest_path = appendpath(to_name, name_tail)
1254
 
            if tree.has_filename(dest_path):
1255
 
                raise BzrError("destination %r already exists" % dest_path)
1256
 
            if f_id in to_idpath:
1257
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
1258
 
 
1259
 
        # OK, so there's a race here, it's possible that someone will
1260
 
        # create a file in this interval and then the rename might be
1261
 
        # left half-done.  But we should have caught most problems.
1262
 
 
1263
 
        for f in from_paths:
1264
 
            name_tail = splitpath(f)[-1]
1265
 
            dest_path = appendpath(to_name, name_tail)
1266
 
            result.append((f, dest_path))
1267
 
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
1268
 
            try:
1269
 
                rename(self.abspath(f), self.abspath(dest_path))
1270
 
            except OSError, e:
1271
 
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1272
 
                        ["rename rolled back"])
1273
 
 
1274
 
        self.working_tree()._write_inventory(inv)
1275
 
        return result
1276
 
 
1277
1091
    def get_parent(self):
1278
1092
        """See Branch.get_parent."""
1279
1093
        import errno
1281
1095
        for l in _locs:
1282
1096
            try:
1283
1097
                return self.controlfile(l, 'r').read().strip('\n')
1284
 
            except IOError, e:
1285
 
                if e.errno != errno.ENOENT:
1286
 
                    raise
 
1098
            except NoSuchFile:
 
1099
                pass
1287
1100
        return None
1288
1101
 
1289
1102
    def get_push_location(self):
1312
1125
    def tree_config(self):
1313
1126
        return TreeConfig(self)
1314
1127
 
1315
 
    def check_revno(self, revno):
1316
 
        """\
1317
 
        Check whether a revno corresponds to any revision.
1318
 
        Zero (the NULL revision) is considered valid.
1319
 
        """
1320
 
        if revno != 0:
1321
 
            self.check_real_revno(revno)
1322
 
            
1323
 
    def check_real_revno(self, revno):
1324
 
        """\
1325
 
        Check whether a revno corresponds to a real revision.
1326
 
        Zero (the NULL revision) is considered invalid
1327
 
        """
1328
 
        if revno < 1 or revno > self.revno():
1329
 
            raise InvalidRevisionNumber(revno)
1330
 
        
1331
1128
    def sign_revision(self, revision_id, gpg_strategy):
1332
1129
        """See Branch.sign_revision."""
1333
1130
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1339
1136
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1340
1137
                                revision_id, "sig")
1341
1138
 
 
1139
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
1140
        """ This function returns the file_id(s) involved in the
 
1141
            changes between the from_revid revision and the to_revid
 
1142
            revision
 
1143
        """
 
1144
        w = self._get_inventory_weave( )
 
1145
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
1146
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
1147
        included = to_set.difference(from_set)
 
1148
        changed = map(w.idx_to_name,included)
 
1149
        return self._fileid_involved_by_set(changed)
 
1150
 
 
1151
    def fileid_involved(self, last_revid=None):
 
1152
        """ This function returns the file_id(s) involved in the
 
1153
            changes up to the revision last_revid
 
1154
            If no parametr is passed, then all file_id[s] present in the
 
1155
            repository are returned
 
1156
        """
 
1157
        w = self._get_inventory_weave( )
 
1158
        if not last_revid:
 
1159
            changed = set(w._names)
 
1160
        else:
 
1161
            included = w.inclusions([w.lookup(last_revid)])
 
1162
            changed = map(w.idx_to_name, included)
 
1163
        return self._fileid_involved_by_set(changed)
 
1164
 
 
1165
    def fileid_involved_by_set(self, changes):
 
1166
        """ This function returns the file_id(s) involved in the
 
1167
            changese present in the set changes
 
1168
        """
 
1169
        w = self._get_inventory_weave( )
 
1170
        return self._fileid_involved_by_set(changes)
 
1171
 
 
1172
    def _fileid_involved_by_set(self, changes):
 
1173
        w = self._get_inventory_weave( )
 
1174
        file_ids = set( )
 
1175
        for line in w._weave:
 
1176
 
 
1177
            # it is ugly, but it is due to the weave structure
 
1178
            if not isinstance(line,basestring): continue
 
1179
 
 
1180
            start = line.find('file_id="')+9
 
1181
            if start < 9: continue
 
1182
            end = line.find('"',start)
 
1183
            assert end>= 0
 
1184
            file_id = line[start:end]
 
1185
 
 
1186
            # check if file_id is already present
 
1187
            if file_id in file_ids: continue
 
1188
 
 
1189
            start = line.find('revision="')+10
 
1190
            if start < 10: continue
 
1191
            end = line.find('"',start)
 
1192
            assert end>= 0
 
1193
            revision_id = line[start:end]
 
1194
 
 
1195
            if revision_id in changes:
 
1196
                file_ids.add(file_id)
 
1197
 
 
1198
        return file_ids
 
1199
 
1342
1200
 
1343
1201
class ScratchBranch(BzrBranch):
1344
1202
    """Special test class: a branch that cleans up after itself.
1382
1240
        ...   orig.base == clone.base
1383
1241
        ...
1384
1242
        False
1385
 
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
 
1243
        >>> os.path.isfile(pathjoin(clone.base, "file1"))
1386
1244
        True
1387
1245
        """
1388
1246
        from shutil import copytree
1389
 
        from tempfile import mkdtemp
 
1247
        from bzrlib.osutils import mkdtemp
1390
1248
        base = mkdtemp()
1391
1249
        os.rmdir(base)
1392
1250
        copytree(self.base, base, symlinks=True)
1400
1258
 
1401
1259
def is_control_file(filename):
1402
1260
    ## FIXME: better check
1403
 
    filename = os.path.normpath(filename)
 
1261
    filename = normpath(filename)
1404
1262
    while filename != '':
1405
1263
        head, tail = os.path.split(filename)
1406
1264
        ## mutter('check %r for control file' % ((head, tail), ))
1410
1268
            break
1411
1269
        filename = head
1412
1270
    return False
1413
 
 
1414
 
 
1415
 
 
1416
 
def gen_file_id(name):
1417
 
    """Return new file id.
1418
 
 
1419
 
    This should probably generate proper UUIDs, but for the moment we
1420
 
    cope with just randomness because running uuidgen every time is
1421
 
    slow."""
1422
 
    import re
1423
 
    from binascii import hexlify
1424
 
    from time import time
1425
 
 
1426
 
    # get last component
1427
 
    idx = name.rfind('/')
1428
 
    if idx != -1:
1429
 
        name = name[idx+1 : ]
1430
 
    idx = name.rfind('\\')
1431
 
    if idx != -1:
1432
 
        name = name[idx+1 : ]
1433
 
 
1434
 
    # make it not a hidden file
1435
 
    name = name.lstrip('.')
1436
 
 
1437
 
    # remove any wierd characters; we don't escape them but rather
1438
 
    # just pull them out
1439
 
    name = re.sub(r'[^\w.]', '', name)
1440
 
 
1441
 
    s = hexlify(rand_bytes(8))
1442
 
    return '-'.join((name, compact_date(time()), s))
1443
 
 
1444
 
 
1445
 
def gen_root_id():
1446
 
    """Return a new tree-root file id."""
1447
 
    return gen_file_id('TREE_ROOT')
1448
 
 
1449