43
42
# repeatedly to calculate deltas. We could perhaps have a weakref
44
43
# cache in memory to make this faster.
46
# TODO: please move the revision-string syntax stuff out of the branch
47
# object; it's clutter
50
def find_branch(f, **args):
51
if f and (f.startswith('http://') or f.startswith('https://')):
53
return remotebranch.RemoteBranch(f, **args)
55
return Branch(f, **args)
58
def find_cached_branch(f, cache_root, **args):
59
from remotebranch import RemoteBranch
60
br = find_branch(f, **args)
61
def cacheify(br, store_name):
62
from store import CachedStore
63
cache_path = os.path.join(cache_root, store_name)
65
new_store = CachedStore(getattr(br, store_name), cache_path)
66
setattr(br, store_name, new_store)
68
if isinstance(br, RemoteBranch):
69
cacheify(br, 'inventory_store')
70
cacheify(br, 'text_store')
71
cacheify(br, 'revision_store')
45
def find_branch(*ignored, **ignored_too):
46
# XXX: leave this here for about one release, then remove it
47
raise NotImplementedError('find_branch() is not supported anymore, '
48
'please use one of the new branch constructors')
75
50
def _relpath(base, path):
76
51
"""Return path relative to base, or raise exception.
146
112
"""Branch holding a history of revisions.
149
Base directory of the branch.
115
Base directory/url of the branch.
119
def __init__(self, *ignored, **ignored_too):
120
raise NotImplementedError('The Branch class is abstract')
124
"""Open an existing branch, rooted at 'base' (url)"""
125
if base and (base.startswith('http://') or base.startswith('https://')):
126
from bzrlib.remotebranch import RemoteBranch
127
return RemoteBranch(base, find_root=False)
129
return LocalBranch(base, find_root=False)
132
def open_containing(url):
133
"""Open an existing branch which contains url.
135
This probes for a branch at url, and searches upwards from there.
137
if url and (url.startswith('http://') or url.startswith('https://')):
138
from bzrlib.remotebranch import RemoteBranch
139
return RemoteBranch(url)
141
return LocalBranch(url)
144
def initialize(base):
145
"""Create a new branch, rooted at 'base' (url)"""
146
if base and (base.startswith('http://') or base.startswith('https://')):
147
from bzrlib.remotebranch import RemoteBranch
148
return RemoteBranch(base, init=True)
150
return LocalBranch(base, init=True)
152
def setup_caching(self, cache_root):
153
"""Subclasses that care about caching should override this, and set
154
up cached stores located under cache_root.
158
class LocalBranch(Branch):
159
"""A branch stored in the actual filesystem.
161
Note that it's "local" in the context of the filesystem; it doesn't
162
really matter if it's on an nfs/smb/afs/coda/... share, as long as
163
it's writable, and can be accessed via the normal filesystem API.
152
166
None, or 'r' or 'w'
678
693
def common_ancestor(self, other, self_revno=None, other_revno=None):
695
>>> from bzrlib.commit import commit
681
696
>>> sb = ScratchBranch(files=['foo', 'foo~'])
682
697
>>> sb.common_ancestor(sb) == (None, None)
684
>>> commit.commit(sb, "Committing first revision", verbose=False)
699
>>> commit(sb, "Committing first revision", verbose=False)
685
700
>>> sb.common_ancestor(sb)[0]
687
702
>>> clone = sb.clone()
688
>>> commit.commit(sb, "Committing second revision", verbose=False)
703
>>> commit(sb, "Committing second revision", verbose=False)
689
704
>>> sb.common_ancestor(sb)[0]
691
706
>>> sb.common_ancestor(clone)[0]
693
>>> commit.commit(clone, "Committing divergent second revision",
708
>>> commit(clone, "Committing divergent second revision",
694
709
... verbose=False)
695
710
>>> sb.common_ancestor(clone)[0]
792
807
pb = bzrlib.ui.ui_factory.progress_bar()
793
808
pb.update('comparing histories')
809
if stop_revision is None:
810
other_revision = other.last_patch()
812
other_revision = other.get_rev_id(stop_revision)
813
count = greedy_fetch(self, other, other_revision, pb)[0]
796
815
revision_ids = self.missing_revisions(other, stop_revision)
797
816
except DivergedBranches, e:
799
if stop_revision is None:
800
end_revision = other.last_patch()
801
818
revision_ids = get_intervening_revisions(self.last_patch(),
819
other_revision, self)
803
820
assert self.last_patch() not in revision_ids
804
821
except bzrlib.errors.NotAncestor:
807
if len(revision_ids) > 0:
808
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
811
824
self.append_revision(*revision_ids)
812
## note("Added %d revisions." % count)
815
827
def install_revisions(self, other, revision_ids, pb):
816
828
if hasattr(other.revision_store, "prefetch"):
817
829
other.revision_store.prefetch(revision_ids)
818
830
if hasattr(other.inventory_store, "prefetch"):
819
inventory_ids = [other.get_revision(r).inventory_id
820
for r in revision_ids]
832
for rev_id in revision_ids:
834
revision = other.get_revision(rev_id).inventory_id
835
inventory_ids.append(revision)
836
except bzrlib.errors.NoSuchRevision:
821
838
other.inventory_store.prefetch(inventory_ids)
905
901
raise bzrlib.errors.NoSuchRevision(self, revno)
906
902
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
904
def revision_tree(self, revision_id):
1075
905
"""Return Tree for a revision on this branch.
1502
1328
The name of a local directory that exists but is empty.
1504
1330
from bzrlib.merge import merge
1505
from bzrlib.branch import Branch
1507
1332
assert isinstance(branch_from, Branch)
1508
1333
assert isinstance(to_location, basestring)
1510
br_to = Branch(to_location, init=True)
1335
br_to = Branch.initialize(to_location)
1511
1336
br_to.set_root_id(branch_from.get_root_id())
1512
if revision is None:
1513
1338
revno = branch_from.revno()
1515
revno, rev_id = branch_from.get_revision_info(revision)
1516
1339
br_to.update_revisions(branch_from, stop_revision=revno)
1517
1340
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1518
1341
check_clean=False, ignore_zero=True)
1520
from_location = pull_loc(branch_from)
1521
br_to.set_parent(pull_loc(branch_from))
1342
br_to.set_parent(branch_from.base)