25
25
from bzrlib.trace import mutter, note
26
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
27
rename, splitpath, sha_file, appendpath,
26
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
28
sha_file, appendpath, file_kind
29
30
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
31
NoSuchRevision, HistoryMissing, NotBranchError,
31
DivergedBranches, LockError, UnlistableStore,
33
33
from bzrlib.textui import show_status
34
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
34
from bzrlib.revision import Revision, validate_revision_id
35
35
from bzrlib.delta import compare_trees
36
36
from bzrlib.tree import EmptyTree, RevisionTree
37
37
from bzrlib.inventory import Inventory
38
38
from bzrlib.weavestore import WeaveStore
39
from bzrlib.store import copy_all, ImmutableStore
39
from bzrlib.store import ImmutableStore
51
51
# cache in memory to make this faster. In general anything can be
52
52
# cached in memory between lock and unlock operations.
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')
54
# TODO: please move the revision-string syntax stuff out of the branch
55
# object; it's clutter
58
def find_branch(f, **args):
59
if f and (f.startswith('http://') or f.startswith('https://')):
61
return remotebranch.RemoteBranch(f, **args)
63
return Branch(f, **args)
66
def find_cached_branch(f, cache_root, **args):
67
from remotebranch import RemoteBranch
68
br = find_branch(f, **args)
69
def cacheify(br, store_name):
70
from meta_store import CachedStore
71
cache_path = os.path.join(cache_root, store_name)
73
new_store = CachedStore(getattr(br, store_name), cache_path)
74
setattr(br, store_name, new_store)
76
if isinstance(br, RemoteBranch):
77
cacheify(br, 'inventory_store')
78
cacheify(br, 'text_store')
79
cacheify(br, 'revision_store')
59
83
def _relpath(base, path):
60
84
"""Return path relative to base, or raise exception.
124
155
"""Branch holding a history of revisions.
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.
158
Base directory of the branch.
185
161
None, or 'r' or 'w'
205
178
# This should match a prefix with a function which accepts
206
179
REVISION_NAMESPACES = {}
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
181
def __init__(self, base, init=False, find_root=True,
229
182
relax_version_check=False):
230
183
"""Create new branch object at a particular location.
232
base -- Base directory for the branch. May be a file:// url.
185
base -- Base directory for the branch.
234
187
init -- If True, create new control files in a previously
235
188
unversioned directory. If False, the branch must already
253
206
self.base = find_branch_root(base)
255
if base.startswith("file://"):
257
208
self.base = os.path.realpath(base)
258
209
if not isdir(self.controlfilename('.')):
259
210
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
260
211
['use "bzr init" to initialize a '
261
212
'new working tree'])
262
213
self._check_format(relax_version_check)
263
cfn = self.controlfilename
214
cfn = self.controlfilename
264
215
if self._branch_format == 4:
265
216
self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
217
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')
218
elif self._branch_format == 5:
219
self.control_weaves = WeaveStore(cfn([]))
220
self.weave_store = WeaveStore(cfn('weaves'))
274
221
self.revision_store = ImmutableStore(cfn('revision-store'))
784
731
def common_ancestor(self, other, self_revno=None, other_revno=None):
786
>>> from bzrlib.commit import commit
787
734
>>> sb = ScratchBranch(files=['foo', 'foo~'])
788
735
>>> sb.common_ancestor(sb) == (None, None)
790
>>> commit(sb, "Committing first revision", verbose=False)
737
>>> commit.commit(sb, "Committing first revision")
791
738
>>> sb.common_ancestor(sb)[0]
793
740
>>> clone = sb.clone()
794
>>> commit(sb, "Committing second revision", verbose=False)
741
>>> commit.commit(sb, "Committing second revision")
795
742
>>> sb.common_ancestor(sb)[0]
797
744
>>> sb.common_ancestor(clone)[0]
799
>>> commit(clone, "Committing divergent second revision",
746
>>> commit.commit(clone, "Committing divergent second revision")
801
747
>>> sb.common_ancestor(clone)[0]
803
749
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
891
837
assert isinstance(stop_revision, int)
892
838
if stop_revision > other_len:
893
839
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
894
841
return other_history[self_len:stop_revision]
896
def update_revisions(self, other, stop_revision=None):
897
"""Pull in new perfect-fit revisions."""
844
def update_revisions(self, other, stop_revno=None):
845
"""Pull in new perfect-fit revisions.
898
847
from bzrlib.fetch import greedy_fetch
899
from bzrlib.revision import get_intervening_revisions
900
if stop_revision is None:
901
stop_revision = other.last_revision()
850
stop_revision = other.lookup_revision(stop_revno)
902
853
greedy_fetch(to_branch=self, from_branch=other,
903
854
revision=stop_revision)
904
pullable_revs = self.missing_revisions(
905
other, other.revision_id_to_revno(stop_revision))
856
pullable_revs = self.missing_revisions(other, stop_revision)
906
858
if pullable_revs:
907
859
greedy_fetch(to_branch=self,
908
860
from_branch=other,
909
861
revision=pullable_revs[-1])
910
862
self.append_revision(*pullable_revs)
913
865
def commit(self, *args, **kw):
914
866
from bzrlib.commit import Commit
915
867
Commit().commit(self, *args, **kw)
870
def lookup_revision(self, revision):
871
"""Return the revision identifier for a given revision information."""
872
revno, info = self._get_revision_info(revision)
917
876
def revision_id_to_revno(self, revision_id):
918
877
"""Given a revision id, return its revno"""
919
if revision_id is None:
921
878
history = self.revision_history()
923
880
return history.index(revision_id) + 1
924
881
except ValueError:
925
882
raise bzrlib.errors.NoSuchRevision(self, revision_id)
885
def get_revision_info(self, revision):
886
"""Return (revno, revision id) for revision identifier.
888
revision can be an integer, in which case it is assumed to be revno (though
889
this will translate negative values into positive ones)
890
revision can also be a string, in which case it is parsed for something like
891
'date:' or 'revid:' etc.
893
revno, rev_id = self._get_revision_info(revision)
895
raise bzrlib.errors.NoSuchRevision(self, revision)
927
898
def get_rev_id(self, revno, history=None):
928
899
"""Find the revision id of the specified revno."""
934
905
raise bzrlib.errors.NoSuchRevision(self, revno)
935
906
return history[revno - 1]
908
def _get_revision_info(self, revision):
909
"""Return (revno, revision id) for revision specifier.
911
revision can be an integer, in which case it is assumed to be revno
912
(though this will translate negative values into positive ones)
913
revision can also be a string, in which case it is parsed for something
914
like 'date:' or 'revid:' etc.
916
A revid is always returned. If it is None, the specifier referred to
917
the null revision. If the revid does not occur in the revision
918
history, revno will be None.
924
try:# Convert to int if possible
925
revision = int(revision)
928
revs = self.revision_history()
929
if isinstance(revision, int):
931
revno = len(revs) + revision + 1
934
rev_id = self.get_rev_id(revno, revs)
935
elif isinstance(revision, basestring):
936
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
937
if revision.startswith(prefix):
938
result = func(self, revs, revision)
940
revno, rev_id = result
943
rev_id = self.get_rev_id(revno, revs)
946
raise BzrError('No namespace registered for string: %r' %
949
raise TypeError('Unhandled revision type %s' % revision)
953
raise bzrlib.errors.NoSuchRevision(self, revision)
956
def _namespace_revno(self, revs, revision):
957
"""Lookup a revision by revision number"""
958
assert revision.startswith('revno:')
960
return (int(revision[6:]),)
963
REVISION_NAMESPACES['revno:'] = _namespace_revno
965
def _namespace_revid(self, revs, revision):
966
assert revision.startswith('revid:')
967
rev_id = revision[len('revid:'):]
969
return revs.index(rev_id) + 1, rev_id
972
REVISION_NAMESPACES['revid:'] = _namespace_revid
974
def _namespace_last(self, revs, revision):
975
assert revision.startswith('last:')
977
offset = int(revision[5:])
982
raise BzrError('You must supply a positive value for --revision last:XXX')
983
return (len(revs) - offset + 1,)
984
REVISION_NAMESPACES['last:'] = _namespace_last
986
def _namespace_tag(self, revs, revision):
987
assert revision.startswith('tag:')
988
raise BzrError('tag: namespace registered, but not implemented.')
989
REVISION_NAMESPACES['tag:'] = _namespace_tag
991
def _namespace_date(self, revs, revision):
992
assert revision.startswith('date:')
994
# Spec for date revisions:
996
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
997
# it can also start with a '+/-/='. '+' says match the first
998
# entry after the given date. '-' is match the first entry before the date
999
# '=' is match the first entry after, but still on the given date.
1001
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1002
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1003
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1004
# May 13th, 2005 at 0:00
1006
# So the proper way of saying 'give me all entries for today' is:
1007
# -r {date:+today}:{date:-tomorrow}
1008
# The default is '=' when not supplied
1011
if val[:1] in ('+', '-', '='):
1012
match_style = val[:1]
1015
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1016
if val.lower() == 'yesterday':
1017
dt = today - datetime.timedelta(days=1)
1018
elif val.lower() == 'today':
1020
elif val.lower() == 'tomorrow':
1021
dt = today + datetime.timedelta(days=1)
1024
# This should be done outside the function to avoid recompiling it.
1025
_date_re = re.compile(
1026
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1028
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1030
m = _date_re.match(val)
1031
if not m or (not m.group('date') and not m.group('time')):
1032
raise BzrError('Invalid revision date %r' % revision)
1035
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1037
year, month, day = today.year, today.month, today.day
1039
hour = int(m.group('hour'))
1040
minute = int(m.group('minute'))
1041
if m.group('second'):
1042
second = int(m.group('second'))
1046
hour, minute, second = 0,0,0
1048
dt = datetime.datetime(year=year, month=month, day=day,
1049
hour=hour, minute=minute, second=second)
1053
if match_style == '-':
1055
elif match_style == '=':
1056
last = dt + datetime.timedelta(days=1)
1059
for i in range(len(revs)-1, -1, -1):
1060
r = self.get_revision(revs[i])
1061
# TODO: Handle timezone.
1062
dt = datetime.datetime.fromtimestamp(r.timestamp)
1063
if first >= dt and (last is None or dt >= last):
1066
for i in range(len(revs)):
1067
r = self.get_revision(revs[i])
1068
# TODO: Handle timezone.
1069
dt = datetime.datetime.fromtimestamp(r.timestamp)
1070
if first <= dt and (last is None or dt <= last):
1072
REVISION_NAMESPACES['date:'] = _namespace_date
937
1074
def revision_tree(self, revision_id):
938
1075
"""Return Tree for a revision on this branch.
1346
1478
return gen_file_id('TREE_ROOT')
1349
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1481
def pull_loc(branch):
1482
# TODO: Should perhaps just make attribute be 'base' in
1483
# RemoteBranch and Branch?
1484
if hasattr(branch, "baseurl"):
1485
return branch.baseurl
1490
def copy_branch(branch_from, to_location, revision=None):
1350
1491
"""Copy branch_from into the existing directory to_location.
1353
1494
If not None, only revisions up to this point will be copied.
1354
The head of the new branch will be that revision. Must be a
1495
The head of the new branch will be that revision. Can be a
1358
1499
The name of a local directory that exists but is empty.
1361
The revision to copy up to
1364
A local branch to copy revisions from, related to branch_from
1366
1501
# TODO: This could be done *much* more efficiently by just copying
1367
1502
# all the whole weaves and revisions, rather than getting one
1368
1503
# revision at a time.
1369
1504
from bzrlib.merge import merge
1505
from bzrlib.branch import Branch
1371
1507
assert isinstance(branch_from, Branch)
1372
1508
assert isinstance(to_location, basestring)
1374
br_to = Branch.initialize(to_location)
1375
mutter("copy branch from %s to %s", branch_from, br_to)
1376
if basis_branch is not None:
1377
basis_branch.push_stores(br_to)
1510
br_to = Branch(to_location, init=True)
1378
1511
br_to.set_root_id(branch_from.get_root_id())
1379
1512
if revision is None:
1380
revision = branch_from.last_revision()
1381
br_to.update_revisions(branch_from, stop_revision=revision)
1515
revno, rev_id = branch_from.get_revision_info(revision)
1516
br_to.update_revisions(branch_from, stop_revno=revno)
1382
1517
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1383
1518
check_clean=False, ignore_zero=True)
1384
br_to.set_parent(branch_from.base)
1520
from_location = pull_loc(branch_from)
1521
br_to.set_parent(pull_loc(branch_from))