15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
21
import traceback, socket, fnmatch, difflib, time
22
from binascii import hexlify
21
from bzrlib.trace import mutter, note
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
23
sha_file, appendpath, file_kind
24
from bzrlib.errors import BzrError
25
from inventory import Inventory
26
from trace import mutter, note
27
from tree import Tree, EmptyTree, RevisionTree
28
from inventory import InventoryEntry, Inventory
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
30
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
31
joinpath, sha_string, file_kind, local_time_offset, appendpath
32
from store import ImmutableStore
33
from revision import Revision
34
from errors import bailout, BzrError
35
from textui import show_status
26
37
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
27
38
## TODO: Maybe include checks for common corruption of newlines, etc?
329
262
fmt = self.controlfile('branch-format', 'r').read()
330
263
fmt.replace('\r\n', '')
331
264
if fmt != BZR_BRANCH_FORMAT:
332
raise BzrError('sorry, branch format %r not supported' % fmt,
333
['use a different bzr version',
334
'or remove the .bzr directory and "bzr init" again'])
336
def get_root_id(self):
337
"""Return the id of this branches root"""
338
inv = self.read_working_inventory()
339
return inv.root.file_id
341
def set_root_id(self, file_id):
342
inv = self.read_working_inventory()
343
orig_root_id = inv.root.file_id
344
del inv._byid[inv.root.file_id]
345
inv.root.file_id = file_id
346
inv._byid[inv.root.file_id] = inv.root
349
if entry.parent_id in (None, orig_root_id):
350
entry.parent_id = inv.root.file_id
351
self._write_inventory(inv)
265
bailout('sorry, branch format %r not supported' % fmt,
266
['use a different bzr version',
267
'or remove the .bzr directory and "bzr init" again'])
353
270
def read_working_inventory(self):
354
271
"""Read the working inventory."""
355
from bzrlib.inventory import Inventory
356
from bzrlib.xml import unpack_xml
357
from time import time
361
# ElementTree does its own conversion from UTF-8, so open in
363
inv = unpack_xml(Inventory,
364
self.controlfile('inventory', 'rb'))
365
mutter("loaded inventory of %d items in %f"
366
% (len(inv), time() - before))
272
self._need_readlock()
274
# ElementTree does its own conversion from UTF-8, so open in
276
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
277
mutter("loaded inventory of %d items in %f"
278
% (len(inv), time.time() - before))
372
282
def _write_inventory(self, inv):
373
283
"""Update the working inventory.
435
337
ids = [None] * len(files)
437
339
assert(len(ids) == len(files))
441
inv = self.read_working_inventory()
442
for f,file_id in zip(files, ids):
443
if is_control_file(f):
444
raise BzrError("cannot add control file %s" % quotefn(f))
449
raise BzrError("cannot add top-level %r" % f)
451
fullpath = os.path.normpath(self.abspath(f))
454
kind = file_kind(fullpath)
456
# maybe something better?
457
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
459
if kind != 'file' and kind != 'directory':
460
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
463
file_id = gen_file_id(f)
464
inv.add_path(f, kind=kind, file_id=file_id)
467
print 'added', quotefn(f)
469
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
471
self._write_inventory(inv)
341
inv = self.read_working_inventory()
342
for f,file_id in zip(files, ids):
343
if is_control_file(f):
344
bailout("cannot add control file %s" % quotefn(f))
349
bailout("cannot add top-level %r" % f)
351
fullpath = os.path.normpath(self.abspath(f))
354
kind = file_kind(fullpath)
356
# maybe something better?
357
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
359
if kind != 'file' and kind != 'directory':
360
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
363
file_id = gen_file_id(f)
364
inv.add_path(f, kind=kind, file_id=file_id)
367
show_status('A', kind, quotefn(f))
369
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
371
self._write_inventory(inv)
476
374
def print_file(self, file, revno):
477
375
"""Print `file` to stdout."""
480
tree = self.revision_tree(self.lookup_revision(revno))
481
# use inventory as it was in that revision
482
file_id = tree.inventory.path2id(file)
484
raise BzrError("%r is not present in revision %s" % (file, revno))
485
tree.print_file(file_id)
376
self._need_readlock()
377
tree = self.revision_tree(self.lookup_revision(revno))
378
# use inventory as it was in that revision
379
file_id = tree.inventory.path2id(file)
381
bailout("%r is not present in revision %d" % (file, revno))
382
tree.print_file(file_id)
490
385
def remove(self, files, verbose=False):
491
386
"""Mark nominated files for removal from the inventory.
564
452
return self.working_tree().unknowns()
567
def append_revision(self, *revision_ids):
568
from bzrlib.atomicfile import AtomicFile
570
for revision_id in revision_ids:
571
mutter("add {%s} to revision-history" % revision_id)
455
def append_revision(self, revision_id):
456
mutter("add {%s} to revision-history" % revision_id)
573
457
rev_history = self.revision_history()
574
rev_history.extend(revision_ids)
576
f = AtomicFile(self.controlfilename('revision-history'))
578
for rev_id in rev_history:
459
tmprhname = self.controlfilename('revision-history.tmp')
460
rhname = self.controlfilename('revision-history')
462
f = file(tmprhname, 'wt')
463
rev_history.append(revision_id)
464
f.write('\n'.join(rev_history))
468
if sys.platform == 'win32':
470
os.rename(tmprhname, rhname)
585
474
def get_revision(self, revision_id):
586
475
"""Return the Revision object for a named revision"""
587
from bzrlib.revision import Revision
588
from bzrlib.xml import unpack_xml
592
if not revision_id or not isinstance(revision_id, basestring):
593
raise ValueError('invalid revision-id: %r' % revision_id)
594
r = unpack_xml(Revision, self.revision_store[revision_id])
476
self._need_readlock()
477
r = Revision.read_xml(self.revision_store[revision_id])
598
478
assert r.revision_id == revision_id
602
def get_revision_sha1(self, revision_id):
603
"""Hash the stored value of a revision, and return it."""
604
# In the future, revision entries will be signed. At that
605
# point, it is probably best *not* to include the signature
606
# in the revision hash. Because that lets you re-sign
607
# the revision, (add signatures/remove signatures) and still
608
# have all hash pointers stay consistent.
609
# But for now, just hash the contents.
610
return sha_file(self.revision_store[revision_id])
613
482
def get_inventory(self, inventory_id):
741
def missing_revisions(self, other, stop_revision=None):
743
If self and other have not diverged, return a list of the revisions
744
present in other, but missing from self.
746
>>> from bzrlib.commit import commit
747
>>> bzrlib.trace.silent = True
748
>>> br1 = ScratchBranch()
749
>>> br2 = ScratchBranch()
750
>>> br1.missing_revisions(br2)
752
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
753
>>> br1.missing_revisions(br2)
755
>>> br2.missing_revisions(br1)
757
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
758
>>> br1.missing_revisions(br2)
760
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
761
>>> br1.missing_revisions(br2)
763
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
764
>>> br1.missing_revisions(br2)
765
Traceback (most recent call last):
766
DivergedBranches: These branches have diverged.
768
self_history = self.revision_history()
769
self_len = len(self_history)
770
other_history = other.revision_history()
771
other_len = len(other_history)
772
common_index = min(self_len, other_len) -1
773
if common_index >= 0 and \
774
self_history[common_index] != other_history[common_index]:
775
raise DivergedBranches(self, other)
777
if stop_revision is None:
778
stop_revision = other_len
779
elif stop_revision > other_len:
780
raise NoSuchRevision(self, stop_revision)
782
return other_history[self_len:stop_revision]
785
def update_revisions(self, other, stop_revision=None):
786
"""Pull in all new revisions from other branch.
788
>>> from bzrlib.commit import commit
789
>>> bzrlib.trace.silent = True
790
>>> br1 = ScratchBranch(files=['foo', 'bar'])
793
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
794
>>> br2 = ScratchBranch()
795
>>> br2.update_revisions(br1)
799
>>> br2.revision_history()
801
>>> br2.update_revisions(br1)
805
>>> br1.text_store.total_size() == br2.text_store.total_size()
808
from bzrlib.progress import ProgressBar
812
from sets import Set as set
816
pb.update('comparing histories')
817
revision_ids = self.missing_revisions(other, stop_revision)
819
if hasattr(other.revision_store, "prefetch"):
820
other.revision_store.prefetch(revision_ids)
821
if hasattr(other.inventory_store, "prefetch"):
822
inventory_ids = [other.get_revision(r).inventory_id
823
for r in revision_ids]
824
other.inventory_store.prefetch(inventory_ids)
829
for rev_id in revision_ids:
831
pb.update('fetching revision', i, len(revision_ids))
832
rev = other.get_revision(rev_id)
833
revisions.append(rev)
834
inv = other.get_inventory(str(rev.inventory_id))
835
for key, entry in inv.iter_entries():
836
if entry.text_id is None:
838
if entry.text_id not in self.text_store:
839
needed_texts.add(entry.text_id)
843
count = self.text_store.copy_multi(other.text_store, needed_texts)
844
print "Added %d texts." % count
845
inventory_ids = [ f.inventory_id for f in revisions ]
846
count = self.inventory_store.copy_multi(other.inventory_store,
848
print "Added %d inventories." % count
849
revision_ids = [ f.revision_id for f in revisions]
850
count = self.revision_store.copy_multi(other.revision_store,
852
for revision_id in revision_ids:
853
self.append_revision(revision_id)
854
print "Added %d revisions." % count
857
553
def commit(self, *args, **kw):
858
555
from bzrlib.commit import commit
859
556
commit(self, *args, **kw)
862
def lookup_revision(self, revision):
863
"""Return the revision identifier for a given revision information."""
864
revno, info = self.get_revision_info(revision)
867
def get_revision_info(self, revision):
868
"""Return (revno, revision id) for revision identifier.
870
revision can be an integer, in which case it is assumed to be revno (though
871
this will translate negative values into positive ones)
872
revision can also be a string, in which case it is parsed for something like
873
'date:' or 'revid:' etc.
878
try:# Convert to int if possible
879
revision = int(revision)
882
revs = self.revision_history()
883
if isinstance(revision, int):
886
# Mabye we should do this first, but we don't need it if revision == 0
888
revno = len(revs) + revision + 1
891
elif isinstance(revision, basestring):
892
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
893
if revision.startswith(prefix):
894
revno = func(self, revs, revision)
897
raise BzrError('No namespace registered for string: %r' % revision)
899
if revno is None or revno <= 0 or revno > len(revs):
900
raise BzrError("no such revision %s" % revision)
901
return revno, revs[revno-1]
903
def _namespace_revno(self, revs, revision):
904
"""Lookup a revision by revision number"""
905
assert revision.startswith('revno:')
907
return int(revision[6:])
910
REVISION_NAMESPACES['revno:'] = _namespace_revno
912
def _namespace_revid(self, revs, revision):
913
assert revision.startswith('revid:')
915
return revs.index(revision[6:]) + 1
918
REVISION_NAMESPACES['revid:'] = _namespace_revid
920
def _namespace_last(self, revs, revision):
921
assert revision.startswith('last:')
923
offset = int(revision[5:])
928
raise BzrError('You must supply a positive value for --revision last:XXX')
929
return len(revs) - offset + 1
930
REVISION_NAMESPACES['last:'] = _namespace_last
932
def _namespace_tag(self, revs, revision):
933
assert revision.startswith('tag:')
934
raise BzrError('tag: namespace registered, but not implemented.')
935
REVISION_NAMESPACES['tag:'] = _namespace_tag
937
def _namespace_date(self, revs, revision):
938
assert revision.startswith('date:')
940
# Spec for date revisions:
942
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
943
# it can also start with a '+/-/='. '+' says match the first
944
# entry after the given date. '-' is match the first entry before the date
945
# '=' is match the first entry after, but still on the given date.
947
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
948
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
949
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
950
# May 13th, 2005 at 0:00
952
# So the proper way of saying 'give me all entries for today' is:
953
# -r {date:+today}:{date:-tomorrow}
954
# The default is '=' when not supplied
957
if val[:1] in ('+', '-', '='):
958
match_style = val[:1]
961
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
962
if val.lower() == 'yesterday':
963
dt = today - datetime.timedelta(days=1)
964
elif val.lower() == 'today':
966
elif val.lower() == 'tomorrow':
967
dt = today + datetime.timedelta(days=1)
970
# This should be done outside the function to avoid recompiling it.
971
_date_re = re.compile(
972
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
974
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
976
m = _date_re.match(val)
977
if not m or (not m.group('date') and not m.group('time')):
978
raise BzrError('Invalid revision date %r' % revision)
981
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
983
year, month, day = today.year, today.month, today.day
985
hour = int(m.group('hour'))
986
minute = int(m.group('minute'))
987
if m.group('second'):
988
second = int(m.group('second'))
992
hour, minute, second = 0,0,0
994
dt = datetime.datetime(year=year, month=month, day=day,
995
hour=hour, minute=minute, second=second)
999
if match_style == '-':
1001
elif match_style == '=':
1002
last = dt + datetime.timedelta(days=1)
1005
for i in range(len(revs)-1, -1, -1):
1006
r = self.get_revision(revs[i])
1007
# TODO: Handle timezone.
1008
dt = datetime.datetime.fromtimestamp(r.timestamp)
1009
if first >= dt and (last is None or dt >= last):
1012
for i in range(len(revs)):
1013
r = self.get_revision(revs[i])
1014
# TODO: Handle timezone.
1015
dt = datetime.datetime.fromtimestamp(r.timestamp)
1016
if first <= dt and (last is None or dt <= last):
1018
REVISION_NAMESPACES['date:'] = _namespace_date
559
def lookup_revision(self, revno):
560
"""Return revision hash for revision number."""
565
# list is 0-based; revisions are 1-based
566
return self.revision_history()[revno-1]
568
raise BzrError("no such revision %s" % revno)
1020
571
def revision_tree(self, revision_id):
1021
572
"""Return Tree for a revision on this branch.
1023
574
`revision_id` may be None for the null revision, in which case
1024
575
an `EmptyTree` is returned."""
1025
from bzrlib.tree import EmptyTree, RevisionTree
1026
# TODO: refactor this to use an existing revision object
1027
# so we don't need to read it in twice.
576
self._need_readlock()
1028
577
if revision_id == None:
1029
return EmptyTree(self.get_root_id())
1031
580
inv = self.get_revision_inventory(revision_id)
1032
581
return RevisionTree(self.text_store, inv)
1058
606
This can change the directory or the filename or both.
608
self._need_writelock()
609
tree = self.working_tree()
611
if not tree.has_filename(from_rel):
612
bailout("can't rename: old working file %r does not exist" % from_rel)
613
if tree.has_filename(to_rel):
614
bailout("can't rename: new working file %r already exists" % to_rel)
616
file_id = inv.path2id(from_rel)
618
bailout("can't rename: old name %r is not versioned" % from_rel)
620
if inv.path2id(to_rel):
621
bailout("can't rename: new name %r is already versioned" % to_rel)
623
to_dir, to_tail = os.path.split(to_rel)
624
to_dir_id = inv.path2id(to_dir)
625
if to_dir_id == None and to_dir != '':
626
bailout("can't determine destination directory id for %r" % to_dir)
628
mutter("rename_one:")
629
mutter(" file_id {%s}" % file_id)
630
mutter(" from_rel %r" % from_rel)
631
mutter(" to_rel %r" % to_rel)
632
mutter(" to_dir %r" % to_dir)
633
mutter(" to_dir_id {%s}" % to_dir_id)
635
inv.rename(file_id, to_dir_id, to_tail)
637
print "%s => %s" % (from_rel, to_rel)
639
from_abs = self.abspath(from_rel)
640
to_abs = self.abspath(to_rel)
1062
tree = self.working_tree()
1063
inv = tree.inventory
1064
if not tree.has_filename(from_rel):
1065
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1066
if tree.has_filename(to_rel):
1067
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1069
file_id = inv.path2id(from_rel)
1071
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1073
if inv.path2id(to_rel):
1074
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1076
to_dir, to_tail = os.path.split(to_rel)
1077
to_dir_id = inv.path2id(to_dir)
1078
if to_dir_id == None and to_dir != '':
1079
raise BzrError("can't determine destination directory id for %r" % to_dir)
1081
mutter("rename_one:")
1082
mutter(" file_id {%s}" % file_id)
1083
mutter(" from_rel %r" % from_rel)
1084
mutter(" to_rel %r" % to_rel)
1085
mutter(" to_dir %r" % to_dir)
1086
mutter(" to_dir_id {%s}" % to_dir_id)
1088
inv.rename(file_id, to_dir_id, to_tail)
1090
print "%s => %s" % (from_rel, to_rel)
1092
from_abs = self.abspath(from_rel)
1093
to_abs = self.abspath(to_rel)
1095
os.rename(from_abs, to_abs)
1097
raise BzrError("failed to rename %r to %r: %s"
1098
% (from_abs, to_abs, e[1]),
1099
["rename rolled back"])
1101
self._write_inventory(inv)
642
os.rename(from_abs, to_abs)
644
bailout("failed to rename %r to %r: %s"
645
% (from_abs, to_abs, e[1]),
646
["rename rolled back"])
648
self._write_inventory(inv)
1106
652
def move(self, from_paths, to_name):
1114
660
Note that to_name is only the last component of the new name;
1115
661
this doesn't change the directory.
1119
## TODO: Option to move IDs only
1120
assert not isinstance(from_paths, basestring)
1121
tree = self.working_tree()
1122
inv = tree.inventory
1123
to_abs = self.abspath(to_name)
1124
if not isdir(to_abs):
1125
raise BzrError("destination %r is not a directory" % to_abs)
1126
if not tree.has_filename(to_name):
1127
raise BzrError("destination %r not in working directory" % to_abs)
1128
to_dir_id = inv.path2id(to_name)
1129
if to_dir_id == None and to_name != '':
1130
raise BzrError("destination %r is not a versioned directory" % to_name)
1131
to_dir_ie = inv[to_dir_id]
1132
if to_dir_ie.kind not in ('directory', 'root_directory'):
1133
raise BzrError("destination %r is not a directory" % to_abs)
1135
to_idpath = inv.get_idpath(to_dir_id)
1137
for f in from_paths:
1138
if not tree.has_filename(f):
1139
raise BzrError("%r does not exist in working tree" % f)
1140
f_id = inv.path2id(f)
1142
raise BzrError("%r is not versioned" % f)
1143
name_tail = splitpath(f)[-1]
1144
dest_path = appendpath(to_name, name_tail)
1145
if tree.has_filename(dest_path):
1146
raise BzrError("destination %r already exists" % dest_path)
1147
if f_id in to_idpath:
1148
raise BzrError("can't move %r to a subdirectory of itself" % f)
1150
# OK, so there's a race here, it's possible that someone will
1151
# create a file in this interval and then the rename might be
1152
# left half-done. But we should have caught most problems.
1154
for f in from_paths:
1155
name_tail = splitpath(f)[-1]
1156
dest_path = appendpath(to_name, name_tail)
1157
print "%s => %s" % (f, dest_path)
1158
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1160
os.rename(self.abspath(f), self.abspath(dest_path))
1162
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1163
["rename rolled back"])
1165
self._write_inventory(inv)
1170
def revert(self, filenames, old_tree=None, backups=True):
1171
"""Restore selected files to the versions from a previous tree.
1174
If true (default) backups are made of files before
1177
from bzrlib.errors import NotVersionedError, BzrError
1178
from bzrlib.atomicfile import AtomicFile
1179
from bzrlib.osutils import backup_file
1181
inv = self.read_working_inventory()
1182
if old_tree is None:
1183
old_tree = self.basis_tree()
1184
old_inv = old_tree.inventory
1187
for fn in filenames:
1188
file_id = inv.path2id(fn)
1190
raise NotVersionedError("not a versioned file", fn)
1191
if not old_inv.has_id(file_id):
1192
raise BzrError("file not present in old tree", fn, file_id)
1193
nids.append((fn, file_id))
1195
# TODO: Rename back if it was previously at a different location
1197
# TODO: If given a directory, restore the entire contents from
1198
# the previous version.
1200
# TODO: Make a backup to a temporary file.
1202
# TODO: If the file previously didn't exist, delete it?
1203
for fn, file_id in nids:
1206
f = AtomicFile(fn, 'wb')
1208
f.write(old_tree.get_file(file_id).read())
1214
def pending_merges(self):
1215
"""Return a list of pending merges.
1217
These are revisions that have been merged into the working
1218
directory but not yet committed.
1220
cfn = self.controlfilename('pending-merges')
1221
if not os.path.exists(cfn):
1224
for l in self.controlfile('pending-merges', 'r').readlines():
1225
p.append(l.rstrip('\n'))
1229
def add_pending_merge(self, revision_id):
1230
from bzrlib.revision import validate_revision_id
1232
validate_revision_id(revision_id)
1234
p = self.pending_merges()
1235
if revision_id in p:
1237
p.append(revision_id)
1238
self.set_pending_merges(p)
1241
def set_pending_merges(self, rev_list):
1242
from bzrlib.atomicfile import AtomicFile
1245
f = AtomicFile(self.controlfilename('pending-merges'))
663
self._need_writelock()
664
## TODO: Option to move IDs only
665
assert not isinstance(from_paths, basestring)
666
tree = self.working_tree()
668
to_abs = self.abspath(to_name)
669
if not isdir(to_abs):
670
bailout("destination %r is not a directory" % to_abs)
671
if not tree.has_filename(to_name):
672
bailout("destination %r not in working directory" % to_abs)
673
to_dir_id = inv.path2id(to_name)
674
if to_dir_id == None and to_name != '':
675
bailout("destination %r is not a versioned directory" % to_name)
676
to_dir_ie = inv[to_dir_id]
677
if to_dir_ie.kind not in ('directory', 'root_directory'):
678
bailout("destination %r is not a directory" % to_abs)
680
to_idpath = Set(inv.get_idpath(to_dir_id))
683
if not tree.has_filename(f):
684
bailout("%r does not exist in working tree" % f)
685
f_id = inv.path2id(f)
687
bailout("%r is not versioned" % f)
688
name_tail = splitpath(f)[-1]
689
dest_path = appendpath(to_name, name_tail)
690
if tree.has_filename(dest_path):
691
bailout("destination %r already exists" % dest_path)
692
if f_id in to_idpath:
693
bailout("can't move %r to a subdirectory of itself" % f)
695
# OK, so there's a race here, it's possible that someone will
696
# create a file in this interval and then the rename might be
697
# left half-done. But we should have caught most problems.
700
name_tail = splitpath(f)[-1]
701
dest_path = appendpath(to_name, name_tail)
702
print "%s => %s" % (f, dest_path)
703
inv.rename(inv.path2id(f), to_dir_id, name_tail)
705
os.rename(self.abspath(f), self.abspath(dest_path))
707
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
708
["rename rolled back"])
710
self._write_inventory(inv)