25
25
sha_file, appendpath, file_kind
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
DivergedBranches, NotBranchError
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
29
29
from bzrlib.textui import show_status
30
30
from bzrlib.revision import Revision
31
from bzrlib.xml import unpack_xml
31
32
from bzrlib.delta import compare_trees
32
33
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://')):
52
from bzrlib.remotebranch import RemoteBranch
53
return RemoteBranch(f, **args)
53
return remotebranch.RemoteBranch(f, **args)
55
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)
127
128
head, tail = os.path.split(f)
129
130
# reached the root, whatever that may be
130
raise NotBranchError('%s is not in a branch' % orig_f)
131
raise bzrlib.errors.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.")
136
144
######################################################################
165
173
def __init__(self, base, init=False, find_root=True):
166
174
"""Create new branch object at a particular location.
168
base -- Base directory for the branch. May be a file:// url.
176
base -- Base directory for the branch.
170
178
init -- If True, create new control files in a previously
171
179
unversioned directory. If False, the branch must already
185
193
self.base = find_branch_root(base)
187
if base.startswith("file://"):
189
195
self.base = os.path.realpath(base)
190
196
if not isdir(self.controlfilename('.')):
197
from errors import NotBranchError
191
198
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
192
199
['use "bzr init" to initialize a new working tree',
193
200
'current bzr can only operate from top-of-tree'])
208
215
def __del__(self):
209
216
if self._lock_mode or self._lock:
210
from bzrlib.warnings import warn
217
from warnings import warn
211
218
warn("branch %r was not explicitly unlocked" % self)
212
219
self._lock.unlock()
214
222
def lock_write(self):
215
223
if self._lock_mode:
216
224
if self._lock_mode != 'w':
217
from bzrlib.errors import LockError
225
from errors import LockError
218
226
raise LockError("can't upgrade to a write lock from %r" %
220
228
self._lock_count += 1
295
303
def _make_control(self):
296
304
from bzrlib.inventory import Inventory
305
from bzrlib.xml import pack_xml
298
307
os.mkdir(self.controlfilename([]))
299
308
self.controlfile('README', 'w').write(
312
321
# if we want per-tree root ids then this is the place to set
313
322
# them; they're not needed for now and so ommitted for
315
f = self.controlfile('inventory','w')
316
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
324
pack_xml(Inventory(), self.controlfile('inventory','w'))
319
326
def _check_format(self):
320
327
"""Check this branch format is supported.
328
335
# on Windows from Linux and so on. I think it might be better
329
336
# to always make all internal files in unix format.
330
337
fmt = self.controlfile('branch-format', 'r').read()
331
fmt = fmt.replace('\r\n', '\n')
338
fmt.replace('\r\n', '')
332
339
if fmt != BZR_BRANCH_FORMAT:
333
340
raise BzrError('sorry, branch format %r not supported' % fmt,
334
341
['use a different bzr version',
354
361
def read_working_inventory(self):
355
362
"""Read the working inventory."""
356
363
from bzrlib.inventory import Inventory
364
from bzrlib.xml import unpack_xml
365
from time import time
359
369
# ElementTree does its own conversion from UTF-8, so open in
361
f = self.controlfile('inventory', 'rb')
362
return bzrlib.xml.serializer_v4.read_inventory(f)
371
inv = unpack_xml(Inventory,
372
self.controlfile('inventory', 'rb'))
373
mutter("loaded inventory of %d items in %f"
374
% (len(inv), time() - before))
371
384
will be committed to the next revision.
373
386
from bzrlib.atomicfile import AtomicFile
387
from bzrlib.xml import pack_xml
375
389
self.lock_write()
377
391
f = AtomicFile(self.controlfilename('inventory'), 'wb')
379
bzrlib.xml.serializer_v4.write_inventory(inv, f)
390
404
"""Inventory for the working copy.""")
393
def add(self, files, ids=None):
407
def add(self, files, verbose=False, ids=None):
394
408
"""Make files versioned.
396
Note that the command line normally calls smart_add instead,
397
which can automatically recurse.
410
Note that the command line normally calls smart_add instead.
399
412
This puts the files in the Added state, so that they will be
400
413
recorded by the next commit.
410
423
TODO: Perhaps have an option to add the ids even if the files do
413
TODO: Perhaps yield the ids and paths as they're added.
426
TODO: Perhaps return the ids of the files? But then again it
427
is easy to retrieve them if they're needed.
429
TODO: Adding a directory should optionally recurse down and
430
add all non-ignored children. Perhaps do that in a
415
433
# TODO: Re-adding a file that is removed in the working copy
416
434
# should probably put it back with the previous ID.
452
470
file_id = gen_file_id(f)
453
471
inv.add_path(f, kind=kind, file_id=file_id)
474
print 'added', quotefn(f)
455
476
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
457
478
self._write_inventory(inv)
570
def get_revision_xml_file(self, revision_id):
591
def get_revision_xml(self, revision_id):
571
592
"""Return XML file object for revision object."""
572
593
if not revision_id or not isinstance(revision_id, basestring):
573
594
raise InvalidRevisionId(revision_id)
578
599
return self.revision_store[revision_id]
579
except (IndexError, KeyError):
580
601
raise bzrlib.errors.NoSuchRevision(self, revision_id)
586
get_revision_xml = get_revision_xml_file
589
606
def get_revision(self, revision_id):
590
607
"""Return the Revision object for a named revision"""
591
xml_file = self.get_revision_xml_file(revision_id)
608
xml_file = self.get_revision_xml(revision_id)
594
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
611
r = unpack_xml(Revision, xml_file)
595
612
except SyntaxError, e:
596
613
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
642
659
parameter which can be either an integer revno or a
644
661
from bzrlib.inventory import Inventory
662
from bzrlib.xml import unpack_xml
646
f = self.get_inventory_xml_file(inventory_id)
647
return bzrlib.xml.serializer_v4.read_inventory(f)
664
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
650
667
def get_inventory_xml(self, inventory_id):
651
668
"""Get inventory XML as a file object."""
652
669
return self.inventory_store[inventory_id]
654
get_inventory_xml_file = get_inventory_xml
657
672
def get_inventory_sha1(self, inventory_id):
688
703
def common_ancestor(self, other, self_revno=None, other_revno=None):
690
>>> from bzrlib.commit import commit
691
706
>>> sb = ScratchBranch(files=['foo', 'foo~'])
692
707
>>> sb.common_ancestor(sb) == (None, None)
694
>>> commit(sb, "Committing first revision", verbose=False)
709
>>> commit.commit(sb, "Committing first revision", verbose=False)
695
710
>>> sb.common_ancestor(sb)[0]
697
712
>>> clone = sb.clone()
698
>>> commit(sb, "Committing second revision", verbose=False)
713
>>> commit.commit(sb, "Committing second revision", verbose=False)
699
714
>>> sb.common_ancestor(sb)[0]
701
716
>>> sb.common_ancestor(clone)[0]
703
>>> commit(clone, "Committing divergent second revision",
718
>>> commit.commit(clone, "Committing divergent second revision",
704
719
... verbose=False)
705
720
>>> sb.common_ancestor(clone)[0]
797
812
"""Pull in all new revisions from other branch.
799
814
from bzrlib.fetch import greedy_fetch
800
from bzrlib.revision import get_intervening_revisions
802
816
pb = bzrlib.ui.ui_factory.progress_bar()
803
817
pb.update('comparing histories')
804
if stop_revision is None:
805
other_revision = other.last_patch()
819
revision_ids = self.missing_revisions(other, stop_revision)
821
if len(revision_ids) > 0:
822
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
807
other_revision = other.lookup_revision(stop_revision)
808
count = greedy_fetch(self, other, other_revision, pb)[0]
810
revision_ids = self.missing_revisions(other, stop_revision)
811
except DivergedBranches, e:
813
revision_ids = get_intervening_revisions(self.last_patch(),
814
other_revision, self)
815
assert self.last_patch() not in revision_ids
816
except bzrlib.errors.NotAncestor:
819
825
self.append_revision(*revision_ids)
826
## note("Added %d revisions." % count)
822
829
def install_revisions(self, other, revision_ids, pb):
823
830
if hasattr(other.revision_store, "prefetch"):
824
831
other.revision_store.prefetch(revision_ids)
825
832
if hasattr(other.inventory_store, "prefetch"):
827
for rev_id in revision_ids:
829
revision = other.get_revision(rev_id).inventory_id
830
inventory_ids.append(revision)
831
except bzrlib.errors.NoSuchRevision:
833
inventory_ids = [other.get_revision(r).inventory_id
834
for r in revision_ids]
833
835
other.inventory_store.prefetch(inventory_ids)
1084
1086
REVISION_NAMESPACES['date:'] = _namespace_date
1087
def _namespace_ancestor(self, revs, revision):
1088
from revision import common_ancestor, MultipleRevisionSources
1089
other_branch = find_branch(_trim_namespace('ancestor', revision))
1090
revision_a = self.last_patch()
1091
revision_b = other_branch.last_patch()
1092
for r, b in ((revision_a, self), (revision_b, other_branch)):
1094
raise bzrlib.errors.NoCommits(b)
1095
revision_source = MultipleRevisionSources(self, other_branch)
1096
result = common_ancestor(revision_a, revision_b, revision_source)
1098
revno = self.revision_id_to_revno(result)
1099
except bzrlib.errors.NoSuchRevision:
1104
REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1106
1088
def revision_tree(self, revision_id):
1107
1089
"""Return Tree for a revision on this branch.
1120
1102
def working_tree(self):
1121
1103
"""Return a `Tree` for the working copy."""
1122
from bzrlib.workingtree import WorkingTree
1104
from workingtree import WorkingTree
1123
1105
return WorkingTree(self.base, self.read_working_inventory())
1172
1154
inv.rename(file_id, to_dir_id, to_tail)
1156
print "%s => %s" % (from_rel, to_rel)
1174
1158
from_abs = self.abspath(from_rel)
1175
1159
to_abs = self.abspath(to_rel)
1196
1180
Note that to_name is only the last component of the new name;
1197
1181
this doesn't change the directory.
1199
This returns a list of (from_path, to_path) pairs for each
1200
entry that is moved.
1203
1183
self.lock_write()
1205
1185
## TODO: Option to move IDs only
1240
1220
for f in from_paths:
1241
1221
name_tail = splitpath(f)[-1]
1242
1222
dest_path = appendpath(to_name, name_tail)
1243
result.append((f, dest_path))
1223
print "%s => %s" % (f, dest_path)
1244
1224
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1246
1226
os.rename(self.abspath(f), self.abspath(dest_path))
1344
def get_parent(self):
1345
"""Return the parent location of the branch.
1347
This is the default location for push/pull/missing. The usual
1348
pattern is that the user can override it by specifying a
1352
_locs = ['parent', 'pull', 'x-pull']
1355
return self.controlfile(l, 'r').read().strip('\n')
1357
if e.errno != errno.ENOENT:
1362
def set_parent(self, url):
1363
# TODO: Maybe delete old location files?
1364
from bzrlib.atomicfile import AtomicFile
1367
f = AtomicFile(self.controlfilename('parent'))
1376
def check_revno(self, revno):
1378
Check whether a revno corresponds to any revision.
1379
Zero (the NULL revision) is considered valid.
1382
self.check_real_revno(revno)
1384
def check_real_revno(self, revno):
1386
Check whether a revno corresponds to a real revision.
1387
Zero (the NULL revision) is considered invalid
1389
if revno < 1 or revno > self.revno():
1390
raise InvalidRevisionNumber(revno)
1395
1323
class ScratchBranch(Branch):
1396
1324
"""Special test class: a branch that cleans up after itself.
1516
1442
return gen_file_id('TREE_ROOT')
1445
def pull_loc(branch):
1446
# TODO: Should perhaps just make attribute be 'base' in
1447
# RemoteBranch and Branch?
1448
if hasattr(branch, "baseurl"):
1449
return branch.baseurl
1519
1454
def copy_branch(branch_from, to_location, revision=None):
1520
1455
"""Copy branch_from into the existing directory to_location.
1523
If not None, only revisions up to this point will be copied.
1524
The head of the new branch will be that revision.
1527
The name of a local directory that exists but is empty.
1457
If revision is not None, the head of the new branch will be revision.
1529
1459
from bzrlib.merge import merge
1531
assert isinstance(branch_from, Branch)
1532
assert isinstance(to_location, basestring)
1460
from bzrlib.branch import Branch
1534
1461
br_to = Branch(to_location, init=True)
1535
1462
br_to.set_root_id(branch_from.get_root_id())
1536
1463
if revision is None:
1540
1467
br_to.update_revisions(branch_from, stop_revision=revno)
1541
1468
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1542
1469
check_clean=False, ignore_zero=True)
1543
br_to.set_parent(branch_from.base)
1470
from_location = pull_loc(branch_from)
1471
br_to.controlfile("x-pull", "wb").write(from_location + "\n")
1546
def _trim_namespace(namespace, spec):
1547
full_namespace = namespace + ':'
1548
assert spec.startswith(full_namespace)
1549
return spec[len(full_namespace):]