~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Merged mailine

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
import os
21
21
import errno
22
22
from warnings import warn
 
23
import xml.sax.saxutils
23
24
from cStringIO import StringIO
24
25
 
25
26
 
27
28
import bzrlib.inventory as inventory
28
29
from bzrlib.trace import mutter, note
29
30
from bzrlib.osutils import (isdir, quotefn,
30
 
                            rename, splitpath, sha_file, appendpath, 
31
 
                            file_kind, abspath)
 
31
                            rename, splitpath, sha_file,
 
32
                            file_kind, abspath, normpath, pathjoin)
32
33
import bzrlib.errors as errors
33
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
35
                           NoSuchRevision, HistoryMissing, NotBranchError,
133
134
        while True:
134
135
            try:
135
136
                return BzrBranch(t), t.relpath(url)
136
 
            except NotBranchError:
137
 
                pass
 
137
            except NotBranchError, e:
 
138
                mutter('not a branch in: %r %s', t.base, e)
138
139
            new_t = t.clone('..')
139
140
            if new_t.base == t.base:
140
141
                # reached the root, whatever that may be
155
156
 
156
157
    def _get_nick(self):
157
158
        cfg = self.tree_config()
158
 
        return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
 
159
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
159
160
 
160
161
    def _set_nick(self, nick):
161
162
        cfg = self.tree_config()
487
488
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
489
        raise NotImplementedError('store_revision_signature is abstract')
489
490
 
 
491
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
492
        """ This function returns the file_id(s) involved in the
 
493
            changes between the from_revid revision and the to_revid
 
494
            revision
 
495
        """
 
496
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
497
 
 
498
    def fileid_involved(self, last_revid=None):
 
499
        """ This function returns the file_id(s) involved in the
 
500
            changes up to the revision last_revid
 
501
            If no parametr is passed, then all file_id[s] present in the
 
502
            repository are returned
 
503
        """
 
504
        raise NotImplementedError('fileid_involved is abstract')
 
505
 
 
506
    def fileid_involved_by_set(self, changes):
 
507
        """ This function returns the file_id(s) involved in the
 
508
            changes present in the set 'changes'
 
509
        """
 
510
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
511
 
 
512
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
513
        """ This function returns the file_id(s) involved in the
 
514
            changes between the from_revid revision and the to_revid
 
515
            revision
 
516
        """
 
517
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
518
 
 
519
    def fileid_involved(self, last_revid=None):
 
520
        """ This function returns the file_id(s) involved in the
 
521
            changes up to the revision last_revid
 
522
            If no parametr is passed, then all file_id[s] present in the
 
523
            repository are returned
 
524
        """
 
525
        raise NotImplementedError('fileid_involved is abstract')
 
526
 
 
527
    def fileid_involved_by_set(self, changes):
 
528
        """ This function returns the file_id(s) involved in the
 
529
            changes present in the set 'changes'
 
530
        """
 
531
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
532
 
 
533
 
