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
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
29
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision
31
from bzrlib.xml import unpack_xml
32
from bzrlib.delta import compare_trees
33
from bzrlib.tree import EmptyTree, RevisionTree
24
from bzrlib.errors import BzrError
38
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
27
## TODO: Maybe include checks for common corruption of newlines, etc?
42
# TODO: Some operations like log might retrieve the same revisions
43
# repeatedly to calculate deltas. We could perhaps have a weakref
44
# cache in memory to make this faster.
46
# TODO: please move the revision-string syntax stuff out of the branch
47
# object; it's clutter
50
31
def find_branch(f, **args):
51
32
if f and (f.startswith('http://') or f.startswith('https://')):
128
108
head, tail = os.path.split(f)
130
110
# reached the root, whatever that may be
131
raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
111
raise BzrError('%r is not in a branch' % orig_f)
136
# XXX: move into bzrlib.errors; subclass BzrError
137
114
class DivergedBranches(Exception):
138
115
def __init__(self, branch1, branch2):
139
116
self.branch1 = branch1
141
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)
144
129
######################################################################
327
312
self.controlfile(f, 'w').write('')
328
313
mutter('created control directory in ' + self.base)
330
# if we want per-tree root ids then this is the place to set
331
# them; they're not needed for now and so ommitted for
333
pack_xml(Inventory(), self.controlfile('inventory','w'))
315
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
336
318
def _check_format(self):
414
396
"""Inventory for the working copy.""")
417
def add(self, files, ids=None):
399
def add(self, files, verbose=False, ids=None):
418
400
"""Make files versioned.
420
Note that the command line normally calls smart_add instead,
421
which can automatically recurse.
402
Note that the command line normally calls smart_add instead.
423
404
This puts the files in the Added state, so that they will be
424
405
recorded by the next commit.
434
415
TODO: Perhaps have an option to add the ids even if the files do
437
TODO: Perhaps yield the ids and paths as they're added.
418
TODO: Perhaps return the ids of the files? But then again it
419
is easy to retrieve them if they're needed.
421
TODO: Adding a directory should optionally recurse down and
422
add all non-ignored children. Perhaps do that in a
425
from bzrlib.textui import show_status
439
426
# TODO: Re-adding a file that is removed in the working copy
440
427
# should probably put it back with the previous ID.
441
428
if isinstance(files, basestring):
594
def get_revision_xml(self, revision_id):
595
"""Return XML file object for revision object."""
596
if not revision_id or not isinstance(revision_id, basestring):
597
raise InvalidRevisionId(revision_id)
602
return self.revision_store[revision_id]
604
raise bzrlib.errors.NoSuchRevision(self, revision_id)
609
585
def get_revision(self, revision_id):
610
586
"""Return the Revision object for a named revision"""
611
xml_file = self.get_revision_xml(revision_id)
587
from bzrlib.revision import Revision
588
from bzrlib.xml import unpack_xml
614
r = unpack_xml(Revision, xml_file)
615
except SyntaxError, e:
616
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
592
if not revision_id or not isinstance(revision_id, basestring):
593
raise ValueError('invalid revision-id: %r' % revision_id)
594
r = unpack_xml(Revision, self.revision_store[revision_id])
620
598
assert r.revision_id == revision_id
624
def get_revision_delta(self, revno):
625
"""Return the delta for one revision.
627
The delta is relative to its mainline predecessor, or the
628
empty tree for revision 1.
630
assert isinstance(revno, int)
631
rh = self.revision_history()
632
if not (1 <= revno <= len(rh)):
633
raise InvalidRevisionNumber(revno)
635
# revno is 1-based; list is 0-based
637
new_tree = self.revision_tree(rh[revno-1])
639
old_tree = EmptyTree()
641
old_tree = self.revision_tree(rh[revno-2])
643
return compare_trees(old_tree, new_tree)
647
602
def get_revision_sha1(self, revision_id):
652
607
# the revision, (add signatures/remove signatures) and still
653
608
# have all hash pointers stay consistent.
654
609
# But for now, just hash the contents.
655
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
610
return sha_file(self.revision_store[revision_id])
658
613
def get_inventory(self, inventory_id):
664
619
from bzrlib.inventory import Inventory
665
620
from bzrlib.xml import unpack_xml
667
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
670
def get_inventory_xml(self, inventory_id):
671
"""Get inventory XML as a file object."""
672
return self.inventory_store[inventory_id]
622
return unpack_xml(Inventory, self.inventory_store[inventory_id])
675
625
def get_inventory_sha1(self, inventory_id):
676
626
"""Return the sha1 hash of the inventory entry
678
return sha_file(self.get_inventory_xml(inventory_id))
628
return sha_file(self.inventory_store[inventory_id])
681
631
def get_revision_inventory(self, revision_id):
747
697
return r+1, my_history[r]
748
698
return None, None
700
def enum_history(self, direction):
701
"""Return (revno, revision_id) for history of branch.
704
'forward' is from earliest to latest
705
'reverse' is from latest to earliest
707
rh = self.revision_history()
708
if direction == 'forward':
713
elif direction == 'reverse':
719
raise ValueError('invalid history direction', direction)
752
723
"""Return current revision number for this branch.
770
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
741
def missing_revisions(self, other, stop_revision=None):
772
743
If self and other have not diverged, return a list of the revisions
773
744
present in other, but missing from self.
806
777
if stop_revision is None:
807
778
stop_revision = other_len
808
779
elif stop_revision > other_len:
809
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
780
raise NoSuchRevision(self, stop_revision)
811
782
return other_history[self_len:stop_revision]
814
785
def update_revisions(self, other, stop_revision=None):
815
786
"""Pull in all new revisions from other branch.
788
>>> from bzrlib.commit import commit
789
>>> bzrlib.trace.silent = True
790
>>> br1 = ScratchBranch(files=['foo', 'bar'])
793
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
794
>>> br2 = ScratchBranch()
795
>>> br2.update_revisions(br1)
799
>>> br2.revision_history()
801
>>> br2.update_revisions(br1)
805
>>> br1.text_store.total_size() == br2.text_store.total_size()
817
from bzrlib.fetch import greedy_fetch
819
pb = bzrlib.ui.ui_factory.progress_bar()
808
from bzrlib.progress import ProgressBar
820
812
pb.update('comparing histories')
822
813
revision_ids = self.missing_revisions(other, stop_revision)
824
if len(revision_ids) > 0:
825
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
828
self.append_revision(*revision_ids)
829
## note("Added %d revisions." % count)
834
def install_revisions(self, other, revision_ids, pb):
835
815
if hasattr(other.revision_store, "prefetch"):
836
816
other.revision_store.prefetch(revision_ids)
837
817
if hasattr(other.inventory_store, "prefetch"):
838
818
inventory_ids = [other.get_revision(r).inventory_id
839
819
for r in revision_ids]
840
820
other.inventory_store.prefetch(inventory_ids)
843
pb = bzrlib.ui.ui_factory.progress_bar()
846
823
needed_texts = set()
850
for i, rev_id in enumerate(revision_ids):
851
pb.update('fetching revision', i+1, len(revision_ids))
853
rev = other.get_revision(rev_id)
854
except bzrlib.errors.NoSuchRevision:
825
for rev_id in revision_ids:
827
pb.update('fetching revision', i, len(revision_ids))
828
rev = other.get_revision(rev_id)
858
829
revisions.append(rev)
859
830
inv = other.get_inventory(str(rev.inventory_id))
860
831
for key, entry in inv.iter_entries():
868
count, cp_fail = self.text_store.copy_multi(other.text_store,
870
#print "Added %d texts." % count
839
count = self.text_store.copy_multi(other.text_store, needed_texts)
840
print "Added %d texts." % count
871
841
inventory_ids = [ f.inventory_id for f in revisions ]
872
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
874
#print "Added %d inventories." % count
842
count = self.inventory_store.copy_multi(other.inventory_store,
844
print "Added %d inventories." % count
875
845
revision_ids = [ f.revision_id for f in revisions]
877
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
880
assert len(cp_fail) == 0
881
return count, failures
846
count = self.revision_store.copy_multi(other.revision_store,
848
for revision_id in revision_ids:
849
self.append_revision(revision_id)
850
print "Added %d revisions." % count
884
853
def commit(self, *args, **kw):
885
854
from bzrlib.commit import commit
886
855
commit(self, *args, **kw)
891
860
revno, info = self.get_revision_info(revision)
895
def revision_id_to_revno(self, revision_id):
896
"""Given a revision id, return its revno"""
897
history = self.revision_history()
899
return history.index(revision_id) + 1
901
raise bzrlib.errors.NoSuchRevision(self, revision_id)
904
863
def get_revision_info(self, revision):
905
864
"""Return (revno, revision id) for revision identifier.
1060
1019
`revision_id` may be None for the null revision, in which case
1061
1020
an `EmptyTree` is returned."""
1021
from bzrlib.tree import EmptyTree, RevisionTree
1062
1022
# TODO: refactor this to use an existing revision object
1063
1023
# so we don't need to read it in twice.
1064
1024
if revision_id == None:
1025
return EmptyTree(self.get_root_id())
1067
1027
inv = self.get_revision_inventory(revision_id)
1068
1028
return RevisionTree(self.text_store, inv)
1191
1150
for f in from_paths:
1192
1151
name_tail = splitpath(f)[-1]
1193
1152
dest_path = appendpath(to_name, name_tail)
1194
result.append((f, dest_path))
1153
print "%s => %s" % (f, dest_path)
1195
1154
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1197
1156
os.rename(self.abspath(f), self.abspath(dest_path))