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'
680
695
def common_ancestor(self, other, self_revno=None, other_revno=None):
697
>>> from bzrlib.commit import commit
683
698
>>> sb = ScratchBranch(files=['foo', 'foo~'])
684
699
>>> sb.common_ancestor(sb) == (None, None)
686
>>> commit.commit(sb, "Committing first revision", verbose=False)
701
>>> commit(sb, "Committing first revision", verbose=False)
687
702
>>> sb.common_ancestor(sb)[0]
689
704
>>> clone = sb.clone()
690
>>> commit.commit(sb, "Committing second revision", verbose=False)
705
>>> commit(sb, "Committing second revision", verbose=False)
691
706
>>> sb.common_ancestor(sb)[0]
693
708
>>> sb.common_ancestor(clone)[0]
695
>>> commit.commit(clone, "Committing divergent second revision",
710
>>> commit(clone, "Committing divergent second revision",
696
711
... verbose=False)
697
712
>>> sb.common_ancestor(clone)[0]
794
809
pb = bzrlib.ui.ui_factory.progress_bar()
795
810
pb.update('comparing histories')
811
if stop_revision is None:
812
other_revision = other.last_patch()
814
other_revision = other.get_rev_id(stop_revision)
815
count = greedy_fetch(self, other, other_revision, pb)[0]
798
817
revision_ids = self.missing_revisions(other, stop_revision)
799
818
except DivergedBranches, e:
801
if stop_revision is None:
802
end_revision = other.last_patch()
803
820
revision_ids = get_intervening_revisions(self.last_patch(),
821
other_revision, self)
805
822
assert self.last_patch() not in revision_ids
806
823
except bzrlib.errors.NotAncestor:
809
if len(revision_ids) > 0:
810
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
813
826
self.append_revision(*revision_ids)
814
## note("Added %d revisions." % count)
817
829
def install_revisions(self, other, revision_ids, pb):
818
830
if hasattr(other.revision_store, "prefetch"):
819
831
other.revision_store.prefetch(revision_ids)
820
832
if hasattr(other.inventory_store, "prefetch"):
821
inventory_ids = [other.get_revision(r).inventory_id
822
for r in revision_ids]
834
for rev_id in revision_ids:
836
revision = other.get_revision(rev_id).inventory_id
837
inventory_ids.append(revision)
838
except bzrlib.errors.NoSuchRevision:
823
840
other.inventory_store.prefetch(inventory_ids)
907
903
raise bzrlib.errors.NoSuchRevision(self, revno)
908
904
return history[revno - 1]
910
def _get_revision_info(self, revision):
911
"""Return (revno, revision id) for revision specifier.
913
revision can be an integer, in which case it is assumed to be revno
914
(though this will translate negative values into positive ones)
915
revision can also be a string, in which case it is parsed for something
916
like 'date:' or 'revid:' etc.
918
A revid is always returned. If it is None, the specifier referred to
919
the null revision. If the revid does not occur in the revision
920
history, revno will be None.
926
try:# Convert to int if possible
927
revision = int(revision)
930
revs = self.revision_history()
931
if isinstance(revision, int):
933
revno = len(revs) + revision + 1
936
rev_id = self.get_rev_id(revno, revs)
937
elif isinstance(revision, basestring):
938
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
939
if revision.startswith(prefix):
940
result = func(self, revs, revision)
942
revno, rev_id = result
945
rev_id = self.get_rev_id(revno, revs)
948
raise BzrError('No namespace registered for string: %r' %
951
raise TypeError('Unhandled revision type %s' % revision)
955
raise bzrlib.errors.NoSuchRevision(self, revision)
958
def _namespace_revno(self, revs, revision):
959
"""Lookup a revision by revision number"""
960
assert revision.startswith('revno:')
962
return (int(revision[6:]),)
965
REVISION_NAMESPACES['revno:'] = _namespace_revno
967
def _namespace_revid(self, revs, revision):
968
assert revision.startswith('revid:')
969
rev_id = revision[len('revid:'):]
971
return revs.index(rev_id) + 1, rev_id
974
REVISION_NAMESPACES['revid:'] = _namespace_revid
976
def _namespace_last(self, revs, revision):
977
assert revision.startswith('last:')
979
offset = int(revision[5:])
984
raise BzrError('You must supply a positive value for --revision last:XXX')
985
return (len(revs) - offset + 1,)
986
REVISION_NAMESPACES['last:'] = _namespace_last
988
def _namespace_tag(self, revs, revision):
989
assert revision.startswith('tag:')
990
raise BzrError('tag: namespace registered, but not implemented.')
991
REVISION_NAMESPACES['tag:'] = _namespace_tag
993
def _namespace_date(self, revs, revision):
994
assert revision.startswith('date:')
996
# Spec for date revisions:
998
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
999
# it can also start with a '+/-/='. '+' says match the first
1000
# entry after the given date. '-' is match the first entry before the date
1001
# '=' is match the first entry after, but still on the given date.
1003
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1004
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1005
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1006
# May 13th, 2005 at 0:00
1008
# So the proper way of saying 'give me all entries for today' is:
1009
# -r {date:+today}:{date:-tomorrow}
1010
# The default is '=' when not supplied
1013
if val[:1] in ('+', '-', '='):
1014
match_style = val[:1]
1017
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1018
if val.lower() == 'yesterday':
1019
dt = today - datetime.timedelta(days=1)
1020
elif val.lower() == 'today':
1022
elif val.lower() == 'tomorrow':
1023
dt = today + datetime.timedelta(days=1)
1026
# This should be done outside the function to avoid recompiling it.
1027
_date_re = re.compile(
1028
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1030
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1032
m = _date_re.match(val)
1033
if not m or (not m.group('date') and not m.group('time')):
1034
raise BzrError('Invalid revision date %r' % revision)
1037
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1039
year, month, day = today.year, today.month, today.day
1041
hour = int(m.group('hour'))
1042
minute = int(m.group('minute'))
1043
if m.group('second'):
1044
second = int(m.group('second'))
1048
hour, minute, second = 0,0,0
1050
dt = datetime.datetime(year=year, month=month, day=day,
1051
hour=hour, minute=minute, second=second)
1055
if match_style == '-':
1057
elif match_style == '=':
1058
last = dt + datetime.timedelta(days=1)
1061
for i in range(len(revs)-1, -1, -1):
1062
r = self.get_revision(revs[i])
1063
# TODO: Handle timezone.
1064
dt = datetime.datetime.fromtimestamp(r.timestamp)
1065
if first >= dt and (last is None or dt >= last):
1068
for i in range(len(revs)):
1069
r = self.get_revision(revs[i])
1070
# TODO: Handle timezone.
1071
dt = datetime.datetime.fromtimestamp(r.timestamp)
1072
if first <= dt and (last is None or dt <= last):
1074
REVISION_NAMESPACES['date:'] = _namespace_date
1076
906
def revision_tree(self, revision_id):
1077
907
"""Return Tree for a revision on this branch.
1506
1332
The name of a local directory that exists but is empty.
1508
1334
from bzrlib.merge import merge
1509
from bzrlib.branch import Branch
1511
1336
assert isinstance(branch_from, Branch)
1512
1337
assert isinstance(to_location, basestring)
1514
br_to = Branch(to_location, init=True)
1339
br_to = Branch.initialize(to_location)
1515
1340
br_to.set_root_id(branch_from.get_root_id())
1516
if revision is None:
1517
1342
revno = branch_from.revno()
1519
revno, rev_id = branch_from.get_revision_info(revision)
1520
1343
br_to.update_revisions(branch_from, stop_revision=revno)
1521
1344
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1522
1345
check_clean=False, ignore_zero=True)
1524
from_location = pull_loc(branch_from)
1525
br_to.set_parent(pull_loc(branch_from))
1346
br_to.set_parent(branch_from.base)