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
######################################################################
258
247
self._lock = None
259
248
self._lock_mode = self._lock_count = None
261
251
def abspath(self, name):
262
252
"""Return absolute filename for something in the branch"""
263
253
return os.path.join(self.base, name)
265
256
def relpath(self, path):
266
257
"""Return path relative to this branch of something inside it.
268
259
Raises an error if path is not in this branch."""
269
260
return _relpath(self.base, path)
271
263
def controlfilename(self, file_or_path):
272
264
"""Return location relative to branch."""
273
265
if isinstance(file_or_path, basestring):
318
312
self.controlfile(f, 'w').write('')
319
313
mutter('created control directory in ' + self.base)
321
# if we want per-tree root ids then this is the place to set
322
# them; they're not needed for now and so ommitted for
324
pack_xml(Inventory(), self.controlfile('inventory','w'))
315
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
326
318
def _check_format(self):
327
319
"""Check this branch format is supported.
369
361
# ElementTree does its own conversion from UTF-8, so open in
371
363
inv = unpack_xml(Inventory,
372
self.controlfile('inventory', 'rb'))
364
self.controlfile('inventory', 'rb'))
373
365
mutter("loaded inventory of %d items in %f"
374
366
% (len(inv), time() - before))
404
396
"""Inventory for the working copy.""")
407
def add(self, files, ids=None):
399
def add(self, files, verbose=False, ids=None):
408
400
"""Make files versioned.
410
Note that the command line normally calls smart_add instead,
411
which can automatically recurse.
402
Note that the command line normally calls smart_add instead.
413
404
This puts the files in the Added state, so that they will be
414
405
recorded by the next commit.
424
415
TODO: Perhaps have an option to add the ids even if the files do
427
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
429
426
# TODO: Re-adding a file that is removed in the working copy
430
427
# should probably put it back with the previous ID.
431
428
if isinstance(files, basestring):
584
def get_revision_xml(self, revision_id):
585
"""Return XML file object for revision object."""
586
if not revision_id or not isinstance(revision_id, basestring):
587
raise InvalidRevisionId(revision_id)
592
return self.revision_store[revision_id]
594
raise bzrlib.errors.NoSuchRevision(self, revision_id)
599
585
def get_revision(self, revision_id):
600
586
"""Return the Revision object for a named revision"""
601
xml_file = self.get_revision_xml(revision_id)
587
from bzrlib.revision import Revision
588
from bzrlib.xml import unpack_xml
604
r = unpack_xml(Revision, xml_file)
605
except SyntaxError, e:
606
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])
610
598
assert r.revision_id == revision_id
614
def get_revision_delta(self, revno):
615
"""Return the delta for one revision.
617
The delta is relative to its mainline predecessor, or the
618
empty tree for revision 1.
620
assert isinstance(revno, int)
621
rh = self.revision_history()
622
if not (1 <= revno <= len(rh)):
623
raise InvalidRevisionNumber(revno)
625
# revno is 1-based; list is 0-based
627
new_tree = self.revision_tree(rh[revno-1])
629
old_tree = EmptyTree()
631
old_tree = self.revision_tree(rh[revno-2])
633
return compare_trees(old_tree, new_tree)
637
602
def get_revision_sha1(self, revision_id):
642
607
# the revision, (add signatures/remove signatures) and still
643
608
# have all hash pointers stay consistent.
644
609
# But for now, just hash the contents.
645
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
610
return sha_file(self.revision_store[revision_id])
648
613
def get_inventory(self, inventory_id):
654
619
from bzrlib.inventory import Inventory
655
620
from bzrlib.xml import unpack_xml
657
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
660
def get_inventory_xml(self, inventory_id):
661
"""Get inventory XML as a file object."""
662
return self.inventory_store[inventory_id]
622
return unpack_xml(Inventory, self.inventory_store[inventory_id])
665
625
def get_inventory_sha1(self, inventory_id):
666
626
"""Return the sha1 hash of the inventory entry
668
return sha_file(self.get_inventory_xml(inventory_id))
628
return sha_file(self.inventory_store[inventory_id])
671
631
def get_revision_inventory(self, revision_id):
737
697
return r+1, my_history[r]
738
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)
742
723
"""Return current revision number for this branch.
760
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
741
def missing_revisions(self, other, stop_revision=None):
762
743
If self and other have not diverged, return a list of the revisions
763
744
present in other, but missing from self.
796
777
if stop_revision is None:
797
778
stop_revision = other_len
798
779
elif stop_revision > other_len:
799
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
780
raise NoSuchRevision(self, stop_revision)
801
782
return other_history[self_len:stop_revision]
804
785
def update_revisions(self, other, stop_revision=None):
805
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()
807
from bzrlib.fetch import greedy_fetch
809
pb = bzrlib.ui.ui_factory.progress_bar()
808
from bzrlib.progress import ProgressBar
812
from sets import Set as set
810
816
pb.update('comparing histories')
812
817
revision_ids = self.missing_revisions(other, stop_revision)
814
if len(revision_ids) > 0:
815
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
818
self.append_revision(*revision_ids)
819
## note("Added %d revisions." % count)
822
def install_revisions(self, other, revision_ids, pb):
823
819
if hasattr(other.revision_store, "prefetch"):
824
820
other.revision_store.prefetch(revision_ids)
825
821
if hasattr(other.inventory_store, "prefetch"):
826
822
inventory_ids = [other.get_revision(r).inventory_id
827
823
for r in revision_ids]
828
824
other.inventory_store.prefetch(inventory_ids)
831
pb = bzrlib.ui.ui_factory.progress_bar()
834
827
needed_texts = set()
838
for i, rev_id in enumerate(revision_ids):
839
pb.update('fetching revision', i+1, len(revision_ids))
841
rev = other.get_revision(rev_id)
842
except bzrlib.errors.NoSuchRevision:
829
for rev_id in revision_ids:
831
pb.update('fetching revision', i, len(revision_ids))
832
rev = other.get_revision(rev_id)
846
833
revisions.append(rev)
847
834
inv = other.get_inventory(str(rev.inventory_id))
848
835
for key, entry in inv.iter_entries():
856
count, cp_fail = self.text_store.copy_multi(other.text_store,
858
#print "Added %d texts." % count
843
count = self.text_store.copy_multi(other.text_store, needed_texts)
844
print "Added %d texts." % count
859
845
inventory_ids = [ f.inventory_id for f in revisions ]
860
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
862
#print "Added %d inventories." % count
846
count = self.inventory_store.copy_multi(other.inventory_store,
848
print "Added %d inventories." % count
863
849
revision_ids = [ f.revision_id for f in revisions]
865
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
868
assert len(cp_fail) == 0
869
return count, failures
850
count = self.revision_store.copy_multi(other.revision_store,
852
for revision_id in revision_ids:
853
self.append_revision(revision_id)
854
print "Added %d revisions." % count
872
857
def commit(self, *args, **kw):
873
858
from bzrlib.commit import commit
874
859
commit(self, *args, **kw)
879
864
revno, info = self.get_revision_info(revision)
883
def revision_id_to_revno(self, revision_id):
884
"""Given a revision id, return its revno"""
885
history = self.revision_history()
887
return history.index(revision_id) + 1
889
raise bzrlib.errors.NoSuchRevision(self, revision_id)
892
867
def get_revision_info(self, revision):
893
868
"""Return (revno, revision id) for revision identifier.
1048
1023
`revision_id` may be None for the null revision, in which case
1049
1024
an `EmptyTree` is returned."""
1025
from bzrlib.tree import EmptyTree, RevisionTree
1050
1026
# TODO: refactor this to use an existing revision object
1051
1027
# so we don't need to read it in twice.
1052
1028
if revision_id == None:
1029
return EmptyTree(self.get_root_id())
1055
1031
inv = self.get_revision_inventory(revision_id)
1056
1032
return RevisionTree(self.text_store, inv)
1179
1154
for f in from_paths:
1180
1155
name_tail = splitpath(f)[-1]
1181
1156
dest_path = appendpath(to_name, name_tail)
1182
result.append((f, dest_path))
1157
print "%s => %s" % (f, dest_path)
1183
1158
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1185
1160
os.rename(self.abspath(f), self.abspath(dest_path))
1402
1375
"""Return a new tree-root file id."""
1403
1376
return gen_file_id('TREE_ROOT')
1406
def pull_loc(branch):
1407
# TODO: Should perhaps just make attribute be 'base' in
1408
# RemoteBranch and Branch?
1409
if hasattr(branch, "baseurl"):
1410
return branch.baseurl
1415
def copy_branch(branch_from, to_location, revision=None):
1416
"""Copy branch_from into the existing directory to_location.
1418
If revision is not None, the head of the new branch will be revision.
1420
from bzrlib.merge import merge
1421
from bzrlib.branch import Branch
1422
br_to = Branch(to_location, init=True)
1423
br_to.set_root_id(branch_from.get_root_id())
1424
if revision is None:
1425
revno = branch_from.revno()
1427
revno, rev_id = branch_from.get_revision_info(revision)
1428
br_to.update_revisions(branch_from, stop_revision=revno)
1429
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1430
check_clean=False, ignore_zero=True)
1431
from_location = pull_loc(branch_from)
1432
br_to.controlfile("x-pull", "wb").write(from_location + "\n")