15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
19
import traceback, socket, fnmatch, difflib, time
20
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
23
from inventory import Inventory
24
from trace import mutter, note
25
from tree import Tree, EmptyTree, RevisionTree
26
from inventory import InventoryEntry, Inventory
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
28
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
29
joinpath, sha_string, file_kind, local_time_offset, appendpath
30
from store import ImmutableStore
31
from revision import Revision
32
from errors import bailout, BzrError
33
from textui import show_status
34
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
36
## TODO: Maybe include checks for common corruption of newlines, etc?
38
# TODO: Some operations like log might retrieve the same revisions
39
# repeatedly to calculate deltas. We could perhaps have a weakref
40
# cache in memory to make this faster.
43
40
def find_branch(f, **args):
44
41
if f and (f.startswith('http://') or f.startswith('https://')):
201
129
__repr__ = __str__
205
if self._lock_mode or self._lock:
206
from warnings import warn
207
warn("branch %r was not explicitly unlocked" % self)
212
def lock_write(self):
214
if self._lock_mode != 'w':
215
from errors import LockError
216
raise LockError("can't upgrade to a write lock from %r" %
218
self._lock_count += 1
220
from bzrlib.lock import WriteLock
222
self._lock = WriteLock(self.controlfilename('branch-lock'))
223
self._lock_mode = 'w'
230
assert self._lock_mode in ('r', 'w'), \
231
"invalid lock mode %r" % self._lock_mode
232
self._lock_count += 1
234
from bzrlib.lock import ReadLock
236
self._lock = ReadLock(self.controlfilename('branch-lock'))
237
self._lock_mode = 'r'
133
def lock(self, mode='w'):
134
"""Lock the on-disk branch, excluding other processes."""
140
om = os.O_WRONLY | os.O_CREAT
145
raise BzrError("invalid locking mode %r" % mode)
148
lockfile = os.open(self.controlfilename('branch-lock'), om)
150
if e.errno == errno.ENOENT:
151
# might not exist on branches from <0.0.4
152
self.controlfile('branch-lock', 'w').close()
153
lockfile = os.open(self.controlfilename('branch-lock'), om)
243
if not self._lock_mode:
244
from errors import LockError
245
raise LockError('branch %r is not locked' % (self))
247
if self._lock_count > 1:
248
self._lock_count -= 1
252
self._lock_mode = self._lock_count = None
157
fcntl.lockf(lockfile, lm)
159
fcntl.lockf(lockfile, fcntl.LOCK_UN)
161
self._lockmode = None
163
self._lockmode = mode
165
warning("please write a locking method for platform %r" % sys.platform)
167
self._lockmode = None
169
self._lockmode = mode
172
def _need_readlock(self):
173
if self._lockmode not in ['r', 'w']:
174
raise BzrError('need read lock on branch, only have %r' % self._lockmode)
176
def _need_writelock(self):
177
if self._lockmode not in ['w']:
178
raise BzrError('need write lock on branch, only have %r' % self._lockmode)
255
181
def abspath(self, name):
336
260
fmt = self.controlfile('branch-format', 'r').read()
337
261
fmt.replace('\r\n', '')
338
262
if fmt != BZR_BRANCH_FORMAT:
339
raise BzrError('sorry, branch format %r not supported' % fmt,
340
['use a different bzr version',
341
'or remove the .bzr directory and "bzr init" again'])
343
def get_root_id(self):
344
"""Return the id of this branches root"""
345
inv = self.read_working_inventory()
346
return inv.root.file_id
348
def set_root_id(self, file_id):
349
inv = self.read_working_inventory()
350
orig_root_id = inv.root.file_id
351
del inv._byid[inv.root.file_id]
352
inv.root.file_id = file_id
353
inv._byid[inv.root.file_id] = inv.root
356
if entry.parent_id in (None, orig_root_id):
357
entry.parent_id = inv.root.file_id
358
self._write_inventory(inv)
263
bailout('sorry, branch format %r not supported' % fmt,
264
['use a different bzr version',
265
'or remove the .bzr directory and "bzr init" again'])
360
268
def read_working_inventory(self):
361
269
"""Read the working inventory."""
362
from bzrlib.inventory import Inventory
363
from bzrlib.xml import unpack_xml
364
from time import time
368
# ElementTree does its own conversion from UTF-8, so open in
370
inv = unpack_xml(Inventory,
371
self.controlfile('inventory', 'rb'))
372
mutter("loaded inventory of %d items in %f"
373
% (len(inv), time() - before))
270
self._need_readlock()
272
# ElementTree does its own conversion from UTF-8, so open in
274
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
275
mutter("loaded inventory of %d items in %f"
276
% (len(inv), time.time() - before))
379
280
def _write_inventory(self, inv):
380
281
"""Update the working inventory.
441
335
ids = [None] * len(files)
443
337
assert(len(ids) == len(files))
447
inv = self.read_working_inventory()
448
for f,file_id in zip(files, ids):
449
if is_control_file(f):
450
raise BzrError("cannot add control file %s" % quotefn(f))
455
raise BzrError("cannot add top-level %r" % f)
457
fullpath = os.path.normpath(self.abspath(f))
460
kind = file_kind(fullpath)
462
# maybe something better?
463
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
465
if kind != 'file' and kind != 'directory':
466
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
469
file_id = gen_file_id(f)
470
inv.add_path(f, kind=kind, file_id=file_id)
473
print 'added', quotefn(f)
475
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
477
self._write_inventory(inv)
339
inv = self.read_working_inventory()
340
for f,file_id in zip(files, ids):
341
if is_control_file(f):
342
bailout("cannot add control file %s" % quotefn(f))
347
bailout("cannot add top-level %r" % f)
349
fullpath = os.path.normpath(self.abspath(f))
352
kind = file_kind(fullpath)
354
# maybe something better?
355
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
357
if kind != 'file' and kind != 'directory':
358
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
361
file_id = gen_file_id(f)
362
inv.add_path(f, kind=kind, file_id=file_id)
365
show_status('A', kind, quotefn(f))
367
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
369
self._write_inventory(inv)
482
372
def print_file(self, file, revno):
483
373
"""Print `file` to stdout."""
486
tree = self.revision_tree(self.lookup_revision(revno))
487
# use inventory as it was in that revision
488
file_id = tree.inventory.path2id(file)
490
raise BzrError("%r is not present in revision %s" % (file, revno))
491
tree.print_file(file_id)
374
self._need_readlock()
375
tree = self.revision_tree(self.lookup_revision(revno))
376
# use inventory as it was in that revision
377
file_id = tree.inventory.path2id(file)
379
bailout("%r is not present in revision %d" % (file, revno))
380
tree.print_file(file_id)
496
383
def remove(self, files, verbose=False):
497
384
"""Mark nominated files for removal from the inventory.
569
450
return self.working_tree().unknowns()
572
def append_revision(self, *revision_ids):
573
from bzrlib.atomicfile import AtomicFile
575
for revision_id in revision_ids:
576
mutter("add {%s} to revision-history" % revision_id)
453
def append_revision(self, revision_id):
454
mutter("add {%s} to revision-history" % revision_id)
578
455
rev_history = self.revision_history()
579
rev_history.extend(revision_ids)
581
f = AtomicFile(self.controlfilename('revision-history'))
583
for rev_id in rev_history:
590
def get_revision_xml(self, revision_id):
591
"""Return XML file object for revision object."""
592
if not revision_id or not isinstance(revision_id, basestring):
593
raise InvalidRevisionId(revision_id)
598
return self.revision_store[revision_id]
600
raise bzrlib.errors.NoSuchRevision(self, revision_id)
457
tmprhname = self.controlfilename('revision-history.tmp')
458
rhname = self.controlfilename('revision-history')
460
f = file(tmprhname, 'wt')
461
rev_history.append(revision_id)
462
f.write('\n'.join(rev_history))
466
if sys.platform == 'win32':
468
os.rename(tmprhname, rhname)
605
472
def get_revision(self, revision_id):
606
473
"""Return the Revision object for a named revision"""
607
xml_file = self.get_revision_xml(revision_id)
610
r = unpack_xml(Revision, xml_file)
611
except SyntaxError, e:
612
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
474
self._need_readlock()
475
r = Revision.read_xml(self.revision_store[revision_id])
616
476
assert r.revision_id == revision_id
620
def get_revision_delta(self, revno):
621
"""Return the delta for one revision.
623
The delta is relative to its mainline predecessor, or the
624
empty tree for revision 1.
626
assert isinstance(revno, int)
627
rh = self.revision_history()
628
if not (1 <= revno <= len(rh)):
629
raise InvalidRevisionNumber(revno)
631
# revno is 1-based; list is 0-based
633
new_tree = self.revision_tree(rh[revno-1])
635
old_tree = EmptyTree()
637
old_tree = self.revision_tree(rh[revno-2])
639
return compare_trees(old_tree, new_tree)
643
def get_revision_sha1(self, revision_id):
644
"""Hash the stored value of a revision, and return it."""
645
# In the future, revision entries will be signed. At that
646
# point, it is probably best *not* to include the signature
647
# in the revision hash. Because that lets you re-sign
648
# the revision, (add signatures/remove signatures) and still
649
# have all hash pointers stay consistent.
650
# But for now, just hash the contents.
651
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
654
480
def get_inventory(self, inventory_id):
655
481
"""Get Inventory object by hash.
657
483
TODO: Perhaps for this and similar methods, take a revision
658
484
parameter which can be either an integer revno or a
660
from bzrlib.inventory import Inventory
661
from bzrlib.xml import unpack_xml
663
return unpack_xml(Inventory, self.inventory_store[inventory_id])
666
def get_inventory_sha1(self, inventory_id):
667
"""Return the sha1 hash of the inventory entry
669
return sha_file(self.inventory_store[inventory_id])
486
self._need_readlock()
487
i = Inventory.read_xml(self.inventory_store[inventory_id])
672
491
def get_revision_inventory(self, revision_id):
673
492
"""Return inventory of a past revision."""
674
# bzr 0.0.6 imposes the constraint that the inventory_id
675
# must be the same as its revision, so this is trivial.
493
self._need_readlock()
676
494
if revision_id == None:
677
from bzrlib.inventory import Inventory
678
return Inventory(self.get_root_id())
680
return self.get_inventory(revision_id)
497
return self.get_inventory(self.get_revision(revision_id).inventory_id)
683
500
def revision_history(self):
686
503
>>> ScratchBranch().revision_history()
691
return [l.rstrip('\r\n') for l in
692
self.controlfile('revision-history', 'r').readlines()]
697
def common_ancestor(self, other, self_revno=None, other_revno=None):
700
>>> sb = ScratchBranch(files=['foo', 'foo~'])
701
>>> sb.common_ancestor(sb) == (None, None)
703
>>> commit.commit(sb, "Committing first revision", verbose=False)
704
>>> sb.common_ancestor(sb)[0]
706
>>> clone = sb.clone()
707
>>> commit.commit(sb, "Committing second revision", verbose=False)
708
>>> sb.common_ancestor(sb)[0]
710
>>> sb.common_ancestor(clone)[0]
712
>>> commit.commit(clone, "Committing divergent second revision",
714
>>> sb.common_ancestor(clone)[0]
716
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
718
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
720
>>> clone2 = sb.clone()
721
>>> sb.common_ancestor(clone2)[0]
723
>>> sb.common_ancestor(clone2, self_revno=1)[0]
725
>>> sb.common_ancestor(clone2, other_revno=1)[0]
728
my_history = self.revision_history()
729
other_history = other.revision_history()
730
if self_revno is None:
731
self_revno = len(my_history)
732
if other_revno is None:
733
other_revno = len(other_history)
734
indices = range(min((self_revno, other_revno)))
737
if my_history[r] == other_history[r]:
738
return r+1, my_history[r]
506
self._need_readlock()
507
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
510
def enum_history(self, direction):
511
"""Return (revno, revision_id) for history of branch.
514
'forward' is from earliest to latest
515
'reverse' is from latest to earliest
517
rh = self.revision_history()
518
if direction == 'forward':
523
elif direction == 'reverse':
529
raise ValueError('invalid history direction', direction)
761
def missing_revisions(self, other, stop_revision=None):
763
If self and other have not diverged, return a list of the revisions
764
present in other, but missing from self.
766
>>> from bzrlib.commit import commit
767
>>> bzrlib.trace.silent = True
768
>>> br1 = ScratchBranch()
769
>>> br2 = ScratchBranch()
770
>>> br1.missing_revisions(br2)
772
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
773
>>> br1.missing_revisions(br2)
775
>>> br2.missing_revisions(br1)
777
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
778
>>> br1.missing_revisions(br2)
780
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
781
>>> br1.missing_revisions(br2)
783
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
784
>>> br1.missing_revisions(br2)
785
Traceback (most recent call last):
786
DivergedBranches: These branches have diverged.
788
self_history = self.revision_history()
789
self_len = len(self_history)
790
other_history = other.revision_history()
791
other_len = len(other_history)
792
common_index = min(self_len, other_len) -1
793
if common_index >= 0 and \
794
self_history[common_index] != other_history[common_index]:
795
raise DivergedBranches(self, other)
797
if stop_revision is None:
798
stop_revision = other_len
799
elif stop_revision > other_len:
800
raise NoSuchRevision(self, stop_revision)
802
return other_history[self_len:stop_revision]
805
def update_revisions(self, other, stop_revision=None):
806
"""Pull in all new revisions from other branch.
808
>>> from bzrlib.commit import commit
809
>>> bzrlib.trace.silent = True
810
>>> br1 = ScratchBranch(files=['foo', 'bar'])
813
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
814
>>> br2 = ScratchBranch()
815
>>> br2.update_revisions(br1)
819
>>> br2.revision_history()
821
>>> br2.update_revisions(br1)
825
>>> br1.text_store.total_size() == br2.text_store.total_size()
828
from bzrlib.progress import ProgressBar
832
pb.update('comparing histories')
833
revision_ids = self.missing_revisions(other, stop_revision)
835
if hasattr(other.revision_store, "prefetch"):
836
other.revision_store.prefetch(revision_ids)
837
if hasattr(other.inventory_store, "prefetch"):
838
inventory_ids = [other.get_revision(r).inventory_id
839
for r in revision_ids]
840
other.inventory_store.prefetch(inventory_ids)
845
for rev_id in revision_ids:
847
pb.update('fetching revision', i, len(revision_ids))
848
rev = other.get_revision(rev_id)
849
revisions.append(rev)
850
inv = other.get_inventory(str(rev.inventory_id))
851
for key, entry in inv.iter_entries():
852
if entry.text_id is None:
854
if entry.text_id not in self.text_store:
855
needed_texts.add(entry.text_id)
859
count = self.text_store.copy_multi(other.text_store, needed_texts)
860
print "Added %d texts." % count
861
inventory_ids = [ f.inventory_id for f in revisions ]
862
count = self.inventory_store.copy_multi(other.inventory_store,
864
print "Added %d inventories." % count
865
revision_ids = [ f.revision_id for f in revisions]
866
count = self.revision_store.copy_multi(other.revision_store,
868
for revision_id in revision_ids:
869
self.append_revision(revision_id)
870
print "Added %d revisions." % count
873
551
def commit(self, *args, **kw):
874
553
from bzrlib.commit import commit
875
554
commit(self, *args, **kw)
878
def lookup_revision(self, revision):
879
"""Return the revision identifier for a given revision information."""
880
revno, info = self.get_revision_info(revision)
883
def get_revision_info(self, revision):
884
"""Return (revno, revision id) for revision identifier.
886
revision can be an integer, in which case it is assumed to be revno (though
887
this will translate negative values into positive ones)
888
revision can also be a string, in which case it is parsed for something like
889
'date:' or 'revid:' etc.
894
try:# Convert to int if possible
895
revision = int(revision)
898
revs = self.revision_history()
899
if isinstance(revision, int):
902
# Mabye we should do this first, but we don't need it if revision == 0
904
revno = len(revs) + revision + 1
907
elif isinstance(revision, basestring):
908
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
909
if revision.startswith(prefix):
910
revno = func(self, revs, revision)
913
raise BzrError('No namespace registered for string: %r' % revision)
915
if revno is None or revno <= 0 or revno > len(revs):
916
raise BzrError("no such revision %s" % revision)
917
return revno, revs[revno-1]
919
def _namespace_revno(self, revs, revision):
920
"""Lookup a revision by revision number"""
921
assert revision.startswith('revno:')
923
return int(revision[6:])
926
REVISION_NAMESPACES['revno:'] = _namespace_revno
928
def _namespace_revid(self, revs, revision):
929
assert revision.startswith('revid:')
931
return revs.index(revision[6:]) + 1
934
REVISION_NAMESPACES['revid:'] = _namespace_revid
936
def _namespace_last(self, revs, revision):
937
assert revision.startswith('last:')
939
offset = int(revision[5:])
944
raise BzrError('You must supply a positive value for --revision last:XXX')
945
return len(revs) - offset + 1
946
REVISION_NAMESPACES['last:'] = _namespace_last
948
def _namespace_tag(self, revs, revision):
949
assert revision.startswith('tag:')
950
raise BzrError('tag: namespace registered, but not implemented.')
951
REVISION_NAMESPACES['tag:'] = _namespace_tag
953
def _namespace_date(self, revs, revision):
954
assert revision.startswith('date:')
956
# Spec for date revisions:
958
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
959
# it can also start with a '+/-/='. '+' says match the first
960
# entry after the given date. '-' is match the first entry before the date
961
# '=' is match the first entry after, but still on the given date.
963
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
964
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
965
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
966
# May 13th, 2005 at 0:00
968
# So the proper way of saying 'give me all entries for today' is:
969
# -r {date:+today}:{date:-tomorrow}
970
# The default is '=' when not supplied
973
if val[:1] in ('+', '-', '='):
974
match_style = val[:1]
977
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
978
if val.lower() == 'yesterday':
979
dt = today - datetime.timedelta(days=1)
980
elif val.lower() == 'today':
982
elif val.lower() == 'tomorrow':
983
dt = today + datetime.timedelta(days=1)
986
# This should be done outside the function to avoid recompiling it.
987
_date_re = re.compile(
988
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
990
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
992
m = _date_re.match(val)
993
if not m or (not m.group('date') and not m.group('time')):
994
raise BzrError('Invalid revision date %r' % revision)
997
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
999
year, month, day = today.year, today.month, today.day
1001
hour = int(m.group('hour'))
1002
minute = int(m.group('minute'))
1003
if m.group('second'):
1004
second = int(m.group('second'))
1008
hour, minute, second = 0,0,0
1010
dt = datetime.datetime(year=year, month=month, day=day,
1011
hour=hour, minute=minute, second=second)
1015
if match_style == '-':
1017
elif match_style == '=':
1018
last = dt + datetime.timedelta(days=1)
1021
for i in range(len(revs)-1, -1, -1):
1022
r = self.get_revision(revs[i])
1023
# TODO: Handle timezone.
1024
dt = datetime.datetime.fromtimestamp(r.timestamp)
1025
if first >= dt and (last is None or dt >= last):
1028
for i in range(len(revs)):
1029
r = self.get_revision(revs[i])
1030
# TODO: Handle timezone.
1031
dt = datetime.datetime.fromtimestamp(r.timestamp)
1032
if first <= dt and (last is None or dt <= last):
1034
REVISION_NAMESPACES['date:'] = _namespace_date
557
def lookup_revision(self, revno):
558
"""Return revision hash for revision number."""
563
# list is 0-based; revisions are 1-based
564
return self.revision_history()[revno-1]
566
raise BzrError("no such revision %s" % revno)
1036
569
def revision_tree(self, revision_id):
1037
570
"""Return Tree for a revision on this branch.
1072
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)
1076
tree = self.working_tree()
1077
inv = tree.inventory
1078
if not tree.has_filename(from_rel):
1079
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1080
if tree.has_filename(to_rel):
1081
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1083
file_id = inv.path2id(from_rel)
1085
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1087
if inv.path2id(to_rel):
1088
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1090
to_dir, to_tail = os.path.split(to_rel)
1091
to_dir_id = inv.path2id(to_dir)
1092
if to_dir_id == None and to_dir != '':
1093
raise BzrError("can't determine destination directory id for %r" % to_dir)
1095
mutter("rename_one:")
1096
mutter(" file_id {%s}" % file_id)
1097
mutter(" from_rel %r" % from_rel)
1098
mutter(" to_rel %r" % to_rel)
1099
mutter(" to_dir %r" % to_dir)
1100
mutter(" to_dir_id {%s}" % to_dir_id)
1102
inv.rename(file_id, to_dir_id, to_tail)
1104
print "%s => %s" % (from_rel, to_rel)
1106
from_abs = self.abspath(from_rel)
1107
to_abs = self.abspath(to_rel)
1109
os.rename(from_abs, to_abs)
1111
raise BzrError("failed to rename %r to %r: %s"
1112
% (from_abs, to_abs, e[1]),
1113
["rename rolled back"])
1115
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)
1120
652
def move(self, from_paths, to_name):
1128
660
Note that to_name is only the last component of the new name;
1129
661
this doesn't change the directory.
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
print "%s => %s" % (f, dest_path)
1172
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1174
os.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)
1184
def revert(self, filenames, old_tree=None, backups=True):
1185
"""Restore selected files to the versions from a previous tree.
1188
If true (default) backups are made of files before
1191
from bzrlib.errors import NotVersionedError, BzrError
1192
from bzrlib.atomicfile import AtomicFile
1193
from bzrlib.osutils import backup_file
1195
inv = self.read_working_inventory()
1196
if old_tree is None:
1197
old_tree = self.basis_tree()
1198
old_inv = old_tree.inventory
1201
for fn in filenames:
1202
file_id = inv.path2id(fn)
1204
raise NotVersionedError("not a versioned file", fn)
1205
if not old_inv.has_id(file_id):
1206
raise BzrError("file not present in old tree", fn, file_id)
1207
nids.append((fn, file_id))
1209
# TODO: Rename back if it was previously at a different location
1211
# TODO: If given a directory, restore the entire contents from
1212
# the previous version.
1214
# TODO: Make a backup to a temporary file.
1216
# TODO: If the file previously didn't exist, delete it?
1217
for fn, file_id in nids:
1220
f = AtomicFile(fn, 'wb')
1222
f.write(old_tree.get_file(file_id).read())
1228
def pending_merges(self):
1229
"""Return a list of pending merges.
1231
These are revisions that have been merged into the working
1232
directory but not yet committed.
1234
cfn = self.controlfilename('pending-merges')
1235
if not os.path.exists(cfn):
1238
for l in self.controlfile('pending-merges', 'r').readlines():
1239
p.append(l.rstrip('\n'))
1243
def add_pending_merge(self, revision_id):
1244
from bzrlib.revision import validate_revision_id
1246
validate_revision_id(revision_id)
1248
p = self.pending_merges()
1249
if revision_id in p:
1251
p.append(revision_id)
1252
self.set_pending_merges(p)
1255
def set_pending_merges(self, rev_list):
1256
from bzrlib.atomicfile import AtomicFile
1259
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 = 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)