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')
136
"""Open an existing branch, rooted at 'base' (url)"""
137
if base and (base.startswith('http://') or base.startswith('https://')):
138
from bzrlib.remotebranch import RemoteBranch
139
return RemoteBranch(base, find_root=False)
141
return LocalBranch(base, find_root=False)
144
def open_containing(url):
145
"""Open an existing branch which contains url.
147
This probes for a branch at url, and searches upwards from there.
149
if url and (url.startswith('http://') or url.startswith('https://')):
150
from bzrlib.remotebranch import RemoteBranch
151
return RemoteBranch(url)
153
return LocalBranch(url)
156
def initialize(base):
157
"""Create a new branch, rooted at 'base' (url)"""
158
if base and (base.startswith('http://') or base.startswith('https://')):
159
from bzrlib.remotebranch import RemoteBranch
160
return RemoteBranch(base, init=True)
162
return LocalBranch(base, init=True)
164
def setup_caching(self, cache_root):
165
"""Subclasses that care about caching should override this, and set
166
up cached stores located under cache_root.
170
class LocalBranch(Branch):
171
"""A branch stored in the actual filesystem.
173
Note that it's "local" in the context of the filesystem; it doesn't
174
really matter if it's on an nfs/smb/afs/coda/... share, as long as
175
it's writable, and can be accessed via the normal filesystem API.
163
178
None, or 'r' or 'w'
180
198
# This should match a prefix with a function which accepts
181
199
REVISION_NAMESPACES = {}
183
def __init__(self, base, init=False, find_root=True):
201
def push_stores(self, branch_to):
202
"""Copy the content of this branches store to branch_to."""
203
if (self._branch_format != branch_to._branch_format
204
or self._branch_format != 4):
205
from bzrlib.fetch import greedy_fetch
206
mutter("falling back to fetch logic to push between %s and %s",
208
greedy_fetch(to_branch=branch_to, from_branch=self,
209
revision=self.last_revision())
212
store_pairs = ((self.text_store, branch_to.text_store),
213
(self.inventory_store, branch_to.inventory_store),
214
(self.revision_store, branch_to.revision_store))
216
for from_store, to_store in store_pairs:
217
copy_all(from_store, to_store)
218
except UnlistableStore:
219
raise UnlistableBranch(from_store)
221
def __init__(self, base, init=False, find_root=True,
222
relax_version_check=False):
184
223
"""Create new branch object at a particular location.
186
base -- Base directory for the branch.
225
base -- Base directory for the branch. May be a file:// url.
188
227
init -- If True, create new control files in a previously
189
228
unversioned directory. If False, the branch must already
202
246
self.base = find_branch_root(base)
248
if base.startswith("file://"):
204
250
self.base = os.path.realpath(base)
205
251
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'))
252
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
253
['use "bzr init" to initialize a '
255
self._check_format(relax_version_check)
256
cfn = self.controlfilename
257
if self._branch_format == 4:
258
self.inventory_store = ImmutableStore(cfn('inventory-store'))
259
self.text_store = ImmutableStore(cfn('text-store'))
260
elif self._branch_format == 5:
261
self.control_weaves = WeaveStore(cfn([]))
262
self.weave_store = WeaveStore(cfn('weaves'))
264
# FIXME: Unify with make_control_files
265
self.control_weaves.put_empty_weave('inventory')
266
self.control_weaves.put_empty_weave('ancestry')
267
self.revision_store = ImmutableStore(cfn('revision-store'))
216
270
def __str__(self):
341
392
In the future, we might need different in-memory Branch
342
393
classes to support downlevel branches. But not yet.
344
fmt = self.controlfile('branch-format', 'r').read()
396
fmt = self.controlfile('branch-format', 'r').read()
398
if e.errno == errno.ENOENT:
399
raise NotBranchError(self.base)
345
403
if fmt == BZR_BRANCH_FORMAT_5:
346
404
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'))
405
elif fmt == BZR_BRANCH_FORMAT_4:
406
self._branch_format = 4
408
if (not relax_version_check
409
and self._branch_format != 5):
410
raise BzrError('sorry, branch format %r not supported' % fmt,
411
['use a different bzr version',
412
'or remove the .bzr directory and "bzr init" again'])
354
414
def get_root_id(self):
355
415
"""Return the id of this branches root"""
639
708
return compare_trees(old_tree, new_tree)
643
711
def get_revision_sha1(self, revision_id):
644
712
"""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))
713
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
716
def _get_ancestry_weave(self):
717
return self.control_weaves.get_weave('ancestry')
654
720
def get_ancestry(self, revision_id):
655
721
"""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))]
724
if revision_id is None:
726
w = self._get_ancestry_weave()
727
return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
662
730
def get_inventory_weave(self):
663
return self.weave_store.get_weave(INVENTORY_FILEID)
731
return self.control_weaves.get_weave('inventory')
666
734
def get_inventory(self, revision_id):
667
735
"""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)
736
xml = self.get_inventory_xml(revision_id)
737
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
673
740
def get_inventory_xml(self, revision_id):
713
777
def common_ancestor(self, other, self_revno=None, other_revno=None):
779
>>> from bzrlib.commit import commit
716
780
>>> sb = ScratchBranch(files=['foo', 'foo~'])
717
781
>>> sb.common_ancestor(sb) == (None, None)
719
>>> commit.commit(sb, "Committing first revision")
783
>>> commit(sb, "Committing first revision", verbose=False)
720
784
>>> sb.common_ancestor(sb)[0]
722
786
>>> clone = sb.clone()
723
>>> commit.commit(sb, "Committing second revision")
787
>>> commit(sb, "Committing second revision", verbose=False)
724
788
>>> sb.common_ancestor(sb)[0]
726
790
>>> sb.common_ancestor(clone)[0]
728
>>> commit.commit(clone, "Committing divergent second revision")
792
>>> commit(clone, "Committing divergent second revision",
729
794
>>> sb.common_ancestor(clone)[0]
731
796
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
812
881
if stop_revision is None:
813
882
stop_revision = other_len
814
elif stop_revision > other_len:
815
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
884
assert isinstance(stop_revision, int)
885
if stop_revision > other_len:
886
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
817
887
return other_history[self_len:stop_revision]
820
889
def update_revisions(self, other, stop_revision=None):
821
"""Pull in all new revisions from other branch.
890
"""Pull in new perfect-fit revisions."""
823
891
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)
892
from bzrlib.revision import get_intervening_revisions
893
if stop_revision is None:
894
stop_revision = other.last_revision()
895
greedy_fetch(to_branch=self, from_branch=other,
896
revision=stop_revision)
897
pullable_revs = self.missing_revisions(
898
other, other.revision_id_to_revno(stop_revision))
900
greedy_fetch(to_branch=self,
902
revision=pullable_revs[-1])
903
self.append_revision(*pullable_revs)
839
906
def commit(self, *args, **kw):
840
907
from bzrlib.commit import Commit
841
908
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
910
def revision_id_to_revno(self, revision_id):
851
911
"""Given a revision id, return its revno"""
912
if revision_id is None:
852
914
history = self.revision_history()
854
916
return history.index(revision_id) + 1
855
917
except ValueError:
856
918
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
920
def get_rev_id(self, revno, history=None):
873
921
"""Find the revision id of the specified revno."""
879
927
raise bzrlib.errors.NoSuchRevision(self, revno)
880
928
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
930
def revision_tree(self, revision_id):
1049
931
"""Return Tree for a revision on this branch.
1453
1339
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):
1342
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1466
1343
"""Copy branch_from into the existing directory to_location.
1469
1346
If not None, only revisions up to this point will be copied.
1470
The head of the new branch will be that revision.
1347
The head of the new branch will be that revision. Must be a
1473
1351
The name of a local directory that exists but is empty.
1354
The revision to copy up to
1357
A local branch to copy revisions from, related to branch_from
1359
# TODO: This could be done *much* more efficiently by just copying
1360
# all the whole weaves and revisions, rather than getting one
1361
# revision at a time.
1475
1362
from bzrlib.merge import merge
1476
from bzrlib.branch import Branch
1478
1364
assert isinstance(branch_from, Branch)
1479
1365
assert isinstance(to_location, basestring)
1481
br_to = Branch(to_location, init=True)
1367
br_to = Branch.initialize(to_location)
1368
if basis_branch is not None:
1369
basis_branch.push_stores(br_to)
1482
1370
br_to.set_root_id(branch_from.get_root_id())
1483
1371
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)
1372
revision = branch_from.last_revision()
1373
br_to.update_revisions(branch_from, stop_revision=revision)
1488
1374
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1489
1375
check_clean=False, ignore_zero=True)
1491
from_location = pull_loc(branch_from)
1492
br_to.set_parent(pull_loc(branch_from))
1376
br_to.set_parent(branch_from.base)