27
from bzrlib.inventory import InventoryEntry
28
27
import bzrlib.inventory as inventory
29
28
from bzrlib.trace import mutter, note
30
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
31
rename, splitpath, sha_file, appendpath,
29
from bzrlib.osutils import (isdir, quotefn,
30
rename, splitpath, sha_file,
31
file_kind, abspath, normpath, pathjoin)
33
32
import bzrlib.errors as errors
34
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
35
34
NoSuchRevision, HistoryMissing, NotBranchError,
237
236
def set_root_id(self, file_id):
238
237
raise NotImplementedError('set_root_id is abstract')
240
def add(self, files, ids=None):
241
"""Make files versioned.
243
Note that the command line normally calls smart_add instead,
244
which can automatically recurse.
246
This puts the files in the Added state, so that they will be
247
recorded by the next commit.
250
List of paths to add, relative to the base of the tree.
253
If set, use these instead of automatically generated ids.
254
Must be the same length as the list of files, but may
255
contain None for ids that are to be autogenerated.
257
TODO: Perhaps have an option to add the ids even if the files do
260
TODO: Perhaps yield the ids and paths as they're added.
262
raise NotImplementedError('add is abstract')
264
def print_file(self, file, revno):
239
def print_file(self, file, revision_id):
265
240
"""Print `file` to stdout."""
266
241
raise NotImplementedError('print_file is abstract')
269
"""Return all unknown files.
271
These are files in the working directory that are not versioned or
272
control files or ignored.
274
>>> from bzrlib.workingtree import WorkingTree
275
>>> b = ScratchBranch(files=['foo', 'foo~'])
276
>>> map(str, b.unknowns())
279
>>> list(b.unknowns())
281
>>> WorkingTree(b.base, b).remove('foo')
282
>>> list(b.unknowns())
285
raise NotImplementedError('unknowns is abstract')
287
243
def append_revision(self, *revision_ids):
288
244
raise NotImplementedError('append_revision is abstract')
494
446
raise NotImplementedError('move is abstract')
496
def revert(self, filenames, old_tree=None, backups=True):
497
"""Restore selected files to the versions from a previous tree.
500
If true (default) backups are made of files before
503
raise NotImplementedError('revert is abstract')
505
def pending_merges(self):
506
"""Return a list of pending merges.
508
These are revisions that have been merged into the working
509
directory but not yet committed.
511
raise NotImplementedError('pending_merges is abstract')
513
def add_pending_merge(self, *revision_ids):
514
# TODO: Perhaps should check at this point that the
515
# history of the revision is actually present?
516
raise NotImplementedError('add_pending_merge is abstract')
518
def set_pending_merges(self, rev_list):
519
raise NotImplementedError('set_pending_merges is abstract')
521
448
def get_parent(self):
522
449
"""Return the parent location of the branch.
560
487
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
561
488
raise NotImplementedError('store_revision_signature is abstract')
490
def fileid_involved_between_revs(self, from_revid, to_revid):
491
""" This function returns the file_id(s) involved in the
492
changes between the from_revid revision and the to_revid
495
raise NotImplementedError('fileid_involved_between_revs is abstract')
497
def fileid_involved(self, last_revid=None):
498
""" This function returns the file_id(s) involved in the
499
changes up to the revision last_revid
500
If no parametr is passed, then all file_id[s] present in the
501
repository are returned
503
raise NotImplementedError('fileid_involved is abstract')
505
def fileid_involved_by_set(self, changes):
506
""" This function returns the file_id(s) involved in the
507
changes present in the set 'changes'
509
raise NotImplementedError('fileid_involved_by_set is abstract')
563
511
class BzrBranch(Branch):
564
512
"""A branch stored in the actual filesystem.
636
588
self._make_control()
637
589
self._check_format(relax_version_check)
639
592
def get_store(name, compressed=True, prefixed=False):
640
# FIXME: This approach of assuming stores are all entirely compressed
641
# or entirely uncompressed is tidy, but breaks upgrade from
642
# some existing branches where there's a mixture; we probably
643
# still want the option to look for both.
644
relpath = self._rel_controlfilename(name)
593
relpath = self._rel_controlfilename(unicode(name))
645
594
store = TextStore(self._transport.clone(relpath),
595
dir_mode=self._dir_mode,
596
file_mode=self._file_mode,
646
597
prefixed=prefixed,
647
598
compressed=compressed)
648
#if self._transport.should_cache():
649
# cache_path = os.path.join(self.cache_root, name)
650
# os.mkdir(cache_path)
651
# store = bzrlib.store.CachedStore(store, cache_path)
653
601
def get_weave(name, prefixed=False):
654
relpath = self._rel_controlfilename(name)
655
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
602
relpath = self._rel_controlfilename(unicode(name))
603
ws = WeaveStore(self._transport.clone(relpath),
605
dir_mode=self._dir_mode,
606
file_mode=self._file_mode)
656
607
if self._transport.should_cache():
657
608
ws.enable_cache = True
662
613
self.text_store = get_store('text-store')
663
614
self.revision_store = get_store('revision-store')
664
615
elif self._branch_format == 5:
665
self.control_weaves = get_weave('')
666
self.weave_store = get_weave('weaves')
667
self.revision_store = get_store('revision-store', compressed=False)
616
self.control_weaves = get_weave(u'')
617
self.weave_store = get_weave(u'weaves')
618
self.revision_store = get_store(u'revision-store', compressed=False)
668
619
elif self._branch_format == 6:
669
self.control_weaves = get_weave('')
670
self.weave_store = get_weave('weaves', prefixed=True)
671
self.revision_store = get_store('revision-store', compressed=False,
620
self.control_weaves = get_weave(u'')
621
self.weave_store = get_weave(u'weaves', prefixed=True)
622
self.revision_store = get_store(u'revision-store', compressed=False,
673
624
self.revision_store.register_suffix('sig')
674
625
self._transaction = None
825
776
f = codecs.getwriter('utf-8')(f, errors='replace')
826
777
path = self._rel_controlfilename(path)
827
778
ctrl_files.append((path, f))
828
self._transport.put_multi(ctrl_files)
779
self._transport.put_multi(ctrl_files, mode=self._file_mode)
781
def _find_modes(self, path=None):
782
"""Determine the appropriate modes for files and directories."""
785
path = self._rel_controlfilename('')
786
st = self._transport.stat(path)
787
except errors.TransportNotPossible:
788
self._dir_mode = 0755
789
self._file_mode = 0644
791
self._dir_mode = st.st_mode & 07777
792
# Remove the sticky and execute bits for files
793
self._file_mode = self._dir_mode & ~07111
794
if not self._set_dir_mode:
795
self._dir_mode = None
796
if not self._set_file_mode:
797
self._file_mode = None
830
799
def _make_control(self):
831
800
from bzrlib.inventory import Inventory
843
812
bzrlib.weavefile.write_weave_v5(Weave(), sio)
844
813
empty_weave = sio.getvalue()
846
dirs = [[], 'revision-store', 'weaves']
815
cfn = self._rel_controlfilename
816
# Since we don't have a .bzr directory, inherit the
817
# mode from the root directory
818
self._find_modes(u'.')
820
dirs = ['', 'revision-store', 'weaves']
847
821
files = [('README',
848
822
"This is a Bazaar-NG control directory.\n"
849
823
"Do not change any files in this directory.\n"),
890
863
'or remove the .bzr directory'
891
864
' and "bzr init" again'])
893
867
def get_root_id(self):
894
868
"""See Branch.get_root_id."""
895
869
inv = self.get_inventory(self.last_revision())
896
870
return inv.root.file_id
899
def set_root_id(self, file_id):
900
"""See Branch.set_root_id."""
901
inv = self.working_tree().read_working_inventory()
902
orig_root_id = inv.root.file_id
903
del inv._byid[inv.root.file_id]
904
inv.root.file_id = file_id
905
inv._byid[inv.root.file_id] = inv.root
908
if entry.parent_id in (None, orig_root_id):
909
entry.parent_id = inv.root.file_id
910
self._write_inventory(inv)
913
def add(self, files, ids=None):
914
"""See Branch.add."""
915
# TODO: Re-adding a file that is removed in the working copy
916
# should probably put it back with the previous ID.
917
if isinstance(files, basestring):
918
assert(ids is None or isinstance(ids, basestring))
924
ids = [None] * len(files)
926
assert(len(ids) == len(files))
928
inv = self.working_tree().read_working_inventory()
929
for f,file_id in zip(files, ids):
930
if is_control_file(f):
931
raise BzrError("cannot add control file %s" % quotefn(f))
936
raise BzrError("cannot add top-level %r" % f)
938
fullpath = os.path.normpath(self.abspath(f))
941
kind = file_kind(fullpath)
943
# maybe something better?
944
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
946
if not InventoryEntry.versionable_kind(kind):
947
raise BzrError('cannot add: not a versionable file ('
948
'i.e. regular file, symlink or directory): %s' % quotefn(f))
951
file_id = gen_file_id(f)
952
inv.add_path(f, kind=kind, file_id=file_id)
954
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
956
self.working_tree()._write_inventory(inv)
959
def print_file(self, file, revno):
873
def print_file(self, file, revision_id):
960
874
"""See Branch.print_file."""
961
tree = self.revision_tree(self.get_rev_id(revno))
875
tree = self.revision_tree(revision_id)
962
876
# use inventory as it was in that revision
963
877
file_id = tree.inventory.path2id(file)
965
raise BzrError("%r is not present in revision %s" % (file, revno))
880
revno = self.revision_id_to_revno(revision_id)
881
except errors.NoSuchRevision:
882
# TODO: This should not be BzrError,
883
# but NoSuchFile doesn't fit either
884
raise BzrError('%r is not present in revision %s'
885
% (file, revision_id))
887
raise BzrError('%r is not present in revision %s'
966
889
tree.print_file(file_id)
969
"""See Branch.unknowns."""
970
return self.working_tree().unknowns()
972
891
@needs_write_lock
973
892
def append_revision(self, *revision_ids):
974
893
"""See Branch.append_revision."""
998
922
except (IndexError, KeyError):
999
923
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1002
get_revision_xml = get_revision_xml_file
1004
925
def get_revision_xml(self, revision_id):
1005
926
"""See Branch.get_revision_xml."""
1006
return self.get_revision_xml_file(revision_id).read()
927
return self._get_revision_xml_file(revision_id).read()
1009
929
def get_revision(self, revision_id):
1010
930
"""See Branch.get_revision."""
1011
xml_file = self.get_revision_xml_file(revision_id)
931
xml_file = self._get_revision_xml_file(revision_id)
1014
934
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1126
def revision_id_to_revno(self, revision_id):
1127
"""Given a revision id, return its revno"""
1128
if revision_id is None:
1130
history = self.revision_history()
1132
return history.index(revision_id) + 1
1134
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1136
def get_rev_id(self, revno, history=None):
1137
"""Find the revision id of the specified revno."""
1141
history = self.revision_history()
1142
elif revno <= 0 or revno > len(history):
1143
raise bzrlib.errors.NoSuchRevision(self, revno)
1144
return history[revno - 1]
1146
1046
def revision_tree(self, revision_id):
1147
1047
"""See Branch.revision_tree."""
1148
1048
# TODO: refactor this to use an existing revision object
1151
1051
return EmptyTree()
1153
1053
inv = self.get_revision_inventory(revision_id)
1154
return RevisionTree(self.weave_store, inv, revision_id)
1054
return RevisionTree(self, inv, revision_id)
1056
def basis_tree(self):
1057
"""See Branch.basis_tree."""
1059
revision_id = self.revision_history()[-1]
1060
xml = self.working_tree().read_basis_inventory(revision_id)
1061
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1062
return RevisionTree(self, inv, revision_id)
1063
except (IndexError, NoSuchFile, NoWorkingTree), e:
1064
return self.revision_tree(self.last_revision())
1156
1066
def working_tree(self):
1157
1067
"""See Branch.working_tree."""
1158
1068
from bzrlib.workingtree import WorkingTree
1159
# TODO: In the future, perhaps WorkingTree should utilize Transport
1160
# RobertCollins 20051003 - I don't think it should - working trees are
1161
# much more complex to keep consistent than our careful .bzr subset.
1162
# instead, we should say that working trees are local only, and optimise
1164
1069
if self._transport.base.find('://') != -1:
1165
1070
raise NoWorkingTree(self.base)
1166
1071
return WorkingTree(self.base, branch=self)
1170
1075
"""See Branch.pull."""
1171
1076
source.lock_read()
1078
old_count = len(self.revision_history())
1174
1080
self.update_revisions(source)
1175
1081
except DivergedBranches:
1176
1082
if not overwrite:
1178
1085
self.set_revision_history(source.revision_history())
1086
new_count = len(self.revision_history())
1087
return new_count - old_count
1180
1089
source.unlock()
1183
def rename_one(self, from_rel, to_rel):
1184
"""See Branch.rename_one."""
1185
tree = self.working_tree()
1186
inv = tree.inventory
1187
if not tree.has_filename(from_rel):
1188
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1189
if tree.has_filename(to_rel):
1190
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1192
file_id = inv.path2id(from_rel)
1194
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1196
if inv.path2id(to_rel):
1197
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1199
to_dir, to_tail = os.path.split(to_rel)
1200
to_dir_id = inv.path2id(to_dir)
1201
if to_dir_id == None and to_dir != '':
1202
raise BzrError("can't determine destination directory id for %r" % to_dir)
1204
mutter("rename_one:")
1205
mutter(" file_id {%s}" % file_id)
1206
mutter(" from_rel %r" % from_rel)
1207
mutter(" to_rel %r" % to_rel)
1208
mutter(" to_dir %r" % to_dir)
1209
mutter(" to_dir_id {%s}" % to_dir_id)
1211
inv.rename(file_id, to_dir_id, to_tail)
1213
from_abs = self.abspath(from_rel)
1214
to_abs = self.abspath(to_rel)
1216
rename(from_abs, to_abs)
1218
raise BzrError("failed to rename %r to %r: %s"
1219
% (from_abs, to_abs, e[1]),
1220
["rename rolled back"])
1222
self.working_tree()._write_inventory(inv)
1225
def move(self, from_paths, to_name):
1226
"""See Branch.move."""
1228
## TODO: Option to move IDs only
1229
assert not isinstance(from_paths, basestring)
1230
tree = self.working_tree()
1231
inv = tree.inventory
1232
to_abs = self.abspath(to_name)
1233
if not isdir(to_abs):
1234
raise BzrError("destination %r is not a directory" % to_abs)
1235
if not tree.has_filename(to_name):
1236
raise BzrError("destination %r not in working directory" % to_abs)
1237
to_dir_id = inv.path2id(to_name)
1238
if to_dir_id == None and to_name != '':
1239
raise BzrError("destination %r is not a versioned directory" % to_name)
1240
to_dir_ie = inv[to_dir_id]
1241
if to_dir_ie.kind not in ('directory', 'root_directory'):
1242
raise BzrError("destination %r is not a directory" % to_abs)
1244
to_idpath = inv.get_idpath(to_dir_id)
1246
for f in from_paths:
1247
if not tree.has_filename(f):
1248
raise BzrError("%r does not exist in working tree" % f)
1249
f_id = inv.path2id(f)
1251
raise BzrError("%r is not versioned" % f)
1252
name_tail = splitpath(f)[-1]
1253
dest_path = appendpath(to_name, name_tail)
1254
if tree.has_filename(dest_path):
1255
raise BzrError("destination %r already exists" % dest_path)
1256
if f_id in to_idpath:
1257
raise BzrError("can't move %r to a subdirectory of itself" % f)
1259
# OK, so there's a race here, it's possible that someone will
1260
# create a file in this interval and then the rename might be
1261
# left half-done. But we should have caught most problems.
1263
for f in from_paths:
1264
name_tail = splitpath(f)[-1]
1265
dest_path = appendpath(to_name, name_tail)
1266
result.append((f, dest_path))
1267
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1269
rename(self.abspath(f), self.abspath(dest_path))
1271
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1272
["rename rolled back"])
1274
self.working_tree()._write_inventory(inv)
1277
1091
def get_parent(self):
1278
1092
"""See Branch.get_parent."""
1312
1125
def tree_config(self):
1313
1126
return TreeConfig(self)
1315
def check_revno(self, revno):
1317
Check whether a revno corresponds to any revision.
1318
Zero (the NULL revision) is considered valid.
1321
self.check_real_revno(revno)
1323
def check_real_revno(self, revno):
1325
Check whether a revno corresponds to a real revision.
1326
Zero (the NULL revision) is considered invalid
1328
if revno < 1 or revno > self.revno():
1329
raise InvalidRevisionNumber(revno)
1331
1128
def sign_revision(self, revision_id, gpg_strategy):
1332
1129
"""See Branch.sign_revision."""
1333
1130
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1339
1136
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1340
1137
revision_id, "sig")
1139
def fileid_involved_between_revs(self, from_revid, to_revid):
1140
""" This function returns the file_id(s) involved in the
1141
changes between the from_revid revision and the to_revid
1144
w = self._get_inventory_weave( )
1145
from_set = set(w.inclusions([w.lookup(from_revid)]))
1146
to_set = set(w.inclusions([w.lookup(to_revid)]))
1147
included = to_set.difference(from_set)
1148
changed = map(w.idx_to_name,included)
1149
return self._fileid_involved_by_set(changed)
1151
def fileid_involved(self, last_revid=None):
1152
""" This function returns the file_id(s) involved in the
1153
changes up to the revision last_revid
1154
If no parametr is passed, then all file_id[s] present in the
1155
repository are returned
1157
w = self._get_inventory_weave( )
1159
changed = set(w._names)
1161
included = w.inclusions([w.lookup(last_revid)])
1162
changed = map(w.idx_to_name, included)
1163
return self._fileid_involved_by_set(changed)
1165
def fileid_involved_by_set(self, changes):
1166
""" This function returns the file_id(s) involved in the
1167
changese present in the set changes
1169
w = self._get_inventory_weave( )
1170
return self._fileid_involved_by_set(changes)
1172
def _fileid_involved_by_set(self, changes):
1173
w = self._get_inventory_weave( )
1175
for line in w._weave:
1177
# it is ugly, but it is due to the weave structure
1178
if not isinstance(line,basestring): continue
1180
start = line.find('file_id="')+9
1181
if start < 9: continue
1182
end = line.find('"',start)
1184
file_id = line[start:end]
1186
# check if file_id is already present
1187
if file_id in file_ids: continue
1189
start = line.find('revision="')+10
1190
if start < 10: continue
1191
end = line.find('"',start)
1193
revision_id = line[start:end]
1195
if revision_id in changes:
1196
file_ids.add(file_id)
1343
1201
class ScratchBranch(BzrBranch):
1344
1202
"""Special test class: a branch that cleans up after itself.
1411
1269
filename = head
1416
def gen_file_id(name):
1417
"""Return new file id.
1419
This should probably generate proper UUIDs, but for the moment we
1420
cope with just randomness because running uuidgen every time is
1423
from binascii import hexlify
1424
from time import time
1426
# get last component
1427
idx = name.rfind('/')
1429
name = name[idx+1 : ]
1430
idx = name.rfind('\\')
1432
name = name[idx+1 : ]
1434
# make it not a hidden file
1435
name = name.lstrip('.')
1437
# remove any wierd characters; we don't escape them but rather
1438
# just pull them out
1439
name = re.sub(r'[^\w.]', '', name)
1441
s = hexlify(rand_bytes(8))
1442
return '-'.join((name, compact_date(time()), s))
1446
"""Return a new tree-root file id."""
1447
return gen_file_id('TREE_ROOT')