~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] refactoring of branch vs working tree, etc (robertc)

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
 
        # XXX This should be a WorkingTree method, not a Branch method.
263
 
        raise NotImplementedError('add is abstract')
264
 
 
265
239
    def print_file(self, file, revno):
266
240
        """Print `file` to stdout."""
267
241
        raise NotImplementedError('print_file is abstract')
268
242
 
269
 
    def unknowns(self):
270
 
        """Return all unknown files.
271
 
 
272
 
        These are files in the working directory that are not versioned or
273
 
        control files or ignored.
274
 
        
275
 
        >>> from bzrlib.workingtree import WorkingTree
276
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
277
 
        >>> map(str, b.unknowns())
278
 
        ['foo']
279
 
        >>> b.add('foo')
280
 
        >>> list(b.unknowns())
281
 
        []
282
 
        >>> WorkingTree(b.base, b).remove('foo')
283
 
        >>> list(b.unknowns())
284
 
        [u'foo']
285
 
        """
286
 
        raise NotImplementedError('unknowns is abstract')
287
 
 
288
243
    def append_revision(self, *revision_ids):
289
244
        raise NotImplementedError('append_revision is abstract')
290
245
 
454
409
        raise NotImplementedError('revision_tree is abstract')
455
410
 
456
411
    def working_tree(self):
457
 
        """Return a `Tree` for the working copy."""
 
412
        """Return a `Tree` for the working copy if this is a local branch."""
458
413
        raise NotImplementedError('working_tree is abstract')
459
414
 
460
415
    def pull(self, source, overwrite=False):
862
817
                            'or remove the .bzr directory'
863
818
                            ' and "bzr init" again'])
864
819
 
 
820
    @needs_read_lock
865
821
    def get_root_id(self):
866
822
        """See Branch.get_root_id."""
867
823
        inv = self.get_inventory(self.last_revision())
868
824
        return inv.root.file_id
869
825
 
870
 
    @needs_write_lock
871
 
    def set_root_id(self, file_id):
872
 
        """See Branch.set_root_id."""
873
 
        inv = self.working_tree().read_working_inventory()
874
 
        orig_root_id = inv.root.file_id
875
 
        del inv._byid[inv.root.file_id]
876
 
        inv.root.file_id = file_id
877
 
        inv._byid[inv.root.file_id] = inv.root
878
 
        for fid in inv:
879
 
            entry = inv[fid]
880
 
            if entry.parent_id in (None, orig_root_id):
881
 
                entry.parent_id = inv.root.file_id
882
 
        self._write_inventory(inv)
883
 
 
884
 
    @needs_write_lock
885
 
    def add(self, files, ids=None):
886
 
        """See Branch.add."""
887
 
        # TODO: Re-adding a file that is removed in the working copy
888
 
        # should probably put it back with the previous ID.
889
 
        if isinstance(files, basestring):
890
 
            assert(ids is None or isinstance(ids, basestring))
891
 
            files = [files]
892
 
            if ids is not None:
893
 
                ids = [ids]
894
 
 
895
 
        if ids is None:
896
 
            ids = [None] * len(files)
897
 
        else:
898
 
            assert(len(ids) == len(files))
899
 
 
900
 
        inv = self.working_tree().read_working_inventory()
901
 
        for f,file_id in zip(files, ids):
902
 
            if is_control_file(f):
903
 
                raise BzrError("cannot add control file %s" % quotefn(f))
904
 
 
905
 
            fp = splitpath(f)
906
 
 
907
 
            if len(fp) == 0:
908
 
                raise BzrError("cannot add top-level %r" % f)
909
 
 
910
 
            fullpath = os.path.normpath(self.abspath(f))
911
 
 
912
 
            try:
913
 
                kind = file_kind(fullpath)
914
 
            except OSError:
915
 
                # maybe something better?
916
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
917
 
 
918
 
            if not InventoryEntry.versionable_kind(kind):
