26
from bzrlib.inventory import InventoryEntry
27
import bzrlib.inventory as inventory
28
26
from bzrlib.trace import mutter, note
29
27
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
30
28
rename, splitpath, sha_file, appendpath,
32
import bzrlib.errors as errors
33
30
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
31
NoSuchRevision, HistoryMissing, NotBranchError,
35
32
DivergedBranches, LockError, UnlistableStore,
36
UnlistableBranch, NoSuchFile, NotVersionedError)
33
UnlistableBranch, NoSuchFile)
37
34
from bzrlib.textui import show_status
38
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
35
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
41
36
from bzrlib.delta import compare_trees
42
37
from bzrlib.tree import EmptyTree, RevisionTree
43
38
from bzrlib.inventory import Inventory
67
59
# XXX: leave this here for about one release, then remove it
68
60
raise NotImplementedError('find_branch() is not supported anymore, '
69
61
'please use one of the new branch constructors')
72
def needs_read_lock(unbound):
73
"""Decorate unbound to take out and release a read lock."""
74
def decorated(self, *args, **kwargs):
77
return unbound(self, *args, **kwargs)
83
def needs_write_lock(unbound):
84
"""Decorate unbound to take out and release a write lock."""
85
def decorated(self, *args, **kwargs):
88
return unbound(self, *args, **kwargs)
62
def _relpath(base, path):
63
"""Return path relative to base, or raise exception.
65
The path may be either an absolute path or a path relative to the
66
current working directory.
68
Lifted out of Branch.relpath for ease of testing.
70
os.path.commonprefix (python2.4) has a bad bug that it works just
71
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
72
avoids that problem."""
73
rp = os.path.abspath(path)
77
while len(head) >= len(base):
80
head, tail = os.path.split(head)
84
raise NotBranchError("path %r is not within branch %r" % (rp, base))
89
def find_branch_root(t):
90
"""Find the branch root enclosing the transport's base.
92
t is a Transport object.
94
It is not necessary that the base of t exists.
96
Basically we keep looking up until we find the control directory or
97
run into the root. If there isn't one, raises NotBranchError.
101
if t.has(bzrlib.BZRDIR):
103
new_t = t.clone('..')
104
if new_t.base == t.base:
105
# reached the root, whatever that may be
106
raise NotBranchError('%s is not in a branch' % orig_base)
93
110
######################################################################
229
234
self._make_control()
230
235
self._check_format(relax_version_check)
232
def get_store(name, compressed=True, prefixed=False):
233
# FIXME: This approach of assuming stores are all entirely compressed
234
# or entirely uncompressed is tidy, but breaks upgrade from
235
# some existing branches where there's a mixture; we probably
236
# still want the option to look for both.
237
def get_store(name, compressed=True):
237
238
relpath = self._rel_controlfilename(name)
239
store = CompressedTextStore(self._transport.clone(relpath),
240
store = CompressedTextStore(self._transport.clone(relpath))
242
store = TextStore(self._transport.clone(relpath),
244
#if self._transport.should_cache():
245
# cache_path = os.path.join(self.cache_root, name)
246
# os.mkdir(cache_path)
247
# store = bzrlib.store.CachedStore(store, cache_path)
242
store = TextStore(self._transport.clone(relpath))
243
if self._transport.should_cache():
244
from meta_store import CachedStore
245
cache_path = os.path.join(self.cache_root, name)
247
store = CachedStore(store, cache_path)
249
def get_weave(name, prefixed=False):
250
250
relpath = self._rel_controlfilename(name)
251
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
251
ws = WeaveStore(self._transport.clone(relpath))
252
252
if self._transport.should_cache():
253
253
ws.enable_cache = True
258
258
self.text_store = get_store('text-store')
259
259
self.revision_store = get_store('revision-store')
260
260
elif self._branch_format == 5:
261
self.control_weaves = get_weave('')
261
self.control_weaves = get_weave([])
262
262
self.weave_store = get_weave('weaves')
263
263
self.revision_store = get_store('revision-store', compressed=False)
264
elif self._branch_format == 6:
265
self.control_weaves = get_weave('')
266
self.weave_store = get_weave('weaves', prefixed=True)
267
self.revision_store = get_store('revision-store', compressed=False,
269
self.revision_store.register_suffix('sig')
270
self._transaction = None
272
265
def __str__(self):
273
266
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
302
295
return self._transport.base
305
base = property(_get_base, doc="The URL for the root of this branch.")
307
def _finish_transaction(self):
308
"""Exit the current transaction."""
309
if self._transaction is None:
310
raise errors.LockError('Branch %s is not in a transaction' %
312
transaction = self._transaction
313
self._transaction = None
316
def get_transaction(self):
317
"""Return the current active transaction.
319
If no transaction is active, this returns a passthrough object
320
for which all data is immediately flushed and no caching happens.
322
if self._transaction is None:
323
return transactions.PassThroughTransaction()
325
return self._transaction
327
def _set_transaction(self, new_transaction):
328
"""Set a new active transaction."""
329
if self._transaction is not None:
330
raise errors.LockError('Branch %s is in a transaction already.' %
332
self._transaction = new_transaction
298
base = property(_get_base)
334
301
def lock_write(self):
335
mutter("lock write: %s (%s)", self, self._lock_count)
336
302
# TODO: Upgrade locking to support using a Transport,
337
303
# and potentially a remote locking protocol
338
304
if self._lock_mode:
358
323
self._rel_controlfilename('branch-lock'))
359
324
self._lock_mode = 'r'
360
325
self._lock_count = 1
361
self._set_transaction(transactions.ReadOnlyTransaction())
362
# 5K may be excessive, but hey, its a knob.
363
self.get_transaction().set_cache_size(5000)
365
327
def unlock(self):
366
mutter("unlock: %s (%s)", self, self._lock_count)
367
328
if not self._lock_mode:
368
329
raise LockError('branch %r is not locked' % (self))
370
331
if self._lock_count > 1:
371
332
self._lock_count -= 1
373
self._finish_transaction()
374
334
self._lock.unlock()
375
335
self._lock = None
376
336
self._lock_mode = self._lock_count = None
378
338
def abspath(self, name):
379
"""Return absolute filename for something in the branch
381
XXX: Robert Collins 20051017 what is this used for? why is it a branch
382
method and not a tree method.
339
"""Return absolute filename for something in the branch"""
384
340
return self._transport.abspath(name)
342
def relpath(self, path):
343
"""Return path relative to this branch of something inside it.
345
Raises an error if path is not in this branch."""
346
return self._transport.relpath(path)
386
349
def _rel_controlfilename(self, file_or_path):
387
if not isinstance(file_or_path, basestring):
388
file_or_path = '/'.join(file_or_path)
389
if file_or_path == '':
391
return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
350
if isinstance(file_or_path, basestring):
351
file_or_path = [file_or_path]
352
return [bzrlib.BZRDIR] + file_or_path
393
354
def controlfilename(self, file_or_path):
394
355
"""Return location relative to branch."""
395
356
return self._transport.abspath(self._rel_controlfilename(file_or_path))
397
359
def controlfile(self, file_or_path, mode='r'):
398
360
"""Open a control file for this branch.
497
459
fmt = self.controlfile('branch-format', 'r').read()
498
460
except NoSuchFile:
499
raise NotBranchError(path=self.base)
500
mutter("got branch format %r", fmt)
501
if fmt == BZR_BRANCH_FORMAT_6:
502
self._branch_format = 6
503
elif fmt == BZR_BRANCH_FORMAT_5:
461
raise NotBranchError(self.base)
463
if fmt == BZR_BRANCH_FORMAT_5:
504
464
self._branch_format = 5
505
465
elif fmt == BZR_BRANCH_FORMAT_4:
506
466
self._branch_format = 4
508
468
if (not relax_version_check
509
and self._branch_format not in (5, 6)):
510
raise errors.UnsupportedFormatError(
511
'sorry, branch format %r not supported' % fmt,
469
and self._branch_format != 5):
470
raise BzrError('sorry, branch format %r not supported' % fmt,
512
471
['use a different bzr version',
513
472
'or remove the .bzr directory'
514
473
' and "bzr init" again'])
530
489
entry.parent_id = inv.root.file_id
531
490
self._write_inventory(inv)
534
492
def read_working_inventory(self):
535
493
"""Read the working inventory."""
536
# ElementTree does its own conversion from UTF-8, so open in
538
f = self.controlfile('inventory', 'rb')
539
return bzrlib.xml5.serializer_v5.read_inventory(f)
496
# ElementTree does its own conversion from UTF-8, so open in
498
f = self.controlfile('inventory', 'rb')
499
return bzrlib.xml5.serializer_v5.read_inventory(f)
542
504
def _write_inventory(self, inv):
543
505
"""Update the working inventory.
546
508
will be committed to the next revision.
548
510
from cStringIO import StringIO
550
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
552
# Transport handles atomicity
553
self.put_controlfile('inventory', sio)
514
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
516
# Transport handles atomicity
517
self.put_controlfile('inventory', sio)
555
521
mutter('wrote working inventory')
557
523
inventory = property(read_working_inventory, _write_inventory, None,
558
524
"""Inventory for the working copy.""")
561
526
def add(self, files, ids=None):
562
527
"""Make files versioned.
594
559
assert(len(ids) == len(files))
596
inv = self.read_working_inventory()
597
for f,file_id in zip(files, ids):
598
if is_control_file(f):
599
raise BzrError("cannot add control file %s" % quotefn(f))
604
raise BzrError("cannot add top-level %r" % f)
606
fullpath = os.path.normpath(self.abspath(f))
609
kind = file_kind(fullpath)
611
# maybe something better?
612
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
614
if not InventoryEntry.versionable_kind(kind):
615
raise BzrError('cannot add: not a versionable file ('
616
'i.e. regular file, symlink or directory): %s' % quotefn(f))
619
file_id = gen_file_id(f)
620
inv.add_path(f, kind=kind, file_id=file_id)
622
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
624
self._write_inventory(inv)
563
inv = self.read_working_inventory()
564
for f,file_id in zip(files, ids):
565
if is_control_file(f):
566
raise BzrError("cannot add control file %s" % quotefn(f))
571
raise BzrError("cannot add top-level %r" % f)
573
fullpath = os.path.normpath(self.abspath(f))
576
kind = file_kind(fullpath)
578
# maybe something better?
579
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
581
if kind not in ('file', 'directory', 'symlink'):
582
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
585
file_id = gen_file_id(f)
586
inv.add_path(f, kind=kind, file_id=file_id)
588
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
590
self._write_inventory(inv)
627
595
def print_file(self, file, revno):
628
596
"""Print `file` to stdout."""
629
tree = self.revision_tree(self.get_rev_id(revno))
630
# use inventory as it was in that revision
631
file_id = tree.inventory.path2id(file)
633
raise BzrError("%r is not present in revision %s" % (file, revno))
634
tree.print_file(file_id)
599
tree = self.revision_tree(self.get_rev_id(revno))
600
# use inventory as it was in that revision
601
file_id = tree.inventory.path2id(file)
603
raise BzrError("%r is not present in revision %s" % (file, revno))
604
tree.print_file(file_id)
609
def remove(self, files, verbose=False):
610
"""Mark nominated files for removal from the inventory.
612
This does not remove their text. This does not run on
614
TODO: Refuse to remove modified files unless --force is given?
616
TODO: Do something useful with directories.
618
TODO: Should this remove the text or not? Tough call; not
619
removing may be useful and the user can just use use rm, and
620
is the opposite of add. Removing it is consistent with most
621
other tools. Maybe an option.
623
## TODO: Normalize names
624
## TODO: Remove nested loops; better scalability
625
if isinstance(files, basestring):
631
tree = self.working_tree()
634
# do this before any modifications
638
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
639
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
641
# having remove it, it must be either ignored or unknown
642
if tree.is_ignored(f):
646
show_status(new_status, inv[fid].kind, quotefn(f))
649
self._write_inventory(inv)
636
653
# FIXME: this doesn't need to be a branch method
637
654
def set_inventory(self, new_inventory_list):
641
658
name = os.path.basename(path)
644
# fixme, there should be a factory function inv,add_??
645
if kind == 'directory':
646
inv.add(inventory.InventoryDirectory(file_id, name, parent))
648
inv.add(inventory.InventoryFile(file_id, name, parent))
649
elif kind == 'symlink':
650
inv.add(inventory.InventoryLink(file_id, name, parent))
652
raise BzrError("unknown kind %r" % kind)
661
inv.add(InventoryEntry(file_id, name, kind, parent))
653
662
self._write_inventory(inv)
655
664
def unknowns(self):
658
667
These are files in the working directory that are not versioned or
659
668
control files or ignored.
661
>>> from bzrlib.workingtree import WorkingTree
662
670
>>> b = ScratchBranch(files=['foo', 'foo~'])
663
>>> map(str, b.unknowns())
671
>>> list(b.unknowns())
666
674
>>> list(b.unknowns())
668
>>> WorkingTree(b.base, b).remove('foo')
669
677
>>> list(b.unknowns())
672
680
return self.working_tree().unknowns()
675
683
def append_revision(self, *revision_ids):
676
684
for revision_id in revision_ids:
677
685
mutter("add {%s} to revision-history" % revision_id)
678
rev_history = self.revision_history()
679
rev_history.extend(revision_ids)
680
self.set_revision_history(rev_history)
683
def set_revision_history(self, rev_history):
684
self.put_controlfile('revision-history', '\n'.join(rev_history))
688
rev_history = self.revision_history()
689
rev_history.extend(revision_ids)
690
self.put_controlfile('revision-history', '\n'.join(rev_history))
686
694
def has_revision(self, revision_id):
687
695
"""True if this branch has a copy of the revision.
689
697
This does not necessarily imply the revision is merge
690
698
or on the mainline."""
691
699
return (revision_id is None
692
or self.revision_store.has_id(revision_id))
700
or revision_id in self.revision_store)
695
702
def get_revision_xml_file(self, revision_id):
696
703
"""Return XML file object for revision object."""
697
704
if not revision_id or not isinstance(revision_id, basestring):
698
raise InvalidRevisionId(revision_id=revision_id, branch=self)
705
raise InvalidRevisionId(revision_id)
700
return self.revision_store.get(revision_id)
701
except (IndexError, KeyError):
702
raise bzrlib.errors.NoSuchRevision(self, revision_id)
710
return self.revision_store[revision_id]
711
except (IndexError, KeyError):
712
raise bzrlib.errors.NoSuchRevision(self, revision_id)
705
717
get_revision_xml = get_revision_xml_file
753
765
# But for now, just hash the contents.
754
766
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
768
def _get_ancestry_weave(self):
769
return self.control_weaves.get_weave('ancestry')
756
771
def get_ancestry(self, revision_id):
757
772
"""Return a list of revision-ids integrated by a revision.
759
This currently returns a list, but the ordering is not guaranteed:
762
775
if revision_id is None:
764
w = self.get_inventory_weave()
765
return [None] + map(w.idx_to_name,
766
w.inclusions([w.lookup(revision_id)]))
777
w = self._get_ancestry_weave()
778
return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
768
780
def get_inventory_weave(self):
769
return self.control_weaves.get_weave('inventory',
770
self.get_transaction())
781
return self.control_weaves.get_weave('inventory')
772
783
def get_inventory(self, revision_id):
773
784
"""Get Inventory object by hash."""
799
810
return self.get_inventory(revision_id)
802
812
def revision_history(self):
803
813
"""Return sequence of revision hashes on to this branch."""
804
transaction = self.get_transaction()
805
history = transaction.map.find_revision_history()
806
if history is not None:
807
mutter("cache hit for revision-history in %s", self)
809
history = [l.rstrip('\r\n') for l in
810
self.controlfile('revision-history', 'r').readlines()]
811
transaction.map.add_revision_history(history)
812
# this call is disabled because revision_history is
813
# not really an object yet, and the transaction is for objects.
814
# transaction.register_clean(history, precious=True)
816
return [l.rstrip('\r\n') for l in
817
self.controlfile('revision-history', 'r').readlines()]
821
def common_ancestor(self, other, self_revno=None, other_revno=None):
823
>>> from bzrlib.commit import commit
824
>>> sb = ScratchBranch(files=['foo', 'foo~'])
825
>>> sb.common_ancestor(sb) == (None, None)
827
>>> commit(sb, "Committing first revision", verbose=False)
828
>>> sb.common_ancestor(sb)[0]
830
>>> clone = sb.clone()
831
>>> commit(sb, "Committing second revision", verbose=False)
832
>>> sb.common_ancestor(sb)[0]
834
>>> sb.common_ancestor(clone)[0]
836
>>> commit(clone, "Committing divergent second revision",
838
>>> sb.common_ancestor(clone)[0]
840
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
842
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
844
>>> clone2 = sb.clone()
845
>>> sb.common_ancestor(clone2)[0]
847
>>> sb.common_ancestor(clone2, self_revno=1)[0]
849
>>> sb.common_ancestor(clone2, other_revno=1)[0]
852
my_history = self.revision_history()
853
other_history = other.revision_history()
854
if self_revno is None:
855
self_revno = len(my_history)
856
if other_revno is None:
857
other_revno = len(other_history)
858
indices = range(min((self_revno, other_revno)))
861
if my_history[r] == other_history[r]:
862
return r+1, my_history[r]
818
867
"""Return current revision number for this branch.
879
933
def update_revisions(self, other, stop_revision=None):
880
934
"""Pull in new perfect-fit revisions."""
881
935
from bzrlib.fetch import greedy_fetch
936
from bzrlib.revision import get_intervening_revisions
882
937
if stop_revision is None:
883
938
stop_revision = other.last_revision()
884
### Should this be checking is_ancestor instead of revision_history?
885
if (stop_revision is not None and
886
stop_revision in self.revision_history()):
888
939
greedy_fetch(to_branch=self, from_branch=other,
889
940
revision=stop_revision)
890
pullable_revs = self.pullable_revisions(other, stop_revision)
891
if len(pullable_revs) > 0:
941
pullable_revs = self.missing_revisions(
942
other, other.revision_id_to_revno(stop_revision))
944
greedy_fetch(to_branch=self,
946
revision=pullable_revs[-1])
892
947
self.append_revision(*pullable_revs)
894
def pullable_revisions(self, other, stop_revision):
895
other_revno = other.revision_id_to_revno(stop_revision)
897
return self.missing_revisions(other, other_revno)
898
except DivergedBranches, e:
900
pullable_revs = get_intervening_revisions(self.last_revision(),
902
assert self.last_revision() not in pullable_revs
904
except bzrlib.errors.NotAncestor:
905
if is_ancestor(self.last_revision(), stop_revision, self):
910
950
def commit(self, *args, **kw):
911
951
from bzrlib.commit import Commit
912
952
Commit().commit(self, *args, **kw)
938
978
an `EmptyTree` is returned."""
939
979
# TODO: refactor this to use an existing revision object
940
980
# so we don't need to read it in twice.
941
if revision_id == None or revision_id == NULL_REVISION:
981
if revision_id == None:
942
982
return EmptyTree()
944
984
inv = self.get_revision_inventory(revision_id)
945
985
return RevisionTree(self.weave_store, inv, revision_id)
947
988
def working_tree(self):
948
989
"""Return a `Tree` for the working copy."""
949
990
from bzrlib.workingtree import WorkingTree
950
# TODO: In the future, perhaps WorkingTree should utilize Transport
951
# RobertCollins 20051003 - I don't think it should - working trees are
952
# much more complex to keep consistent than our careful .bzr subset.
953
# instead, we should say that working trees are local only, and optimise
955
return WorkingTree(self.base, branch=self)
991
# TODO: In the future, WorkingTree should utilize Transport
992
return WorkingTree(self._transport.base, self.read_working_inventory())
958
def pull(self, source, overwrite=False):
962
self.update_revisions(source)
963
except DivergedBranches:
966
self.set_revision_history(source.revision_history())
970
995
def basis_tree(self):
971
996
"""Return `Tree` object for last revision.
975
1000
return self.revision_tree(self.last_revision())
978
1003
def rename_one(self, from_rel, to_rel):
979
1004
"""Rename one file.
981
1006
This can change the directory or the filename or both.
983
tree = self.working_tree()
985
if not tree.has_filename(from_rel):
986
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
987
if tree.has_filename(to_rel):
988
raise BzrError("can't rename: new working file %r already exists" % to_rel)
990
file_id = inv.path2id(from_rel)
992
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
994
if inv.path2id(to_rel):
995
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
997
to_dir, to_tail = os.path.split(to_rel)
998
to_dir_id = inv.path2id(to_dir)
999
if to_dir_id == None and to_dir != '':
1000
raise BzrError("can't determine destination directory id for %r" % to_dir)
1002
mutter("rename_one:")
1003
mutter(" file_id {%s}" % file_id)
1004
mutter(" from_rel %r" % from_rel)
1005
mutter(" to_rel %r" % to_rel)
1006
mutter(" to_dir %r" % to_dir)
1007
mutter(" to_dir_id {%s}" % to_dir_id)
1009
inv.rename(file_id, to_dir_id, to_tail)
1011
from_abs = self.abspath(from_rel)
1012
to_abs = self.abspath(to_rel)
1014
rename(from_abs, to_abs)
1016
raise BzrError("failed to rename %r to %r: %s"
1017
% (from_abs, to_abs, e[1]),
1018
["rename rolled back"])
1020
self._write_inventory(inv)
1010
tree = self.working_tree()
1011
inv = tree.inventory
1012
if not tree.has_filename(from_rel):
1013
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1014
if tree.has_filename(to_rel):
1015
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1017
file_id = inv.path2id(from_rel)
1019
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1021
if inv.path2id(to_rel):
1022
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1024
to_dir, to_tail = os.path.split(to_rel)
1025
to_dir_id = inv.path2id(to_dir)
1026
if to_dir_id == None and to_dir != '':
1027
raise BzrError("can't determine destination directory id for %r" % to_dir)
1029
mutter("rename_one:")
1030
mutter(" file_id {%s}" % file_id)
1031
mutter(" from_rel %r" % from_rel)
1032
mutter(" to_rel %r" % to_rel)
1033
mutter(" to_dir %r" % to_dir)
1034
mutter(" to_dir_id {%s}" % to_dir_id)
1036
inv.rename(file_id, to_dir_id, to_tail)
1038
from_abs = self.abspath(from_rel)
1039
to_abs = self.abspath(to_rel)
1041
rename(from_abs, to_abs)
1043
raise BzrError("failed to rename %r to %r: %s"
1044
% (from_abs, to_abs, e[1]),
1045
["rename rolled back"])
1047
self._write_inventory(inv)
1023
1052
def move(self, from_paths, to_name):
1024
1053
"""Rename files.
1035
1064
entry that is moved.
1038
## TODO: Option to move IDs only
1039
assert not isinstance(from_paths, basestring)
1040
tree = self.working_tree()
1041
inv = tree.inventory
1042
to_abs = self.abspath(to_name)
1043
if not isdir(to_abs):
1044
raise BzrError("destination %r is not a directory" % to_abs)
1045
if not tree.has_filename(to_name):
1046
raise BzrError("destination %r not in working directory" % to_abs)
1047
to_dir_id = inv.path2id(to_name)
1048
if to_dir_id == None and to_name != '':
1049
raise BzrError("destination %r is not a versioned directory" % to_name)
1050
to_dir_ie = inv[to_dir_id]
1051
if to_dir_ie.kind not in ('directory', 'root_directory'):
1052
raise BzrError("destination %r is not a directory" % to_abs)
1054
to_idpath = inv.get_idpath(to_dir_id)
1056
for f in from_paths:
1057
if not tree.has_filename(f):
1058
raise BzrError("%r does not exist in working tree" % f)
1059
f_id = inv.path2id(f)
1061
raise BzrError("%r is not versioned" % f)
1062
name_tail = splitpath(f)[-1]
1063
dest_path = appendpath(to_name, name_tail)
1064
if tree.has_filename(dest_path):
1065
raise BzrError("destination %r already exists" % dest_path)
1066
if f_id in to_idpath:
1067
raise BzrError("can't move %r to a subdirectory of itself" % f)
1069
# OK, so there's a race here, it's possible that someone will
1070
# create a file in this interval and then the rename might be
1071
# left half-done. But we should have caught most problems.
1073
for f in from_paths:
1074
name_tail = splitpath(f)[-1]
1075
dest_path = appendpath(to_name, name_tail)
1076
result.append((f, dest_path))
1077
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1079
rename(self.abspath(f), self.abspath(dest_path))
1081
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1082
["rename rolled back"])
1084
self._write_inventory(inv)
1069
## TODO: Option to move IDs only
1070
assert not isinstance(from_paths, basestring)
1071
tree = self.working_tree()
1072
inv = tree.inventory
1073
to_abs = self.abspath(to_name)
1074
if not isdir(to_abs):
1075
raise BzrError("destination %r is not a directory" % to_abs)
1076
if not tree.has_filename(to_name):
1077
raise BzrError("destination %r not in working directory" % to_abs)
1078
to_dir_id = inv.path2id(to_name)
1079
if to_dir_id == None and to_name != '':
1080
raise BzrError("destination %r is not a versioned directory" % to_name)
1081
to_dir_ie = inv[to_dir_id]
1082
if to_dir_ie.kind not in ('directory', 'root_directory'):
1083
raise BzrError("destination %r is not a directory" % to_abs)
1085
to_idpath = inv.get_idpath(to_dir_id)
1087
for f in from_paths:
1088
if not tree.has_filename(f):
1089
raise BzrError("%r does not exist in working tree" % f)
1090
f_id = inv.path2id(f)
1092
raise BzrError("%r is not versioned" % f)
1093
name_tail = splitpath(f)[-1]
1094
dest_path = appendpath(to_name, name_tail)
1095
if tree.has_filename(dest_path):
1096
raise BzrError("destination %r already exists" % dest_path)
1097
if f_id in to_idpath:
1098
raise BzrError("can't move %r to a subdirectory of itself" % f)
1100
# OK, so there's a race here, it's possible that someone will
1101
# create a file in this interval and then the rename might be
1102
# left half-done. But we should have caught most problems.
1104
for f in from_paths:
1105
name_tail = splitpath(f)[-1]
1106
dest_path = appendpath(to_name, name_tail)
1107
result.append((f, dest_path))
1108
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1110
rename(self.abspath(f), self.abspath(dest_path))
1112
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1113
["rename rolled back"])
1115
self._write_inventory(inv)
1180
def get_push_location(self):
1181
"""Return the None or the location to push this branch to."""
1182
config = bzrlib.config.BranchConfig(self)
1183
push_loc = config.get_user_option('push_location')
1186
def set_push_location(self, location):
1187
"""Set a new push location for this branch."""
1188
config = bzrlib.config.LocationConfig(self.base)
1189
config.set_user_option('push_location', location)
1192
1223
def set_parent(self, url):
1193
1224
# TODO: Maybe delete old location files?
1194
1225
from bzrlib.atomicfile import AtomicFile
1195
f = AtomicFile(self.controlfilename('parent'))
1228
f = AtomicFile(self.controlfilename('parent'))
1202
1237
def check_revno(self, revno):
1215
1250
if revno < 1 or revno > self.revno():
1216
1251
raise InvalidRevisionNumber(revno)
1218
def sign_revision(self, revision_id, gpg_strategy):
1219
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1220
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1223
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1224
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1228
1257
class ScratchBranch(_Branch):
1232
1261
>>> isdir(b.base)
1234
1263
>>> bd = b.base
1235
>>> b._transport.__del__()
1240
def __init__(self, files=[], dirs=[], transport=None):
1268
def __init__(self, files=[], dirs=[], base=None):
1241
1269
"""Make a test branch.
1243
1271
This creates a temporary directory and runs init-tree in it.
1245
1273
If any files are listed, they are created in the working copy.
1247
if transport is None:
1248
transport = bzrlib.transport.local.ScratchTransport()
1249
super(ScratchBranch, self).__init__(transport, init=True)
1251
super(ScratchBranch, self).__init__(transport)
1275
from tempfile import mkdtemp
1280
if isinstance(base, basestring):
1281
base = get_transport(base)
1282
_Branch.__init__(self, base, init=init)
1254
1284
self._transport.mkdir(d)
1275
1305
base = mkdtemp()
1277
1307
copytree(self.base, base, symlinks=True)
1278
return ScratchBranch(
1279
transport=bzrlib.transport.local.ScratchTransport(base))
1308
return ScratchBranch(base=base)
1314
"""Destroy the test branch, removing the scratch directory."""
1315
from shutil import rmtree
1318
mutter("delete ScratchBranch %s" % self.base)
1321
# Work around for shutil.rmtree failing on Windows when
1322
# readonly files are encountered
1323
mutter("hit exception in destroying ScratchBranch: %s" % e)
1324
for root, dirs, files in os.walk(self.base, topdown=False):
1326
os.chmod(os.path.join(root, name), 0700)
1328
self._transport = None
1282
1332
######################################################################