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
22
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
sha_file, appendpath, file_kind
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
from bzrlib.textui import show_status
29
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
31
from bzrlib.delta import compare_trees
32
from bzrlib.tree import EmptyTree, RevisionTree
33
from bzrlib.progress import ProgressBar
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
36
37
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
37
38
## TODO: Maybe include checks for common corruption of newlines, etc?
40
# TODO: Some operations like log might retrieve the same revisions
41
# repeatedly to calculate deltas. We could perhaps have a weakref
42
# cache in memory to make this faster.
44
# TODO: please move the revision-string syntax stuff out of the branch
45
# object; it's clutter
48
42
def find_branch(f, **args):
49
43
if f and (f.startswith('http://') or f.startswith('https://')):
341
262
fmt = self.controlfile('branch-format', 'r').read()
342
263
fmt.replace('\r\n', '')
343
264
if fmt != BZR_BRANCH_FORMAT:
344
raise BzrError('sorry, branch format %r not supported' % fmt,
345
['use a different bzr version',
346
'or remove the .bzr directory and "bzr init" again'])
348
def get_root_id(self):
349
"""Return the id of this branches root"""
350
inv = self.read_working_inventory()
351
return inv.root.file_id
353
def set_root_id(self, file_id):
354
inv = self.read_working_inventory()
355
orig_root_id = inv.root.file_id
356
del inv._byid[inv.root.file_id]
357
inv.root.file_id = file_id
358
inv._byid[inv.root.file_id] = inv.root
361
if entry.parent_id in (None, orig_root_id):
362
entry.parent_id = inv.root.file_id
363
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'])
365
270
def read_working_inventory(self):
366
271
"""Read the working inventory."""
367
from bzrlib.inventory import Inventory
368
from bzrlib.xml import unpack_xml
369
from time import time
373
# ElementTree does its own conversion from UTF-8, so open in
375
inv = unpack_xml(Inventory,
376
self.controlfile('inventory', 'rb'))
377
mutter("loaded inventory of %d items in %f"
378
% (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))
384
282
def _write_inventory(self, inv):
385
283
"""Update the working inventory.
446
337
ids = [None] * len(files)
448
339
assert(len(ids) == len(files))
452
inv = self.read_working_inventory()
453
for f,file_id in zip(files, ids):
454
if is_control_file(f):
455
raise BzrError("cannot add control file %s" % quotefn(f))
460
raise BzrError("cannot add top-level %r" % f)
462
fullpath = os.path.normpath(self.abspath(f))
465
kind = file_kind(fullpath)
467
# maybe something better?
468
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
470
if kind != 'file' and kind != 'directory':
471
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
474
file_id = gen_file_id(f)
475
inv.add_path(f, kind=kind, file_id=file_id)
478
print 'added', quotefn(f)
480
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
482
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)
487
374
def print_file(self, file, revno):
488
375
"""Print `file` to stdout."""
491
tree = self.revision_tree(self.lookup_revision(revno))
492
# use inventory as it was in that revision
493
file_id = tree.inventory.path2id(file)
495
raise BzrError("%r is not present in revision %s" % (file, revno))
496
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)
501
385
def remove(self, files, verbose=False):
502
386
"""Mark nominated files for removal from the inventory.
574
452
return self.working_tree().unknowns()
577
def append_revision(self, *revision_ids):
578
from bzrlib.atomicfile import AtomicFile
580
for revision_id in revision_ids:
581
mutter("add {%s} to revision-history" % revision_id)
455
def append_revision(self, revision_id):
456
mutter("add {%s} to revision-history" % revision_id)
583
457
rev_history = self.revision_history()
584
rev_history.extend(revision_ids)
586
f = AtomicFile(self.controlfilename('revision-history'))
588
for rev_id in rev_history:
595
def get_revision_xml(self, revision_id):
596
"""Return XML file object for revision object."""
597
if not revision_id or not isinstance(revision_id, basestring):
598
raise InvalidRevisionId(revision_id)
603
return self.revision_store[revision_id]
605
raise bzrlib.errors.NoSuchRevision(self, revision_id)
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)
610
474
def get_revision(self, revision_id):
611
475
"""Return the Revision object for a named revision"""
612
xml_file = self.get_revision_xml(revision_id)
615
r = unpack_xml(Revision, xml_file)
616
except SyntaxError, e:
617
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
476
self._need_readlock()
477
r = Revision.read_xml(self.revision_store[revision_id])
621
478
assert r.revision_id == revision_id
625
def get_revision_delta(self, revno):
626
"""Return the delta for one revision.
628
The delta is relative to its mainline predecessor, or the
629
empty tree for revision 1.
631
assert isinstance(revno, int)
632
rh = self.revision_history()
633
if not (1 <= revno <= len(rh)):
634
raise InvalidRevisionNumber(revno)
636
# revno is 1-based; list is 0-based
638
new_tree = self.revision_tree(rh[revno-1])
640
old_tree = EmptyTree()
642
old_tree = self.revision_tree(rh[revno-2])
644
return compare_trees(old_tree, new_tree)
648
def get_revision_sha1(self, revision_id):
649
"""Hash the stored value of a revision, and return it."""
650
# In the future, revision entries will be signed. At that
651
# point, it is probably best *not* to include the signature
652
# in the revision hash. Because that lets you re-sign
653
# the revision, (add signatures/remove signatures) and still
654
# have all hash pointers stay consistent.
655
# But for now, just hash the contents.
656
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
659
482
def get_inventory(self, inventory_id):
660
483
"""Get Inventory object by hash.
662
485
TODO: Perhaps for this and similar methods, take a revision
663
486
parameter which can be either an integer revno or a
665
from bzrlib.inventory import Inventory
666
from bzrlib.xml import unpack_xml
668
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
671
def get_inventory_xml(self, inventory_id):
672
"""Get inventory XML as a file object."""
673
return self.inventory_store[inventory_id]
676
def get_inventory_sha1(self, inventory_id):
677
"""Return the sha1 hash of the inventory entry
679
return sha_file(self.get_inventory_xml(inventory_id))
488
self._need_readlock()
489
i = Inventory.read_xml(self.inventory_store[inventory_id])
682
493
def get_revision_inventory(self, revision_id):
683
494
"""Return inventory of a past revision."""
684
# bzr 0.0.6 imposes the constraint that the inventory_id
685
# must be the same as its revision, so this is trivial.
495
self._need_readlock()
686
496
if revision_id == None:
687
from bzrlib.inventory import Inventory
688
return Inventory(self.get_root_id())
690
return self.get_inventory(revision_id)
499
return self.get_inventory(self.get_revision(revision_id).inventory_id)
693
502
def revision_history(self):
696
505
>>> ScratchBranch().revision_history()
701
return [l.rstrip('\r\n') for l in
702
self.controlfile('revision-history', 'r').readlines()]
707
def common_ancestor(self, other, self_revno=None, other_revno=None):
710
>>> sb = ScratchBranch(files=['foo', 'foo~'])
711
>>> sb.common_ancestor(sb) == (None, None)
713
>>> commit.commit(sb, "Committing first revision", verbose=False)
714
>>> sb.common_ancestor(sb)[0]
716
>>> clone = sb.clone()
717
>>> commit.commit(sb, "Committing second revision", verbose=False)
718
>>> sb.common_ancestor(sb)[0]
720
>>> sb.common_ancestor(clone)[0]
722
>>> commit.commit(clone, "Committing divergent second revision",
724
>>> sb.common_ancestor(clone)[0]
726
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
728
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
730
>>> clone2 = sb.clone()
731
>>> sb.common_ancestor(clone2)[0]
733
>>> sb.common_ancestor(clone2, self_revno=1)[0]
735
>>> sb.common_ancestor(clone2, other_revno=1)[0]
738
my_history = self.revision_history()
739
other_history = other.revision_history()
740
if self_revno is None:
741
self_revno = len(my_history)
742
if other_revno is None:
743
other_revno = len(other_history)
744
indices = range(min((self_revno, other_revno)))
747
if my_history[r] == other_history[r]:
748
return r+1, my_history[r]
508
self._need_readlock()
509
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
512
def enum_history(self, direction):
513
"""Return (revno, revision_id) for history of branch.
516
'forward' is from earliest to latest
517
'reverse' is from latest to earliest
519
rh = self.revision_history()
520
if direction == 'forward':
525
elif direction == 'reverse':
531
raise BzrError('invalid history direction %r' % direction)
771
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
773
If self and other have not diverged, return a list of the revisions
774
present in other, but missing from self.
776
>>> from bzrlib.commit import commit
777
>>> bzrlib.trace.silent = True
778
>>> br1 = ScratchBranch()
779
>>> br2 = ScratchBranch()
780
>>> br1.missing_revisions(br2)
782
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
783
>>> br1.missing_revisions(br2)
785
>>> br2.missing_revisions(br1)
787
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
788
>>> br1.missing_revisions(br2)
790
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
791
>>> br1.missing_revisions(br2)
793
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
794
>>> br1.missing_revisions(br2)
795
Traceback (most recent call last):
796
DivergedBranches: These branches have diverged.
798
self_history = self.revision_history()
799
self_len = len(self_history)
800
other_history = other.revision_history()
801
other_len = len(other_history)
802
common_index = min(self_len, other_len) -1
803
if common_index >= 0 and \
804
self_history[common_index] != other_history[common_index]:
805
raise DivergedBranches(self, other)
807
if stop_revision is None:
808
stop_revision = other_len
809
elif stop_revision > other_len:
810
raise NoSuchRevision(self, stop_revision)
812
return other_history[self_len:stop_revision]
815
def update_revisions(self, other, stop_revision=None):
816
"""Pull in all new revisions from other branch.
818
>>> from bzrlib.commit import commit
819
>>> bzrlib.trace.silent = True
820
>>> br1 = ScratchBranch(files=['foo', 'bar'])
823
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
824
>>> br2 = ScratchBranch()
825
>>> br2.update_revisions(br1)
829
>>> br2.revision_history()
831
>>> br2.update_revisions(br1)
833
>>> br1.text_store.total_size() == br2.text_store.total_size()
836
from bzrlib.fetch import greedy_fetch
838
pb.update('comparing histories')
839
revision_ids = self.missing_revisions(other, stop_revision)
840
if len(revision_ids) > 0:
841
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
844
self.append_revision(*revision_ids)
845
print "Added %d revisions." % count
847
def install_revisions(self, other, revision_ids, pb=None):
850
if hasattr(other.revision_store, "prefetch"):
851
other.revision_store.prefetch(revision_ids)
852
if hasattr(other.inventory_store, "prefetch"):
853
inventory_ids = [other.get_revision(r).inventory_id
854
for r in revision_ids]
855
other.inventory_store.prefetch(inventory_ids)
861
for i, rev_id in enumerate(revision_ids):
862
pb.update('fetching revision', i+1, len(revision_ids))
864
rev = other.get_revision(rev_id)
865
except bzrlib.errors.NoSuchRevision:
868
revisions.append(rev)
869
inv = other.get_inventory(str(rev.inventory_id))
870
for key, entry in inv.iter_entries():
871
if entry.text_id is None:
873
if entry.text_id not in self.text_store:
874
needed_texts.add(entry.text_id)
878
count, cp_fail = self.text_store.copy_multi(other.text_store,
880
print "Added %d texts." % count
881
inventory_ids = [ f.inventory_id for f in revisions ]
882
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
884
print "Added %d inventories." % count
885
revision_ids = [ f.revision_id for f in revisions]
886
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
889
assert len(cp_fail) == 0
890
return count, failures
892
553
def commit(self, *args, **kw):
893
555
from bzrlib.commit import commit
894
556
commit(self, *args, **kw)
897
def lookup_revision(self, revision):
898
"""Return the revision identifier for a given revision information."""
899
revno, info = self.get_revision_info(revision)
902
def get_revision_info(self, revision):
903
"""Return (revno, revision id) for revision identifier.
905
revision can be an integer, in which case it is assumed to be revno (though
906
this will translate negative values into positive ones)
907
revision can also be a string, in which case it is parsed for something like
908
'date:' or 'revid:' etc.
913
try:# Convert to int if possible
914
revision = int(revision)
917
revs = self.revision_history()
918
if isinstance(revision, int):
921
# Mabye we should do this first, but we don't need it if revision == 0
923
revno = len(revs) + revision + 1
926
elif isinstance(revision, basestring):
927
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
928
if revision.startswith(prefix):
929
revno = func(self, revs, revision)
932
raise BzrError('No namespace registered for string: %r' % revision)
934
if revno is None or revno <= 0 or revno > len(revs):
935
raise BzrError("no such revision %s" % revision)
936
return revno, revs[revno-1]
938
def _namespace_revno(self, revs, revision):
939
"""Lookup a revision by revision number"""
940
assert revision.startswith('revno:')
942
return int(revision[6:])
945
REVISION_NAMESPACES['revno:'] = _namespace_revno
947
def _namespace_revid(self, revs, revision):
948
assert revision.startswith('revid:')
950
return revs.index(revision[6:]) + 1
953
REVISION_NAMESPACES['revid:'] = _namespace_revid
955
def _namespace_last(self, revs, revision):
956
assert revision.startswith('last:')
958
offset = int(revision[5:])
963
raise BzrError('You must supply a positive value for --revision last:XXX')
964
return len(revs) - offset + 1
965
REVISION_NAMESPACES['last:'] = _namespace_last
967
def _namespace_tag(self, revs, revision):
968
assert revision.startswith('tag:')
969
raise BzrError('tag: namespace registered, but not implemented.')
970
REVISION_NAMESPACES['tag:'] = _namespace_tag
972
def _namespace_date(self, revs, revision):
973
assert revision.startswith('date:')
975
# Spec for date revisions:
977
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
978
# it can also start with a '+/-/='. '+' says match the first
979
# entry after the given date. '-' is match the first entry before the date
980
# '=' is match the first entry after, but still on the given date.
982
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
983
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
984
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
985
# May 13th, 2005 at 0:00
987
# So the proper way of saying 'give me all entries for today' is:
988
# -r {date:+today}:{date:-tomorrow}
989
# The default is '=' when not supplied
992
if val[:1] in ('+', '-', '='):
993
match_style = val[:1]
996
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
997
if val.lower() == 'yesterday':
998
dt = today - datetime.timedelta(days=1)
999
elif val.lower() == 'today':
1001
elif val.lower() == 'tomorrow':
1002
dt = today + datetime.timedelta(days=1)
1005
# This should be done outside the function to avoid recompiling it.
1006
_date_re = re.compile(
1007
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1009
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1011
m = _date_re.match(val)
1012
if not m or (not m.group('date') and not m.group('time')):
1013
raise BzrError('Invalid revision date %r' % revision)
1016
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1018
year, month, day = today.year, today.month, today.day
1020
hour = int(m.group('hour'))
1021
minute = int(m.group('minute'))
1022
if m.group('second'):
1023
second = int(m.group('second'))
1027
hour, minute, second = 0,0,0
1029
dt = datetime.datetime(year=year, month=month, day=day,
1030
hour=hour, minute=minute, second=second)
1034
if match_style == '-':
1036
elif match_style == '=':
1037
last = dt + datetime.timedelta(days=1)
1040
for i in range(len(revs)-1, -1, -1):
1041
r = self.get_revision(revs[i])
1042
# TODO: Handle timezone.
1043
dt = datetime.datetime.fromtimestamp(r.timestamp)
1044
if first >= dt and (last is None or dt >= last):
1047
for i in range(len(revs)):
1048
r = self.get_revision(revs[i])
1049
# TODO: Handle timezone.
1050
dt = datetime.datetime.fromtimestamp(r.timestamp)
1051
if first <= dt and (last is None or dt <= last):
1053
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)
1055
571
def revision_tree(self, revision_id):
1056
572
"""Return Tree for a revision on this branch.
1058
574
`revision_id` may be None for the null revision, in which case
1059
575
an `EmptyTree` is returned."""
1060
# TODO: refactor this to use an existing revision object
1061
# so we don't need to read it in twice.
576
self._need_readlock()
1062
577
if revision_id == None:
1063
578
return EmptyTree()
1091
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)
1095
tree = self.working_tree()
1096
inv = tree.inventory
1097
if not tree.has_filename(from_rel):
1098
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1099
if tree.has_filename(to_rel):
1100
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1102
file_id = inv.path2id(from_rel)
1104
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1106
if inv.path2id(to_rel):
1107
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1109
to_dir, to_tail = os.path.split(to_rel)
1110
to_dir_id = inv.path2id(to_dir)
1111
if to_dir_id == None and to_dir != '':
1112
raise BzrError("can't determine destination directory id for %r" % to_dir)
1114
mutter("rename_one:")
1115
mutter(" file_id {%s}" % file_id)
1116
mutter(" from_rel %r" % from_rel)
1117
mutter(" to_rel %r" % to_rel)
1118
mutter(" to_dir %r" % to_dir)
1119
mutter(" to_dir_id {%s}" % to_dir_id)
1121
inv.rename(file_id, to_dir_id, to_tail)
1123
print "%s => %s" % (from_rel, to_rel)
1125
from_abs = self.abspath(from_rel)
1126
to_abs = self.abspath(to_rel)
1128
os.rename(from_abs, to_abs)
1130
raise BzrError("failed to rename %r to %r: %s"
1131
% (from_abs, to_abs, e[1]),
1132
["rename rolled back"])
1134
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)
1139
652
def move(self, from_paths, to_name):
1147
660
Note that to_name is only the last component of the new name;
1148
661
this doesn't change the directory.
1152
## TODO: Option to move IDs only
1153
assert not isinstance(from_paths, basestring)
1154
tree = self.working_tree()
1155
inv = tree.inventory
1156
to_abs = self.abspath(to_name)
1157
if not isdir(to_abs):
1158
raise BzrError("destination %r is not a directory" % to_abs)
1159
if not tree.has_filename(to_name):
1160
raise BzrError("destination %r not in working directory" % to_abs)
1161
to_dir_id = inv.path2id(to_name)
1162
if to_dir_id == None and to_name != '':
1163
raise BzrError("destination %r is not a versioned directory" % to_name)
1164
to_dir_ie = inv[to_dir_id]
1165
if to_dir_ie.kind not in ('directory', 'root_directory'):
1166
raise BzrError("destination %r is not a directory" % to_abs)
1168
to_idpath = inv.get_idpath(to_dir_id)
1170
for f in from_paths:
1171
if not tree.has_filename(f):
1172
raise BzrError("%r does not exist in working tree" % f)
1173
f_id = inv.path2id(f)
1175
raise BzrError("%r is not versioned" % f)
1176
name_tail = splitpath(f)[-1]
1177
dest_path = appendpath(to_name, name_tail)
1178
if tree.has_filename(dest_path):
1179
raise BzrError("destination %r already exists" % dest_path)
1180
if f_id in to_idpath:
1181
raise BzrError("can't move %r to a subdirectory of itself" % f)
1183
# OK, so there's a race here, it's possible that someone will
1184
# create a file in this interval and then the rename might be
1185
# left half-done. But we should have caught most problems.
1187
for f in from_paths:
1188
name_tail = splitpath(f)[-1]
1189
dest_path = appendpath(to_name, name_tail)
1190
print "%s => %s" % (f, dest_path)
1191
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1193
os.rename(self.abspath(f), self.abspath(dest_path))
1195
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1196
["rename rolled back"])
1198
self._write_inventory(inv)
1203
def revert(self, filenames, old_tree=None, backups=True):
1204
"""Restore selected files to the versions from a previous tree.
1207
If true (default) backups are made of files before
1210
from bzrlib.errors import NotVersionedError, BzrError
1211
from bzrlib.atomicfile import AtomicFile
1212
from bzrlib.osutils import backup_file
1214
inv = self.read_working_inventory()
1215
if old_tree is None:
1216
old_tree = self.basis_tree()
1217
old_inv = old_tree.inventory
1220
for fn in filenames:
1221
file_id = inv.path2id(fn)
1223
raise NotVersionedError("not a versioned file", fn)
1224
if not old_inv.has_id(file_id):
1225
raise BzrError("file not present in old tree", fn, file_id)
1226
nids.append((fn, file_id))
1228
# TODO: Rename back if it was previously at a different location
1230
# TODO: If given a directory, restore the entire contents from
1231
# the previous version.
1233
# TODO: Make a backup to a temporary file.
1235
# TODO: If the file previously didn't exist, delete it?
1236
for fn, file_id in nids:
1239
f = AtomicFile(fn, 'wb')
1241
f.write(old_tree.get_file(file_id).read())
1247
def pending_merges(self):
1248
"""Return a list of pending merges.
1250
These are revisions that have been merged into the working
1251
directory but not yet committed.
1253
cfn = self.controlfilename('pending-merges')
1254
if not os.path.exists(cfn):
1257
for l in self.controlfile('pending-merges', 'r').readlines():
1258
p.append(l.rstrip('\n'))
1262
def add_pending_merge(self, revision_id):
1263
from bzrlib.revision import validate_revision_id
1265
validate_revision_id(revision_id)
1267
p = self.pending_merges()
1268
if revision_id in p:
1270
p.append(revision_id)
1271
self.set_pending_merges(p)
1274
def set_pending_merges(self, rev_list):
1275
from bzrlib.atomicfile import AtomicFile
1278
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)