919
 
                raise BzrError('cannot add: not a versionable file ('
920
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
921
 
 
922
 
            if file_id is None:
923
 
                file_id = gen_file_id(f)
924
 
            inv.add_path(f, kind=kind, file_id=file_id)
925
 
 
926
 
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
927
 
 
928
 
        self.working_tree()._write_inventory(inv)
929
 
 
930
826
    @needs_read_lock
931
827
    def print_file(self, file, revno):
932
828
        """See Branch.print_file."""
937
833
            raise BzrError("%r is not present in revision %s" % (file, revno))
938
834
        tree.print_file(file_id)
939
835
 
940
 
    def unknowns(self):
941
 
        """See Branch.unknowns."""
942
 
        return self.working_tree().unknowns()
943
 
 
944
836
    @needs_write_lock
945
837
    def append_revision(self, *revision_ids):
946
838
        """See Branch.append_revision."""
1103
995
    def working_tree(self):
1104
996
        """See Branch.working_tree."""
1105
997
        from bzrlib.workingtree import WorkingTree
1106
 
        # TODO: In the future, perhaps WorkingTree should utilize Transport
1107
 
        # RobertCollins 20051003 - I don't think it should - working trees are
1108
 
        # much more complex to keep consistent than our careful .bzr subset.
1109
 
        # instead, we should say that working trees are local only, and optimise
1110
 
        # for that.
1111
998
        if self._transport.base.find('://') != -1:
1112
999
            raise NoWorkingTree(self.base)
1113
1000
        return WorkingTree(self.base, branch=self)
1129
1016
        finally:
1130
1017
            source.unlock()
1131
1018
 
1132
 
    @needs_write_lock
1133
 
    def rename_one(self, from_rel, to_rel):
1134
 
        """See Branch.rename_one."""
1135
 
        tree = self.working_tree()
1136
 
        inv = tree.inventory
1137
 
        if not tree.has_filename(from_rel):
1138
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1139
 
        if tree.has_filename(to_rel):
1140
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
1141
 
 
1142
 
        file_id = inv.path2id(from_rel)
1143
 
        if file_id == None:
1144
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1145
 
 
1146
 
        if inv.path2id(to_rel):
1147
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1148
 
 
1149
 
        to_dir, to_tail = os.path.split(to_rel)
1150
 
        to_dir_id = inv.path2id(to_dir)
1151
 
        if to_dir_id == None and to_dir != '':
1152
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
1153
 
 
1154
 
        mutter("rename_one:")
1155
 
        mutter("  file_id    {%s}" % file_id)
1156
 
        mutter("  from_rel   %r" % from_rel)
1157
 
        mutter("  to_rel     %r" % to_rel)
1158
 
        mutter("  to_dir     %r" % to_dir)
1159
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
1160
 
 
1161
 
        inv.rename(file_id, to_dir_id, to_tail)
1162
 
 
1163
 
        from_abs = self.abspath(from_rel)
1164
 
        to_abs = self.abspath(to_rel)
1165
 
        try:
1166
 
            rename(from_abs, to_abs)
1167
 
        except OSError, e:
1168
 
            raise BzrError("failed to rename %r to %r: %s"
1169
 
                    % (from_abs, to_abs, e[1]),
1170
 
                    ["rename rolled back"])
1171
 
 
1172
 
        self.working_tree()._write_inventory(inv)
1173
 
 
1174
 
    @needs_write_lock
1175
 
    def move(self, from_paths, to_name):
1176
 
        """See Branch.move."""
1177
 
        result = []
1178
 
        ## TODO: Option to move IDs only
1179
 
        assert not isinstance(from_paths, basestring)
1180
 
        tree = self.working_tree()
1181
 
        inv = tree.inventory
1182
 
        to_abs = self.abspath(to_name)
1183
 
        if not isdir(to_abs):
1184
 
            raise BzrError("destination %r is not a directory" % to_abs)
1185
 
        if not tree.has_filename(to_name):
1186
 
            raise BzrError("destination %r not in working directory" % to_abs)
1187
 
        to_dir_id = inv.path2id(to_name)
1188
 
        if to_dir_id == None and to_name != '':
1189
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
1190
 
        to_dir_ie = inv[to_dir_id]
1191
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
1192
 
            raise BzrError("destination %r is not a directory" % to_abs)
1193
 
 
1194
 
        to_idpath = inv.get_idpath(to_dir_id)
1195
 
 
1196
 
        for f in from_paths:
1197
 
            if not tree.has_filename(f):
1198
 
                raise BzrError("%r does not exist in working tree" % f)
1199
 
            f_id = inv.path2id(f)
1200
 
            if f_id == None:
1201
 
                raise BzrError("%r is not versioned" % f)
1202
 
            name_tail = splitpath(f)[-1]
1203
 
            dest_path = appendpath(to_name, name_tail)
1204
 
            if tree.has_filename(dest_path):
1205
 
                raise BzrError("destination %r already exists" % dest_path)
1206
 
            if f_id in to_idpath:
1207
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
1208
 
 
1209
 
        # OK, so there's a race here, it's possible that someone will
1210
 
        # create a file in this interval and then the rename might be
1211
 
        # left half-done.  But we should have caught most problems.
1212
 
 
1213
 
        for f in from_paths:
1214
 
            name_tail = splitpath(f)[-1]
1215
 
            dest_path = appendpath(to_name, name_tail)
1216
 
            result.append((f, dest_path))
1217
 
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
1218
 
            try:
1219
 
                rename(self.abspath(f), self.abspath(dest_path))
1220
 
            except OSError, e:
1221
 
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1222
 
                        ["rename rolled back"])
1223
 
 
1224
 
        self.working_tree()._write_inventory(inv)
1225
 
        return result
1226
 
 
1227
1019
    def get_parent(self):
1228
1020
        """See Branch.get_parent."""
1229
1021
        import errno
1344
1136
            break
1345
1137
        filename = head
1346
1138
    return False
1347
 
 
1348
 
 
1349
 
 
1350
 
def gen_file_id(name):
1351
 
    """Return new file id.
1352
 
 
1353
 
    This should probably generate proper UUIDs, but for the moment we
1354
 
    cope with just randomness because running uuidgen every time is
1355
 
    slow."""
1356
 
    import re
1357
 
    from binascii import hexlify
1358
 
    from time import time
1359
 
 
1360
 
    # get last component
1361
 
    idx = name.rfind('/')
1362
 
    if idx != -1:
1363
 
        name = name[idx+1 : ]
1364
 
    idx = name.rfind('\\')
1365
 
    if idx != -1:
1366
 
        name = name[idx+1 : ]
1367
 
 
1368
 
    # make it not a hidden file
1369
 
    name = name.lstrip('.')
1370
 
 
1371
 
    # remove any wierd characters; we don't escape them but rather
1372
 
    # just pull them out
1373
 
    name = re.sub(r'[^\w.]', '', name)
1374
 
 
1375
 
    s = hexlify(rand_bytes(8))
1376
 
    return '-'.join((name, compact_date(time()), s))
1377
 
 
1378
 
 
1379
 
def gen_root_id():
1380
 
    """Return a new tree-root file id."""
1381
 
    return gen_file_id('TREE_ROOT')
1382
 
 
1383