490
534
class BzrBranch(Branch):
491
535
    """A branch stored in the actual filesystem.
492
536
 
512
556
    _lock_count = None
513
557
    _lock = None
514
558
    _inventory_weave = None
 
559
    # If set to False (by a plugin, etc) BzrBranch will not set the
 
560
    # mode on created files or directories
 
561
    _set_file_mode = True
 
562
    _set_dir_mode = True
515
563
    
516
564
    # Map some sort of prefix into a namespace
517
565
    # stuff like "revno:10", "revid:", etc.
562
610
        if init:
563
611
            self._make_control()
564
612
        self._check_format(relax_version_check)
 
613
        self._find_modes()
565
614
 
566
615
        def get_store(name, compressed=True, prefixed=False):
567
 
            # FIXME: This approach of assuming stores are all entirely compressed
568
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
569
 
            # some existing branches where there's a mixture; we probably 
570
 
            # still want the option to look for both.
571
616
            relpath = self._rel_controlfilename(unicode(name))
572
617
            store = TextStore(self._transport.clone(relpath),
 
618
                              dir_mode=self._dir_mode,
 
619
                              file_mode=self._file_mode,
573
620
                              prefixed=prefixed,
574
621
                              compressed=compressed)
575
 
            #if self._transport.should_cache():
576
 
            #    cache_path = os.path.join(self.cache_root, name)
577
 
            #    os.mkdir(cache_path)
578
 
            #    store = bzrlib.store.CachedStore(store, cache_path)
579
622
            return store
580
623
 
581
624
        def get_weave(name, prefixed=False):
582
625
            relpath = self._rel_controlfilename(unicode(name))
583
 
            ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
 
626
            ws = WeaveStore(self._transport.clone(relpath),
 
627
                            prefixed=prefixed,
 
628
                            dir_mode=self._dir_mode,
 
629
                            file_mode=self._file_mode)
584
630
            if self._transport.should_cache():
585
631
                ws.enable_cache = True
586
632
            return ws
753
799
                    f = codecs.getwriter('utf-8')(f, errors='replace')
754
800
            path = self._rel_controlfilename(path)
755
801
            ctrl_files.append((path, f))
756
 
        self._transport.put_multi(ctrl_files)
 
802
        self._transport.put_multi(ctrl_files, mode=self._file_mode)
 
803
 
 
804
    def _find_modes(self, path=None):
 
805
        """Determine the appropriate modes for files and directories."""
 
806
        try:
 
807
            if path is None:
 
808
                path = self._rel_controlfilename('')
 
809
            st = self._transport.stat(path)
 
810
        except errors.TransportNotPossible:
 
811
            self._dir_mode = 0755
 
812
            self._file_mode = 0644
 
813
        else:
 
814
            self._dir_mode = st.st_mode & 07777
 
815
            # Remove the sticky and execute bits for files
 
816
            self._file_mode = self._dir_mode & ~07111
 
817
        if not self._set_dir_mode:
 
818
            self._dir_mode = None
 
819
        if not self._set_file_mode:
 
820
            self._file_mode = None
757
821
 
758
822
    def _make_control(self):
759
823
        from bzrlib.inventory import Inventory
771
835
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
772
836
        empty_weave = sio.getvalue()
773
837
 
774
 
        dirs = [[], 'revision-store', 'weaves']
 
838
        cfn = self._rel_controlfilename
 
839
        # Since we don't have a .bzr directory, inherit the
 
840
        # mode from the root directory
 
841
        self._find_modes(u'.')
 
842
 
 
843
        dirs = ['', 'revision-store', 'weaves']
775
844
        files = [('README', 
776
845
            "This is a Bazaar-NG control directory.\n"
777
846
            "Do not change any files in this directory.\n"),
782
851
            ('pending-merges', ''),
783
852
            ('inventory', empty_inv),
784
853
            ('inventory.weave', empty_weave),
785
 
            ('ancestry.weave', empty_weave)
786
854
        ]
787
 
        cfn = self._rel_controlfilename
788
 
        self._transport.mkdir_multi([cfn(d) for d in dirs])
 
855
        self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
789
856
        self.put_controlfiles(files)
790
857
        mutter('created control directory in ' + self._transport.base)
791
858
 
1006
1073
            return EmptyTree()
1007
1074
        else:
1008
1075
            inv = self.get_revision_inventory(revision_id)
1009
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1076
            return RevisionTree(self, inv, revision_id)
1010
1077
 
1011
1078
    def basis_tree(self):
1012
1079
        """See Branch.basis_tree."""
1014
1081
            revision_id = self.revision_history()[-1]
1015
1082
            xml = self.working_tree().read_basis_inventory(revision_id)
1016
1083
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1017
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1084
            return RevisionTree(self, inv, revision_id)
1018
1085
        except (IndexError, NoSuchFile, NoWorkingTree), e:
1019
1086
            return self.revision_tree(self.last_revision())
1020
1087
 
1091
1158
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1092
1159
                                revision_id, "sig")
1093
1160
 
 
1161
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
1162
        """Find file_id(s) which are involved in the changes between revisions.
 
1163
 
 
1164
        This determines the set of revisions which are involved, and then
 
1165
        finds all file ids affected by those revisions.
 
1166
        """
 
1167
        # TODO: jam 20060119 This code assumes that w.inclusions will
 
1168
        #       always be correct. But because of the presence of ghosts
 
1169
        #       it is possible to be wrong.
 
1170
        #       One specific example from Robert Collins:
 
1171
        #       Two branches, with revisions ABC, and AD
 
1172
        #       C is a ghost merge of D.
 
1173
        #       Inclusions doesn't recognize D as an ancestor.
 
1174
        #       If D is ever merged in the future, the weave
 
1175
        #       won't be fixed, because AD never saw revision C
 
1176
        #       to cause a conflict which would force a reweave.
 
1177
        w = self._get_inventory_weave()
 
1178
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
1179
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
1180
        included = to_set.difference(from_set)
 
1181
        changed = map(w.idx_to_name, included)
 
1182
        return self._fileid_involved_by_set(changed)
 
1183
 
 
1184
    def fileid_involved(self, last_revid=None):
 
1185
        """Find all file_ids modified in the ancestry of last_revid.
 
