15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
21
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
25
23
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
24
from bzrlib.errors import BzrError
34
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
27
## 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.
42
# TODO: please move the revision-string syntax stuff out of the branch
43
# object; it's clutter
46
31
def find_branch(f, **args):
47
32
if f and (f.startswith('http://') or f.startswith('https://')):
133
118
Exception.__init__(self, "These branches have diverged.")
121
class NoSuchRevision(BzrError):
122
def __init__(self, branch, revision):
124
self.revision = revision
125
msg = "Branch %s has no revision %d" % (branch, revision)
126
BzrError.__init__(self, msg)
136
129
######################################################################
319
312
self.controlfile(f, 'w').write('')
320
313
mutter('created control directory in ' + self.base)
322
# if we want per-tree root ids then this is the place to set
323
# them; they're not needed for now and so ommitted for
325
315
pack_xml(Inventory(), self.controlfile('inventory','w'))
343
333
['use a different bzr version',
344
334
'or remove the .bzr directory and "bzr init" again'])
346
def get_root_id(self):
347
"""Return the id of this branches root"""
348
inv = self.read_working_inventory()
349
return inv.root.file_id
351
def set_root_id(self, file_id):
352
inv = self.read_working_inventory()
353
orig_root_id = inv.root.file_id
354
del inv._byid[inv.root.file_id]
355
inv.root.file_id = file_id
356
inv._byid[inv.root.file_id] = inv.root
359
if entry.parent_id in (None, orig_root_id):
360
entry.parent_id = inv.root.file_id
361
self._write_inventory(inv)
363
338
def read_working_inventory(self):
364
339
"""Read the working inventory."""
371
346
# ElementTree does its own conversion from UTF-8, so open in
373
348
inv = unpack_xml(Inventory,
374
self.controlfile('inventory', 'rb'))
349
self.controlfile('inventory', 'rb'))
375
350
mutter("loaded inventory of %d items in %f"
376
351
% (len(inv), time() - before))
432
407
add all non-ignored children. Perhaps do that in a
433
408
higher-level method.
410
from bzrlib.textui import show_status
435
411
# TODO: Re-adding a file that is removed in the working copy
436
412
# should probably put it back with the previous ID.
437
413
if isinstance(files, basestring):
510
486
is the opposite of add. Removing it is consistent with most
511
487
other tools. Maybe an option.
489
from bzrlib.textui import show_status
513
490
## TODO: Normalize names
514
491
## TODO: Remove nested loops; better scalability
515
492
if isinstance(files, basestring):
544
521
# FIXME: this doesn't need to be a branch method
545
522
def set_inventory(self, new_inventory_list):
546
523
from bzrlib.inventory import Inventory, InventoryEntry
547
inv = Inventory(self.get_root_id())
548
525
for path, file_id, parent, kind in new_inventory_list:
549
526
name = os.path.basename(path)
572
549
return self.working_tree().unknowns()
575
def append_revision(self, *revision_ids):
552
def append_revision(self, revision_id):
576
553
from bzrlib.atomicfile import AtomicFile
578
for revision_id in revision_ids:
579
mutter("add {%s} to revision-history" % revision_id)
581
rev_history = self.revision_history()
582
rev_history.extend(revision_ids)
555
mutter("add {%s} to revision-history" % revision_id)
556
rev_history = self.revision_history() + [revision_id]
584
558
f = AtomicFile(self.controlfilename('revision-history'))
593
def get_revision_xml(self, revision_id):
594
"""Return XML file object for revision object."""
595
if not revision_id or not isinstance(revision_id, basestring):
596
raise InvalidRevisionId(revision_id)
601
return self.revision_store[revision_id]
603
raise bzrlib.errors.NoSuchRevision(self, revision_id)
608
567
def get_revision(self, revision_id):
609
568
"""Return the Revision object for a named revision"""
610
xml_file = self.get_revision_xml(revision_id)
569
from bzrlib.revision import Revision
570
from bzrlib.xml import unpack_xml
613
r = unpack_xml(Revision, xml_file)
614
except SyntaxError, e:
615
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
574
if not revision_id or not isinstance(revision_id, basestring):
575
raise ValueError('invalid revision-id: %r' % revision_id)
576
r = unpack_xml(Revision, self.revision_store[revision_id])
619
580
assert r.revision_id == revision_id
623
def get_revision_delta(self, revno):
624
"""Return the delta for one revision.
626
The delta is relative to its mainline predecessor, or the
627
empty tree for revision 1.
629
assert isinstance(revno, int)
630
rh = self.revision_history()
631
if not (1 <= revno <= len(rh)):
632
raise InvalidRevisionNumber(revno)
634
# revno is 1-based; list is 0-based
636
new_tree = self.revision_tree(rh[revno-1])
638
old_tree = EmptyTree()
640
old_tree = self.revision_tree(rh[revno-2])
642
return compare_trees(old_tree, new_tree)
646
584
def get_revision_sha1(self, revision_id):
651
589
# the revision, (add signatures/remove signatures) and still
652
590
# have all hash pointers stay consistent.
653
591
# But for now, just hash the contents.
654
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
592
return sha_file(self.revision_store[revision_id])
657
595
def get_inventory(self, inventory_id):
663
601
from bzrlib.inventory import Inventory
664
602
from bzrlib.xml import unpack_xml
666
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
669
def get_inventory_xml(self, inventory_id):
670
"""Get inventory XML as a file object."""
671
return self.inventory_store[inventory_id]
604
return unpack_xml(Inventory, self.inventory_store[inventory_id])
674
607
def get_inventory_sha1(self, inventory_id):
675
608
"""Return the sha1 hash of the inventory entry
677
return sha_file(self.get_inventory_xml(inventory_id))
610
return sha_file(self.inventory_store[inventory_id])
680
613
def get_revision_inventory(self, revision_id):
683
616
# must be the same as its revision, so this is trivial.
684
617
if revision_id == None:
685
618
from bzrlib.inventory import Inventory
686
return Inventory(self.get_root_id())
688
621
return self.get_inventory(revision_id)
746
679
return r+1, my_history[r]
747
680
return None, None
682
def enum_history(self, direction):
683
"""Return (revno, revision_id) for history of branch.
686
'forward' is from earliest to latest
687
'reverse' is from latest to earliest
689
rh = self.revision_history()
690
if direction == 'forward':
695
elif direction == 'reverse':
701
raise ValueError('invalid history direction', direction)
751
705
"""Return current revision number for this branch.
769
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
723
def missing_revisions(self, other, stop_revision=None):
771
725
If self and other have not diverged, return a list of the revisions
772
726
present in other, but missing from self.
810
764
return other_history[self_len:stop_revision]
813
def update_revisions(self, other, stop_revision=None, revision_ids=None):
767
def update_revisions(self, other, stop_revision=None):
814
768
"""Pull in all new revisions from other branch.
816
770
>>> from bzrlib.commit import commit
836
790
from bzrlib.progress import ProgressBar
794
from sets import Set as set
838
796
pb = ProgressBar()
840
798
pb.update('comparing histories')
841
if revision_ids is None:
842
revision_ids = self.missing_revisions(other, stop_revision)
799
revision_ids = self.missing_revisions(other, stop_revision)
844
801
if hasattr(other.revision_store, "prefetch"):
845
802
other.revision_store.prefetch(revision_ids)
1048
1004
`revision_id` may be None for the null revision, in which case
1049
1005
an `EmptyTree` is returned."""
1006
from bzrlib.tree import EmptyTree, RevisionTree
1050
1007
# TODO: refactor this to use an existing revision object
1051
1008
# so we don't need to read it in twice.
1052
1009
if revision_id == None:
1393
1351
s = hexlify(rand_bytes(8))
1394
1352
return '-'.join((name, compact_date(time()), s))
1398
"""Return a new tree-root file id."""
1399
return gen_file_id('TREE_ROOT')