23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
25
sha_file, appendpath, file_kind
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
DivergedBranches, NotBranchError
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
29
28
from bzrlib.textui import show_status
30
29
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
31
31
from bzrlib.delta import compare_trees
32
32
from bzrlib.tree import EmptyTree, RevisionTree
33
from bzrlib.progress import ProgressBar
38
36
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
37
## TODO: Maybe include checks for common corruption of newlines, etc?
50
48
def find_branch(f, **args):
51
49
if f and (f.startswith('http://') or f.startswith('https://')):
52
from bzrlib.remotebranch import RemoteBranch
53
return RemoteBranch(f, **args)
51
return remotebranch.RemoteBranch(f, **args)
55
53
return Branch(f, **args)
58
56
def find_cached_branch(f, cache_root, **args):
59
from bzrlib.remotebranch import RemoteBranch
57
from remotebranch import RemoteBranch
60
58
br = find_branch(f, **args)
61
59
def cacheify(br, store_name):
62
from bzrlib.meta_store import CachedStore
60
from meta_store import CachedStore
63
61
cache_path = os.path.join(cache_root, store_name)
64
62
os.mkdir(cache_path)
65
63
new_store = CachedStore(getattr(br, store_name), cache_path)
127
125
head, tail = os.path.split(f)
129
127
# reached the root, whatever that may be
130
raise NotBranchError('%s is not in a branch' % orig_f)
128
raise BzrError('%r is not in a branch' % orig_f)
131
class DivergedBranches(Exception):
132
def __init__(self, branch1, branch2):
133
self.branch1 = branch1
134
self.branch2 = branch2
135
Exception.__init__(self, "These branches have diverged.")
136
138
######################################################################
250
256
self._lock = None
251
257
self._lock_mode = self._lock_count = None
253
260
def abspath(self, name):
254
261
"""Return absolute filename for something in the branch"""
255
262
return os.path.join(self.base, name)
257
265
def relpath(self, path):
258
266
"""Return path relative to this branch of something inside it.
260
268
Raises an error if path is not in this branch."""
261
269
return _relpath(self.base, path)
263
272
def controlfilename(self, file_or_path):
264
273
"""Return location relative to branch."""
265
274
if isinstance(file_or_path, basestring):
328
339
# on Windows from Linux and so on. I think it might be better
329
340
# to always make all internal files in unix format.
330
341
fmt = self.controlfile('branch-format', 'r').read()
331
fmt = fmt.replace('\r\n', '\n')
342
fmt.replace('\r\n', '')
332
343
if fmt != BZR_BRANCH_FORMAT:
333
344
raise BzrError('sorry, branch format %r not supported' % fmt,
334
345
['use a different bzr version',
354
365
def read_working_inventory(self):
355
366
"""Read the working inventory."""
356
367
from bzrlib.inventory import Inventory
368
from bzrlib.xml import unpack_xml
369
from time import time
359
373
# ElementTree does its own conversion from UTF-8, so open in
361
f = self.controlfile('inventory', 'rb')
362
return bzrlib.xml.serializer_v4.read_inventory(f)
375
inv = unpack_xml(Inventory,
376
self.controlfile('inventory', 'rb'))
377
mutter("loaded inventory of %d items in %f"
378
% (len(inv), time() - before))
390
408
"""Inventory for the working copy.""")
393
def add(self, files, ids=None):
411
def add(self, files, verbose=False, ids=None):
394
412
"""Make files versioned.
396
Note that the command line normally calls smart_add instead,
397
which can automatically recurse.
414
Note that the command line normally calls smart_add instead.
399
416
This puts the files in the Added state, so that they will be
400
417
recorded by the next commit.
578
603
return self.revision_store[revision_id]
580
605
raise bzrlib.errors.NoSuchRevision(self, revision_id)
586
get_revision_xml = get_revision_xml_file
589
610
def get_revision(self, revision_id):
590
611
"""Return the Revision object for a named revision"""
591
xml_file = self.get_revision_xml_file(revision_id)
612
xml_file = self.get_revision_xml(revision_id)
594
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
615
r = unpack_xml(Revision, xml_file)
595
616
except SyntaxError, e:
596
617
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
642
663
parameter which can be either an integer revno or a
644
665
from bzrlib.inventory import Inventory
666
from bzrlib.xml import unpack_xml
646
f = self.get_inventory_xml_file(inventory_id)
647
return bzrlib.xml.serializer_v4.read_inventory(f)
668
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
650
671
def get_inventory_xml(self, inventory_id):
651
672
"""Get inventory XML as a file object."""
652
673
return self.inventory_store[inventory_id]
654
get_inventory_xml_file = get_inventory_xml
657
676
def get_inventory_sha1(self, inventory_id):
688
707
def common_ancestor(self, other, self_revno=None, other_revno=None):
690
>>> from bzrlib.commit import commit
691
710
>>> sb = ScratchBranch(files=['foo', 'foo~'])
692
711
>>> sb.common_ancestor(sb) == (None, None)
694
>>> commit(sb, "Committing first revision", verbose=False)
713
>>> commit.commit(sb, "Committing first revision", verbose=False)
695
714
>>> sb.common_ancestor(sb)[0]
697
716
>>> clone = sb.clone()
698
>>> commit(sb, "Committing second revision", verbose=False)
717
>>> commit.commit(sb, "Committing second revision", verbose=False)
699
718
>>> sb.common_ancestor(sb)[0]
701
720
>>> sb.common_ancestor(clone)[0]
703
>>> commit(clone, "Committing divergent second revision",
722
>>> commit.commit(clone, "Committing divergent second revision",
704
723
... verbose=False)
705
724
>>> sb.common_ancestor(clone)[0]
788
807
if stop_revision is None:
789
808
stop_revision = other_len
790
809
elif stop_revision > other_len:
791
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
810
raise NoSuchRevision(self, stop_revision)
793
812
return other_history[self_len:stop_revision]
796
815
def update_revisions(self, other, stop_revision=None):
797
816
"""Pull in all new revisions from other branch.
818
>>> from bzrlib.commit import commit
819
>>> bzrlib.trace.silent = True
820
>>> br1 = ScratchBranch(files=['foo', 'bar'])
823
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
824
>>> br2 = ScratchBranch()
825
>>> br2.update_revisions(br1)
829
>>> br2.revision_history()
831
>>> br2.update_revisions(br1)
833
>>> br1.text_store.total_size() == br2.text_store.total_size()
799
836
from bzrlib.fetch import greedy_fetch
800
from bzrlib.revision import get_intervening_revisions
802
pb = bzrlib.ui.ui_factory.progress_bar()
803
838
pb.update('comparing histories')
804
if stop_revision is None:
805
other_revision = other.last_patch()
839
revision_ids = self.missing_revisions(other, stop_revision)
840
if len(revision_ids) > 0:
841
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
807
other_revision = other.lookup_revision(stop_revision)
808
count = greedy_fetch(self, other, other_revision, pb)[0]
810
revision_ids = self.missing_revisions(other, stop_revision)
811
except DivergedBranches, e:
813
revision_ids = get_intervening_revisions(self.last_patch(),
814
other_revision, self)
815
assert self.last_patch() not in revision_ids
816
except bzrlib.errors.NotAncestor:
819
844
self.append_revision(*revision_ids)
822
def install_revisions(self, other, revision_ids, pb):
845
print "Added %d revisions." % count
847
def install_revisions(self, other, revision_ids, pb=None):
823
850
if hasattr(other.revision_store, "prefetch"):
824
851
other.revision_store.prefetch(revision_ids)
825
852
if hasattr(other.inventory_store, "prefetch"):
827
for rev_id in revision_ids:
829
revision = other.get_revision(rev_id).inventory_id
830
inventory_ids.append(revision)
831
except bzrlib.errors.NoSuchRevision:
853
inventory_ids = [other.get_revision(r).inventory_id
854
for r in revision_ids]
833
855
other.inventory_store.prefetch(inventory_ids)
836
pb = bzrlib.ui.ui_factory.progress_bar()
839
858
needed_texts = set()
843
861
for i, rev_id in enumerate(revision_ids):
844
862
pb.update('fetching revision', i+1, len(revision_ids))
861
878
count, cp_fail = self.text_store.copy_multi(other.text_store,
863
#print "Added %d texts." % count
880
print "Added %d texts." % count
864
881
inventory_ids = [ f.inventory_id for f in revisions ]
865
882
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
867
#print "Added %d inventories." % count
884
print "Added %d inventories." % count
868
885
revision_ids = [ f.revision_id for f in revisions]
870
886
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
872
888
permit_failure=True)
873
889
assert len(cp_fail) == 0
874
890
return count, failures
877
892
def commit(self, *args, **kw):
878
893
from bzrlib.commit import commit
879
894
commit(self, *args, **kw)
882
897
def lookup_revision(self, revision):
883
898
"""Return the revision identifier for a given revision information."""
884
revno, info = self._get_revision_info(revision)
899
revno, info = self.get_revision_info(revision)
888
def revision_id_to_revno(self, revision_id):
889
"""Given a revision id, return its revno"""
890
history = self.revision_history()
892
return history.index(revision_id) + 1
894
raise bzrlib.errors.NoSuchRevision(self, revision_id)
897
902
def get_revision_info(self, revision):
898
903
"""Return (revno, revision id) for revision identifier.
902
907
revision can also be a string, in which case it is parsed for something like
903
908
'date:' or 'revid:' etc.
905
revno, rev_id = self._get_revision_info(revision)
907
raise bzrlib.errors.NoSuchRevision(self, revision)
910
def get_rev_id(self, revno, history=None):
911
"""Find the revision id of the specified revno."""
915
history = self.revision_history()
916
elif revno <= 0 or revno > len(history):
917
raise bzrlib.errors.NoSuchRevision(self, revno)
918
return history[revno - 1]
920
def _get_revision_info(self, revision):
921
"""Return (revno, revision id) for revision specifier.
923
revision can be an integer, in which case it is assumed to be revno
924
(though this will translate negative values into positive ones)
925
revision can also be a string, in which case it is parsed for something
926
like 'date:' or 'revid:' etc.
928
A revid is always returned. If it is None, the specifier referred to
929
the null revision. If the revid does not occur in the revision
930
history, revno will be None.
933
910
if revision is None:
940
917
revs = self.revision_history()
941
918
if isinstance(revision, int):
921
# Mabye we should do this first, but we don't need it if revision == 0
943
923
revno = len(revs) + revision + 1
946
rev_id = self.get_rev_id(revno, revs)
947
926
elif isinstance(revision, basestring):
948
927
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
949
928
if revision.startswith(prefix):
950
result = func(self, revs, revision)
952
revno, rev_id = result
955
rev_id = self.get_rev_id(revno, revs)
929
revno = func(self, revs, revision)
958
raise BzrError('No namespace registered for string: %r' %
961
raise TypeError('Unhandled revision type %s' % revision)
932
raise BzrError('No namespace registered for string: %r' % revision)
965
raise bzrlib.errors.NoSuchRevision(self, revision)
934
if revno is None or revno <= 0 or revno > len(revs):
935
raise BzrError("no such revision %s" % revision)
936
return revno, revs[revno-1]
968
938
def _namespace_revno(self, revs, revision):
969
939
"""Lookup a revision by revision number"""
970
940
assert revision.startswith('revno:')
972
return (int(revision[6:]),)
942
return int(revision[6:])
973
943
except ValueError:
975
945
REVISION_NAMESPACES['revno:'] = _namespace_revno
977
947
def _namespace_revid(self, revs, revision):
978
948
assert revision.startswith('revid:')
979
rev_id = revision[len('revid:'):]
981
return revs.index(rev_id) + 1, rev_id
950
return revs.index(revision[6:]) + 1
982
951
except ValueError:
984
953
REVISION_NAMESPACES['revid:'] = _namespace_revid
986
955
def _namespace_last(self, revs, revision):
1073
1042
# TODO: Handle timezone.
1074
1043
dt = datetime.datetime.fromtimestamp(r.timestamp)
1075
1044
if first >= dt and (last is None or dt >= last):
1078
1047
for i in range(len(revs)):
1079
1048
r = self.get_revision(revs[i])
1080
1049
# TODO: Handle timezone.
1081
1050
dt = datetime.datetime.fromtimestamp(r.timestamp)
1082
1051
if first <= dt and (last is None or dt <= last):
1084
1053
REVISION_NAMESPACES['date:'] = _namespace_date
1087
def _namespace_ancestor(self, revs, revision):
1088
from revision import common_ancestor, MultipleRevisionSources
1089
other_branch = find_branch(_trim_namespace('ancestor', revision))
1090
revision_a = self.last_patch()
1091
revision_b = other_branch.last_patch()
1092
for r, b in ((revision_a, self), (revision_b, other_branch)):
1094
raise bzrlib.errors.NoCommits(b)
1095
revision_source = MultipleRevisionSources(self, other_branch)
1096
result = common_ancestor(revision_a, revision_b, revision_source)
1098
revno = self.revision_id_to_revno(result)
1099
except bzrlib.errors.NoSuchRevision:
1104
REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1106
1055
def revision_tree(self, revision_id):
1107
1056
"""Return Tree for a revision on this branch.
1344
def get_parent(self):
1345
"""Return the parent location of the branch.
1347
This is the default location for push/pull/missing. The usual
1348
pattern is that the user can override it by specifying a
1352
_locs = ['parent', 'pull', 'x-pull']
1355
return self.controlfile(l, 'r').read().strip('\n')
1357
if e.errno != errno.ENOENT:
1362
def set_parent(self, url):
1363
# TODO: Maybe delete old location files?
1364
from bzrlib.atomicfile import AtomicFile
1367
f = AtomicFile(self.controlfilename('parent'))
1376
def check_revno(self, revno):
1378
Check whether a revno corresponds to any revision.
1379
Zero (the NULL revision) is considered valid.
1382
self.check_real_revno(revno)
1384
def check_real_revno(self, revno):
1386
Check whether a revno corresponds to a real revision.
1387
Zero (the NULL revision) is considered invalid
1389
if revno < 1 or revno > self.revno():
1390
raise InvalidRevisionNumber(revno)
1395
1290
class ScratchBranch(Branch):
1396
1291
"""Special test class: a branch that cleans up after itself.
1515
1408
"""Return a new tree-root file id."""
1516
1409
return gen_file_id('TREE_ROOT')
1519
def copy_branch(branch_from, to_location, revision=None):
1520
"""Copy branch_from into the existing directory to_location.
1523
If not None, only revisions up to this point will be copied.
1524
The head of the new branch will be that revision.
1527
The name of a local directory that exists but is empty.
1529
from bzrlib.merge import merge
1531
assert isinstance(branch_from, Branch)
1532
assert isinstance(to_location, basestring)
1534
br_to = Branch(to_location, init=True)
1535
br_to.set_root_id(branch_from.get_root_id())
1536
if revision is None:
1537
revno = branch_from.revno()
1539
revno, rev_id = branch_from.get_revision_info(revision)
1540
br_to.update_revisions(branch_from, stop_revision=revno)
1541
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1542
check_clean=False, ignore_zero=True)
1543
br_to.set_parent(branch_from.base)
1546
def _trim_namespace(namespace, spec):
1547
full_namespace = namespace + ':'
1548
assert spec.startswith(full_namespace)
1549
return spec[len(full_namespace):]