25
25
sha_file, appendpath, file_kind
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
DivergedBranches, NotBranchError
29
29
from bzrlib.textui import show_status
30
30
from bzrlib.revision import Revision
31
from bzrlib.xml import unpack_xml
32
31
from bzrlib.delta import compare_trees
33
32
from bzrlib.tree import EmptyTree, RevisionTree
50
50
def find_branch(f, **args):
51
51
if f and (f.startswith('http://') or f.startswith('https://')):
53
return remotebranch.RemoteBranch(f, **args)
52
from bzrlib.remotebranch import RemoteBranch
53
return RemoteBranch(f, **args)
55
55
return Branch(f, **args)
58
58
def find_cached_branch(f, cache_root, **args):
59
from remotebranch import RemoteBranch
59
from bzrlib.remotebranch import RemoteBranch
60
60
br = find_branch(f, **args)
61
61
def cacheify(br, store_name):
62
from meta_store import CachedStore
62
from bzrlib.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)
128
127
head, tail = os.path.split(f)
130
129
# reached the root, whatever that may be
131
raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
130
raise NotBranchError('%s is not in a branch' % orig_f)
136
# XXX: move into bzrlib.errors; subclass BzrError
137
class DivergedBranches(Exception):
138
def __init__(self, branch1, branch2):
139
self.branch1 = branch1
140
self.branch2 = branch2
141
Exception.__init__(self, "These branches have diverged.")
144
136
######################################################################
173
165
def __init__(self, base, init=False, find_root=True):
174
166
"""Create new branch object at a particular location.
176
base -- Base directory for the branch.
168
base -- Base directory for the branch. May be a file:// url.
178
170
init -- If True, create new control files in a previously
179
171
unversioned directory. If False, the branch must already
193
185
self.base = find_branch_root(base)
187
if base.startswith("file://"):
195
189
self.base = os.path.realpath(base)
196
190
if not isdir(self.controlfilename('.')):
197
from errors import NotBranchError
198
191
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
199
192
['use "bzr init" to initialize a new working tree',
200
193
'current bzr can only operate from top-of-tree'])
215
208
def __del__(self):
216
209
if self._lock_mode or self._lock:
217
from warnings import warn
210
from bzrlib.warnings import warn
218
211
warn("branch %r was not explicitly unlocked" % self)
219
212
self._lock.unlock()
222
214
def lock_write(self):
223
215
if self._lock_mode:
224
216
if self._lock_mode != 'w':
225
from errors import LockError
217
from bzrlib.errors import LockError
226
218
raise LockError("can't upgrade to a write lock from %r" %
228
220
self._lock_count += 1
321
312
# if we want per-tree root ids then this is the place to set
322
313
# them; they're not needed for now and so ommitted for
324
pack_xml(Inventory(), self.controlfile('inventory','w'))
315
f = self.controlfile('inventory','w')
316
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
326
319
def _check_format(self):
327
320
"""Check this branch format is supported.
335
328
# on Windows from Linux and so on. I think it might be better
336
329
# to always make all internal files in unix format.
337
330
fmt = self.controlfile('branch-format', 'r').read()
338
fmt.replace('\r\n', '')
331
fmt = fmt.replace('\r\n', '\n')
339
332
if fmt != BZR_BRANCH_FORMAT:
340
333
raise BzrError('sorry, branch format %r not supported' % fmt,
341
334
['use a different bzr version',
361
354
def read_working_inventory(self):
362
355
"""Read the working inventory."""
363
356
from bzrlib.inventory import Inventory
364
from bzrlib.xml import unpack_xml
365
from time import time
369
359
# ElementTree does its own conversion from UTF-8, so open in
371
inv = unpack_xml(Inventory,
372
self.controlfile('inventory', 'rb'))
373
mutter("loaded inventory of %d items in %f"
374
% (len(inv), time() - before))
361
f = self.controlfile('inventory', 'rb')
362
return bzrlib.xml.serializer_v4.read_inventory(f)
384
371
will be committed to the next revision.
386
373
from bzrlib.atomicfile import AtomicFile
387
from bzrlib.xml import pack_xml
389
375
self.lock_write()
391
377
f = AtomicFile(self.controlfilename('inventory'), 'wb')
379
bzrlib.xml.serializer_v4.write_inventory(inv, f)
584
def get_revision_xml(self, revision_id):
570
def get_revision_xml_file(self, revision_id):
585
571
"""Return XML file object for revision object."""
586
572
if not revision_id or not isinstance(revision_id, basestring):
587
573
raise InvalidRevisionId(revision_id)
586
get_revision_xml = get_revision_xml_file
599
589
def get_revision(self, revision_id):
600
590
"""Return the Revision object for a named revision"""
601
xml_file = self.get_revision_xml(revision_id)
591
xml_file = self.get_revision_xml_file(revision_id)
604
r = unpack_xml(Revision, xml_file)
594
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
605
595
except SyntaxError, e:
606
596
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
652
642
parameter which can be either an integer revno or a
654
644
from bzrlib.inventory import Inventory
655
from bzrlib.xml import unpack_xml
657
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
646
f = self.get_inventory_xml_file(inventory_id)
647
return bzrlib.xml.serializer_v4.read_inventory(f)
660
650
def get_inventory_xml(self, inventory_id):
661
651
"""Get inventory XML as a file object."""
662
652
return self.inventory_store[inventory_id]
654
get_inventory_xml_file = get_inventory_xml
665
657
def get_inventory_sha1(self, inventory_id):
696
688
def common_ancestor(self, other, self_revno=None, other_revno=None):
690
>>> from bzrlib.commit import commit
699
691
>>> sb = ScratchBranch(files=['foo', 'foo~'])
700
692
>>> sb.common_ancestor(sb) == (None, None)
702
>>> commit.commit(sb, "Committing first revision", verbose=False)
694
>>> commit(sb, "Committing first revision", verbose=False)
703
695
>>> sb.common_ancestor(sb)[0]
705
697
>>> clone = sb.clone()
706
>>> commit.commit(sb, "Committing second revision", verbose=False)
698
>>> commit(sb, "Committing second revision", verbose=False)
707
699
>>> sb.common_ancestor(sb)[0]
709
701
>>> sb.common_ancestor(clone)[0]
711
>>> commit.commit(clone, "Committing divergent second revision",
703
>>> commit(clone, "Committing divergent second revision",
712
704
... verbose=False)
713
705
>>> sb.common_ancestor(clone)[0]
805
797
"""Pull in all new revisions from other branch.
807
799
from bzrlib.fetch import greedy_fetch
800
from bzrlib.revision import get_intervening_revisions
809
802
pb = bzrlib.ui.ui_factory.progress_bar()
810
803
pb.update('comparing histories')
812
revision_ids = self.missing_revisions(other, stop_revision)
806
revision_ids = self.missing_revisions(other, stop_revision)
807
except DivergedBranches, e:
809
if stop_revision is None:
810
end_revision = other.last_patch()
811
revision_ids = get_intervening_revisions(self.last_patch(),
813
assert self.last_patch() not in revision_ids
814
except bzrlib.errors.NotAncestor:
814
817
if len(revision_ids) > 0:
815
818
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
877
880
def lookup_revision(self, revision):
878
881
"""Return the revision identifier for a given revision information."""
879
revno, info = self.get_revision_info(revision)
882
revno, info = self._get_revision_info(revision)
897
900
revision can also be a string, in which case it is parsed for something like
898
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.
900
931
if revision is None:
907
938
revs = self.revision_history()
908
939
if isinstance(revision, int):
911
# Mabye we should do this first, but we don't need it if revision == 0
913
941
revno = len(revs) + revision + 1
944
rev_id = self.get_rev_id(revno, revs)
916
945
elif isinstance(revision, basestring):
917
946
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
918
947
if revision.startswith(prefix):
919
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)
922
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)
924
if revno is None or revno <= 0 or revno > len(revs):
925
raise BzrError("no such revision %s" % revision)
926
return revno, revs[revno-1]
963
raise bzrlib.errors.NoSuchRevision(self, revision)
928
966
def _namespace_revno(self, revs, revision):
929
967
"""Lookup a revision by revision number"""
930
968
assert revision.startswith('revno:')
932
return int(revision[6:])
970
return (int(revision[6:]),)
933
971
except ValueError:
935
973
REVISION_NAMESPACES['revno:'] = _namespace_revno
937
975
def _namespace_revid(self, revs, revision):
938
976
assert revision.startswith('revid:')
977
rev_id = revision[len('revid:'):]
940
return revs.index(revision[6:]) + 1
979
return revs.index(rev_id) + 1, rev_id
941
980
except ValueError:
943
982
REVISION_NAMESPACES['revid:'] = _namespace_revid
945
984
def _namespace_last(self, revs, revision):
948
987
offset = int(revision[5:])
949
988
except ValueError:
953
992
raise BzrError('You must supply a positive value for --revision last:XXX')
954
return len(revs) - offset + 1
993
return (len(revs) - offset + 1,)
955
994
REVISION_NAMESPACES['last:'] = _namespace_last
957
996
def _namespace_tag(self, revs, revision):
1032
1071
# TODO: Handle timezone.
1033
1072
dt = datetime.datetime.fromtimestamp(r.timestamp)
1034
1073
if first >= dt and (last is None or dt >= last):
1037
1076
for i in range(len(revs)):
1038
1077
r = self.get_revision(revs[i])
1039
1078
# TODO: Handle timezone.
1040
1079
dt = datetime.datetime.fromtimestamp(r.timestamp)
1041
1080
if first <= dt and (last is None or dt <= last):
1043
1082
REVISION_NAMESPACES['date:'] = _namespace_date
1045
1084
def revision_tree(self, revision_id):
1059
1098
def working_tree(self):
1060
1099
"""Return a `Tree` for the working copy."""
1061
from workingtree import WorkingTree
1100
from bzrlib.workingtree import WorkingTree
1062
1101
return WorkingTree(self.base, self.read_working_inventory())
1322
def get_parent(self):
1323
"""Return the parent location of the branch.
1325
This is the default location for push/pull/missing. The usual
1326
pattern is that the user can override it by specifying a
1330
_locs = ['parent', 'pull', 'x-pull']
1333
return self.controlfile(l, 'r').read().strip('\n')
1335
if e.errno != errno.ENOENT:
1340
def set_parent(self, url):
1341
# TODO: Maybe delete old location files?
1342
from bzrlib.atomicfile import AtomicFile
1345
f = AtomicFile(self.controlfilename('parent'))
1354
def check_revno(self, revno):
1356
Check whether a revno corresponds to any revision.
1357
Zero (the NULL revision) is considered valid.
1360
self.check_real_revno(revno)
1362
def check_real_revno(self, revno):
1364
Check whether a revno corresponds to a real revision.
1365
Zero (the NULL revision) is considered invalid
1367
if revno < 1 or revno > self.revno():
1368
raise InvalidRevisionNumber(revno)
1284
1373
class ScratchBranch(Branch):
1285
1374
"""Special test class: a branch that cleans up after itself.
1403
1494
return gen_file_id('TREE_ROOT')
1406
def pull_loc(branch):
1407
# TODO: Should perhaps just make attribute be 'base' in
1408
# RemoteBranch and Branch?
1409
if hasattr(branch, "baseurl"):
1410
return branch.baseurl
1415
1497
def copy_branch(branch_from, to_location, revision=None):
1416
1498
"""Copy branch_from into the existing directory to_location.
1418
If revision is not None, the head of the new branch will be revision.
1501
If not None, only revisions up to this point will be copied.
1502
The head of the new branch will be that revision.
1505
The name of a local directory that exists but is empty.
1420
1507
from bzrlib.merge import merge
1421
from bzrlib.branch import Branch
1509
assert isinstance(branch_from, Branch)
1510
assert isinstance(to_location, basestring)
1422
1512
br_to = Branch(to_location, init=True)
1423
1513
br_to.set_root_id(branch_from.get_root_id())
1424
1514
if revision is None:
1428
1518
br_to.update_revisions(branch_from, stop_revision=revno)
1429
1519
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1430
1520
check_clean=False, ignore_zero=True)
1431
from_location = pull_loc(branch_from)
1432
br_to.controlfile("x-pull", "wb").write(from_location + "\n")
1522
br_to.set_parent(branch_from.base)