~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-11-28 05:13:41 UTC
  • mfrom: (1185.33.54 merge-recovered)
  • Revision ID: robertc@robertcollins.net-20051128051341-059936f2f29a12c8
Merge from Martin. Adjust check to work with HTTP again.

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, 
 
29
from bzrlib.osutils import (isdir, quotefn,
31
30
                            rename, splitpath, sha_file, appendpath, 
32
31
                            file_kind, abspath)
33
32
import bzrlib.errors as errors
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
239
    def print_file(self, file, revno):
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
 
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
 
890
817
                            'or remove the .bzr directory'
891
818
                            ' and "bzr init" again'])
892
819
 
 
820
    @needs_read_lock
893
821
    def get_root_id(self):
894
822
        """See Branch.get_root_id."""
895
823
        inv = self.get_inventory(self.last_revision())
896
824
        return inv.root.file_id
897
825
 
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
826
    @needs_read_lock
959
827
    def print_file(self, file, revno):
960
828
        """See Branch.print_file."""
965
833
            raise BzrError("%r is not present in revision %s" % (file, revno))
966
834
        tree.print_file(file_id)
967
835
 
968
 
    def unknowns(self):
969
 
        """See Branch.unknowns."""
970
 
        return self.working_tree().unknowns()
971
 
 
972
836
    @needs_write_lock
973
837
    def append_revision(self, *revision_ids):
974
838
        """See Branch.append_revision."""
989
853
                or self.revision_store.has_id(revision_id))
990
854
 
991
855
    @needs_read_lock
992
 
    def get_revision_xml_file(self, revision_id):
993
 
        """See Branch.get_revision_xml_file."""
 
856
    def _get_revision_xml_file(self, revision_id):
994
857
        if not revision_id or not isinstance(revision_id, basestring):
995
858
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
996
859
        try:
998
861
        except (IndexError, KeyError):
999
862
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
1000
863
 
1001
 
    #deprecated
1002
 
    get_revision_xml = get_revision_xml_file
1003
 
 
1004
864
    def get_revision_xml(self, revision_id):
1005
865
        """See Branch.get_revision_xml."""
1006
 
        return self.get_revision_xml_file(revision_id).read()
1007
 
 
 
866
        return self._get_revision_xml_file(revision_id).read()
1008
867
 
1009
868
    def get_revision(self, revision_id):
1010
869
        """See Branch.get_revision."""
1011
 
        xml_file = self.get_revision_xml_file(revision_id)
 
870
        xml_file = self._get_revision_xml_file(revision_id)
1012
871
 
1013
872
        try:
1014
873
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1123
982
                else:
1124
983
                    raise e
1125
984
        
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
985
    def revision_tree(self, revision_id):
1147
986
        """See Branch.revision_tree."""
1148
987
        # TODO: refactor this to use an existing revision object
1156
995
    def working_tree(self):
1157
996
        """See Branch.working_tree."""
1158
997
        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
998
        if self._transport.base.find('://') != -1:
1165
999
            raise NoWorkingTree(self.base)
1166
1000
        return WorkingTree(self.base, branch=self)
1170
1004
        """See Branch.pull."""
1171
1005
        source.lock_read()
1172
1006
        try:
 
1007
            old_count = len(self.revision_history())
1173
1008
            try:
1174
1009
                self.update_revisions(source)
1175
1010
            except DivergedBranches:
1176
1011
                if not overwrite:
1177
1012
                    raise
1178
1013
                self.set_revision_history(source.revision_history())
 
1014
            new_count = len(self.revision_history())
 
1015
            return new_count - old_count
1179
1016
        finally:
1180
1017
            source.unlock()
1181
1018
 
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
1019
    def get_parent(self):
1278
1020
        """See Branch.get_parent."""
1279
1021
        import errno
1312
1054
    def tree_config(self):
1313
1055
        return TreeConfig(self)
1314
1056
 
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
1057
    def sign_revision(self, revision_id, gpg_strategy):
1332
1058
        """See Branch.sign_revision."""
1333
1059
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1410
1136
            break
1411
1137
        filename = head
1412
1138
    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