43
43
# repeatedly to calculate deltas. We could perhaps have a weakref
44
44
# 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 meta_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')
46
def find_branch(*ignored, **ignored_too):
47
# XXX: leave this here for about one release, then remove it
48
raise NotImplementedError('find_branch() is not supported anymore, '
49
'please use one of the new branch constructors')
75
51
def _relpath(base, path):
76
52
"""Return path relative to base, or raise exception.
148
116
"""Branch holding a history of revisions.
151
Base directory of the branch.
119
Base directory/url of the branch.
123
def __init__(self, *ignored, **ignored_too):
124
raise NotImplementedError('The Branch class is abstract')
128
"""Open an existing branch, rooted at 'base' (url)"""
129
if base and (base.startswith('http://') or base.startswith('https://')):
130
from bzrlib.remotebranch import RemoteBranch
131
return RemoteBranch(base, find_root=False)
133
return LocalBranch(base, find_root=False)
136
def open_containing(url):
137
"""Open an existing branch, containing url (search upwards for the root)
139
if url and (url.startswith('http://') or url.startswith('https://')):
140
from bzrlib.remotebranch import RemoteBranch
141
return RemoteBranch(url)
143
return LocalBranch(url)
146
def initialize(base):
147
"""Create a new branch, rooted at 'base' (url)"""
148
if base and (base.startswith('http://') or base.startswith('https://')):
149
from bzrlib.remotebranch import RemoteBranch
150
return RemoteBranch(base, init=True)
152
return LocalBranch(base, init=True)
154
def setup_caching(self, cache_root):
155
"""Subclasses that care about caching should override this, and set
156
up cached stores located under cache_root.
160
class LocalBranch(Branch):
161
"""A branch stored in the actual filesystem.
163
Note that it's "local" in the context of the filesystem; it doesn't
164
really matter if it's on an nfs/smb/afs/coda/... share, as long as
165
it's writable, and can be accessed via the normal filesystem API.
154
168
None, or 'r' or 'w'
696
708
def common_ancestor(self, other, self_revno=None, other_revno=None):
710
>>> from bzrlib.commit import commit
699
711
>>> sb = ScratchBranch(files=['foo', 'foo~'])
700
712
>>> sb.common_ancestor(sb) == (None, None)
702
>>> commit.commit(sb, "Committing first revision", verbose=False)
714
>>> commit(sb, "Committing first revision", verbose=False)
703
715
>>> sb.common_ancestor(sb)[0]
705
717
>>> clone = sb.clone()
706
>>> commit.commit(sb, "Committing second revision", verbose=False)
718
>>> commit(sb, "Committing second revision", verbose=False)
707
719
>>> sb.common_ancestor(sb)[0]
709
721
>>> sb.common_ancestor(clone)[0]
711
>>> commit.commit(clone, "Committing divergent second revision",
723
>>> commit(clone, "Committing divergent second revision",
712
724
... verbose=False)
713
725
>>> sb.common_ancestor(clone)[0]
810
822
pb = bzrlib.ui.ui_factory.progress_bar()
811
823
pb.update('comparing histories')
824
if stop_revision is None:
825
other_revision = other.last_patch()
827
other_revision = other.get_rev_id(stop_revision)
828
count = greedy_fetch(self, other, other_revision, pb)[0]
814
830
revision_ids = self.missing_revisions(other, stop_revision)
815
831
except DivergedBranches, e:
817
if stop_revision is None:
818
end_revision = other.last_patch()
819
833
revision_ids = get_intervening_revisions(self.last_patch(),
834
other_revision, self)
821
835
assert self.last_patch() not in revision_ids
822
836
except bzrlib.errors.NotAncestor:
825
if len(revision_ids) > 0:
826
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
829
839
self.append_revision(*revision_ids)
830
## note("Added %d revisions." % count)
833
842
def install_revisions(self, other, revision_ids, pb):
928
918
raise bzrlib.errors.NoSuchRevision(self, revno)
929
919
return history[revno - 1]
931
def _get_revision_info(self, revision):
932
"""Return (revno, revision id) for revision specifier.
934
revision can be an integer, in which case it is assumed to be revno
935
(though this will translate negative values into positive ones)
936
revision can also be a string, in which case it is parsed for something
937
like 'date:' or 'revid:' etc.
939
A revid is always returned. If it is None, the specifier referred to
940
the null revision. If the revid does not occur in the revision
941
history, revno will be None.
947
try:# Convert to int if possible
948
revision = int(revision)
951
revs = self.revision_history()
952
if isinstance(revision, int):
954
revno = len(revs) + revision + 1
957
rev_id = self.get_rev_id(revno, revs)
958
elif isinstance(revision, basestring):
959
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
960
if revision.startswith(prefix):
961
result = func(self, revs, revision)
963
revno, rev_id = result
966
rev_id = self.get_rev_id(revno, revs)
969
raise BzrError('No namespace registered for string: %r' %
972
raise TypeError('Unhandled revision type %s' % revision)
976
raise bzrlib.errors.NoSuchRevision(self, revision)
979
def _namespace_revno(self, revs, revision):
980
"""Lookup a revision by revision number"""
981
assert revision.startswith('revno:')
983
return (int(revision[6:]),)
986
REVISION_NAMESPACES['revno:'] = _namespace_revno
988
def _namespace_revid(self, revs, revision):
989
assert revision.startswith('revid:')
990
rev_id = revision[len('revid:'):]
992
return revs.index(rev_id) + 1, rev_id
995
REVISION_NAMESPACES['revid:'] = _namespace_revid
997
def _namespace_last(self, revs, revision):
998
assert revision.startswith('last:')
1000
offset = int(revision[5:])
1005
raise BzrError('You must supply a positive value for --revision last:XXX')
1006
return (len(revs) - offset + 1,)
1007
REVISION_NAMESPACES['last:'] = _namespace_last
1009
def _namespace_tag(self, revs, revision):
1010
assert revision.startswith('tag:')
1011
raise BzrError('tag: namespace registered, but not implemented.')
1012
REVISION_NAMESPACES['tag:'] = _namespace_tag
1014
def _namespace_date(self, revs, revision):
1015
assert revision.startswith('date:')
1017
# Spec for date revisions:
1019
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1020
# it can also start with a '+/-/='. '+' says match the first
1021
# entry after the given date. '-' is match the first entry before the date
1022
# '=' is match the first entry after, but still on the given date.
1024
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1025
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1026
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1027
# May 13th, 2005 at 0:00
1029
# So the proper way of saying 'give me all entries for today' is:
1030
# -r {date:+today}:{date:-tomorrow}
1031
# The default is '=' when not supplied
1034
if val[:1] in ('+', '-', '='):
1035
match_style = val[:1]
1038
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1039
if val.lower() == 'yesterday':
1040
dt = today - datetime.timedelta(days=1)
1041
elif val.lower() == 'today':
1043
elif val.lower() == 'tomorrow':
1044
dt = today + datetime.timedelta(days=1)
1047
# This should be done outside the function to avoid recompiling it.
1048
_date_re = re.compile(
1049
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1051
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1053
m = _date_re.match(val)
1054
if not m or (not m.group('date') and not m.group('time')):
1055
raise BzrError('Invalid revision date %r' % revision)
1058
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1060
year, month, day = today.year, today.month, today.day
1062
hour = int(m.group('hour'))
1063
minute = int(m.group('minute'))
1064
if m.group('second'):
1065
second = int(m.group('second'))
1069
hour, minute, second = 0,0,0
1071
dt = datetime.datetime(year=year, month=month, day=day,
1072
hour=hour, minute=minute, second=second)
1076
if match_style == '-':
1078
elif match_style == '=':
1079
last = dt + datetime.timedelta(days=1)
1082
for i in range(len(revs)-1, -1, -1):
1083
r = self.get_revision(revs[i])
1084
# TODO: Handle timezone.
1085
dt = datetime.datetime.fromtimestamp(r.timestamp)
1086
if first >= dt and (last is None or dt >= last):
1089
for i in range(len(revs)):
1090
r = self.get_revision(revs[i])
1091
# TODO: Handle timezone.
1092
dt = datetime.datetime.fromtimestamp(r.timestamp)
1093
if first <= dt and (last is None or dt <= last):
1095
REVISION_NAMESPACES['date:'] = _namespace_date
1097
922
def revision_tree(self, revision_id):
1098
923
"""Return Tree for a revision on this branch.
1527
1344
The name of a local directory that exists but is empty.
1529
1346
from bzrlib.merge import merge
1530
from bzrlib.branch import Branch
1347
from bzrlib.revisionspec import RevisionSpec
1532
1349
assert isinstance(branch_from, Branch)
1533
1350
assert isinstance(to_location, basestring)
1535
br_to = Branch(to_location, init=True)
1352
br_to = Branch.initialize(to_location)
1536
1353
br_to.set_root_id(branch_from.get_root_id())
1537
1354
if revision is None:
1538
1355
revno = branch_from.revno()
1540
revno, rev_id = branch_from.get_revision_info(revision)
1357
revno, rev_id = RevisionSpec(revision).in_history(branch_from)
1541
1358
br_to.update_revisions(branch_from, stop_revision=revno)
1542
1359
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1543
1360
check_clean=False, ignore_zero=True)
1545
from_location = pull_loc(branch_from)
1546
br_to.set_parent(pull_loc(branch_from))
1361
br_to.set_parent(branch_from.base)