20
from cStringIO import StringIO
21
from warnings import warn
23
25
from bzrlib.trace import mutter, note
24
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
26
sha_file, appendpath, file_kind
26
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
27
rename, splitpath, sha_file, appendpath,
28
29
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
NoSuchRevision, HistoryMissing, NotBranchError,
31
DivergedBranches, LockError, UnlistableStore,
30
33
from bzrlib.textui import show_status
31
from bzrlib.revision import Revision
34
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
32
35
from bzrlib.delta import compare_trees
33
36
from bzrlib.tree import EmptyTree, RevisionTree
34
37
from bzrlib.inventory import Inventory
35
38
from bzrlib.weavestore import WeaveStore
36
from bzrlib.store import ImmutableStore
39
from bzrlib.store import copy_all, ImmutableStore
41
INVENTORY_FILEID = '__inventory'
42
ANCESTRY_FILEID = '__ancestry'
45
44
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
46
45
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
47
46
## TODO: Maybe include checks for common corruption of newlines, etc?
52
51
# cache in memory to make this faster. In general anything can be
53
52
# cached in memory between lock and unlock operations.
55
# TODO: please move the revision-string syntax stuff out of the branch
56
# object; it's clutter
59
def find_branch(f, **args):
60
if f and (f.startswith('http://') or f.startswith('https://')):
62
return remotebranch.RemoteBranch(f, **args)
64
return Branch(f, **args)
67
def find_cached_branch(f, cache_root, **args):
68
from remotebranch import RemoteBranch
69
br = find_branch(f, **args)
70
def cacheify(br, store_name):
71
from meta_store import CachedStore
72
cache_path = os.path.join(cache_root, store_name)
74
new_store = CachedStore(getattr(br, store_name), cache_path)
75
setattr(br, store_name, new_store)
77
if isinstance(br, RemoteBranch):
78
cacheify(br, 'inventory_store')
79
cacheify(br, 'text_store')
80
cacheify(br, 'revision_store')
54
def find_branch(*ignored, **ignored_too):
55
# XXX: leave this here for about one release, then remove it
56
raise NotImplementedError('find_branch() is not supported anymore, '
57
'please use one of the new branch constructors')
84
59
def _relpath(base, path):
85
60
"""Return path relative to base, or raise exception.
157
124
"""Branch holding a history of revisions.
160
Base directory of the branch.
127
Base directory/url of the branch.
131
def __init__(self, *ignored, **ignored_too):
132
raise NotImplementedError('The Branch class is abstract')
135
def open_downlevel(base):
136
"""Open a branch which may be of an old format.
138
Only local branches are supported."""
139
return LocalBranch(base, find_root=False, relax_version_check=True)
143
"""Open an existing branch, rooted at 'base' (url)"""
144
if base and (base.startswith('http://') or base.startswith('https://')):
145
from bzrlib.remotebranch import RemoteBranch
146
return RemoteBranch(base, find_root=False)
148
return LocalBranch(base, find_root=False)
151
def open_containing(url):
152
"""Open an existing branch which contains url.
154
This probes for a branch at url, and searches upwards from there.
156
if url and (url.startswith('http://') or url.startswith('https://')):
157
from bzrlib.remotebranch import RemoteBranch
158
return RemoteBranch(url)
160
return LocalBranch(url)
163
def initialize(base):
164
"""Create a new branch, rooted at 'base' (url)"""
165
if base and (base.startswith('http://') or base.startswith('https://')):
166
from bzrlib.remotebranch import RemoteBranch
167
return RemoteBranch(base, init=True)
169
return LocalBranch(base, init=True)
171
def setup_caching(self, cache_root):
172
"""Subclasses that care about caching should override this, and set
173
up cached stores located under cache_root.
177
class LocalBranch(Branch):
178
"""A branch stored in the actual filesystem.
180
Note that it's "local" in the context of the filesystem; it doesn't
181
really matter if it's on an nfs/smb/afs/coda/... share, as long as
182
it's writable, and can be accessed via the normal filesystem API.
163
185
None, or 'r' or 'w'
180
205
# This should match a prefix with a function which accepts
181
206
REVISION_NAMESPACES = {}
183
def __init__(self, base, init=False, find_root=True):
208
def push_stores(self, branch_to):
209
"""Copy the content of this branches store to branch_to."""
210
if (self._branch_format != branch_to._branch_format
211
or self._branch_format != 4):
212
from bzrlib.fetch import greedy_fetch
213
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
214
self, self._branch_format, branch_to, branch_to._branch_format)
215
greedy_fetch(to_branch=branch_to, from_branch=self,
216
revision=self.last_revision())
219
store_pairs = ((self.text_store, branch_to.text_store),
220
(self.inventory_store, branch_to.inventory_store),
221
(self.revision_store, branch_to.revision_store))
223
for from_store, to_store in store_pairs:
224
copy_all(from_store, to_store)
225
except UnlistableStore:
226
raise UnlistableBranch(from_store)
228
def __init__(self, base, init=False, find_root=True,
229
relax_version_check=False):
184
230
"""Create new branch object at a particular location.
186
base -- Base directory for the branch.
232
base -- Base directory for the branch. May be a file:// url.
188
234
init -- If True, create new control files in a previously
189
235
unversioned directory. If False, the branch must already
202
253
self.base = find_branch_root(base)
255
if base.startswith("file://"):
204
257
self.base = os.path.realpath(base)
205
258
if not isdir(self.controlfilename('.')):
206
from errors import NotBranchError
207
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
208
['use "bzr init" to initialize a new working tree',
209
'current bzr can only operate from top-of-tree'])
212
self.weave_store = WeaveStore(self.controlfilename('weaves'))
213
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
259
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
260
['use "bzr init" to initialize a '
262
self._check_format(relax_version_check)
263
cfn = self.controlfilename
264
if self._branch_format == 4:
265
self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
self.text_store = ImmutableStore(cfn('text-store'))
267
elif self._branch_format == 5:
268
self.control_weaves = WeaveStore(cfn([]))
269
self.weave_store = WeaveStore(cfn('weaves'))
271
# FIXME: Unify with make_control_files
272
self.control_weaves.put_empty_weave('inventory')
273
self.control_weaves.put_empty_weave('ancestry')
274
self.revision_store = ImmutableStore(cfn('revision-store'))
216
277
def __str__(self):
341
399
In the future, we might need different in-memory Branch
342
400
classes to support downlevel branches. But not yet.
344
fmt = self.controlfile('branch-format', 'r').read()
403
fmt = self.controlfile('branch-format', 'r').read()
405
if e.errno == errno.ENOENT:
406
raise NotBranchError(self.base)
345
410
if fmt == BZR_BRANCH_FORMAT_5:
346
411
self._branch_format = 5
348
raise BzrError('sorry, branch format "%s" not supported; '
349
'use a different bzr version, '
350
'or run "bzr upgrade", '
351
'or remove the .bzr directory and "bzr init" again'
352
% fmt.rstrip('\n\r'))
412
elif fmt == BZR_BRANCH_FORMAT_4:
413
self._branch_format = 4
415
if (not relax_version_check
416
and self._branch_format != 5):
417
raise BzrError('sorry, branch format %r not supported' % fmt,
418
['use a different bzr version',
419
'or remove the .bzr directory and "bzr init" again'])
354
421
def get_root_id(self):
355
422
"""Return the id of this branches root"""
639
715
return compare_trees(old_tree, new_tree)
643
718
def get_revision_sha1(self, revision_id):
644
719
"""Hash the stored value of a revision, and return it."""
645
# In the future, revision entries will be signed. At that
646
# point, it is probably best *not* to include the signature
647
# in the revision hash. Because that lets you re-sign
648
# the revision, (add signatures/remove signatures) and still
649
# have all hash pointers stay consistent.
650
# But for now, just hash the contents.
651
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
720
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
723
def _get_ancestry_weave(self):
724
return self.control_weaves.get_weave('ancestry')
654
727
def get_ancestry(self, revision_id):
655
728
"""Return a list of revision-ids integrated by a revision.
657
w = self.weave_store.get_weave(ANCESTRY_FILEID)
659
return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
731
if revision_id is None:
733
w = self._get_ancestry_weave()
734
return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
662
737
def get_inventory_weave(self):
663
return self.weave_store.get_weave(INVENTORY_FILEID)
738
return self.control_weaves.get_weave('inventory')
666
741
def get_inventory(self, revision_id):
667
742
"""Get Inventory object by hash."""
668
# FIXME: The text gets passed around a lot coming from the weave.
669
f = StringIO(self.get_inventory_xml(revision_id))
670
return bzrlib.xml5.serializer_v5.read_inventory(f)
743
xml = self.get_inventory_xml(revision_id)
744
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
673
747
def get_inventory_xml(self, revision_id):
713
784
def common_ancestor(self, other, self_revno=None, other_revno=None):
786
>>> from bzrlib.commit import commit
716
787
>>> sb = ScratchBranch(files=['foo', 'foo~'])
717
788
>>> sb.common_ancestor(sb) == (None, None)
719
>>> commit.commit(sb, "Committing first revision")
790
>>> commit(sb, "Committing first revision", verbose=False)
720
791
>>> sb.common_ancestor(sb)[0]
722
793
>>> clone = sb.clone()
723
>>> commit.commit(sb, "Committing second revision")
794
>>> commit(sb, "Committing second revision", verbose=False)
724
795
>>> sb.common_ancestor(sb)[0]
726
797
>>> sb.common_ancestor(clone)[0]
728
>>> commit.commit(clone, "Committing divergent second revision")
799
>>> commit(clone, "Committing divergent second revision",
729
801
>>> sb.common_ancestor(clone)[0]
731
803
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
812
888
if stop_revision is None:
813
889
stop_revision = other_len
814
elif stop_revision > other_len:
815
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
891
assert isinstance(stop_revision, int)
892
if stop_revision > other_len:
893
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
817
894
return other_history[self_len:stop_revision]
820
896
def update_revisions(self, other, stop_revision=None):
821
"""Pull in all new revisions from other branch.
897
"""Pull in new perfect-fit revisions."""
823
898
from bzrlib.fetch import greedy_fetch
825
pb = bzrlib.ui.ui_factory.progress_bar()
826
pb.update('comparing histories')
828
revision_ids = self.missing_revisions(other, stop_revision)
830
if len(revision_ids) > 0:
831
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
834
self.append_revision(*revision_ids)
835
## note("Added %d revisions." % count)
899
from bzrlib.revision import get_intervening_revisions
900
if stop_revision is None:
901
stop_revision = other.last_revision()
902
greedy_fetch(to_branch=self, from_branch=other,
903
revision=stop_revision)
904
pullable_revs = self.missing_revisions(
905
other, other.revision_id_to_revno(stop_revision))
907
greedy_fetch(to_branch=self,
909
revision=pullable_revs[-1])
910
self.append_revision(*pullable_revs)
839
913
def commit(self, *args, **kw):
840
914
from bzrlib.commit import Commit
841
915
Commit().commit(self, *args, **kw)
844
def lookup_revision(self, revision):
845
"""Return the revision identifier for a given revision information."""
846
revno, info = self._get_revision_info(revision)
850
917
def revision_id_to_revno(self, revision_id):
851
918
"""Given a revision id, return its revno"""
919
if revision_id is None:
852
921
history = self.revision_history()
854
923
return history.index(revision_id) + 1
855
924
except ValueError:
856
925
raise bzrlib.errors.NoSuchRevision(self, revision_id)
859
def get_revision_info(self, revision):
860
"""Return (revno, revision id) for revision identifier.
862
revision can be an integer, in which case it is assumed to be revno (though
863
this will translate negative values into positive ones)
864
revision can also be a string, in which case it is parsed for something like
865
'date:' or 'revid:' etc.
867
revno, rev_id = self._get_revision_info(revision)
869
raise bzrlib.errors.NoSuchRevision(self, revision)
872
927
def get_rev_id(self, revno, history=None):
873
928
"""Find the revision id of the specified revno."""
879
934
raise bzrlib.errors.NoSuchRevision(self, revno)
880
935
return history[revno - 1]
882
def _get_revision_info(self, revision):
883
"""Return (revno, revision id) for revision specifier.
885
revision can be an integer, in which case it is assumed to be revno
886
(though this will translate negative values into positive ones)
887
revision can also be a string, in which case it is parsed for something
888
like 'date:' or 'revid:' etc.
890
A revid is always returned. If it is None, the specifier referred to
891
the null revision. If the revid does not occur in the revision
892
history, revno will be None.
898
try:# Convert to int if possible
899
revision = int(revision)
902
revs = self.revision_history()
903
if isinstance(revision, int):
905
revno = len(revs) + revision + 1
908
rev_id = self.get_rev_id(revno, revs)
909
elif isinstance(revision, basestring):
910
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
911
if revision.startswith(prefix):
912
result = func(self, revs, revision)
914
revno, rev_id = result
917
rev_id = self.get_rev_id(revno, revs)
920
raise BzrError('No namespace registered for string: %r' %
923
raise TypeError('Unhandled revision type %s' % revision)
927
raise bzrlib.errors.NoSuchRevision(self, revision)
930
def _namespace_revno(self, revs, revision):
931
"""Lookup a revision by revision number"""
932
assert revision.startswith('revno:')
934
return (int(revision[6:]),)
937
REVISION_NAMESPACES['revno:'] = _namespace_revno
939
def _namespace_revid(self, revs, revision):
940
assert revision.startswith('revid:')
941
rev_id = revision[len('revid:'):]
943
return revs.index(rev_id) + 1, rev_id
946
REVISION_NAMESPACES['revid:'] = _namespace_revid
948
def _namespace_last(self, revs, revision):
949
assert revision.startswith('last:')
951
offset = int(revision[5:])
956
raise BzrError('You must supply a positive value for --revision last:XXX')
957
return (len(revs) - offset + 1,)
958
REVISION_NAMESPACES['last:'] = _namespace_last
960
def _namespace_tag(self, revs, revision):
961
assert revision.startswith('tag:')
962
raise BzrError('tag: namespace registered, but not implemented.')
963
REVISION_NAMESPACES['tag:'] = _namespace_tag
965
def _namespace_date(self, revs, revision):
966
assert revision.startswith('date:')
968
# Spec for date revisions:
970
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
971
# it can also start with a '+/-/='. '+' says match the first
972
# entry after the given date. '-' is match the first entry before the date
973
# '=' is match the first entry after, but still on the given date.
975
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
976
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
977
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
978
# May 13th, 2005 at 0:00
980
# So the proper way of saying 'give me all entries for today' is:
981
# -r {date:+today}:{date:-tomorrow}
982
# The default is '=' when not supplied
985
if val[:1] in ('+', '-', '='):
986
match_style = val[:1]
989
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
990
if val.lower() == 'yesterday':
991
dt = today - datetime.timedelta(days=1)
992
elif val.lower() == 'today':
994
elif val.lower() == 'tomorrow':
995
dt = today + datetime.timedelta(days=1)
998
# This should be done outside the function to avoid recompiling it.
999
_date_re = re.compile(
1000
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1002
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1004
m = _date_re.match(val)
1005
if not m or (not m.group('date') and not m.group('time')):
1006
raise BzrError('Invalid revision date %r' % revision)
1009
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1011
year, month, day = today.year, today.month, today.day
1013
hour = int(m.group('hour'))
1014
minute = int(m.group('minute'))
1015
if m.group('second'):
1016
second = int(m.group('second'))
1020
hour, minute, second = 0,0,0
1022
dt = datetime.datetime(year=year, month=month, day=day,
1023
hour=hour, minute=minute, second=second)
1027
if match_style == '-':
1029
elif match_style == '=':
1030
last = dt + datetime.timedelta(days=1)
1033
for i in range(len(revs)-1, -1, -1):
1034
r = self.get_revision(revs[i])
1035
# TODO: Handle timezone.
1036
dt = datetime.datetime.fromtimestamp(r.timestamp)
1037
if first >= dt and (last is None or dt >= last):
1040
for i in range(len(revs)):
1041
r = self.get_revision(revs[i])
1042
# TODO: Handle timezone.
1043
dt = datetime.datetime.fromtimestamp(r.timestamp)
1044
if first <= dt and (last is None or dt <= last):
1046
REVISION_NAMESPACES['date:'] = _namespace_date
1048
937
def revision_tree(self, revision_id):
1049
938
"""Return Tree for a revision on this branch.
1453
1346
return gen_file_id('TREE_ROOT')
1456
def pull_loc(branch):
1457
# TODO: Should perhaps just make attribute be 'base' in
1458
# RemoteBranch and Branch?
1459
if hasattr(branch, "baseurl"):
1460
return branch.baseurl
1465
def copy_branch(branch_from, to_location, revision=None):
1466
"""Copy branch_from into the existing directory to_location.
1469
If not None, only revisions up to this point will be copied.
1470
The head of the new branch will be that revision.
1473
The name of a local directory that exists but is empty.
1475
from bzrlib.merge import merge
1476
from bzrlib.branch import Branch
1478
assert isinstance(branch_from, Branch)
1479
assert isinstance(to_location, basestring)
1481
br_to = Branch(to_location, init=True)
1482
br_to.set_root_id(branch_from.get_root_id())
1483
if revision is None:
1484
revno = branch_from.revno()
1486
revno, rev_id = branch_from.get_revision_info(revision)
1487
br_to.update_revisions(branch_from, stop_revision=revno)
1488
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1489
check_clean=False, ignore_zero=True)
1491
from_location = pull_loc(branch_from)
1492
br_to.set_parent(pull_loc(branch_from))