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
40
39
from bzrlib.delta import compare_trees
41
40
from bzrlib.tree import EmptyTree, RevisionTree
42
41
from bzrlib.inventory import Inventory
66
64
# XXX: leave this here for about one release, then remove it
67
65
raise NotImplementedError('find_branch() is not supported anymore, '
68
66
'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)
67
def _relpath(base, path):
68
"""Return path relative to base, or raise exception.
70
The path may be either an absolute path or a path relative to the
71
current working directory.
73
Lifted out of Branch.relpath for ease of testing.
75
os.path.commonprefix (python2.4) has a bad bug that it works just
76
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
77
avoids that problem."""
78
rp = os.path.abspath(path)
82
while len(head) >= len(base):
85
head, tail = os.path.split(head)
89
raise NotBranchError("path %r is not within branch %r" % (rp, base))
92
94
######################################################################
259
260
self.text_store = get_store('text-store')
260
261
self.revision_store = get_store('revision-store')
261
262
elif self._branch_format == 5:
262
self.control_weaves = get_weave('')
263
self.control_weaves = get_weave([])
263
264
self.weave_store = get_weave('weaves')
264
265
self.revision_store = get_store('revision-store', compressed=False)
265
266
elif self._branch_format == 6:
266
self.control_weaves = get_weave('')
267
self.control_weaves = get_weave([])
267
268
self.weave_store = get_weave('weaves', prefixed=True)
268
269
self.revision_store = get_store('revision-store', compressed=False,
270
self.revision_store.register_suffix('sig')
271
271
self._transaction = None
273
273
def __str__(self):
377
377
self._lock_mode = self._lock_count = None
379
379
def abspath(self, name):
380
"""Return absolute filename for something in the branch
382
XXX: Robert Collins 20051017 what is this used for? why is it a branch
383
method and not a tree method.
380
"""Return absolute filename for something in the branch"""
385
381
return self._transport.abspath(name)
383
def relpath(self, path):
384
"""Return path relative to this branch of something inside it.
386
Raises an error if path is not in this branch."""
387
return self._transport.relpath(path)
387
390
def _rel_controlfilename(self, file_or_path):
388
if not isinstance(file_or_path, basestring):
389
file_or_path = '/'.join(file_or_path)
390
if file_or_path == '':
392
return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
391
if isinstance(file_or_path, basestring):
392
file_or_path = [file_or_path]
393
return [bzrlib.BZRDIR] + file_or_path
394
395
def controlfilename(self, file_or_path):
395
396
"""Return location relative to branch."""
532
533
entry.parent_id = inv.root.file_id
533
534
self._write_inventory(inv)
536
536
def read_working_inventory(self):
537
537
"""Read the working inventory."""
538
# ElementTree does its own conversion from UTF-8, so open in
540
f = self.controlfile('inventory', 'rb')
541
return bzrlib.xml5.serializer_v5.read_inventory(f)
540
# ElementTree does its own conversion from UTF-8, so open in
542
f = self.controlfile('inventory', 'rb')
543
return bzrlib.xml5.serializer_v5.read_inventory(f)
544
548
def _write_inventory(self, inv):
545
549
"""Update the working inventory.
548
552
will be committed to the next revision.
550
554
from cStringIO import StringIO
552
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
554
# Transport handles atomicity
555
self.put_controlfile('inventory', sio)
558
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
560
# Transport handles atomicity
561
self.put_controlfile('inventory', sio)
557
565
mutter('wrote working inventory')
559
567
inventory = property(read_working_inventory, _write_inventory, None,
560
568
"""Inventory for the working copy.""")
563
570
def add(self, files, ids=None):
564
571
"""Make files versioned.
596
603
assert(len(ids) == len(files))
598
inv = self.read_working_inventory()
599
for f,file_id in zip(files, ids):
600
if is_control_file(f):
601
raise BzrError("cannot add control file %s" % quotefn(f))
606
raise BzrError("cannot add top-level %r" % f)
608
fullpath = os.path.normpath(self.abspath(f))
611
kind = file_kind(fullpath)
613
# maybe something better?
614
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
616
if not InventoryEntry.versionable_kind(kind):
617
raise BzrError('cannot add: not a versionable file ('
618
'i.e. regular file, symlink or directory): %s' % quotefn(f))
621
file_id = gen_file_id(f)
622
inv.add_path(f, kind=kind, file_id=file_id)
624
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
626
self._write_inventory(inv)
607
inv = self.read_working_inventory()
608
for f,file_id in zip(files, ids):
609
if is_control_file(f):
610
raise BzrError("cannot add control file %s" % quotefn(f))
615
raise BzrError("cannot add top-level %r" % f)
617
fullpath = os.path.normpath(self.abspath(f))
620
kind = file_kind(fullpath)
622
# maybe something better?
623
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
625
if not InventoryEntry.versionable_kind(kind):
626
raise BzrError('cannot add: not a versionable file ('
627
'i.e. regular file, symlink or directory): %s' % quotefn(f))
630
file_id = gen_file_id(f)
631
inv.add_path(f, kind=kind, file_id=file_id)
633
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
635
self._write_inventory(inv)
629
640
def print_file(self, file, revno):
630
641
"""Print `file` to stdout."""
631
tree = self.revision_tree(self.get_rev_id(revno))
632
# use inventory as it was in that revision
633
file_id = tree.inventory.path2id(file)
635
raise BzrError("%r is not present in revision %s" % (file, revno))
636
tree.print_file(file_id)
644
tree = self.revision_tree(self.get_rev_id(revno))
645
# use inventory as it was in that revision
646
file_id = tree.inventory.path2id(file)
648
raise BzrError("%r is not present in revision %s" % (file, revno))
649
tree.print_file(file_id)
654
def remove(self, files, verbose=False):
655
"""Mark nominated files for removal from the inventory.
657
This does not remove their text. This does not run on
659
TODO: Refuse to remove modified files unless --force is given?
661
TODO: Do something useful with directories.
663
TODO: Should this remove the text or not? Tough call; not
664
removing may be useful and the user can just use use rm, and
665
is the opposite of add. Removing it is consistent with most
666
other tools. Maybe an option.
668
## TODO: Normalize names
669
## TODO: Remove nested loops; better scalability
670
if isinstance(files, basestring):
676
tree = self.working_tree()
679
# do this before any modifications
683
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
684
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
686
# having remove it, it must be either ignored or unknown
687
if tree.is_ignored(f):
691
show_status(new_status, inv[fid].kind, quotefn(f))
694
self._write_inventory(inv)
638
698
# FIXME: this doesn't need to be a branch method
639
699
def set_inventory(self, new_inventory_list):
660
720
These are files in the working directory that are not versioned or
661
721
control files or ignored.
663
>>> from bzrlib.workingtree import WorkingTree
664
723
>>> b = ScratchBranch(files=['foo', 'foo~'])
665
>>> map(str, b.unknowns())
724
>>> list(b.unknowns())
668
727
>>> list(b.unknowns())
670
>>> WorkingTree(b.base, b).remove('foo')
671
730
>>> list(b.unknowns())
674
733
return self.working_tree().unknowns()
677
736
def append_revision(self, *revision_ids):
678
737
for revision_id in revision_ids:
679
738
mutter("add {%s} to revision-history" % revision_id)
680
rev_history = self.revision_history()
681
rev_history.extend(revision_ids)
682
self.set_revision_history(rev_history)
685
def set_revision_history(self, rev_history):
686
self.put_controlfile('revision-history', '\n'.join(rev_history))
741
rev_history = self.revision_history()
742
rev_history.extend(revision_ids)
743
self.put_controlfile('revision-history', '\n'.join(rev_history))
688
747
def has_revision(self, revision_id):
689
748
"""True if this branch has a copy of the revision.
691
750
This does not necessarily imply the revision is merge
692
751
or on the mainline."""
693
752
return (revision_id is None
694
or self.revision_store.has_id(revision_id))
753
or revision_id in self.revision_store)
697
755
def get_revision_xml_file(self, revision_id):
698
756
"""Return XML file object for revision object."""
699
757
if not revision_id or not isinstance(revision_id, basestring):
700
758
raise InvalidRevisionId(revision_id)
702
return self.revision_store.get(revision_id)
703
except (IndexError, KeyError):
704
raise bzrlib.errors.NoSuchRevision(self, revision_id)
763
return self.revision_store[revision_id]
764
except (IndexError, KeyError):
765
raise bzrlib.errors.NoSuchRevision(self, revision_id)
707
770
get_revision_xml = get_revision_xml_file
801
864
return self.get_inventory(revision_id)
804
866
def revision_history(self):
805
867
"""Return sequence of revision hashes on to this branch."""
806
transaction = self.get_transaction()
807
history = transaction.map.find_revision_history()
808
if history is not None:
809
mutter("cache hit for revision-history in %s", self)
870
transaction = self.get_transaction()
871
history = transaction.map.find_revision_history()
872
if history is not None:
873
mutter("cache hit for revision-history in %s", self)
875
history = [l.rstrip('\r\n') for l in
876
self.controlfile('revision-history', 'r').readlines()]
877
transaction.map.add_revision_history(history)
878
# this call is disabled because revision_history is
879
# not really an object yet, and the transaction is for objects.
880
# transaction.register_clean(history, precious=True)
810
881
return list(history)
811
history = [l.rstrip('\r\n') for l in
812
self.controlfile('revision-history', 'r').readlines()]
813
transaction.map.add_revision_history(history)
814
# this call is disabled because revision_history is
815
# not really an object yet, and the transaction is for objects.
816
# transaction.register_clean(history, precious=True)
885
def common_ancestor(self, other, self_revno=None, other_revno=None):
887
>>> from bzrlib.commit import commit
888
>>> sb = ScratchBranch(files=['foo', 'foo~'])
889
>>> sb.common_ancestor(sb) == (None, None)
891
>>> commit(sb, "Committing first revision", verbose=False)
892
>>> sb.common_ancestor(sb)[0]
894
>>> clone = sb.clone()
895
>>> commit(sb, "Committing second revision", verbose=False)
896
>>> sb.common_ancestor(sb)[0]
898
>>> sb.common_ancestor(clone)[0]
900
>>> commit(clone, "Committing divergent second revision",
902
>>> sb.common_ancestor(clone)[0]
904
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
906
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
908
>>> clone2 = sb.clone()
909
>>> sb.common_ancestor(clone2)[0]
911
>>> sb.common_ancestor(clone2, self_revno=1)[0]
913
>>> sb.common_ancestor(clone2, other_revno=1)[0]
916
my_history = self.revision_history()
917
other_history = other.revision_history()
918
if self_revno is None:
919
self_revno = len(my_history)
920
if other_revno is None:
921
other_revno = len(other_history)
922
indices = range(min((self_revno, other_revno)))
925
if my_history[r] == other_history[r]:
926
return r+1, my_history[r]
820
931
"""Return current revision number for this branch.
881
997
def update_revisions(self, other, stop_revision=None):
882
998
"""Pull in new perfect-fit revisions."""
883
# FIXME: If the branches have diverged, but the latest
884
# revision in this branch is completely merged into the other,
885
# then we should still be able to pull.
886
999
from bzrlib.fetch import greedy_fetch
1000
from bzrlib.revision import get_intervening_revisions
887
1001
if stop_revision is None:
888
1002
stop_revision = other.last_revision()
889
### Should this be checking is_ancestor instead of revision_history?
890
1003
if (stop_revision is not None and
891
1004
stop_revision in self.revision_history()):
893
1006
greedy_fetch(to_branch=self, from_branch=other,
894
1007
revision=stop_revision)
895
pullable_revs = self.pullable_revisions(other, stop_revision)
896
if len(pullable_revs) > 0:
1008
pullable_revs = self.missing_revisions(
1009
other, other.revision_id_to_revno(stop_revision))
1011
greedy_fetch(to_branch=self,
1013
revision=pullable_revs[-1])
897
1014
self.append_revision(*pullable_revs)
899
def pullable_revisions(self, other, stop_revision):
900
other_revno = other.revision_id_to_revno(stop_revision)
902
return self.missing_revisions(other, other_revno)
903
except DivergedBranches, e:
905
pullable_revs = get_intervening_revisions(self.last_revision(),
907
assert self.last_revision() not in pullable_revs
909
except bzrlib.errors.NotAncestor:
910
if is_ancestor(self.last_revision(), stop_revision, self):
915
1017
def commit(self, *args, **kw):
916
1018
from bzrlib.commit import Commit
917
1019
Commit().commit(self, *args, **kw)
949
1051
inv = self.get_revision_inventory(revision_id)
950
1052
return RevisionTree(self.weave_store, inv, revision_id)
952
1055
def working_tree(self):
953
1056
"""Return a `Tree` for the working copy."""
954
1057
from bzrlib.workingtree import WorkingTree
955
# TODO: In the future, perhaps WorkingTree should utilize Transport
1058
# TODO: In the future, WorkingTree should utilize Transport
956
1059
# RobertCollins 20051003 - I don't think it should - working trees are
957
1060
# much more complex to keep consistent than our careful .bzr subset.
958
1061
# instead, we should say that working trees are local only, and optimise
960
return WorkingTree(self.base, branch=self)
1063
return WorkingTree(self._transport.base, self.read_working_inventory())
963
1066
def basis_tree(self):
968
1071
return self.revision_tree(self.last_revision())
971
1074
def rename_one(self, from_rel, to_rel):
972
1075
"""Rename one file.
974
1077
This can change the directory or the filename or both.
976
tree = self.working_tree()
978
if not tree.has_filename(from_rel):
979
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
980
if tree.has_filename(to_rel):
981
raise BzrError("can't rename: new working file %r already exists" % to_rel)
983
file_id = inv.path2id(from_rel)
985
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
987
if inv.path2id(to_rel):
988
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
990
to_dir, to_tail = os.path.split(to_rel)
991
to_dir_id = inv.path2id(to_dir)
992
if to_dir_id == None and to_dir != '':
993
raise BzrError("can't determine destination directory id for %r" % to_dir)
995
mutter("rename_one:")
996
mutter(" file_id {%s}" % file_id)
997
mutter(" from_rel %r" % from_rel)
998
mutter(" to_rel %r" % to_rel)
999
mutter(" to_dir %r" % to_dir)
1000
mutter(" to_dir_id {%s}" % to_dir_id)
1002
inv.rename(file_id, to_dir_id, to_tail)
1004
from_abs = self.abspath(from_rel)
1005
to_abs = self.abspath(to_rel)
1007
rename(from_abs, to_abs)
1009
raise BzrError("failed to rename %r to %r: %s"
1010
% (from_abs, to_abs, e[1]),
1011
["rename rolled back"])
1013
self._write_inventory(inv)
1081
tree = self.working_tree()
1082
inv = tree.inventory
1083
if not tree.has_filename(from_rel):
1084
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1085
if tree.has_filename(to_rel):
1086
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1088
file_id = inv.path2id(from_rel)
1090
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1092
if inv.path2id(to_rel):
1093
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1095
to_dir, to_tail = os.path.split(to_rel)
1096
to_dir_id = inv.path2id(to_dir)
1097
if to_dir_id == None and to_dir != '':
1098
raise BzrError("can't determine destination directory id for %r" % to_dir)
1100
mutter("rename_one:")
1101
mutter(" file_id {%s}" % file_id)
1102
mutter(" from_rel %r" % from_rel)
1103
mutter(" to_rel %r" % to_rel)
1104
mutter(" to_dir %r" % to_dir)
1105
mutter(" to_dir_id {%s}" % to_dir_id)
1107
inv.rename(file_id, to_dir_id, to_tail)
1109
from_abs = self.abspath(from_rel)
1110
to_abs = self.abspath(to_rel)
1112
rename(from_abs, to_abs)
1114
raise BzrError("failed to rename %r to %r: %s"
1115
% (from_abs, to_abs, e[1]),
1116
["rename rolled back"])
1118
self._write_inventory(inv)
1016
1123
def move(self, from_paths, to_name):
1017
1124
"""Rename files.
1028
1135
entry that is moved.
1031
## TODO: Option to move IDs only
1032
assert not isinstance(from_paths, basestring)
1033
tree = self.working_tree()
1034
inv = tree.inventory
1035
to_abs = self.abspath(to_name)
1036
if not isdir(to_abs):
1037
raise BzrError("destination %r is not a directory" % to_abs)
1038
if not tree.has_filename(to_name):
1039
raise BzrError("destination %r not in working directory" % to_abs)
1040
to_dir_id = inv.path2id(to_name)
1041
if to_dir_id == None and to_name != '':
1042
raise BzrError("destination %r is not a versioned directory" % to_name)
1043
to_dir_ie = inv[to_dir_id]
1044
if to_dir_ie.kind not in ('directory', 'root_directory'):
1045
raise BzrError("destination %r is not a directory" % to_abs)
1047
to_idpath = inv.get_idpath(to_dir_id)
1049
for f in from_paths:
1050
if not tree.has_filename(f):
1051
raise BzrError("%r does not exist in working tree" % f)
1052
f_id = inv.path2id(f)
1054
raise BzrError("%r is not versioned" % f)
1055
name_tail = splitpath(f)[-1]
1056
dest_path = appendpath(to_name, name_tail)
1057
if tree.has_filename(dest_path):
1058
raise BzrError("destination %r already exists" % dest_path)
1059
if f_id in to_idpath:
1060
raise BzrError("can't move %r to a subdirectory of itself" % f)
1062
# OK, so there's a race here, it's possible that someone will
1063
# create a file in this interval and then the rename might be
1064
# left half-done. But we should have caught most problems.
1066
for f in from_paths:
1067
name_tail = splitpath(f)[-1]
1068
dest_path = appendpath(to_name, name_tail)
1069
result.append((f, dest_path))
1070
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1072
rename(self.abspath(f), self.abspath(dest_path))
1074
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1075
["rename rolled back"])
1077
self._write_inventory(inv)
1140
## TODO: Option to move IDs only
1141
assert not isinstance(from_paths, basestring)
1142
tree = self.working_tree()
1143
inv = tree.inventory
1144
to_abs = self.abspath(to_name)
1145
if not isdir(to_abs):
1146
raise BzrError("destination %r is not a directory" % to_abs)
1147
if not tree.has_filename(to_name):
1148
raise BzrError("destination %r not in working directory" % to_abs)
1149
to_dir_id = inv.path2id(to_name)
1150
if to_dir_id == None and to_name != '':
1151
raise BzrError("destination %r is not a versioned directory" % to_name)
1152
to_dir_ie = inv[to_dir_id]
1153
if to_dir_ie.kind not in ('directory', 'root_directory'):
1154
raise BzrError("destination %r is not a directory" % to_abs)
1156
to_idpath = inv.get_idpath(to_dir_id)
1158
for f in from_paths:
1159
if not tree.has_filename(f):
1160
raise BzrError("%r does not exist in working tree" % f)
1161
f_id = inv.path2id(f)
1163
raise BzrError("%r is not versioned" % f)
1164
name_tail = splitpath(f)[-1]
1165
dest_path = appendpath(to_name, name_tail)
1166
if tree.has_filename(dest_path):
1167
raise BzrError("destination %r already exists" % dest_path)
1168
if f_id in to_idpath:
1169
raise BzrError("can't move %r to a subdirectory of itself" % f)
1171
# OK, so there's a race here, it's possible that someone will
1172
# create a file in this interval and then the rename might be
1173
# left half-done. But we should have caught most problems.
1175
for f in from_paths:
1176
name_tail = splitpath(f)[-1]
1177
dest_path = appendpath(to_name, name_tail)
1178
result.append((f, dest_path))
1179
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1181
rename(self.abspath(f), self.abspath(dest_path))
1183
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1184
["rename rolled back"])
1186
self._write_inventory(inv)
1197
1318
if revno < 1 or revno > self.revno():
1198
1319
raise InvalidRevisionNumber(revno)
1200
def sign_revision(self, revision_id, gpg_strategy):
1201
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1202
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1205
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1206
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1210
1325
class ScratchBranch(_Branch):
1214
1329
>>> isdir(b.base)
1216
1331
>>> bd = b.base
1217
>>> b._transport.__del__()
1222
def __init__(self, files=[], dirs=[], transport=None):
1336
def __init__(self, files=[], dirs=[], base=None):
1223
1337
"""Make a test branch.
1225
1339
This creates a temporary directory and runs init-tree in it.
1227
1341
If any files are listed, they are created in the working copy.
1229
if transport is None:
1230
transport = bzrlib.transport.local.ScratchTransport()
1231
super(ScratchBranch, self).__init__(transport, init=True)
1233
super(ScratchBranch, self).__init__(transport)
1343
from tempfile import mkdtemp
1348
if isinstance(base, basestring):
1349
base = get_transport(base)
1350
_Branch.__init__(self, base, init=init)
1236
1352
self._transport.mkdir(d)
1257
1373
base = mkdtemp()
1259
1375
copytree(self.base, base, symlinks=True)
1260
return ScratchBranch(
1261
transport=bzrlib.transport.local.ScratchTransport(base))
1376
return ScratchBranch(base=base)
1382
"""Destroy the test branch, removing the scratch directory."""
1383
from shutil import rmtree
1386
mutter("delete ScratchBranch %s" % self.base)
1389
# Work around for shutil.rmtree failing on Windows when
1390
# readonly files are encountered
1391
mutter("hit exception in destroying ScratchBranch: %s" % e)
1392
for root, dirs, files in os.walk(self.base, topdown=False):
1394
os.chmod(os.path.join(root, name), 0700)
1396
self._transport = None
1264
1400
######################################################################