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
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
21
from bzrlib.trace import mutter, note
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
23
sha_file, appendpath, file_kind
24
from bzrlib.errors import BzrError
35
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
36
27
## TODO: Maybe include checks for common corruption of newlines, etc?
315
333
['use a different bzr version',
316
334
'or remove the .bzr directory and "bzr init" again'])
336
def get_root_id(self):
337
"""Return the id of this branches root"""
338
inv = self.read_working_inventory()
339
return inv.root.file_id
341
def set_root_id(self, file_id):
342
inv = self.read_working_inventory()
343
orig_root_id = inv.root.file_id
344
del inv._byid[inv.root.file_id]
345
inv.root.file_id = file_id
346
inv._byid[inv.root.file_id] = inv.root
349
if entry.parent_id in (None, orig_root_id):
350
entry.parent_id = inv.root.file_id
351
self._write_inventory(inv)
320
353
def read_working_inventory(self):
321
354
"""Read the working inventory."""
323
# ElementTree does its own conversion from UTF-8, so open in
355
from bzrlib.inventory import Inventory
356
from bzrlib.xml import unpack_xml
357
from time import time
327
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
361
# ElementTree does its own conversion from UTF-8, so open in
363
inv = unpack_xml(Inventory,
364
self.controlfile('inventory', 'rb'))
328
365
mutter("loaded inventory of %d items in %f"
329
% (len(inv), time.time() - before))
366
% (len(inv), time() - before))
520
564
return self.working_tree().unknowns()
523
def append_revision(self, revision_id):
524
mutter("add {%s} to revision-history" % revision_id)
567
def append_revision(self, *revision_ids):
568
from bzrlib.atomicfile import AtomicFile
570
for revision_id in revision_ids:
571
mutter("add {%s} to revision-history" % revision_id)
525
573
rev_history = self.revision_history()
527
tmprhname = self.controlfilename('revision-history.tmp')
528
rhname = self.controlfilename('revision-history')
530
f = file(tmprhname, 'wt')
531
rev_history.append(revision_id)
532
f.write('\n'.join(rev_history))
536
if sys.platform == 'win32':
538
os.rename(tmprhname, rhname)
574
rev_history.extend(revision_ids)
576
f = AtomicFile(self.controlfilename('revision-history'))
578
for rev_id in rev_history:
542
585
def get_revision(self, revision_id):
543
586
"""Return the Revision object for a named revision"""
544
if not revision_id or not isinstance(revision_id, basestring):
545
raise ValueError('invalid revision-id: %r' % revision_id)
546
r = Revision.read_xml(self.revision_store[revision_id])
587
from bzrlib.revision import Revision
588
from bzrlib.xml import unpack_xml
592
if not revision_id or not isinstance(revision_id, basestring):
593
raise ValueError('invalid revision-id: %r' % revision_id)
594
r = unpack_xml(Revision, self.revision_store[revision_id])
547
598
assert r.revision_id == revision_id
550
602
def get_revision_sha1(self, revision_id):
551
603
"""Hash the stored value of a revision, and return it."""
787
857
def commit(self, *args, **kw):
789
858
from bzrlib.commit import commit
790
859
commit(self, *args, **kw)
793
def lookup_revision(self, revno):
794
"""Return revision hash for revision number."""
799
# list is 0-based; revisions are 1-based
800
return self.revision_history()[revno-1]
802
raise BzrError("no such revision %s" % revno)
862
def lookup_revision(self, revision):
863
"""Return the revision identifier for a given revision information."""
864
revno, info = self.get_revision_info(revision)
867
def get_revision_info(self, revision):
868
"""Return (revno, revision id) for revision identifier.
870
revision can be an integer, in which case it is assumed to be revno (though
871
this will translate negative values into positive ones)
872
revision can also be a string, in which case it is parsed for something like
873
'date:' or 'revid:' etc.
878
try:# Convert to int if possible
879
revision = int(revision)
882
revs = self.revision_history()
883
if isinstance(revision, int):
886
# Mabye we should do this first, but we don't need it if revision == 0
888
revno = len(revs) + revision + 1
891
elif isinstance(revision, basestring):
892
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
893
if revision.startswith(prefix):
894
revno = func(self, revs, revision)
897
raise BzrError('No namespace registered for string: %r' % revision)
899
if revno is None or revno <= 0 or revno > len(revs):
900
raise BzrError("no such revision %s" % revision)
901
return revno, revs[revno-1]
903
def _namespace_revno(self, revs, revision):
904
"""Lookup a revision by revision number"""
905
assert revision.startswith('revno:')
907
return int(revision[6:])
910
REVISION_NAMESPACES['revno:'] = _namespace_revno
912
def _namespace_revid(self, revs, revision):
913
assert revision.startswith('revid:')
915
return revs.index(revision[6:]) + 1
918
REVISION_NAMESPACES['revid:'] = _namespace_revid
920
def _namespace_last(self, revs, revision):
921
assert revision.startswith('last:')
923
offset = int(revision[5:])
928
raise BzrError('You must supply a positive value for --revision last:XXX')
929
return len(revs) - offset + 1
930
REVISION_NAMESPACES['last:'] = _namespace_last
932
def _namespace_tag(self, revs, revision):
933
assert revision.startswith('tag:')
934
raise BzrError('tag: namespace registered, but not implemented.')
935
REVISION_NAMESPACES['tag:'] = _namespace_tag
937
def _namespace_date(self, revs, revision):
938
assert revision.startswith('date:')
940
# Spec for date revisions:
942
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
943
# it can also start with a '+/-/='. '+' says match the first
944
# entry after the given date. '-' is match the first entry before the date
945
# '=' is match the first entry after, but still on the given date.
947
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
948
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
949
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
950
# May 13th, 2005 at 0:00
952
# So the proper way of saying 'give me all entries for today' is:
953
# -r {date:+today}:{date:-tomorrow}
954
# The default is '=' when not supplied
957
if val[:1] in ('+', '-', '='):
958
match_style = val[:1]
961
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
962
if val.lower() == 'yesterday':
963
dt = today - datetime.timedelta(days=1)
964
elif val.lower() == 'today':
966
elif val.lower() == 'tomorrow':
967
dt = today + datetime.timedelta(days=1)
970
# This should be done outside the function to avoid recompiling it.
971
_date_re = re.compile(
972
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
974
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
976
m = _date_re.match(val)
977
if not m or (not m.group('date') and not m.group('time')):
978
raise BzrError('Invalid revision date %r' % revision)
981
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
983
year, month, day = today.year, today.month, today.day
985
hour = int(m.group('hour'))
986
minute = int(m.group('minute'))
987
if m.group('second'):
988
second = int(m.group('second'))
992
hour, minute, second = 0,0,0
994
dt = datetime.datetime(year=year, month=month, day=day,
995
hour=hour, minute=minute, second=second)
999
if match_style == '-':
1001
elif match_style == '=':
1002
last = dt + datetime.timedelta(days=1)
1005
for i in range(len(revs)-1, -1, -1):
1006
r = self.get_revision(revs[i])
1007
# TODO: Handle timezone.
1008
dt = datetime.datetime.fromtimestamp(r.timestamp)
1009
if first >= dt and (last is None or dt >= last):
1012
for i in range(len(revs)):
1013
r = self.get_revision(revs[i])
1014
# TODO: Handle timezone.
1015
dt = datetime.datetime.fromtimestamp(r.timestamp)
1016
if first <= dt and (last is None or dt <= last):
1018
REVISION_NAMESPACES['date:'] = _namespace_date
805
1020
def revision_tree(self, revision_id):
806
1021
"""Return Tree for a revision on this branch.
808
1023
`revision_id` may be None for the null revision, in which case
809
1024
an `EmptyTree` is returned."""
1025
from bzrlib.tree import EmptyTree, RevisionTree
810
1026
# TODO: refactor this to use an existing revision object
811
1027
# so we don't need to read it in twice.
812
1028
if revision_id == None:
1029
return EmptyTree(self.get_root_id())
815
1031
inv = self.get_revision_inventory(revision_id)
816
1032
return RevisionTree(self.text_store, inv)
1170
def revert(self, filenames, old_tree=None, backups=True):
1171
"""Restore selected files to the versions from a previous tree.
1174
If true (default) backups are made of files before
1177
from bzrlib.errors import NotVersionedError, BzrError
1178
from bzrlib.atomicfile import AtomicFile
1179
from bzrlib.osutils import backup_file
1181
inv = self.read_working_inventory()
1182
if old_tree is None:
1183
old_tree = self.basis_tree()
1184
old_inv = old_tree.inventory
1187
for fn in filenames:
1188
file_id = inv.path2id(fn)
1190
raise NotVersionedError("not a versioned file", fn)
1191
if not old_inv.has_id(file_id):
1192
raise BzrError("file not present in old tree", fn, file_id)
1193
nids.append((fn, file_id))
1195
# TODO: Rename back if it was previously at a different location
1197
# TODO: If given a directory, restore the entire contents from
1198
# the previous version.
1200
# TODO: Make a backup to a temporary file.
1202
# TODO: If the file previously didn't exist, delete it?
1203
for fn, file_id in nids:
1206
f = AtomicFile(fn, 'wb')
1208
f.write(old_tree.get_file(file_id).read())
1214
def pending_merges(self):
1215
"""Return a list of pending merges.
1217
These are revisions that have been merged into the working
1218
directory but not yet committed.
1220
cfn = self.controlfilename('pending-merges')
1221
if not os.path.exists(cfn):
1224
for l in self.controlfile('pending-merges', 'r').readlines():
1225
p.append(l.rstrip('\n'))
1229
def add_pending_merge(self, revision_id):
1230
from bzrlib.revision import validate_revision_id
1232
validate_revision_id(revision_id)
1234
p = self.pending_merges()
1235
if revision_id in p:
1237
p.append(revision_id)
1238
self.set_pending_merges(p)
1241
def set_pending_merges(self, rev_list):
1242
from bzrlib.atomicfile import AtomicFile
1245
f = AtomicFile(self.controlfilename('pending-merges'))
954
1257
class ScratchBranch(Branch):
955
1258
"""Special test class: a branch that cleans up after itself.