33
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
35
NoSuchRevision, HistoryMissing, NotBranchError,
35
36
DivergedBranches, LockError, UnlistableStore,
36
UnlistableBranch, NoSuchFile, NotVersionedError)
37
UnlistableBranch, NoSuchFile, NotVersionedError,
37
39
from bzrlib.textui import show_status
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
40
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
40
43
from bzrlib.delta import compare_trees
41
44
from bzrlib.tree import EmptyTree, RevisionTree
208
211
"""Create new branch object at a particular location.
210
213
transport -- A Transport object, defining how to access files.
211
(If a string, transport.transport() will be used to
212
create a Transport object)
214
215
init -- If True, create new control files in a previously
215
216
unversioned directory. If False, the branch must already
318
316
"""Return the current active transaction.
320
318
If no transaction is active, this returns a passthrough object
321
for which all data is immedaitely flushed and no caching happens.
319
for which all data is immediately flushed and no caching happens.
323
321
if self._transaction is None:
324
322
return transactions.PassThroughTransaction()
417
414
elif mode == 'wb':
418
415
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
419
416
elif mode == 'r':
417
# XXX: Do we really want errors='replace'? Perhaps it should be
418
# an error, or at least reported, if there's incorrectly-encoded
419
# data inside a file.
420
# <https://launchpad.net/products/bzr/+bug/3823>
420
421
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
421
422
elif mode == 'w':
422
423
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
518
519
def get_root_id(self):
519
520
"""Return the id of this branches root"""
520
inv = self.read_working_inventory()
521
inv = self.get_inventory(self.last_revision())
521
522
return inv.root.file_id
523
def set_root_id(self, file_id):
524
inv = self.read_working_inventory()
525
orig_root_id = inv.root.file_id
526
del inv._byid[inv.root.file_id]
527
inv.root.file_id = file_id
528
inv._byid[inv.root.file_id] = inv.root
531
if entry.parent_id in (None, orig_root_id):
532
entry.parent_id = inv.root.file_id
533
self._write_inventory(inv)
536
def read_working_inventory(self):
537
"""Read the working inventory."""
538
# ElementTree does its own conversion from UTF-8, so open in
540
f = self.controlfile('inventory', 'rb')
541
return bzrlib.xml5.serializer_v5.read_inventory(f)
544
def _write_inventory(self, inv):
545
"""Update the working inventory.
547
That is to say, the inventory describing changes underway, that
548
will be committed to the next revision.
550
from cStringIO import StringIO
552
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
554
# Transport handles atomicity
555
self.put_controlfile('inventory', sio)
557
mutter('wrote working inventory')
559
inventory = property(read_working_inventory, _write_inventory, None,
560
"""Inventory for the working copy.""")
562
524
@needs_write_lock
563
525
def add(self, files, ids=None):
564
526
"""Make files versioned.
596
558
assert(len(ids) == len(files))
598
inv = self.read_working_inventory()
560
inv = self.working_tree().read_working_inventory()
599
561
for f,file_id in zip(files, ids):
600
562
if is_control_file(f):
601
563
raise BzrError("cannot add control file %s" % quotefn(f))
624
586
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
626
self._write_inventory(inv)
588
self.working_tree()._write_inventory(inv)
629
591
def print_file(self, file, revno):
635
597
raise BzrError("%r is not present in revision %s" % (file, revno))
636
598
tree.print_file(file_id)
638
# FIXME: this doesn't need to be a branch method
639
def set_inventory(self, new_inventory_list):
640
from bzrlib.inventory import Inventory, InventoryEntry
641
inv = Inventory(self.get_root_id())
642
for path, file_id, parent, kind in new_inventory_list:
643
name = os.path.basename(path)
646
# fixme, there should be a factory function inv,add_??
647
if kind == 'directory':
648
inv.add(inventory.InventoryDirectory(file_id, name, parent))
650
inv.add(inventory.InventoryFile(file_id, name, parent))
651
elif kind == 'symlink':
652
inv.add(inventory.InventoryLink(file_id, name, parent))
654
raise BzrError("unknown kind %r" % kind)
655
self._write_inventory(inv)
657
600
def unknowns(self):
658
601
"""Return all unknown files.
697
640
def get_revision_xml_file(self, revision_id):
698
641
"""Return XML file object for revision object."""
699
642
if not revision_id or not isinstance(revision_id, basestring):
700
raise InvalidRevisionId(revision_id=revision_id)
643
raise InvalidRevisionId(revision_id=revision_id, branch=self)
702
645
return self.revision_store.get(revision_id)
703
646
except (IndexError, KeyError):
796
739
# bzr 0.0.6 and later imposes the constraint that the inventory_id
797
740
# must be the same as its revision, so this is trivial.
798
741
if revision_id == None:
799
return Inventory(self.get_root_id())
742
# This does not make sense: if there is no revision,
743
# then it is the current tree inventory surely ?!
744
# and thus get_root_id() is something that looks at the last
745
# commit on the branch, and the get_root_id is an inventory check.
746
raise NotImplementedError
747
# return Inventory(self.get_root_id())
801
749
return self.get_inventory(revision_id)
881
829
def update_revisions(self, other, stop_revision=None):
882
830
"""Pull in new perfect-fit revisions."""
883
# FIXME: If the branches have diverged, but the latest
884
# revision in this branch is completely merged into the other,
885
# then we should still be able to pull.
886
831
from bzrlib.fetch import greedy_fetch
887
832
if stop_revision is None:
888
833
stop_revision = other.last_revision()
915
def commit(self, *args, **kw):
916
from bzrlib.commit import Commit
917
Commit().commit(self, *args, **kw)
919
860
def revision_id_to_revno(self, revision_id):
920
861
"""Given a revision id, return its revno"""
921
862
if revision_id is None:
957
898
# much more complex to keep consistent than our careful .bzr subset.
958
899
# instead, we should say that working trees are local only, and optimise
901
if self._transport.base.find('://') != -1:
902
raise NoWorkingTree(self.base)
960
903
return WorkingTree(self.base, branch=self)
906
def pull(self, source, overwrite=False):
910
self.update_revisions(source)
911
except DivergedBranches:
914
self.set_revision_history(source.revision_history())
963
918
def basis_tree(self):
964
919
"""Return `Tree` object for last revision.
1010
965
% (from_abs, to_abs, e[1]),
1011
966
["rename rolled back"])
1013
self._write_inventory(inv)
968
self.working_tree()._write_inventory(inv)
1015
970
@needs_write_lock
1016
971
def move(self, from_paths, to_name):
1074
1029
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1075
1030
["rename rolled back"])
1077
self._write_inventory(inv)
1032
self.working_tree()._write_inventory(inv)
1081
def revert(self, filenames, old_tree=None, backups=True):
1082
"""Restore selected files to the versions from a previous tree.
1085
If true (default) backups are made of files before
1088
from bzrlib.atomicfile import AtomicFile
1089
from bzrlib.osutils import backup_file
1091
inv = self.read_working_inventory()
1092
if old_tree is None:
1093
old_tree = self.basis_tree()
1094
old_inv = old_tree.inventory
1097
for fn in filenames:
1098
file_id = inv.path2id(fn)
1100
raise NotVersionedError(path=fn)
1101
if not old_inv.has_id(file_id):
1102
raise BzrError("file not present in old tree", fn, file_id)
1103
nids.append((fn, file_id))
1105
# TODO: Rename back if it was previously at a different location
1107
# TODO: If given a directory, restore the entire contents from
1108
# the previous version.
1110
# TODO: Make a backup to a temporary file.
1112
# TODO: If the file previously didn't exist, delete it?
1113
for fn, file_id in nids:
1116
f = AtomicFile(fn, 'wb')
1118
f.write(old_tree.get_file(file_id).read())
1124
def pending_merges(self):
1125
"""Return a list of pending merges.
1127
These are revisions that have been merged into the working
1128
directory but not yet committed.
1130
cfn = self._rel_controlfilename('pending-merges')
1131
if not self._transport.has(cfn):
1134
for l in self.controlfile('pending-merges', 'r').readlines():
1135
p.append(l.rstrip('\n'))
1139
def add_pending_merge(self, *revision_ids):
1140
# TODO: Perhaps should check at this point that the
1141
# history of the revision is actually present?
1142
p = self.pending_merges()
1144
for rev_id in revision_ids:
1150
self.set_pending_merges(p)
1153
def set_pending_merges(self, rev_list):
1154
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1156
1035
def get_parent(self):
1157
1036
"""Return the parent location of the branch.
1052
def get_push_location(self):
1053
"""Return the None or the location to push this branch to."""
1054
config = bzrlib.config.BranchConfig(self)
1055
push_loc = config.get_user_option('push_location')
1058
def set_push_location(self, location):
1059
"""Set a new push location for this branch."""
1060
config = bzrlib.config.LocationConfig(self.base)
1061
config.set_user_option('push_location', location)
1173
1063
@needs_write_lock
1174
1064
def set_parent(self, url):
1175
1065
# TODO: Maybe delete old location files?