25
25
from bzrlib.trace import mutter, note
26
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
28
sha_file, appendpath, file_kind
26
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
27
rename, splitpath, sha_file, appendpath,
30
29
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
31
30
NoSuchRevision, HistoryMissing, NotBranchError,
31
DivergedBranches, LockError)
33
32
from bzrlib.textui import show_status
34
33
from bzrlib.revision import Revision, validate_revision_id
35
34
from bzrlib.delta import compare_trees
51
50
# cache in memory to make this faster. In general anything can be
52
51
# cached in memory between lock and unlock operations.
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')
53
def find_branch(*ignored, **ignored_too):
54
# XXX: leave this here for about one release, then remove it
55
raise NotImplementedError('find_branch() is not supported anymore, '
56
'please use one of the new branch constructors')
83
58
def _relpath(base, path):
84
59
"""Return path relative to base, or raise exception.
155
123
"""Branch holding a history of revisions.
158
Base directory of the branch.
126
Base directory/url of the branch.
130
def __init__(self, *ignored, **ignored_too):
131
raise NotImplementedError('The Branch class is abstract')
135
"""Open an existing branch, rooted at 'base' (url)"""
136
if base and (base.startswith('http://') or base.startswith('https://')):
137
from bzrlib.remotebranch import RemoteBranch
138
return RemoteBranch(base, find_root=False)
140
return LocalBranch(base, find_root=False)
143
def open_containing(url):
144
"""Open an existing branch which contains url.
146
This probes for a branch at url, and searches upwards from there.
148
if url and (url.startswith('http://') or url.startswith('https://')):
149
from bzrlib.remotebranch import RemoteBranch
150
return RemoteBranch(url)
152
return LocalBranch(url)
155
def initialize(base):
156
"""Create a new branch, rooted at 'base' (url)"""
157
if base and (base.startswith('http://') or base.startswith('https://')):
158
from bzrlib.remotebranch import RemoteBranch
159
return RemoteBranch(base, init=True)
161
return LocalBranch(base, init=True)
163
def setup_caching(self, cache_root):
164
"""Subclasses that care about caching should override this, and set
165
up cached stores located under cache_root.
169
class LocalBranch(Branch):
170
"""A branch stored in the actual filesystem.
172
Note that it's "local" in the context of the filesystem; it doesn't
173
really matter if it's on an nfs/smb/afs/coda/... share, as long as
174
it's writable, and can be accessed via the normal filesystem API.
161
177
None, or 'r' or 'w'
206
225
self.base = find_branch_root(base)
227
if base.startswith("file://"):
208
229
self.base = os.path.realpath(base)
209
230
if not isdir(self.controlfilename('.')):
210
231
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
211
232
['use "bzr init" to initialize a '
212
233
'new working tree'])
213
234
self._check_format(relax_version_check)
214
cfn = self.controlfilename
235
cfn = self.controlfilename
215
236
if self._branch_format == 4:
216
237
self.inventory_store = ImmutableStore(cfn('inventory-store'))
217
238
self.text_store = ImmutableStore(cfn('text-store'))
218
elif self._branch_format == 5:
219
self.control_weaves = WeaveStore(cfn([]))
220
self.weave_store = WeaveStore(cfn('weaves'))
239
elif self._branch_format == 5:
240
self.control_weaves = WeaveStore(cfn([]))
241
self.weave_store = WeaveStore(cfn('weaves'))
243
# FIXME: Unify with make_control_files
244
self.control_weaves.put_empty_weave('inventory')
245
self.control_weaves.put_empty_weave('ancestry')
221
246
self.revision_store = ImmutableStore(cfn('revision-store'))
731
756
def common_ancestor(self, other, self_revno=None, other_revno=None):
758
>>> from bzrlib.commit import commit
734
759
>>> sb = ScratchBranch(files=['foo', 'foo~'])
735
760
>>> sb.common_ancestor(sb) == (None, None)
737
>>> commit.commit(sb, "Committing first revision")
762
>>> commit(sb, "Committing first revision", verbose=False)
738
763
>>> sb.common_ancestor(sb)[0]
740
765
>>> clone = sb.clone()
741
>>> commit.commit(sb, "Committing second revision")
766
>>> commit(sb, "Committing second revision", verbose=False)
742
767
>>> sb.common_ancestor(sb)[0]
744
769
>>> sb.common_ancestor(clone)[0]
746
>>> commit.commit(clone, "Committing divergent second revision")
771
>>> commit(clone, "Committing divergent second revision",
747
773
>>> sb.common_ancestor(clone)[0]
749
775
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
837
863
assert isinstance(stop_revision, int)
838
864
if stop_revision > other_len:
839
865
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
841
866
return other_history[self_len:stop_revision]
844
def update_revisions(self, other, stop_revno=None):
845
"""Pull in new perfect-fit revisions.
868
def update_revisions(self, other, stop_revision=None):
869
"""Pull in new perfect-fit revisions."""
847
870
from bzrlib.fetch import greedy_fetch
850
stop_revision = other.lookup_revision(stop_revno)
871
from bzrlib.revision import get_intervening_revisions
872
if stop_revision is None:
873
stop_revision = other.last_revision()
853
874
greedy_fetch(to_branch=self, from_branch=other,
854
875
revision=stop_revision)
856
pullable_revs = self.missing_revisions(other, stop_revision)
876
pullable_revs = self.missing_revisions(
877
other, other.revision_id_to_revno(stop_revision))
858
878
if pullable_revs:
859
879
greedy_fetch(to_branch=self,
860
880
from_branch=other,
861
881
revision=pullable_revs[-1])
862
882
self.append_revision(*pullable_revs)
865
884
def commit(self, *args, **kw):
866
885
from bzrlib.commit import Commit
867
886
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)
876
888
def revision_id_to_revno(self, revision_id):
877
889
"""Given a revision id, return its revno"""
890
if revision_id is None:
878
892
history = self.revision_history()
880
894
return history.index(revision_id) + 1
881
895
except ValueError:
882
896
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)
898
898
def get_rev_id(self, revno, history=None):
899
899
"""Find the revision id of the specified revno."""
905
905
raise bzrlib.errors.NoSuchRevision(self, revno)
906
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
1074
908
def revision_tree(self, revision_id):
1075
909
"""Return Tree for a revision on this branch.
1502
1332
# all the whole weaves and revisions, rather than getting one
1503
1333
# revision at a time.
1504
1334
from bzrlib.merge import merge
1505
from bzrlib.branch import Branch
1507
1336
assert isinstance(branch_from, Branch)
1508
1337
assert isinstance(to_location, basestring)
1510
br_to = Branch(to_location, init=True)
1339
br_to = Branch.initialize(to_location)
1511
1340
br_to.set_root_id(branch_from.get_root_id())
1512
1341
if revision is None:
1515
revno, rev_id = branch_from.get_revision_info(revision)
1516
br_to.update_revisions(branch_from, stop_revno=revno)
1342
revision = branch_from.last_revision()
1343
br_to.update_revisions(branch_from, stop_revision=revision)
1517
1344
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1518
1345
check_clean=False, ignore_zero=True)
1520
from_location = pull_loc(branch_from)
1521
br_to.set_parent(pull_loc(branch_from))
1346
br_to.set_parent(branch_from.base)