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, NotVersionedError)
36
UnlistableBranch, NoSuchFile)
37
37
from bzrlib.textui import show_status
38
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
41
40
from bzrlib.delta import compare_trees
42
41
from bzrlib.tree import EmptyTree, RevisionTree
68
67
raise NotImplementedError('find_branch() is not supported anymore, '
69
68
'please use one of the new branch constructors')
72
def needs_read_lock(unbound):
73
"""Decorate unbound to take out and release a read lock."""
74
def decorated(self, *args, **kwargs):
77
return unbound(self, *args, **kwargs)
83
def needs_write_lock(unbound):
84
"""Decorate unbound to take out and release a write lock."""
85
def decorated(self, *args, **kwargs):
88
return unbound(self, *args, **kwargs)
93
70
######################################################################
258
236
self.text_store = get_store('text-store')
259
237
self.revision_store = get_store('revision-store')
260
238
elif self._branch_format == 5:
261
self.control_weaves = get_weave('')
239
self.control_weaves = get_weave([])
262
240
self.weave_store = get_weave('weaves')
263
241
self.revision_store = get_store('revision-store', compressed=False)
264
242
elif self._branch_format == 6:
265
self.control_weaves = get_weave('')
243
self.control_weaves = get_weave([])
266
244
self.weave_store = get_weave('weaves', prefixed=True)
267
245
self.revision_store = get_store('revision-store', compressed=False,
384
362
return self._transport.abspath(name)
386
364
def _rel_controlfilename(self, file_or_path):
387
if not isinstance(file_or_path, basestring):
388
file_or_path = '/'.join(file_or_path)
389
if file_or_path == '':
391
return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
365
if isinstance(file_or_path, basestring):
366
file_or_path = [file_or_path]
367
return [bzrlib.BZRDIR] + file_or_path
393
369
def controlfilename(self, file_or_path):
394
370
"""Return location relative to branch."""
395
371
return self._transport.abspath(self._rel_controlfilename(file_or_path))
397
374
def controlfile(self, file_or_path, mode='r'):
398
375
"""Open a control file for this branch.
530
507
entry.parent_id = inv.root.file_id
531
508
self._write_inventory(inv)
534
510
def read_working_inventory(self):
535
511
"""Read the working inventory."""
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)
514
# ElementTree does its own conversion from UTF-8, so open in
516
f = self.controlfile('inventory', 'rb')
517
return bzrlib.xml5.serializer_v5.read_inventory(f)
542
522
def _write_inventory(self, inv):
543
523
"""Update the working inventory.
546
526
will be committed to the next revision.
548
528
from cStringIO import StringIO
550
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
552
# Transport handles atomicity
553
self.put_controlfile('inventory', sio)
532
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
534
# Transport handles atomicity
535
self.put_controlfile('inventory', sio)
555
539
mutter('wrote working inventory')
557
541
inventory = property(read_working_inventory, _write_inventory, None,
558
542
"""Inventory for the working copy.""")
561
544
def add(self, files, ids=None):
562
545
"""Make files versioned.
594
577
assert(len(ids) == len(files))
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)
581
inv = self.read_working_inventory()
582
for f,file_id in zip(files, ids):
583
if is_control_file(f):
584
raise BzrError("cannot add control file %s" % quotefn(f))
589
raise BzrError("cannot add top-level %r" % f)
591
fullpath = os.path.normpath(self.abspath(f))
594
kind = file_kind(fullpath)
596
# maybe something better?
597
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
599
if not InventoryEntry.versionable_kind(kind):
600
raise BzrError('cannot add: not a versionable file ('
601
'i.e. regular file, symlink or directory): %s' % quotefn(f))
604
file_id = gen_file_id(f)
605
inv.add_path(f, kind=kind, file_id=file_id)
607
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
609
self._write_inventory(inv)
627
614
def print_file(self, file, revno):
628
615
"""Print `file` to stdout."""
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)
618
tree = self.revision_tree(self.get_rev_id(revno))
619
# use inventory as it was in that revision
620
file_id = tree.inventory.path2id(file)
622
raise BzrError("%r is not present in revision %s" % (file, revno))
623
tree.print_file(file_id)
628
def remove(self, files, verbose=False):
629
"""Mark nominated files for removal from the inventory.
631
This does not remove their text. This does not run on
633
TODO: Refuse to remove modified files unless --force is given?
635
TODO: Do something useful with directories.
637
TODO: Should this remove the text or not? Tough call; not
638
removing may be useful and the user can just use use rm, and
639
is the opposite of add. Removing it is consistent with most
640
other tools. Maybe an option.
642
## TODO: Normalize names
643
## TODO: Remove nested loops; better scalability
644
if isinstance(files, basestring):
650
tree = self.working_tree()
653
# do this before any modifications
657
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
658
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
660
# having remove it, it must be either ignored or unknown
661
if tree.is_ignored(f):
665
show_status(new_status, inv[fid].kind, quotefn(f))
668
self._write_inventory(inv)
636
672
# FIXME: this doesn't need to be a branch method
637
673
def set_inventory(self, new_inventory_list):
658
694
These are files in the working directory that are not versioned or
659
695
control files or ignored.
661
>>> from bzrlib.workingtree import WorkingTree
662
697
>>> b = ScratchBranch(files=['foo', 'foo~'])
663
>>> map(str, b.unknowns())
698
>>> list(b.unknowns())
666
701
>>> list(b.unknowns())
668
>>> WorkingTree(b.base, b).remove('foo')
669
704
>>> list(b.unknowns())
672
707
return self.working_tree().unknowns()
675
710
def append_revision(self, *revision_ids):
676
711
for revision_id in revision_ids:
677
712
mutter("add {%s} to revision-history" % revision_id)
678
rev_history = self.revision_history()
679
rev_history.extend(revision_ids)
680
self.set_revision_history(rev_history)
683
def set_revision_history(self, rev_history):
684
self.put_controlfile('revision-history', '\n'.join(rev_history))
715
rev_history = self.revision_history()
716
rev_history.extend(revision_ids)
717
self.put_controlfile('revision-history', '\n'.join(rev_history))
686
721
def has_revision(self, revision_id):
687
722
"""True if this branch has a copy of the revision.
691
726
return (revision_id is None
692
727
or self.revision_store.has_id(revision_id))
695
729
def get_revision_xml_file(self, revision_id):
696
730
"""Return XML file object for revision object."""
697
731
if not revision_id or not isinstance(revision_id, basestring):
698
raise InvalidRevisionId(revision_id=revision_id, branch=self)
732
raise InvalidRevisionId(revision_id)
700
return self.revision_store.get(revision_id)
701
except (IndexError, KeyError):
702
raise bzrlib.errors.NoSuchRevision(self, revision_id)
737
return self.revision_store.get(revision_id)
738
except (IndexError, KeyError):
739
raise bzrlib.errors.NoSuchRevision(self, revision_id)
705
744
get_revision_xml = get_revision_xml_file
799
838
return self.get_inventory(revision_id)
802
840
def revision_history(self):
803
841
"""Return sequence of revision hashes on to this branch."""
804
transaction = self.get_transaction()
805
history = transaction.map.find_revision_history()
806
if history is not None:
807
mutter("cache hit for revision-history in %s", self)
844
transaction = self.get_transaction()
845
history = transaction.map.find_revision_history()
846
if history is not None:
847
mutter("cache hit for revision-history in %s", self)
849
history = [l.rstrip('\r\n') for l in
850
self.controlfile('revision-history', 'r').readlines()]
851
transaction.map.add_revision_history(history)
852
# this call is disabled because revision_history is
853
# not really an object yet, and the transaction is for objects.
854
# transaction.register_clean(history, precious=True)
808
855
return list(history)
809
history = [l.rstrip('\r\n') for l in
810
self.controlfile('revision-history', 'r').readlines()]
811
transaction.map.add_revision_history(history)
812
# this call is disabled because revision_history is
813
# not really an object yet, and the transaction is for objects.
814
# transaction.register_clean(history, precious=True)
818
860
"""Return current revision number for this branch.
947
994
def working_tree(self):
948
995
"""Return a `Tree` for the working copy."""
949
996
from bzrlib.workingtree import WorkingTree
950
# TODO: In the future, perhaps WorkingTree should utilize Transport
997
# TODO: In the future, WorkingTree should utilize Transport
951
998
# RobertCollins 20051003 - I don't think it should - working trees are
952
999
# much more complex to keep consistent than our careful .bzr subset.
953
1000
# instead, we should say that working trees are local only, and optimise
955
1002
return WorkingTree(self.base, branch=self)
958
def pull(self, source, overwrite=False):
962
self.update_revisions(source)
963
except DivergedBranches:
966
self.set_revision_history(source.revision_history())
970
1005
def basis_tree(self):
971
1006
"""Return `Tree` object for last revision.
975
1010
return self.revision_tree(self.last_revision())
978
1013
def rename_one(self, from_rel, to_rel):
979
1014
"""Rename one file.
981
1016
This can change the directory or the filename or both.
983
tree = self.working_tree()
985
if not tree.has_filename(from_rel):
986
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
987
if tree.has_filename(to_rel):
988
raise BzrError("can't rename: new working file %r already exists" % to_rel)
990
file_id = inv.path2id(from_rel)
992
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
994
if inv.path2id(to_rel):
995
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
997
to_dir, to_tail = os.path.split(to_rel)
998
to_dir_id = inv.path2id(to_dir)
999
if to_dir_id == None and to_dir != '':
1000
raise BzrError("can't determine destination directory id for %r" % to_dir)
1002
mutter("rename_one:")
1003
mutter(" file_id {%s}" % file_id)
1004
mutter(" from_rel %r" % from_rel)
1005
mutter(" to_rel %r" % to_rel)
1006
mutter(" to_dir %r" % to_dir)
1007
mutter(" to_dir_id {%s}" % to_dir_id)
1009
inv.rename(file_id, to_dir_id, to_tail)
1011
from_abs = self.abspath(from_rel)
1012
to_abs = self.abspath(to_rel)
1014
rename(from_abs, to_abs)
1016
raise BzrError("failed to rename %r to %r: %s"
1017
% (from_abs, to_abs, e[1]),
1018
["rename rolled back"])
1020
self._write_inventory(inv)
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)
1023
1062
def move(self, from_paths, to_name):
1024
1063
"""Rename files.
1035
1074
entry that is moved.
1038
## TODO: Option to move IDs only
1039
assert not isinstance(from_paths, basestring)
1040
tree = self.working_tree()
1041
inv = tree.inventory
1042
to_abs = self.abspath(to_name)
1043
if not isdir(to_abs):
1044
raise BzrError("destination %r is not a directory" % to_abs)
1045
if not tree.has_filename(to_name):
1046
raise BzrError("destination %r not in working directory" % to_abs)
1047
to_dir_id = inv.path2id(to_name)
1048
if to_dir_id == None and to_name != '':
1049
raise BzrError("destination %r is not a versioned directory" % to_name)
1050
to_dir_ie = inv[to_dir_id]
1051
if to_dir_ie.kind not in ('directory', 'root_directory'):
1052
raise BzrError("destination %r is not a directory" % to_abs)
1054
to_idpath = inv.get_idpath(to_dir_id)
1056
for f in from_paths:
1057
if not tree.has_filename(f):
1058
raise BzrError("%r does not exist in working tree" % f)
1059
f_id = inv.path2id(f)
1061
raise BzrError("%r is not versioned" % f)
1062
name_tail = splitpath(f)[-1]
1063
dest_path = appendpath(to_name, name_tail)
1064
if tree.has_filename(dest_path):
1065
raise BzrError("destination %r already exists" % dest_path)
1066
if f_id in to_idpath:
1067
raise BzrError("can't move %r to a subdirectory of itself" % f)
1069
# OK, so there's a race here, it's possible that someone will
1070
# create a file in this interval and then the rename might be
1071
# left half-done. But we should have caught most problems.
1073
for f in from_paths:
1074
name_tail = splitpath(f)[-1]
1075
dest_path = appendpath(to_name, name_tail)
1076
result.append((f, dest_path))
1077
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1079
rename(self.abspath(f), self.abspath(dest_path))
1081
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1082
["rename rolled back"])
1084
self._write_inventory(inv)
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)
1180
def get_push_location(self):
1181
"""Return the None or the location to push this branch to."""
1182
config = bzrlib.config.BranchConfig(self)
1183
push_loc = config.get_user_option('push_location')
1186
def set_push_location(self, location):
1187
"""Set a new push location for this branch."""
1188
config = bzrlib.config.LocationConfig(self.base)
1189
config.set_user_option('push_location', location)
1192
1230
def set_parent(self, url):
1193
1231
# TODO: Maybe delete old location files?
1194
1232
from bzrlib.atomicfile import AtomicFile
1195
f = AtomicFile(self.controlfilename('parent'))
1235
f = AtomicFile(self.controlfilename('parent'))
1202
1244
def check_revno(self, revno):
1216
1258
raise InvalidRevisionNumber(revno)
1218
1260
def sign_revision(self, revision_id, gpg_strategy):
1219
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1220
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1223
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1224
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1263
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1264
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1228
1270
class ScratchBranch(_Branch):