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))
94
def find_branch_root(t):
95
"""Find the branch root enclosing the transport's base.
97
t is a Transport object.
99
It is not necessary that the base of t exists.
101
Basically we keep looking up until we find the control directory or
102
run into the root. If there isn't one, raises NotBranchError.
106
if t.has(bzrlib.BZRDIR):
108
new_t = t.clone('..')
109
if new_t.base == t.base:
110
# reached the root, whatever that may be
111
raise NotBranchError('%s is not in a branch' % orig_base)
92
115
######################################################################
259
270
self.text_store = get_store('text-store')
260
271
self.revision_store = get_store('revision-store')
261
272
elif self._branch_format == 5:
262
self.control_weaves = get_weave('')
273
self.control_weaves = get_weave([])
263
274
self.weave_store = get_weave('weaves')
264
275
self.revision_store = get_store('revision-store', compressed=False)
265
276
elif self._branch_format == 6:
266
self.control_weaves = get_weave('')
277
self.control_weaves = get_weave([])
267
278
self.weave_store = get_weave('weaves', prefixed=True)
268
279
self.revision_store = get_store('revision-store', compressed=False,
270
self.revision_store.register_suffix('sig')
271
281
self._transaction = None
273
283
def __str__(self):
377
383
self._lock_mode = self._lock_count = None
379
385
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.
386
"""Return absolute filename for something in the branch"""
385
387
return self._transport.abspath(name)
389
def relpath(self, path):
390
"""Return path relative to this branch of something inside it.
392
Raises an error if path is not in this branch."""
393
return self._transport.relpath(path)
387
396
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)
397
if isinstance(file_or_path, basestring):
398
file_or_path = [file_or_path]
399
return [bzrlib.BZRDIR] + file_or_path
394
401
def controlfilename(self, file_or_path):
395
402
"""Return location relative to branch."""
532
539
entry.parent_id = inv.root.file_id
533
540
self._write_inventory(inv)
536
542
def read_working_inventory(self):
537
543
"""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)
546
# ElementTree does its own conversion from UTF-8, so open in
548
f = self.controlfile('inventory', 'rb')
549
return bzrlib.xml5.serializer_v5.read_inventory(f)
544
554
def _write_inventory(self, inv):
545
555
"""Update the working inventory.
548
558
will be committed to the next revision.
550
560
from cStringIO import StringIO
552
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
554
# Transport handles atomicity
555
self.put_controlfile('inventory', sio)
564
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
566
# Transport handles atomicity
567
self.put_controlfile('inventory', sio)
557
571
mutter('wrote working inventory')
559
573
inventory = property(read_working_inventory, _write_inventory, None,
560
574
"""Inventory for the working copy.""")
563
576
def add(self, files, ids=None):
564
577
"""Make files versioned.
596
609
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)
613
inv = self.read_working_inventory()
614
for f,file_id in zip(files, ids):
615
if is_control_file(f):
616
raise BzrError("cannot add control file %s" % quotefn(f))
621
raise BzrError("cannot add top-level %r" % f)
623
fullpath = os.path.normpath(self.abspath(f))
626
kind = file_kind(fullpath)
628
# maybe something better?
629
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
631
if not InventoryEntry.versionable_kind(kind):
632
raise BzrError('cannot add: not a versionable file ('
633
'i.e. regular file, symlink or directory): %s' % quotefn(f))
636
file_id = gen_file_id(f)
637
inv.add_path(f, kind=kind, file_id=file_id)
639
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
641
self._write_inventory(inv)
629
646
def print_file(self, file, revno):
630
647
"""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)
650
tree = self.revision_tree(self.get_rev_id(revno))
651
# use inventory as it was in that revision
652
file_id = tree.inventory.path2id(file)
654
raise BzrError("%r is not present in revision %s" % (file, revno))
655
tree.print_file(file_id)
660
def remove(self, files, verbose=False):
661
"""Mark nominated files for removal from the inventory.
663
This does not remove their text. This does not run on
665
TODO: Refuse to remove modified files unless --force is given?
667
TODO: Do something useful with directories.
669
TODO: Should this remove the text or not? Tough call; not
670
removing may be useful and the user can just use use rm, and
671
is the opposite of add. Removing it is consistent with most
672
other tools. Maybe an option.
674
## TODO: Normalize names
675
## TODO: Remove nested loops; better scalability
676
if isinstance(files, basestring):
682
tree = self.working_tree()
685
# do this before any modifications
689
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
690
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
692
# having remove it, it must be either ignored or unknown
693
if tree.is_ignored(f):
697
show_status(new_status, inv[fid].kind, quotefn(f))
700
self._write_inventory(inv)
638
704
# FIXME: this doesn't need to be a branch method
639
705
def set_inventory(self, new_inventory_list):
660
726
These are files in the working directory that are not versioned or
661
727
control files or ignored.
663
>>> from bzrlib.workingtree import WorkingTree
664
729
>>> b = ScratchBranch(files=['foo', 'foo~'])
665
>>> map(str, b.unknowns())
730
>>> list(b.unknowns())
668
733
>>> list(b.unknowns())
670
>>> WorkingTree(b.base, b).remove('foo')
671
736
>>> list(b.unknowns())
674
739
return self.working_tree().unknowns()
677
742
def append_revision(self, *revision_ids):
678
743
for revision_id in revision_ids:
679
744
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))
747
rev_history = self.revision_history()
748
rev_history.extend(revision_ids)
749
self.put_controlfile('revision-history', '\n'.join(rev_history))
688
753
def has_revision(self, revision_id):
689
754
"""True if this branch has a copy of the revision.
691
756
This does not necessarily imply the revision is merge
692
757
or on the mainline."""
693
758
return (revision_id is None
694
or self.revision_store.has_id(revision_id))
759
or revision_id in self.revision_store)
697
761
def get_revision_xml_file(self, revision_id):
698
762
"""Return XML file object for revision object."""
699
763
if not revision_id or not isinstance(revision_id, basestring):
700
764
raise InvalidRevisionId(revision_id)
702
return self.revision_store.get(revision_id)
703
except (IndexError, KeyError):
704
raise bzrlib.errors.NoSuchRevision(self, revision_id)
769
return self.revision_store[revision_id]
770
except (IndexError, KeyError):
771
raise bzrlib.errors.NoSuchRevision(self, revision_id)
707
776
get_revision_xml = get_revision_xml_file
801
870
return self.get_inventory(revision_id)
804
872
def revision_history(self):
805
873
"""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)
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)
876
return [l.rstrip('\r\n') for l in
877
self.controlfile('revision-history', 'r').readlines()]
881
def common_ancestor(self, other, self_revno=None, other_revno=None):
883
>>> from bzrlib.commit import commit
884
>>> sb = ScratchBranch(files=['foo', 'foo~'])
885
>>> sb.common_ancestor(sb) == (None, None)
887
>>> commit(sb, "Committing first revision", verbose=False)
888
>>> sb.common_ancestor(sb)[0]
890
>>> clone = sb.clone()
891
>>> commit(sb, "Committing second revision", verbose=False)
892
>>> sb.common_ancestor(sb)[0]
894
>>> sb.common_ancestor(clone)[0]
896
>>> commit(clone, "Committing divergent second revision",
898
>>> sb.common_ancestor(clone)[0]
900
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
902
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
904
>>> clone2 = sb.clone()
905
>>> sb.common_ancestor(clone2)[0]
907
>>> sb.common_ancestor(clone2, self_revno=1)[0]
909
>>> sb.common_ancestor(clone2, other_revno=1)[0]
912
my_history = self.revision_history()
913
other_history = other.revision_history()
914
if self_revno is None:
915
self_revno = len(my_history)
916
if other_revno is None:
917
other_revno = len(other_history)
918
indices = range(min((self_revno, other_revno)))
921
if my_history[r] == other_history[r]:
922
return r+1, my_history[r]
820
927
"""Return current revision number for this branch.
881
993
def update_revisions(self, other, stop_revision=None):
882
994
"""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
995
from bzrlib.fetch import greedy_fetch
996
from bzrlib.revision import get_intervening_revisions
887
997
if stop_revision is None:
888
998
stop_revision = other.last_revision()
889
### Should this be checking is_ancestor instead of revision_history?
890
if (stop_revision is not None and
891
stop_revision in self.revision_history()):
893
999
greedy_fetch(to_branch=self, from_branch=other,
894
1000
revision=stop_revision)
895
pullable_revs = self.pullable_revisions(other, stop_revision)
896
if len(pullable_revs) > 0:
1001
pullable_revs = self.missing_revisions(
1002
other, other.revision_id_to_revno(stop_revision))
1004
greedy_fetch(to_branch=self,
1006
revision=pullable_revs[-1])
897
1007
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
1010
def commit(self, *args, **kw):
916
1011
from bzrlib.commit import Commit
917
1012
Commit().commit(self, *args, **kw)
949
1044
inv = self.get_revision_inventory(revision_id)
950
1045
return RevisionTree(self.weave_store, inv, revision_id)
952
1048
def working_tree(self):
953
1049
"""Return a `Tree` for the working copy."""
954
1050
from bzrlib.workingtree import WorkingTree
955
# TODO: In the future, perhaps WorkingTree should utilize Transport
1051
# TODO: In the future, WorkingTree should utilize Transport
956
1052
# RobertCollins 20051003 - I don't think it should - working trees are
957
1053
# much more complex to keep consistent than our careful .bzr subset.
958
1054
# instead, we should say that working trees are local only, and optimise
960
return WorkingTree(self.base, branch=self)
1056
return WorkingTree(self._transport.base, self.read_working_inventory())
963
1059
def basis_tree(self):
968
1064
return self.revision_tree(self.last_revision())
971
1067
def rename_one(self, from_rel, to_rel):
972
1068
"""Rename one file.
974
1070
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)
1074
tree = self.working_tree()
1075
inv = tree.inventory
1076
if not tree.has_filename(from_rel):
1077
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1078
if tree.has_filename(to_rel):
1079
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1081
file_id = inv.path2id(from_rel)
1083
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1085
if inv.path2id(to_rel):
1086
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1088
to_dir, to_tail = os.path.split(to_rel)
1089
to_dir_id = inv.path2id(to_dir)
1090
if to_dir_id == None and to_dir != '':
1091
raise BzrError("can't determine destination directory id for %r" % to_dir)
1093
mutter("rename_one:")
1094
mutter(" file_id {%s}" % file_id)
1095
mutter(" from_rel %r" % from_rel)
1096
mutter(" to_rel %r" % to_rel)
1097
mutter(" to_dir %r" % to_dir)
1098
mutter(" to_dir_id {%s}" % to_dir_id)
1100
inv.rename(file_id, to_dir_id, to_tail)
1102
from_abs = self.abspath(from_rel)
1103
to_abs = self.abspath(to_rel)
1105
rename(from_abs, to_abs)
1107
raise BzrError("failed to rename %r to %r: %s"
1108
% (from_abs, to_abs, e[1]),
1109
["rename rolled back"])
1111
self._write_inventory(inv)
1016
1116
def move(self, from_paths, to_name):
1017
1117
"""Rename files.
1028
1128
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)
1133
## TODO: Option to move IDs only
1134
assert not isinstance(from_paths, basestring)
1135
tree = self.working_tree()
1136
inv = tree.inventory
1137
to_abs = self.abspath(to_name)
1138
if not isdir(to_abs):
1139
raise BzrError("destination %r is not a directory" % to_abs)
1140
if not tree.has_filename(to_name):
1141
raise BzrError("destination %r not in working directory" % to_abs)
1142
to_dir_id = inv.path2id(to_name)
1143
if to_dir_id == None and to_name != '':
1144
raise BzrError("destination %r is not a versioned directory" % to_name)
1145
to_dir_ie = inv[to_dir_id]
1146
if to_dir_ie.kind not in ('directory', 'root_directory'):
1147
raise BzrError("destination %r is not a directory" % to_abs)
1149
to_idpath = inv.get_idpath(to_dir_id)
1151
for f in from_paths:
1152
if not tree.has_filename(f):
1153
raise BzrError("%r does not exist in working tree" % f)
1154
f_id = inv.path2id(f)
1156
raise BzrError("%r is not versioned" % f)
1157
name_tail = splitpath(f)[-1]
1158
dest_path = appendpath(to_name, name_tail)
1159
if tree.has_filename(dest_path):
1160
raise BzrError("destination %r already exists" % dest_path)
1161
if f_id in to_idpath:
1162
raise BzrError("can't move %r to a subdirectory of itself" % f)
1164
# OK, so there's a race here, it's possible that someone will
1165
# create a file in this interval and then the rename might be
1166
# left half-done. But we should have caught most problems.
1168
for f in from_paths:
1169
name_tail = splitpath(f)[-1]
1170
dest_path = appendpath(to_name, name_tail)
1171
result.append((f, dest_path))
1172
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1174
rename(self.abspath(f), self.abspath(dest_path))
1176
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1177
["rename rolled back"])
1179
self._write_inventory(inv)
1197
1311
if revno < 1 or revno > self.revno():
1198
1312
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
1318
class ScratchBranch(_Branch):
1214
1322
>>> isdir(b.base)
1216
1324
>>> bd = b.base
1217
>>> b._transport.__del__()
1222
def __init__(self, files=[], dirs=[], transport=None):
1329
def __init__(self, files=[], dirs=[], base=None):
1223
1330
"""Make a test branch.
1225
1332
This creates a temporary directory and runs init-tree in it.
1227
1334
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)
1336
from tempfile import mkdtemp
1341
if isinstance(base, basestring):
1342
base = get_transport(base)
1343
_Branch.__init__(self, base, init=init)
1236
1345
self._transport.mkdir(d)
1257
1366
base = mkdtemp()
1259
1368
copytree(self.base, base, symlinks=True)
1260
return ScratchBranch(
1261
transport=bzrlib.transport.local.ScratchTransport(base))
1369
return ScratchBranch(base=base)
1375
"""Destroy the test branch, removing the scratch directory."""
1376
from shutil import rmtree
1379
mutter("delete ScratchBranch %s" % self.base)
1382
# Work around for shutil.rmtree failing on Windows when
1383
# readonly files are encountered
1384
mutter("hit exception in destroying ScratchBranch: %s" % e)
1385
for root, dirs, files in os.walk(self.base, topdown=False):
1387
os.chmod(os.path.join(root, name), 0700)
1389
self._transport = None
1264
1393
######################################################################