50
50
def find_branch(f, **args):
51
51
if f and (f.startswith('http://') or f.startswith('https://')):
52
from bzrlib.remotebranch import RemoteBranch
53
return RemoteBranch(f, **args)
53
return remotebranch.RemoteBranch(f, **args)
55
return LocalBranch(f, **args)
55
return Branch(f, **args)
58
58
def find_cached_branch(f, cache_root, **args):
59
from bzrlib.remotebranch import RemoteBranch
59
from remotebranch import RemoteBranch
60
60
br = find_branch(f, **args)
61
61
def cacheify(br, store_name):
62
from bzrlib.meta_store import CachedStore
62
from meta_store import CachedStore
63
63
cache_path = os.path.join(cache_root, store_name)
64
64
os.mkdir(cache_path)
65
65
new_store = CachedStore(getattr(br, store_name), cache_path)
705
703
def common_ancestor(self, other, self_revno=None, other_revno=None):
707
>>> from bzrlib.commit import commit
708
706
>>> sb = ScratchBranch(files=['foo', 'foo~'])
709
707
>>> sb.common_ancestor(sb) == (None, None)
711
>>> commit(sb, "Committing first revision", verbose=False)
709
>>> commit.commit(sb, "Committing first revision", verbose=False)
712
710
>>> sb.common_ancestor(sb)[0]
714
712
>>> clone = sb.clone()
715
>>> commit(sb, "Committing second revision", verbose=False)
713
>>> commit.commit(sb, "Committing second revision", verbose=False)
716
714
>>> sb.common_ancestor(sb)[0]
718
716
>>> sb.common_ancestor(clone)[0]
720
>>> commit(clone, "Committing divergent second revision",
718
>>> commit.commit(clone, "Committing divergent second revision",
721
719
... verbose=False)
722
720
>>> sb.common_ancestor(clone)[0]
886
884
def lookup_revision(self, revision):
887
"""Return the revision identifier for a given revision specifier."""
888
# XXX: I'm not sure this method belongs here; I'd rather have the
889
# revision spec stuff be an UI thing, and branch blissfully unaware
891
# Also, I'm not entirely happy with this method returning None
892
# when the revision doesn't exist.
893
# But I'm keeping the contract I found, because this seems to be
894
# used in a lot of places - and when I do change these, I'd rather
895
# figure out case-by-case which ones actually want to care about
896
# revision specs (eg, they are UI-level) and which ones should trust
897
# that they have a revno/revid.
898
# -- lalo@exoweb.net, 2005-09-07
899
from bzrlib.errors import NoSuchRevision
900
from bzrlib.revisionspec import RevisionSpec
902
spec = RevisionSpec(self, revision)
903
except NoSuchRevision:
885
"""Return the revision identifier for a given revision information."""
886
revno, info = self.get_revision_info(revision)
908
890
def revision_id_to_revno(self, revision_id):
914
896
raise bzrlib.errors.NoSuchRevision(self, revision_id)
917
def get_rev_id(self, revno, history=None):
918
"""Find the revision id of the specified revno."""
922
history = self.revision_history()
923
elif revno <= 0 or revno > len(history):
924
raise bzrlib.errors.NoSuchRevision(self, revno)
925
return history[revno - 1]
899
def get_revision_info(self, revision):
900
"""Return (revno, revision id) for revision identifier.
902
revision can be an integer, in which case it is assumed to be revno (though
903
this will translate negative values into positive ones)
904
revision can also be a string, in which case it is parsed for something like
905
'date:' or 'revid:' etc.
910
try:# Convert to int if possible
911
revision = int(revision)
914
revs = self.revision_history()
915
if isinstance(revision, int):
918
# Mabye we should do this first, but we don't need it if revision == 0
920
revno = len(revs) + revision + 1
923
elif isinstance(revision, basestring):
924
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
925
if revision.startswith(prefix):
926
revno = func(self, revs, revision)
929
raise BzrError('No namespace registered for string: %r' % revision)
931
if revno is None or revno <= 0 or revno > len(revs):
932
raise BzrError("no such revision %s" % revision)
933
return revno, revs[revno-1]
935
def _namespace_revno(self, revs, revision):
936
"""Lookup a revision by revision number"""
937
assert revision.startswith('revno:')
939
return int(revision[6:])
942
REVISION_NAMESPACES['revno:'] = _namespace_revno
944
def _namespace_revid(self, revs, revision):
945
assert revision.startswith('revid:')
947
return revs.index(revision[6:]) + 1
950
REVISION_NAMESPACES['revid:'] = _namespace_revid
952
def _namespace_last(self, revs, revision):
953
assert revision.startswith('last:')
955
offset = int(revision[5:])
960
raise BzrError('You must supply a positive value for --revision last:XXX')
961
return len(revs) - offset + 1
962
REVISION_NAMESPACES['last:'] = _namespace_last
964
def _namespace_tag(self, revs, revision):
965
assert revision.startswith('tag:')
966
raise BzrError('tag: namespace registered, but not implemented.')
967
REVISION_NAMESPACES['tag:'] = _namespace_tag
969
def _namespace_date(self, revs, revision):
970
assert revision.startswith('date:')
972
# Spec for date revisions:
974
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
975
# it can also start with a '+/-/='. '+' says match the first
976
# entry after the given date. '-' is match the first entry before the date
977
# '=' is match the first entry after, but still on the given date.
979
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
980
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
981
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
982
# May 13th, 2005 at 0:00
984
# So the proper way of saying 'give me all entries for today' is:
985
# -r {date:+today}:{date:-tomorrow}
986
# The default is '=' when not supplied
989
if val[:1] in ('+', '-', '='):
990
match_style = val[:1]
993
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
994
if val.lower() == 'yesterday':
995
dt = today - datetime.timedelta(days=1)
996
elif val.lower() == 'today':
998
elif val.lower() == 'tomorrow':
999
dt = today + datetime.timedelta(days=1)
1002
# This should be done outside the function to avoid recompiling it.
1003
_date_re = re.compile(
1004
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1006
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1008
m = _date_re.match(val)
1009
if not m or (not m.group('date') and not m.group('time')):
1010
raise BzrError('Invalid revision date %r' % revision)
1013
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1015
year, month, day = today.year, today.month, today.day
1017
hour = int(m.group('hour'))
1018
minute = int(m.group('minute'))
1019
if m.group('second'):
1020
second = int(m.group('second'))
1024
hour, minute, second = 0,0,0
1026
dt = datetime.datetime(year=year, month=month, day=day,
1027
hour=hour, minute=minute, second=second)
1031
if match_style == '-':
1033
elif match_style == '=':
1034
last = dt + datetime.timedelta(days=1)
1037
for i in range(len(revs)-1, -1, -1):
1038
r = self.get_revision(revs[i])
1039
# TODO: Handle timezone.
1040
dt = datetime.datetime.fromtimestamp(r.timestamp)
1041
if first >= dt and (last is None or dt >= last):
1044
for i in range(len(revs)):
1045
r = self.get_revision(revs[i])
1046
# TODO: Handle timezone.
1047
dt = datetime.datetime.fromtimestamp(r.timestamp)
1048
if first <= dt and (last is None or dt <= last):
1050
REVISION_NAMESPACES['date:'] = _namespace_date
927
1052
def revision_tree(self, revision_id):
928
1053
"""Return Tree for a revision on this branch.
1165
def get_parent(self):
1166
"""Return the parent location of the branch.
1168
This is the default location for push/pull/missing. The usual
1169
pattern is that the user can override it by specifying a
1173
_locs = ['parent', 'pull', 'x-pull']
1176
return self.controlfile(l, 'r').read().strip('\n')
1178
if e.errno != errno.ENOENT:
1183
def set_parent(self, url):
1184
# TODO: Maybe delete old location files?
1185
from bzrlib.atomicfile import AtomicFile
1188
f = AtomicFile(self.controlfilename('parent'))
1197
def check_revno(self, revno):
1199
Check whether a revno corresponds to any revision.
1200
Zero (the NULL revision) is considered valid.
1203
self.check_real_revno(revno)
1205
def check_real_revno(self, revno):
1207
Check whether a revno corresponds to a real revision.
1208
Zero (the NULL revision) is considered invalid
1210
if revno < 1 or revno > self.revno():
1211
raise InvalidRevisionNumber(revno)
1216
class ScratchBranch(LocalBranch):
1287
class ScratchBranch(Branch):
1217
1288
"""Special test class: a branch that cleans up after itself.
1219
1290
>>> b = ScratchBranch()
1337
1406
return gen_file_id('TREE_ROOT')
1409
def pull_loc(branch):
1410
# TODO: Should perhaps just make attribute be 'base' in
1411
# RemoteBranch and Branch?
1412
if hasattr(branch, "baseurl"):
1413
return branch.baseurl
1340
1418
def copy_branch(branch_from, to_location, revision=None):
1341
1419
"""Copy branch_from into the existing directory to_location.
1344
If not None, only revisions up to this point will be copied.
1345
The head of the new branch will be that revision.
1348
The name of a local directory that exists but is empty.
1421
If revision is not None, the head of the new branch will be revision.
1350
1423
from bzrlib.merge import merge
1351
from bzrlib.revisionspec import RevisionSpec
1353
assert isinstance(branch_from, Branch)
1354
assert isinstance(to_location, basestring)
1424
from bzrlib.branch import Branch
1356
1425
br_to = Branch(to_location, init=True)
1357
1426
br_to.set_root_id(branch_from.get_root_id())
1358
1427
if revision is None:
1359
1428
revno = branch_from.revno()
1361
revno, rev_id = RevisionSpec(branch_from, revision)
1430
revno, rev_id = branch_from.get_revision_info(revision)
1362
1431
br_to.update_revisions(branch_from, stop_revision=revno)
1363
1432
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1364
1433
check_clean=False, ignore_zero=True)
1366
from_location = branch_from.base
1367
br_to.set_parent(branch_from.base)
1434
from_location = pull_loc(branch_from)
1435
br_to.controlfile("x-pull", "wb").write(from_location + "\n")