1186
 
 
1187
        :param last_revid: If None, last_revision() will be used.
 
1188
        """
 
1189
        w = self._get_inventory_weave()
 
1190
        if not last_revid:
 
1191
            changed = set(w._names)
 
1192
        else:
 
1193
            included = w.inclusions([w.lookup(last_revid)])
 
1194
            changed = map(w.idx_to_name, included)
 
1195
        return self._fileid_involved_by_set(changed)
 
1196
 
 
1197
    def fileid_involved_by_set(self, changes):
 
1198
        """Find all file_ids modified by the set of revisions passed in.
 
1199
 
 
1200
        :param changes: A set() of revision ids
 
1201
        """
 
1202
        # TODO: jam 20060119 This line does *nothing*, remove it.
 
1203
        #       or better yet, change _fileid_involved_by_set so
 
1204
        #       that it takes the inventory weave, rather than
 
1205
        #       pulling it out by itself.
 
1206
        w = self._get_inventory_weave()
 
1207
        return self._fileid_involved_by_set(changes)
 
1208
 
 
1209
    def _fileid_involved_by_set(self, changes):
 
1210
        """Find the set of file-ids affected by the set of revisions.
 
1211
 
 
1212
        :param changes: A set() of revision ids.
 
1213
        :return: A set() of file ids.
 
1214
        
 
1215
        This peaks at the Weave, interpreting each line, looking to
 
1216
        see if it mentions one of the revisions. And if so, includes
 
1217
        the file id mentioned.
 
1218
        This expects both the Weave format, and the serialization
 
1219
        to have a single line per file/directory, and to have
 
1220
        fileid="" and revision="" on that line.
 
1221
        """
 
1222
        assert self._branch_format in (5, 6), \
 
1223
            "fileid_involved only supported for branches which store inventory as xml"
 
1224
 
 
1225
        w = self._get_inventory_weave()
 
1226
        file_ids = set()
 
1227
        for line in w._weave:
 
1228
 
 
1229
            # it is ugly, but it is due to the weave structure
 
1230
            if not isinstance(line, basestring): continue
 
1231
 
 
1232
            start = line.find('file_id="')+9
 
1233
            if start < 9: continue
 
1234
            end = line.find('"', start)
 
1235
            assert end>= 0
 
1236
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
1237
 
 
1238
            # check if file_id is already present
 
1239
            if file_id in file_ids: continue
 
1240
 
 
1241
            start = line.find('revision="')+10
 
1242
            if start < 10: continue
 
1243
            end = line.find('"', start)
 
1244
            assert end>= 0
 
1245
            revision_id = xml.sax.saxutils.unescape(line[start:end])
 
1246
 
 
1247
            if revision_id in changes:
 
1248
                file_ids.add(file_id)
 
1249
 
 
1250
        return file_ids
 
1251
 
1094
1252
 
1095
1253
class ScratchBranch(BzrBranch):
1096
1254
    """Special test class: a branch that cleans up after itself.
1134
1292
        ...   orig.base == clone.base
1135
1293
        ...
1136
1294
        False
1137
 
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
 
1295
        >>> os.path.isfile(pathjoin(clone.base, "file1"))
1138
1296
        True
1139
1297
        """
1140
1298
        from shutil import copytree
1141
 
        from tempfile import mkdtemp
 
1299
        from bzrlib.osutils import mkdtemp
1142
1300
        base = mkdtemp()
1143
1301
        os.rmdir(base)
1144
1302
        copytree(self.base, base, symlinks=True)
1152
1310
 
1153
1311
def is_control_file(filename):
1154
1312
    ## FIXME: better check
1155
 
    filename = os.path.normpath(filename)
 
1313
    filename = normpath(filename)
1156
1314
    while filename != '':
1157
1315
        head, tail = os.path.split(filename)
1158
 
        ## mutter('check %r for control file' % ((head, tail), ))
 
1316
        ## mutter('check %r for control file' % ((head, tail),))
1159
1317
        if tail == bzrlib.BZRDIR:
1160
1318
            return True
1161
1319
        if filename == head: