33
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
35
DivergedBranches, LockError, UnlistableStore,
36
UnlistableBranch, NoSuchFile)
36
UnlistableBranch, NoSuchFile, NotVersionedError)
37
37
from bzrlib.textui import show_status
38
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
66
67
raise NotImplementedError('find_branch() is not supported anymore, '
67
68
'please use one of the new branch constructors')
71
def needs_read_lock(unbound):
72
"""Decorate unbound to take out and release a read lock."""
73
def decorated(self, *args, **kwargs):
76
return unbound(self, *args, **kwargs)
82
def needs_write_lock(unbound):
83
"""Decorate unbound to take out and release a write lock."""
84
def decorated(self, *args, **kwargs):
87
return unbound(self, *args, **kwargs)
69
92
######################################################################
505
530
entry.parent_id = inv.root.file_id
506
531
self._write_inventory(inv)
508
534
def read_working_inventory(self):
509
535
"""Read the working inventory."""
512
# ElementTree does its own conversion from UTF-8, so open in
514
f = self.controlfile('inventory', 'rb')
515
return bzrlib.xml5.serializer_v5.read_inventory(f)
536
# ElementTree does its own conversion from UTF-8, so open in
538
f = self.controlfile('inventory', 'rb')
539
return bzrlib.xml5.serializer_v5.read_inventory(f)
520
542
def _write_inventory(self, inv):
521
543
"""Update the working inventory.
524
546
will be committed to the next revision.
526
548
from cStringIO import StringIO
530
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
532
# Transport handles atomicity
533
self.put_controlfile('inventory', sio)
550
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
552
# Transport handles atomicity
553
self.put_controlfile('inventory', sio)
537
555
mutter('wrote working inventory')
539
557
inventory = property(read_working_inventory, _write_inventory, None,
540
558
"""Inventory for the working copy.""")
542
561
def add(self, files, ids=None):
543
562
"""Make files versioned.
575
594
assert(len(ids) == len(files))
579
inv = self.read_working_inventory()
580
for f,file_id in zip(files, ids):
581
if is_control_file(f):
582
raise BzrError("cannot add control file %s" % quotefn(f))
587
raise BzrError("cannot add top-level %r" % f)
589
fullpath = os.path.normpath(self.abspath(f))
592
kind = file_kind(fullpath)
594
# maybe something better?
595
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
597
if not InventoryEntry.versionable_kind(kind):
598
raise BzrError('cannot add: not a versionable file ('
599
'i.e. regular file, symlink or directory): %s' % quotefn(f))
602
file_id = gen_file_id(f)
603
inv.add_path(f, kind=kind, file_id=file_id)
605
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
607
self._write_inventory(inv)
596
inv = self.read_working_inventory()
597
for f,file_id in zip(files, ids):
598
if is_control_file(f):
599
raise BzrError("cannot add control file %s" % quotefn(f))
604
raise BzrError("cannot add top-level %r" % f)
606
fullpath = os.path.normpath(self.abspath(f))
609
kind = file_kind(fullpath)
611
# maybe something better?
612
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
614
if not InventoryEntry.versionable_kind(kind):
615
raise BzrError('cannot add: not a versionable file ('
616
'i.e. regular file, symlink or directory): %s' % quotefn(f))
619
file_id = gen_file_id(f)
620
inv.add_path(f, kind=kind, file_id=file_id)
622
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
624
self._write_inventory(inv)
612
627
def print_file(self, file, revno):
613
628
"""Print `file` to stdout."""
616
tree = self.revision_tree(self.get_rev_id(revno))
617
# use inventory as it was in that revision
618
file_id = tree.inventory.path2id(file)
620
raise BzrError("%r is not present in revision %s" % (file, revno))
621
tree.print_file(file_id)
626
def remove(self, files, verbose=False):
627
"""Mark nominated files for removal from the inventory.
629
This does not remove their text. This does not run on
631
TODO: Refuse to remove modified files unless --force is given?
633
TODO: Do something useful with directories.
635
TODO: Should this remove the text or not? Tough call; not
636
removing may be useful and the user can just use use rm, and
637
is the opposite of add. Removing it is consistent with most
638
other tools. Maybe an option.
640
## TODO: Normalize names
641
## TODO: Remove nested loops; better scalability
642
if isinstance(files, basestring):
648
tree = self.working_tree()
651
# do this before any modifications
655
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
656
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
658
# having remove it, it must be either ignored or unknown
659
if tree.is_ignored(f):
663
show_status(new_status, inv[fid].kind, quotefn(f))
666
self._write_inventory(inv)
629
tree = self.revision_tree(self.get_rev_id(revno))
630
# use inventory as it was in that revision
631
file_id = tree.inventory.path2id(file)
633
raise BzrError("%r is not present in revision %s" % (file, revno))
634
tree.print_file(file_id)
670
636
# FIXME: this doesn't need to be a branch method
671
637
def set_inventory(self, new_inventory_list):
692
658
These are files in the working directory that are not versioned or
693
659
control files or ignored.
661
>>> from bzrlib.workingtree import WorkingTree
695
662
>>> b = ScratchBranch(files=['foo', 'foo~'])
696
663
>>> map(str, b.unknowns())
699
666
>>> list(b.unknowns())
702
>>> map(str, b.unknowns())
668
>>> WorkingTree(b.base, b).remove('foo')
669
>>> list(b.unknowns())
705
672
return self.working_tree().unknowns()
708
675
def append_revision(self, *revision_ids):
709
676
for revision_id in revision_ids:
710
677
mutter("add {%s} to revision-history" % revision_id)
713
rev_history = self.revision_history()
714
rev_history.extend(revision_ids)
715
self.put_controlfile('revision-history', '\n'.join(rev_history))
678
rev_history = self.revision_history()
679
rev_history.extend(revision_ids)
680
self.put_controlfile('revision-history', '\n'.join(rev_history))
719
682
def has_revision(self, revision_id):
720
683
"""True if this branch has a copy of the revision.
722
685
This does not necessarily imply the revision is merge
723
686
or on the mainline."""
724
687
return (revision_id is None
725
or revision_id in self.revision_store)
688
or self.revision_store.has_id(revision_id))
727
691
def get_revision_xml_file(self, revision_id):
728
692
"""Return XML file object for revision object."""
729
693
if not revision_id or not isinstance(revision_id, basestring):
730
694
raise InvalidRevisionId(revision_id)
735
return self.revision_store[revision_id]
736
except (IndexError, KeyError):
737
raise bzrlib.errors.NoSuchRevision(self, revision_id)
696
return self.revision_store.get(revision_id)
697
except (IndexError, KeyError):
698
raise bzrlib.errors.NoSuchRevision(self, revision_id)
742
701
get_revision_xml = get_revision_xml_file
836
795
return self.get_inventory(revision_id)
838
798
def revision_history(self):
839
799
"""Return sequence of revision hashes on to this branch."""
842
transaction = self.get_transaction()
843
history = transaction.map.find_revision_history()
844
if history is not None:
845
mutter("cache hit for revision-history in %s", self)
847
history = [l.rstrip('\r\n') for l in
848
self.controlfile('revision-history', 'r').readlines()]
849
transaction.map.add_revision_history(history)
850
# this call is disabled because revision_history is
851
# not really an object yet, and the transaction is for objects.
852
# transaction.register_clean(history, precious=True)
800
transaction = self.get_transaction()
801
history = transaction.map.find_revision_history()
802
if history is not None:
803
mutter("cache hit for revision-history in %s", self)
853
804
return list(history)
805
history = [l.rstrip('\r\n') for l in
806
self.controlfile('revision-history', 'r').readlines()]
807
transaction.map.add_revision_history(history)
808
# this call is disabled because revision_history is
809
# not really an object yet, and the transaction is for objects.
810
# transaction.register_clean(history, precious=True)
858
814
"""Return current revision number for this branch.
990
943
inv = self.get_revision_inventory(revision_id)
991
944
return RevisionTree(self.weave_store, inv, revision_id)
994
946
def working_tree(self):
995
947
"""Return a `Tree` for the working copy."""
996
948
from bzrlib.workingtree import WorkingTree
997
# TODO: In the future, WorkingTree should utilize Transport
949
# TODO: In the future, perhaps WorkingTree should utilize Transport
998
950
# RobertCollins 20051003 - I don't think it should - working trees are
999
951
# much more complex to keep consistent than our careful .bzr subset.
1000
952
# instead, we should say that working trees are local only, and optimise
1010
962
return self.revision_tree(self.last_revision())
1013
965
def rename_one(self, from_rel, to_rel):
1014
966
"""Rename one file.
1016
968
This can change the directory or the filename or both.
970
tree = self.working_tree()
972
if not tree.has_filename(from_rel):
973
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
974
if tree.has_filename(to_rel):
975
raise BzrError("can't rename: new working file %r already exists" % to_rel)
977
file_id = inv.path2id(from_rel)
979
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
981
if inv.path2id(to_rel):
982
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
984
to_dir, to_tail = os.path.split(to_rel)
985
to_dir_id = inv.path2id(to_dir)
986
if to_dir_id == None and to_dir != '':
987
raise BzrError("can't determine destination directory id for %r" % to_dir)
989
mutter("rename_one:")
990
mutter(" file_id {%s}" % file_id)
991
mutter(" from_rel %r" % from_rel)
992
mutter(" to_rel %r" % to_rel)
993
mutter(" to_dir %r" % to_dir)
994
mutter(" to_dir_id {%s}" % to_dir_id)
996
inv.rename(file_id, to_dir_id, to_tail)
998
from_abs = self.abspath(from_rel)
999
to_abs = self.abspath(to_rel)
1020
tree = self.working_tree()
1021
inv = tree.inventory
1022
if not tree.has_filename(from_rel):
1023
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1024
if tree.has_filename(to_rel):
1025
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1027
file_id = inv.path2id(from_rel)
1029
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1031
if inv.path2id(to_rel):
1032
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1034
to_dir, to_tail = os.path.split(to_rel)
1035
to_dir_id = inv.path2id(to_dir)
1036
if to_dir_id == None and to_dir != '':
1037
raise BzrError("can't determine destination directory id for %r" % to_dir)
1039
mutter("rename_one:")
1040
mutter(" file_id {%s}" % file_id)
1041
mutter(" from_rel %r" % from_rel)
1042
mutter(" to_rel %r" % to_rel)
1043
mutter(" to_dir %r" % to_dir)
1044
mutter(" to_dir_id {%s}" % to_dir_id)
1046
inv.rename(file_id, to_dir_id, to_tail)
1048
from_abs = self.abspath(from_rel)
1049
to_abs = self.abspath(to_rel)
1051
rename(from_abs, to_abs)
1053
raise BzrError("failed to rename %r to %r: %s"
1054
% (from_abs, to_abs, e[1]),
1055
["rename rolled back"])
1057
self._write_inventory(inv)
1001
rename(from_abs, to_abs)
1003
raise BzrError("failed to rename %r to %r: %s"
1004
% (from_abs, to_abs, e[1]),
1005
["rename rolled back"])
1007
self._write_inventory(inv)
1062
1010
def move(self, from_paths, to_name):
1063
1011
"""Rename files.
1074
1022
entry that is moved.
1079
## TODO: Option to move IDs only
1080
assert not isinstance(from_paths, basestring)
1081
tree = self.working_tree()
1082
inv = tree.inventory
1083
to_abs = self.abspath(to_name)
1084
if not isdir(to_abs):
1085
raise BzrError("destination %r is not a directory" % to_abs)
1086
if not tree.has_filename(to_name):
1087
raise BzrError("destination %r not in working directory" % to_abs)
1088
to_dir_id = inv.path2id(to_name)
1089
if to_dir_id == None and to_name != '':
1090
raise BzrError("destination %r is not a versioned directory" % to_name)
1091
to_dir_ie = inv[to_dir_id]
1092
if to_dir_ie.kind not in ('directory', 'root_directory'):
1093
raise BzrError("destination %r is not a directory" % to_abs)
1095
to_idpath = inv.get_idpath(to_dir_id)
1097
for f in from_paths:
1098
if not tree.has_filename(f):
1099
raise BzrError("%r does not exist in working tree" % f)
1100
f_id = inv.path2id(f)
1102
raise BzrError("%r is not versioned" % f)
1103
name_tail = splitpath(f)[-1]
1104
dest_path = appendpath(to_name, name_tail)
1105
if tree.has_filename(dest_path):
1106
raise BzrError("destination %r already exists" % dest_path)
1107
if f_id in to_idpath:
1108
raise BzrError("can't move %r to a subdirectory of itself" % f)
1110
# OK, so there's a race here, it's possible that someone will
1111
# create a file in this interval and then the rename might be
1112
# left half-done. But we should have caught most problems.
1114
for f in from_paths:
1115
name_tail = splitpath(f)[-1]
1116
dest_path = appendpath(to_name, name_tail)
1117
result.append((f, dest_path))
1118
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1120
rename(self.abspath(f), self.abspath(dest_path))
1122
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1123
["rename rolled back"])
1125
self._write_inventory(inv)
1025
## TODO: Option to move IDs only
1026
assert not isinstance(from_paths, basestring)
1027
tree = self.working_tree()
1028
inv = tree.inventory
1029
to_abs = self.abspath(to_name)
1030
if not isdir(to_abs):
1031
raise BzrError("destination %r is not a directory" % to_abs)
1032
if not tree.has_filename(to_name):
1033
raise BzrError("destination %r not in working directory" % to_abs)
1034
to_dir_id = inv.path2id(to_name)
1035
if to_dir_id == None and to_name != '':
1036
raise BzrError("destination %r is not a versioned directory" % to_name)
1037
to_dir_ie = inv[to_dir_id]
1038
if to_dir_ie.kind not in ('directory', 'root_directory'):
1039
raise BzrError("destination %r is not a directory" % to_abs)
1041
to_idpath = inv.get_idpath(to_dir_id)
1043
for f in from_paths:
1044
if not tree.has_filename(f):
1045
raise BzrError("%r does not exist in working tree" % f)
1046
f_id = inv.path2id(f)
1048
raise BzrError("%r is not versioned" % f)
1049
name_tail = splitpath(f)[-1]
1050
dest_path = appendpath(to_name, name_tail)
1051
if tree.has_filename(dest_path):
1052
raise BzrError("destination %r already exists" % dest_path)
1053
if f_id in to_idpath:
1054
raise BzrError("can't move %r to a subdirectory of itself" % f)
1056
# OK, so there's a race here, it's possible that someone will
1057
# create a file in this interval and then the rename might be
1058
# left half-done. But we should have caught most problems.
1060
for f in from_paths:
1061
name_tail = splitpath(f)[-1]
1062
dest_path = appendpath(to_name, name_tail)
1063
result.append((f, dest_path))
1064
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1066
rename(self.abspath(f), self.abspath(dest_path))
1068
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1069
["rename rolled back"])
1071
self._write_inventory(inv)
1257
1191
if revno < 1 or revno > self.revno():
1258
1192
raise InvalidRevisionNumber(revno)
1194
def sign_revision(self, revision_id, gpg_strategy):
1195
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1196
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1199
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1200
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1264
1204
class ScratchBranch(_Branch):
1268
1208
>>> isdir(b.base)
1270
1210
>>> bd = b.base
1211
>>> b._transport.__del__()
1275
def __init__(self, files=[], dirs=[], base=None):
1216
def __init__(self, files=[], dirs=[], transport=None):
1276
1217
"""Make a test branch.
1278
1219
This creates a temporary directory and runs init-tree in it.
1280
1221
If any files are listed, they are created in the working copy.
1282
from tempfile import mkdtemp
1287
if isinstance(base, basestring):
1288
base = get_transport(base)
1289
_Branch.__init__(self, base, init=init)
1223
if transport is None:
1224
transport = bzrlib.transport.local.ScratchTransport()
1225
super(ScratchBranch, self).__init__(transport, init=True)
1227
super(ScratchBranch, self).__init__(transport)
1291
1230
self._transport.mkdir(d)
1312
1251
base = mkdtemp()
1314
1253
copytree(self.base, base, symlinks=True)
1315
return ScratchBranch(base=base)
1321
"""Destroy the test branch, removing the scratch directory."""
1322
from shutil import rmtree
1325
mutter("delete ScratchBranch %s" % self.base)
1328
# Work around for shutil.rmtree failing on Windows when
1329
# readonly files are encountered
1330
mutter("hit exception in destroying ScratchBranch: %s" % e)
1331
for root, dirs, files in os.walk(self.base, topdown=False):
1333
os.chmod(os.path.join(root, name), 0700)
1335
self._transport = None
1254
return ScratchBranch(
1255
transport=bzrlib.transport.local.ScratchTransport(base))
1339
1258
######################################################################