15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22
from bzrlib.trace import mutter, note
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
23
25
sha_file, appendpath, file_kind
24
from bzrlib.errors import BzrError
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
27
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision
31
from bzrlib.delta import compare_trees
32
from bzrlib.tree import EmptyTree, RevisionTree
33
from bzrlib.inventory import Inventory
34
from bzrlib.weavestore import WeaveStore
40
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
41
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
27
42
## TODO: Maybe include checks for common corruption of newlines, etc?
45
# TODO: Some operations like log might retrieve the same revisions
46
# repeatedly to calculate deltas. We could perhaps have a weakref
47
# cache in memory to make this faster.
49
# TODO: please move the revision-string syntax stuff out of the branch
50
# object; it's clutter
31
53
def find_branch(f, **args):
32
54
if f and (f.startswith('http://') or f.startswith('https://')):
108
131
head, tail = os.path.split(f)
110
133
# reached the root, whatever that may be
111
raise BzrError('%r is not in a branch' % orig_f)
134
raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
139
# XXX: move into bzrlib.errors; subclass BzrError
114
140
class DivergedBranches(Exception):
115
141
def __init__(self, branch1, branch2):
116
142
self.branch1 = branch1
118
144
Exception.__init__(self, "These branches have diverged.")
121
class NoSuchRevision(BzrError):
122
def __init__(self, branch, revision):
124
self.revision = revision
125
msg = "Branch %s has no revision %d" % (branch, revision)
126
BzrError.__init__(self, msg)
129
147
######################################################################
247
261
self._lock = None
248
262
self._lock_mode = self._lock_count = None
251
264
def abspath(self, name):
252
265
"""Return absolute filename for something in the branch"""
253
266
return os.path.join(self.base, name)
256
268
def relpath(self, path):
257
269
"""Return path relative to this branch of something inside it.
259
271
Raises an error if path is not in this branch."""
260
272
return _relpath(self.base, path)
263
274
def controlfilename(self, file_or_path):
264
275
"""Return location relative to branch."""
265
276
if isinstance(file_or_path, basestring):
293
304
raise BzrError("invalid controlfile mode %r" % mode)
297
306
def _make_control(self):
298
from bzrlib.inventory import Inventory
299
from bzrlib.xml import pack_xml
301
307
os.mkdir(self.controlfilename([]))
302
308
self.controlfile('README', 'w').write(
303
309
"This is a Bazaar-NG control directory.\n"
304
310
"Do not change any files in this directory.\n")
305
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
306
for d in ('text-store', 'inventory-store', 'revision-store'):
311
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
312
for d in ('text-store', 'inventory-store', 'revision-store',
307
314
os.mkdir(self.controlfilename(d))
308
315
for f in ('revision-history', 'merged-patches',
309
316
'pending-merged-patches', 'branch-name',
312
319
self.controlfile(f, 'w').write('')
313
320
mutter('created control directory in ' + self.base)
315
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
322
# if we want per-tree root ids then this is the place to set
323
# them; they're not needed for now and so ommitted for
325
f = self.controlfile('inventory','w')
326
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
318
329
def _check_format(self):
319
330
"""Check this branch format is supported.
321
The current tool only supports the current unstable format.
332
The format level is stored, as an integer, in
333
self._branch_format for code that needs to check it later.
323
335
In the future, we might need different in-memory Branch
324
336
classes to support downlevel branches. But not yet.
326
# This ignores newlines so that we can open branches created
327
# on Windows from Linux and so on. I think it might be better
328
# to always make all internal files in unix format.
329
338
fmt = self.controlfile('branch-format', 'r').read()
330
fmt.replace('\r\n', '')
331
if fmt != BZR_BRANCH_FORMAT:
332
raise BzrError('sorry, branch format %r not supported' % fmt,
333
['use a different bzr version',
334
'or remove the .bzr directory and "bzr init" again'])
339
if fmt == BZR_BRANCH_FORMAT_5:
340
self._branch_format = 5
342
raise BzrError('sorry, branch format "%s" not supported; '
343
'use a different bzr version, '
344
'or run "bzr upgrade", '
345
'or remove the .bzr directory and "bzr init" again'
346
% fmt.rstrip('\n\r'))
336
348
def get_root_id(self):
337
349
"""Return the id of this branches root"""
353
365
def read_working_inventory(self):
354
366
"""Read the working inventory."""
355
from bzrlib.inventory import Inventory
356
from bzrlib.xml import unpack_xml
357
from time import time
361
369
# ElementTree does its own conversion from UTF-8, so open in
363
inv = unpack_xml(Inventory,
364
self.controlfile('inventory', 'rb'))
365
mutter("loaded inventory of %d items in %f"
366
% (len(inv), time() - before))
371
f = self.controlfile('inventory', 'rb')
372
return bzrlib.xml5.serializer_v5.read_inventory(f)
396
400
"""Inventory for the working copy.""")
399
def add(self, files, verbose=False, ids=None):
403
def add(self, files, ids=None):
400
404
"""Make files versioned.
402
Note that the command line normally calls smart_add instead.
406
Note that the command line normally calls smart_add instead,
407
which can automatically recurse.
404
409
This puts the files in the Added state, so that they will be
405
410
recorded by the next commit.
415
420
TODO: Perhaps have an option to add the ids even if the files do
418
TODO: Perhaps return the ids of the files? But then again it
419
is easy to retrieve them if they're needed.
421
TODO: Adding a directory should optionally recurse down and
422
add all non-ignored children. Perhaps do that in a
423
TODO: Perhaps yield the ids and paths as they're added.
425
from bzrlib.textui import show_status
426
425
# TODO: Re-adding a file that is removed in the working copy
427
426
# should probably put it back with the previous ID.
428
427
if isinstance(files, basestring):
580
def get_revision_xml_file(self, revision_id):
581
"""Return XML file object for revision object."""
582
if not revision_id or not isinstance(revision_id, basestring):
583
raise InvalidRevisionId(revision_id)
588
return self.revision_store[revision_id]
590
raise bzrlib.errors.NoSuchRevision(self, revision_id)
596
get_revision_xml = get_revision_xml_file
585
599
def get_revision(self, revision_id):
586
600
"""Return the Revision object for a named revision"""
587
from bzrlib.revision import Revision
588
from bzrlib.xml import unpack_xml
601
xml_file = self.get_revision_xml_file(revision_id)
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])
604
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
605
except SyntaxError, e:
606
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
598
610
assert r.revision_id == revision_id
614
def get_revision_delta(self, revno):
615
"""Return the delta for one revision.
617
The delta is relative to its mainline predecessor, or the
618
empty tree for revision 1.
620
assert isinstance(revno, int)
621
rh = self.revision_history()
622
if not (1 <= revno <= len(rh)):
623
raise InvalidRevisionNumber(revno)
625
# revno is 1-based; list is 0-based
627
new_tree = self.revision_tree(rh[revno-1])
629
old_tree = EmptyTree()
631
old_tree = self.revision_tree(rh[revno-2])
633
return compare_trees(old_tree, new_tree)
602
637
def get_revision_sha1(self, revision_id):
607
642
# the revision, (add signatures/remove signatures) and still
608
643
# have all hash pointers stay consistent.
609
644
# But for now, just hash the contents.
610
return sha_file(self.revision_store[revision_id])
613
def get_inventory(self, inventory_id):
645
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
648
def get_inventory(self, revision_id):
614
649
"""Get Inventory object by hash.
616
651
TODO: Perhaps for this and similar methods, take a revision
617
652
parameter which can be either an integer revno or a
619
from bzrlib.inventory import Inventory
620
from bzrlib.xml import unpack_xml
622
return unpack_xml(Inventory, self.inventory_store[inventory_id])
654
f = self.get_inventory_xml_file(revision_id)
655
return bzrlib.xml5.serializer_v5.read_inventory(f)
658
def get_inventory_xml(self, revision_id):
659
"""Get inventory XML as a file object."""
661
assert isinstance(revision_id, basestring), type(revision_id)
662
return self.inventory_store[revision_id]
664
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
666
get_inventory_xml_file = get_inventory_xml
625
def get_inventory_sha1(self, inventory_id):
669
def get_inventory_sha1(self, revision_id):
626
670
"""Return the sha1 hash of the inventory entry
628
return sha_file(self.inventory_store[inventory_id])
672
return sha_file(self.get_inventory_xml_file(revision_id))
631
675
def get_revision_inventory(self, revision_id):
697
740
return r+1, my_history[r]
698
741
return None, None
700
def enum_history(self, direction):
701
"""Return (revno, revision_id) for history of branch.
704
'forward' is from earliest to latest
705
'reverse' is from latest to earliest
707
rh = self.revision_history()
708
if direction == 'forward':
713
elif direction == 'reverse':
719
raise ValueError('invalid history direction', direction)
723
745
"""Return current revision number for this branch.
741
def missing_revisions(self, other, stop_revision=None):
763
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
743
765
If self and other have not diverged, return a list of the revisions
744
766
present in other, but missing from self.
777
799
if stop_revision is None:
778
800
stop_revision = other_len
779
801
elif stop_revision > other_len:
780
raise NoSuchRevision(self, stop_revision)
802
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
782
804
return other_history[self_len:stop_revision]
785
807
def update_revisions(self, other, stop_revision=None):
786
808
"""Pull in all new revisions from other branch.
788
>>> from bzrlib.commit import commit
789
>>> bzrlib.trace.silent = True
790
>>> br1 = ScratchBranch(files=['foo', 'bar'])
793
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
794
>>> br2 = ScratchBranch()
795
>>> br2.update_revisions(br1)
799
>>> br2.revision_history()
801
>>> br2.update_revisions(br1)
805
>>> br1.text_store.total_size() == br2.text_store.total_size()
808
from bzrlib.progress import ProgressBar
810
from bzrlib.fetch import greedy_fetch
812
pb = bzrlib.ui.ui_factory.progress_bar()
812
813
pb.update('comparing histories')
813
815
revision_ids = self.missing_revisions(other, stop_revision)
817
if len(revision_ids) > 0:
818
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
821
self.append_revision(*revision_ids)
822
## note("Added %d revisions." % count)
825
def install_revisions(self, other, revision_ids, pb):
815
826
if hasattr(other.revision_store, "prefetch"):
816
827
other.revision_store.prefetch(revision_ids)
817
828
if hasattr(other.inventory_store, "prefetch"):
818
829
inventory_ids = [other.get_revision(r).inventory_id
819
830
for r in revision_ids]
820
831
other.inventory_store.prefetch(inventory_ids)
834
pb = bzrlib.ui.ui_factory.progress_bar()
823
837
needed_texts = set()
825
for rev_id in revision_ids:
827
pb.update('fetching revision', i, len(revision_ids))
828
rev = other.get_revision(rev_id)
841
for i, rev_id in enumerate(revision_ids):
842
pb.update('fetching revision', i+1, len(revision_ids))
844
rev = other.get_revision(rev_id)
845
except bzrlib.errors.NoSuchRevision:
829
849
revisions.append(rev)
830
850
inv = other.get_inventory(str(rev.inventory_id))
831
851
for key, entry in inv.iter_entries():
839
count = self.text_store.copy_multi(other.text_store, needed_texts)
840
print "Added %d texts." % count
859
count, cp_fail = self.text_store.copy_multi(other.text_store,
861
#print "Added %d texts." % count
841
862
inventory_ids = [ f.inventory_id for f in revisions ]
842
count = self.inventory_store.copy_multi(other.inventory_store,
844
print "Added %d inventories." % count
863
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
865
#print "Added %d inventories." % count
845
866
revision_ids = [ f.revision_id for f in revisions]
846
count = self.revision_store.copy_multi(other.revision_store,
848
for revision_id in revision_ids:
849
self.append_revision(revision_id)
850
print "Added %d revisions." % count
868
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
871
assert len(cp_fail) == 0
872
return count, failures
853
875
def commit(self, *args, **kw):
854
from bzrlib.commit import commit
855
commit(self, *args, **kw)
876
from bzrlib.commit import Commit
877
Commit().commit(self, *args, **kw)
858
880
def lookup_revision(self, revision):
859
881
"""Return the revision identifier for a given revision information."""
860
revno, info = self.get_revision_info(revision)
882
revno, info = self._get_revision_info(revision)
886
def revision_id_to_revno(self, revision_id):
887
"""Given a revision id, return its revno"""
888
history = self.revision_history()
890
return history.index(revision_id) + 1
892
raise bzrlib.errors.NoSuchRevision(self, revision_id)
863
895
def get_revision_info(self, revision):
864
896
"""Return (revno, revision id) for revision identifier.
868
900
revision can also be a string, in which case it is parsed for something like
869
901
'date:' or 'revid:' etc.
903
revno, rev_id = self._get_revision_info(revision)
905
raise bzrlib.errors.NoSuchRevision(self, revision)
908
def get_rev_id(self, revno, history=None):
909
"""Find the revision id of the specified revno."""
913
history = self.revision_history()
914
elif revno <= 0 or revno > len(history):
915
raise bzrlib.errors.NoSuchRevision(self, revno)
916
return history[revno - 1]
918
def _get_revision_info(self, revision):
919
"""Return (revno, revision id) for revision specifier.
921
revision can be an integer, in which case it is assumed to be revno
922
(though this will translate negative values into positive ones)
923
revision can also be a string, in which case it is parsed for something
924
like 'date:' or 'revid:' etc.
926
A revid is always returned. If it is None, the specifier referred to
927
the null revision. If the revid does not occur in the revision
928
history, revno will be None.
871
931
if revision is None:
878
938
revs = self.revision_history()
879
939
if isinstance(revision, int):
882
# Mabye we should do this first, but we don't need it if revision == 0
884
941
revno = len(revs) + revision + 1
944
rev_id = self.get_rev_id(revno, revs)
887
945
elif isinstance(revision, basestring):
888
946
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
889
947
if revision.startswith(prefix):
890
revno = func(self, revs, revision)
948
result = func(self, revs, revision)
950
revno, rev_id = result
953
rev_id = self.get_rev_id(revno, revs)
893
raise BzrError('No namespace registered for string: %r' % revision)
956
raise BzrError('No namespace registered for string: %r' %
959
raise TypeError('Unhandled revision type %s' % revision)
895
if revno is None or revno <= 0 or revno > len(revs):
896
raise BzrError("no such revision %s" % revision)
897
return revno, revs[revno-1]
963
raise bzrlib.errors.NoSuchRevision(self, revision)
899
966
def _namespace_revno(self, revs, revision):
900
967
"""Lookup a revision by revision number"""
901
968
assert revision.startswith('revno:')
903
return int(revision[6:])
970
return (int(revision[6:]),)
904
971
except ValueError:
906
973
REVISION_NAMESPACES['revno:'] = _namespace_revno
908
975
def _namespace_revid(self, revs, revision):
909
976
assert revision.startswith('revid:')
977
rev_id = revision[len('revid:'):]
911
return revs.index(revision[6:]) + 1
979
return revs.index(rev_id) + 1, rev_id
912
980
except ValueError:
914
982
REVISION_NAMESPACES['revid:'] = _namespace_revid
916
984
def _namespace_last(self, revs, revision):
919
987
offset = int(revision[5:])
920
988
except ValueError:
924
992
raise BzrError('You must supply a positive value for --revision last:XXX')
925
return len(revs) - offset + 1
993
return (len(revs) - offset + 1,)
926
994
REVISION_NAMESPACES['last:'] = _namespace_last
928
996
def _namespace_tag(self, revs, revision):
1003
1071
# TODO: Handle timezone.
1004
1072
dt = datetime.datetime.fromtimestamp(r.timestamp)
1005
1073
if first >= dt and (last is None or dt >= last):
1008
1076
for i in range(len(revs)):
1009
1077
r = self.get_revision(revs[i])
1010
1078
# TODO: Handle timezone.
1011
1079
dt = datetime.datetime.fromtimestamp(r.timestamp)
1012
1080
if first <= dt and (last is None or dt <= last):
1014
1082
REVISION_NAMESPACES['date:'] = _namespace_date
1016
1084
def revision_tree(self, revision_id):
1019
1087
`revision_id` may be None for the null revision, in which case
1020
1088
an `EmptyTree` is returned."""
1021
from bzrlib.tree import EmptyTree, RevisionTree
1022
1089
# TODO: refactor this to use an existing revision object
1023
1090
# so we don't need to read it in twice.
1024
1091
if revision_id == None:
1025
return EmptyTree(self.get_root_id())
1027
1094
inv = self.get_revision_inventory(revision_id)
1028
return RevisionTree(self.text_store, inv)
1095
return RevisionTree(self.weave_store, inv, revision_id)
1031
1098
def working_tree(self):
1040
1107
If there are no revisions yet, return an `EmptyTree`.
1042
from bzrlib.tree import EmptyTree, RevisionTree
1043
r = self.last_patch()
1045
return EmptyTree(self.get_root_id())
1047
return RevisionTree(self.text_store, self.get_revision_inventory(r))
1109
return self.revision_tree(self.last_patch())
1051
1112
def rename_one(self, from_rel, to_rel):
1150
1213
for f in from_paths:
1151
1214
name_tail = splitpath(f)[-1]
1152
1215
dest_path = appendpath(to_name, name_tail)
1153
print "%s => %s" % (f, dest_path)
1216
result.append((f, dest_path))
1154
1217
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1156
1219
os.rename(self.abspath(f), self.abspath(dest_path))
1317
def get_parent(self):
1318
"""Return the parent location of the branch.
1320
This is the default location for push/pull/missing. The usual
1321
pattern is that the user can override it by specifying a
1325
_locs = ['parent', 'pull', 'x-pull']
1328
return self.controlfile(l, 'r').read().strip('\n')
1330
if e.errno != errno.ENOENT:
1335
def set_parent(self, url):
1336
# TODO: Maybe delete old location files?
1337
from bzrlib.atomicfile import AtomicFile
1340
f = AtomicFile(self.controlfilename('parent'))
1349
def check_revno(self, revno):
1351
Check whether a revno corresponds to any revision.
1352
Zero (the NULL revision) is considered valid.
1355
self.check_real_revno(revno)
1357
def check_real_revno(self, revno):
1359
Check whether a revno corresponds to a real revision.
1360
Zero (the NULL revision) is considered invalid
1362
if revno < 1 or revno > self.revno():
1363
raise InvalidRevisionNumber(revno)
1253
1368
class ScratchBranch(Branch):
1254
1369
"""Special test class: a branch that cleans up after itself.
1371
1488
"""Return a new tree-root file id."""
1372
1489
return gen_file_id('TREE_ROOT')
1492
def pull_loc(branch):
1493
# TODO: Should perhaps just make attribute be 'base' in
1494
# RemoteBranch and Branch?
1495
if hasattr(branch, "baseurl"):
1496
return branch.baseurl
1501
def copy_branch(branch_from, to_location, revision=None):
1502
"""Copy branch_from into the existing directory to_location.
1505
If not None, only revisions up to this point will be copied.
1506
The head of the new branch will be that revision.
1509
The name of a local directory that exists but is empty.
1511
from bzrlib.merge import merge
1512
from bzrlib.branch import Branch
1514
assert isinstance(branch_from, Branch)
1515
assert isinstance(to_location, basestring)
1517
br_to = Branch(to_location, init=True)
1518
br_to.set_root_id(branch_from.get_root_id())
1519
if revision is None:
1520
revno = branch_from.revno()
1522
revno, rev_id = branch_from.get_revision_info(revision)
1523
br_to.update_revisions(branch_from, stop_revision=revno)
1524
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1525
check_clean=False, ignore_zero=True)
1527
from_location = pull_loc(branch_from)
1528
br_to.set_parent(pull_loc(branch_from))