15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
19
import traceback, socket, fnmatch, difflib, time
20
from binascii import hexlify
22
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
sha_file, appendpath, file_kind
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
from bzrlib.textui import show_status
29
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
31
from bzrlib.delta import compare_trees
32
from bzrlib.tree import EmptyTree, RevisionTree
23
from inventory import Inventory
24
from trace import mutter, note
25
from tree import Tree, EmptyTree, RevisionTree
26
from inventory import InventoryEntry, Inventory
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
28
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
29
joinpath, sha_file, sha_string, file_kind, local_time_offset, appendpath
30
from store import ImmutableStore
31
from revision import Revision
32
from errors import BzrError
33
from textui import show_status
34
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
36
## TODO: Maybe include checks for common corruption of newlines, etc?
38
# TODO: Some operations like log might retrieve the same revisions
39
# repeatedly to calculate deltas. We could perhaps have a weakref
40
# cache in memory to make this faster.
42
# TODO: please move the revision-string syntax stuff out of the branch
43
# object; it's clutter
46
40
def find_branch(f, **args):
47
41
if f and (f.startswith('http://') or f.startswith('https://')):
51
45
return Branch(f, **args)
54
def find_cached_branch(f, cache_root, **args):
55
from remotebranch import RemoteBranch
56
br = find_branch(f, **args)
57
def cacheify(br, store_name):
58
from meta_store import CachedStore
59
cache_path = os.path.join(cache_root, store_name)
61
new_store = CachedStore(getattr(br, store_name), cache_path)
62
setattr(br, store_name, new_store)
64
if isinstance(br, RemoteBranch):
65
cacheify(br, 'inventory_store')
66
cacheify(br, 'text_store')
67
cacheify(br, 'revision_store')
71
49
def _relpath(base, path):
72
50
"""Return path relative to base, or raise exception.
347
315
['use a different bzr version',
348
316
'or remove the .bzr directory and "bzr init" again'])
350
def get_root_id(self):
351
"""Return the id of this branches root"""
352
inv = self.read_working_inventory()
353
return inv.root.file_id
355
def set_root_id(self, file_id):
356
inv = self.read_working_inventory()
357
orig_root_id = inv.root.file_id
358
del inv._byid[inv.root.file_id]
359
inv.root.file_id = file_id
360
inv._byid[inv.root.file_id] = inv.root
363
if entry.parent_id in (None, orig_root_id):
364
entry.parent_id = inv.root.file_id
365
self._write_inventory(inv)
367
320
def read_working_inventory(self):
368
321
"""Read the working inventory."""
369
from bzrlib.inventory import Inventory
370
from bzrlib.xml import unpack_xml
371
from time import time
323
# ElementTree does its own conversion from UTF-8, so open in
375
# ElementTree does its own conversion from UTF-8, so open in
377
inv = unpack_xml(Inventory,
378
self.controlfile('inventory', 'rb'))
327
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
379
328
mutter("loaded inventory of %d items in %f"
380
% (len(inv), time() - before))
329
% (len(inv), time.time() - before))
597
def get_revision_xml(self, revision_id):
598
"""Return XML file object for revision object."""
599
if not revision_id or not isinstance(revision_id, basestring):
600
raise InvalidRevisionId(revision_id)
605
return self.revision_store[revision_id]
607
raise bzrlib.errors.NoSuchRevision(self, revision_id)
612
541
def get_revision(self, revision_id):
613
542
"""Return the Revision object for a named revision"""
614
xml_file = self.get_revision_xml(revision_id)
617
r = unpack_xml(Revision, xml_file)
618
except SyntaxError, e:
619
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
543
if not revision_id or not isinstance(revision_id, basestring):
544
raise ValueError('invalid revision-id: %r' % revision_id)
545
r = Revision.read_xml(self.revision_store[revision_id])
623
546
assert r.revision_id == revision_id
627
def get_revision_delta(self, revno):
628
"""Return the delta for one revision.
630
The delta is relative to its mainline predecessor, or the
631
empty tree for revision 1.
633
assert isinstance(revno, int)
634
rh = self.revision_history()
635
if not (1 <= revno <= len(rh)):
636
raise InvalidRevisionNumber(revno)
638
# revno is 1-based; list is 0-based
640
new_tree = self.revision_tree(rh[revno-1])
642
old_tree = EmptyTree()
644
old_tree = self.revision_tree(rh[revno-2])
646
return compare_trees(old_tree, new_tree)
650
549
def get_revision_sha1(self, revision_id):
651
550
"""Hash the stored value of a revision, and return it."""
652
551
# In the future, revision entries will be signed. At that
664
563
TODO: Perhaps for this and similar methods, take a revision
665
564
parameter which can be either an integer revno or a
667
from bzrlib.inventory import Inventory
668
from bzrlib.xml import unpack_xml
670
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
673
def get_inventory_xml(self, inventory_id):
674
"""Get inventory XML as a file object."""
675
return self.inventory_store[inventory_id]
566
i = Inventory.read_xml(self.inventory_store[inventory_id])
678
569
def get_inventory_sha1(self, inventory_id):
679
570
"""Return the sha1 hash of the inventory entry
681
return sha_file(self.get_inventory_xml(inventory_id))
572
return sha_file(self.inventory_store[inventory_id])
684
575
def get_revision_inventory(self, revision_id):
685
576
"""Return inventory of a past revision."""
686
# bzr 0.0.6 imposes the constraint that the inventory_id
687
# must be the same as its revision, so this is trivial.
688
577
if revision_id == None:
689
from bzrlib.inventory import Inventory
690
return Inventory(self.get_root_id())
692
return self.get_inventory(revision_id)
580
return self.get_inventory(self.get_revision(revision_id).inventory_id)
695
583
def revision_history(self):
887
788
commit(self, *args, **kw)
890
def lookup_revision(self, revision):
891
"""Return the revision identifier for a given revision information."""
892
revno, info = self.get_revision_info(revision)
895
def get_revision_info(self, revision):
896
"""Return (revno, revision id) for revision identifier.
898
revision can be an integer, in which case it is assumed to be revno (though
899
this will translate negative values into positive ones)
900
revision can also be a string, in which case it is parsed for something like
901
'date:' or 'revid:' etc.
906
try:# Convert to int if possible
907
revision = int(revision)
910
revs = self.revision_history()
911
if isinstance(revision, int):
914
# Mabye we should do this first, but we don't need it if revision == 0
916
revno = len(revs) + revision + 1
919
elif isinstance(revision, basestring):
920
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
921
if revision.startswith(prefix):
922
revno = func(self, revs, revision)
925
raise BzrError('No namespace registered for string: %r' % revision)
927
if revno is None or revno <= 0 or revno > len(revs):
928
raise BzrError("no such revision %s" % revision)
929
return revno, revs[revno-1]
931
def _namespace_revno(self, revs, revision):
932
"""Lookup a revision by revision number"""
933
assert revision.startswith('revno:')
935
return int(revision[6:])
938
REVISION_NAMESPACES['revno:'] = _namespace_revno
940
def _namespace_revid(self, revs, revision):
941
assert revision.startswith('revid:')
943
return revs.index(revision[6:]) + 1
946
REVISION_NAMESPACES['revid:'] = _namespace_revid
948
def _namespace_last(self, revs, revision):
949
assert revision.startswith('last:')
951
offset = int(revision[5:])
956
raise BzrError('You must supply a positive value for --revision last:XXX')
957
return len(revs) - offset + 1
958
REVISION_NAMESPACES['last:'] = _namespace_last
960
def _namespace_tag(self, revs, revision):
961
assert revision.startswith('tag:')
962
raise BzrError('tag: namespace registered, but not implemented.')
963
REVISION_NAMESPACES['tag:'] = _namespace_tag
965
def _namespace_date(self, revs, revision):
966
assert revision.startswith('date:')
968
# Spec for date revisions:
970
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
971
# it can also start with a '+/-/='. '+' says match the first
972
# entry after the given date. '-' is match the first entry before the date
973
# '=' is match the first entry after, but still on the given date.
975
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
976
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
977
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
978
# May 13th, 2005 at 0:00
980
# So the proper way of saying 'give me all entries for today' is:
981
# -r {date:+today}:{date:-tomorrow}
982
# The default is '=' when not supplied
985
if val[:1] in ('+', '-', '='):
986
match_style = val[:1]
989
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
990
if val.lower() == 'yesterday':
991
dt = today - datetime.timedelta(days=1)
992
elif val.lower() == 'today':
994
elif val.lower() == 'tomorrow':
995
dt = today + datetime.timedelta(days=1)
998
# This should be done outside the function to avoid recompiling it.
999
_date_re = re.compile(
1000
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1002
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1004
m = _date_re.match(val)
1005
if not m or (not m.group('date') and not m.group('time')):
1006
raise BzrError('Invalid revision date %r' % revision)
1009
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1011
year, month, day = today.year, today.month, today.day
1013
hour = int(m.group('hour'))
1014
minute = int(m.group('minute'))
1015
if m.group('second'):
1016
second = int(m.group('second'))
1020
hour, minute, second = 0,0,0
1022
dt = datetime.datetime(year=year, month=month, day=day,
1023
hour=hour, minute=minute, second=second)
1027
if match_style == '-':
1029
elif match_style == '=':
1030
last = dt + datetime.timedelta(days=1)
1033
for i in range(len(revs)-1, -1, -1):
1034
r = self.get_revision(revs[i])
1035
# TODO: Handle timezone.
1036
dt = datetime.datetime.fromtimestamp(r.timestamp)
1037
if first >= dt and (last is None or dt >= last):
1040
for i in range(len(revs)):
1041
r = self.get_revision(revs[i])
1042
# TODO: Handle timezone.
1043
dt = datetime.datetime.fromtimestamp(r.timestamp)
1044
if first <= dt and (last is None or dt <= last):
1046
REVISION_NAMESPACES['date:'] = _namespace_date
791
def lookup_revision(self, revno):
792
"""Return revision hash for revision number."""
797
# list is 0-based; revisions are 1-based
798
return self.revision_history()[revno-1]
800
raise BzrError("no such revision %s" % revno)
1048
803
def revision_tree(self, revision_id):
1049
804
"""Return Tree for a revision on this branch.