28
28
from bzrlib.trace import mutter, note
29
29
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
30
30
rename, splitpath, sha_file, appendpath,
32
32
import bzrlib.errors as errors
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
######################################################################
503
528
entry.parent_id = inv.root.file_id
504
529
self._write_inventory(inv)
506
532
def read_working_inventory(self):
507
533
"""Read the working inventory."""
510
# ElementTree does its own conversion from UTF-8, so open in
512
f = self.controlfile('inventory', 'rb')
513
return bzrlib.xml5.serializer_v5.read_inventory(f)
534
# ElementTree does its own conversion from UTF-8, so open in
536
f = self.controlfile('inventory', 'rb')
537
return bzrlib.xml5.serializer_v5.read_inventory(f)
518
540
def _write_inventory(self, inv):
519
541
"""Update the working inventory.
522
544
will be committed to the next revision.
524
546
from cStringIO import StringIO
528
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
530
# Transport handles atomicity
531
self.put_controlfile('inventory', sio)
548
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
550
# Transport handles atomicity
551
self.put_controlfile('inventory', sio)
535
553
mutter('wrote working inventory')
537
555
inventory = property(read_working_inventory, _write_inventory, None,
538
556
"""Inventory for the working copy.""")
540
559
def add(self, files, ids=None):
541
560
"""Make files versioned.
573
592
assert(len(ids) == len(files))
577
inv = self.read_working_inventory()
578
for f,file_id in zip(files, ids):
579
if is_control_file(f):
580
raise BzrError("cannot add control file %s" % quotefn(f))
585
raise BzrError("cannot add top-level %r" % f)
587
fullpath = os.path.normpath(self.abspath(f))
590
kind = file_kind(fullpath)
592
# maybe something better?
593
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
595
if not InventoryEntry.versionable_kind(kind):
596
raise BzrError('cannot add: not a versionable file ('
597
'i.e. regular file, symlink or directory): %s' % quotefn(f))
600
file_id = gen_file_id(f)
601
inv.add_path(f, kind=kind, file_id=file_id)
603
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
605
self._write_inventory(inv)
594
inv = self.read_working_inventory()
595
for f,file_id in zip(files, ids):
596
if is_control_file(f):
597
raise BzrError("cannot add control file %s" % quotefn(f))
602
raise BzrError("cannot add top-level %r" % f)
604
fullpath = os.path.normpath(self.abspath(f))
607
kind = file_kind(fullpath)
609
# maybe something better?
610
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
612
if not InventoryEntry.versionable_kind(kind):
613
raise BzrError('cannot add: not a versionable file ('
614
'i.e. regular file, symlink or directory): %s' % quotefn(f))
617
file_id = gen_file_id(f)
618
inv.add_path(f, kind=kind, file_id=file_id)
620
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
622
self._write_inventory(inv)
610
625
def print_file(self, file, revno):
611
626
"""Print `file` to stdout."""
614
tree = self.revision_tree(self.get_rev_id(revno))
615
# use inventory as it was in that revision
616
file_id = tree.inventory.path2id(file)
618
raise BzrError("%r is not present in revision %s" % (file, revno))
619
tree.print_file(file_id)
624
def remove(self, files, verbose=False):
625
"""Mark nominated files for removal from the inventory.
627
This does not remove their text. This does not run on
629
TODO: Refuse to remove modified files unless --force is given?
631
TODO: Do something useful with directories.
633
TODO: Should this remove the text or not? Tough call; not
634
removing may be useful and the user can just use use rm, and
635
is the opposite of add. Removing it is consistent with most
636
other tools. Maybe an option.
638
## TODO: Normalize names
639
## TODO: Remove nested loops; better scalability
640
if isinstance(files, basestring):
646
tree = self.working_tree()
649
# do this before any modifications
653
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
654
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
656
# having remove it, it must be either ignored or unknown
657
if tree.is_ignored(f):
661
show_status(new_status, inv[fid].kind, quotefn(f))
664
self._write_inventory(inv)
627
tree = self.revision_tree(self.get_rev_id(revno))
628
# use inventory as it was in that revision
629
file_id = tree.inventory.path2id(file)
631
raise BzrError("%r is not present in revision %s" % (file, revno))
632
tree.print_file(file_id)
668
634
# FIXME: this doesn't need to be a branch method
669
635
def set_inventory(self, new_inventory_list):
690
656
These are files in the working directory that are not versioned or
691
657
control files or ignored.
659
>>> from bzrlib.workingtree import WorkingTree
693
660
>>> b = ScratchBranch(files=['foo', 'foo~'])
694
>>> list(b.unknowns())
661
>>> map(str, b.unknowns())
697
664
>>> list(b.unknowns())
666
>>> WorkingTree(b.base, b).remove('foo')
700
667
>>> list(b.unknowns())
703
670
return self.working_tree().unknowns()
706
673
def append_revision(self, *revision_ids):
707
674
for revision_id in revision_ids:
708
675
mutter("add {%s} to revision-history" % revision_id)
711
rev_history = self.revision_history()
712
rev_history.extend(revision_ids)
713
self.put_controlfile('revision-history', '\n'.join(rev_history))
676
rev_history = self.revision_history()
677
rev_history.extend(revision_ids)
678
self.put_controlfile('revision-history', '\n'.join(rev_history))
717
680
def has_revision(self, revision_id):
718
681
"""True if this branch has a copy of the revision.
720
683
This does not necessarily imply the revision is merge
721
684
or on the mainline."""
722
685
return (revision_id is None
723
or revision_id in self.revision_store)
686
or self.revision_store.has_id(revision_id))
725
689
def get_revision_xml_file(self, revision_id):
726
690
"""Return XML file object for revision object."""
727
691
if not revision_id or not isinstance(revision_id, basestring):
728
692
raise InvalidRevisionId(revision_id)
733
return self.revision_store[revision_id]
734
except (IndexError, KeyError):
735
raise bzrlib.errors.NoSuchRevision(self, revision_id)
694
return self.revision_store.get(revision_id)
695
except (IndexError, KeyError):
696
raise bzrlib.errors.NoSuchRevision(self, revision_id)
740
699
get_revision_xml = get_revision_xml_file
834
793
return self.get_inventory(revision_id)
836
796
def revision_history(self):
837
797
"""Return sequence of revision hashes on to this branch."""
840
transaction = self.get_transaction()
841
history = transaction.map.find_revision_history()
842
if history is not None:
843
mutter("cache hit for revision-history in %s", self)
845
history = [l.rstrip('\r\n') for l in
846
self.controlfile('revision-history', 'r').readlines()]
847
transaction.map.add_revision_history(history)
848
# this call is disabled because revision_history is
849
# not really an object yet, and the transaction is for objects.
850
# transaction.register_clean(history, precious=True)
798
transaction = self.get_transaction()
799
history = transaction.map.find_revision_history()
800
if history is not None:
801
mutter("cache hit for revision-history in %s", self)
851
802
return list(history)
803
history = [l.rstrip('\r\n') for l in
804
self.controlfile('revision-history', 'r').readlines()]
805
transaction.map.add_revision_history(history)
806
# this call is disabled because revision_history is
807
# not really an object yet, and the transaction is for objects.
808
# transaction.register_clean(history, precious=True)
856
812
"""Return current revision number for this branch.
988
941
inv = self.get_revision_inventory(revision_id)
989
942
return RevisionTree(self.weave_store, inv, revision_id)
992
944
def working_tree(self):
993
945
"""Return a `Tree` for the working copy."""
994
946
from bzrlib.workingtree import WorkingTree
995
# TODO: In the future, WorkingTree should utilize Transport
947
# TODO: In the future, perhaps WorkingTree should utilize Transport
996
948
# RobertCollins 20051003 - I don't think it should - working trees are
997
949
# much more complex to keep consistent than our careful .bzr subset.
998
950
# instead, we should say that working trees are local only, and optimise
1008
960
return self.revision_tree(self.last_revision())
1011
963
def rename_one(self, from_rel, to_rel):
1012
964
"""Rename one file.
1014
966
This can change the directory or the filename or both.
968
tree = self.working_tree()
970
if not tree.has_filename(from_rel):
971
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
972
if tree.has_filename(to_rel):
973
raise BzrError("can't rename: new working file %r already exists" % to_rel)
975
file_id = inv.path2id(from_rel)
977
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
979
if inv.path2id(to_rel):
980
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
982
to_dir, to_tail = os.path.split(to_rel)
983
to_dir_id = inv.path2id(to_dir)
984
if to_dir_id == None and to_dir != '':
985
raise BzrError("can't determine destination directory id for %r" % to_dir)
987
mutter("rename_one:")
988
mutter(" file_id {%s}" % file_id)
989
mutter(" from_rel %r" % from_rel)
990
mutter(" to_rel %r" % to_rel)
991
mutter(" to_dir %r" % to_dir)
992
mutter(" to_dir_id {%s}" % to_dir_id)
994
inv.rename(file_id, to_dir_id, to_tail)
996
from_abs = self.abspath(from_rel)
997
to_abs = self.abspath(to_rel)
1018
tree = self.working_tree()
1019
inv = tree.inventory
1020
if not tree.has_filename(from_rel):
1021
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1022
if tree.has_filename(to_rel):
1023
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1025
file_id = inv.path2id(from_rel)
1027
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1029
if inv.path2id(to_rel):
1030
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1032
to_dir, to_tail = os.path.split(to_rel)
1033
to_dir_id = inv.path2id(to_dir)
1034
if to_dir_id == None and to_dir != '':
1035
raise BzrError("can't determine destination directory id for %r" % to_dir)
1037
mutter("rename_one:")
1038
mutter(" file_id {%s}" % file_id)
1039
mutter(" from_rel %r" % from_rel)
1040
mutter(" to_rel %r" % to_rel)
1041
mutter(" to_dir %r" % to_dir)
1042
mutter(" to_dir_id {%s}" % to_dir_id)
1044
inv.rename(file_id, to_dir_id, to_tail)
1046
from_abs = self.abspath(from_rel)
1047
to_abs = self.abspath(to_rel)
1049
rename(from_abs, to_abs)
1051
raise BzrError("failed to rename %r to %r: %s"
1052
% (from_abs, to_abs, e[1]),
1053
["rename rolled back"])
1055
self._write_inventory(inv)
999
rename(from_abs, to_abs)
1001
raise BzrError("failed to rename %r to %r: %s"
1002
% (from_abs, to_abs, e[1]),
1003
["rename rolled back"])
1005
self._write_inventory(inv)
1060
1008
def move(self, from_paths, to_name):
1061
1009
"""Rename files.
1072
1020
entry that is moved.
1077
## TODO: Option to move IDs only
1078
assert not isinstance(from_paths, basestring)
1079
tree = self.working_tree()
1080
inv = tree.inventory
1081
to_abs = self.abspath(to_name)
1082
if not isdir(to_abs):
1083
raise BzrError("destination %r is not a directory" % to_abs)
1084
if not tree.has_filename(to_name):
1085
raise BzrError("destination %r not in working directory" % to_abs)
1086
to_dir_id = inv.path2id(to_name)
1087
if to_dir_id == None and to_name != '':
1088
raise BzrError("destination %r is not a versioned directory" % to_name)
1089
to_dir_ie = inv[to_dir_id]
1090
if to_dir_ie.kind not in ('directory', 'root_directory'):
1091
raise BzrError("destination %r is not a directory" % to_abs)
1093
to_idpath = inv.get_idpath(to_dir_id)
1095
for f in from_paths:
1096
if not tree.has_filename(f):
1097
raise BzrError("%r does not exist in working tree" % f)
1098
f_id = inv.path2id(f)
1100
raise BzrError("%r is not versioned" % f)
1101
name_tail = splitpath(f)[-1]
1102
dest_path = appendpath(to_name, name_tail)
1103
if tree.has_filename(dest_path):
1104
raise BzrError("destination %r already exists" % dest_path)
1105
if f_id in to_idpath:
1106
raise BzrError("can't move %r to a subdirectory of itself" % f)
1108
# OK, so there's a race here, it's possible that someone will
1109
# create a file in this interval and then the rename might be
1110
# left half-done. But we should have caught most problems.
1112
for f in from_paths:
1113
name_tail = splitpath(f)[-1]
1114
dest_path = appendpath(to_name, name_tail)
1115
result.append((f, dest_path))
1116
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1118
rename(self.abspath(f), self.abspath(dest_path))
1120
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1121
["rename rolled back"])
1123
self._write_inventory(inv)
1023
## TODO: Option to move IDs only
1024
assert not isinstance(from_paths, basestring)
1025
tree = self.working_tree()
1026
inv = tree.inventory
1027
to_abs = self.abspath(to_name)
1028
if not isdir(to_abs):
1029
raise BzrError("destination %r is not a directory" % to_abs)
1030
if not tree.has_filename(to_name):
1031
raise BzrError("destination %r not in working directory" % to_abs)
1032
to_dir_id = inv.path2id(to_name)
1033
if to_dir_id == None and to_name != '':
1034
raise BzrError("destination %r is not a versioned directory" % to_name)
1035
to_dir_ie = inv[to_dir_id]
1036
if to_dir_ie.kind not in ('directory', 'root_directory'):
1037
raise BzrError("destination %r is not a directory" % to_abs)
1039
to_idpath = inv.get_idpath(to_dir_id)
1041
for f in from_paths:
1042
if not tree.has_filename(f):
1043
raise BzrError("%r does not exist in working tree" % f)
1044
f_id = inv.path2id(f)
1046
raise BzrError("%r is not versioned" % f)
1047
name_tail = splitpath(f)[-1]
1048
dest_path = appendpath(to_name, name_tail)
1049
if tree.has_filename(dest_path):
1050
raise BzrError("destination %r already exists" % dest_path)
1051
if f_id in to_idpath:
1052
raise BzrError("can't move %r to a subdirectory of itself" % f)
1054
# OK, so there's a race here, it's possible that someone will
1055
# create a file in this interval and then the rename might be
1056
# left half-done. But we should have caught most problems.
1058
for f in from_paths:
1059
name_tail = splitpath(f)[-1]
1060
dest_path = appendpath(to_name, name_tail)
1061
result.append((f, dest_path))
1062
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1064
rename(self.abspath(f), self.abspath(dest_path))
1066
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1067
["rename rolled back"])
1069
self._write_inventory(inv)
1255
1189
if revno < 1 or revno > self.revno():
1256
1190
raise InvalidRevisionNumber(revno)
1192
def sign_revision(self, revision_id, gpg_strategy):
1193
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1194
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1197
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1198
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1262
1202
class ScratchBranch(_Branch):
1266
1206
>>> isdir(b.base)
1268
1208
>>> bd = b.base
1209
>>> b._transport.__del__()
1273
def __init__(self, files=[], dirs=[], base=None):
1214
def __init__(self, files=[], dirs=[], transport=None):
1274
1215
"""Make a test branch.
1276
1217
This creates a temporary directory and runs init-tree in it.
1278
1219
If any files are listed, they are created in the working copy.
1280
from tempfile import mkdtemp
1285
if isinstance(base, basestring):
1286
base = get_transport(base)
1287
_Branch.__init__(self, base, init=init)
1221
if transport is None:
1222
transport = bzrlib.transport.local.ScratchTransport()
1223
super(ScratchBranch, self).__init__(transport, init=True)
1225
super(ScratchBranch, self).__init__(transport)
1289
1228
self._transport.mkdir(d)
1310
1249
base = mkdtemp()
1312
1251
copytree(self.base, base, symlinks=True)
1313
return ScratchBranch(base=base)
1319
"""Destroy the test branch, removing the scratch directory."""
1320
from shutil import rmtree
1323
mutter("delete ScratchBranch %s" % self.base)
1326
# Work around for shutil.rmtree failing on Windows when
1327
# readonly files are encountered
1328
mutter("hit exception in destroying ScratchBranch: %s" % e)
1329
for root, dirs, files in os.walk(self.base, topdown=False):
1331
os.chmod(os.path.join(root, name), 0700)
1333
self._transport = None
1252
return ScratchBranch(
1253
transport=bzrlib.transport.local.ScratchTransport(base))
1337
1256
######################################################################