32
29
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
33
30
NoSuchRevision, HistoryMissing, NotBranchError,
34
31
DivergedBranches, LockError, UnlistableStore,
35
UnlistableBranch, NoSuchFile)
36
33
from bzrlib.textui import show_status
37
from bzrlib.revision import Revision
34
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
38
35
from bzrlib.delta import compare_trees
39
36
from bzrlib.tree import EmptyTree, RevisionTree
40
37
from bzrlib.inventory import Inventory
41
from bzrlib.store import copy_all
42
from bzrlib.store.compressed_text import CompressedTextStore
43
from bzrlib.store.text import TextStore
44
from bzrlib.store.weave import WeaveStore
45
from bzrlib.transport import Transport, get_transport
38
from bzrlib.weavestore import WeaveStore
39
from bzrlib.store import copy_all, ImmutableStore
88
83
return os.sep.join(s)
91
def find_branch_root(t):
92
"""Find the branch root enclosing the transport's base.
94
t is a Transport object.
96
It is not necessary that the base of t exists.
86
def find_branch_root(f=None):
87
"""Find the branch root enclosing f, or pwd.
89
f may be a filename or a URL.
91
It is not necessary that f exists.
98
93
Basically we keep looking up until we find the control directory or
99
94
run into the root. If there isn't one, raises NotBranchError.
98
elif hasattr(os.path, 'realpath'):
99
f = os.path.realpath(f)
101
f = os.path.abspath(f)
102
if not os.path.exists(f):
103
raise BzrError('%r does not exist' % f)
103
if t.has(bzrlib.BZRDIR):
105
new_t = t.clone('..')
106
if new_t.base == t.base:
109
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
111
head, tail = os.path.split(f)
107
113
# reached the root, whatever that may be
108
raise NotBranchError('%s is not in a branch' % orig_base)
114
raise NotBranchError('%s is not in a branch' % orig_f)
112
120
######################################################################
124
132
raise NotImplementedError('The Branch class is abstract')
127
def open_downlevel(base):
128
"""Open a branch which may be of an old format.
130
Only local branches are supported."""
131
return _Branch(get_transport(base), relax_version_check=True)
135
136
"""Open an existing branch, rooted at 'base' (url)"""
136
t = get_transport(base)
137
mutter("trying to open %r with transport %r", base, t)
137
if base and (base.startswith('http://') or base.startswith('https://')):
138
from bzrlib.remotebranch import RemoteBranch
139
return RemoteBranch(base, find_root=False)
141
return LocalBranch(base, find_root=False)
141
144
def open_containing(url):
144
147
This probes for a branch at url, and searches upwards from there.
146
t = get_transport(url)
147
t = find_branch_root(t)
149
if url and (url.startswith('http://') or url.startswith('https://')):
150
from bzrlib.remotebranch import RemoteBranch
151
return RemoteBranch(url)
153
return LocalBranch(url)
151
156
def initialize(base):
152
157
"""Create a new branch, rooted at 'base' (url)"""
153
t = get_transport(base)
154
return _Branch(t, init=True)
158
if base and (base.startswith('http://') or base.startswith('https://')):
159
from bzrlib.remotebranch import RemoteBranch
160
return RemoteBranch(base, init=True)
162
return LocalBranch(base, init=True)
156
164
def setup_caching(self, cache_root):
157
165
"""Subclasses that care about caching should override this, and set
158
166
up cached stores located under cache_root.
160
self.cache_root = cache_root
163
class _Branch(Branch):
170
class LocalBranch(Branch):
164
171
"""A branch stored in the actual filesystem.
166
173
Note that it's "local" in the context of the filesystem; it doesn't
211
218
except UnlistableStore:
212
219
raise UnlistableBranch(from_store)
214
def __init__(self, transport, init=False,
221
def __init__(self, base, init=False, find_root=True,
215
222
relax_version_check=False):
216
223
"""Create new branch object at a particular location.
218
transport -- A Transport object, defining how to access files.
219
(If a string, transport.transport() will be used to
220
create a Transport object)
225
base -- Base directory for the branch. May be a file:// url.
222
227
init -- If True, create new control files in a previously
223
228
unversioned directory. If False, the branch must already
231
find_root -- If true and init is false, find the root of the
232
existing branch containing base.
226
234
relax_version_check -- If true, the usual check for the branch
227
235
version is not applied. This is intended only for
228
236
upgrade/recovery type use; it's not guaranteed that
231
239
In the test suite, creation of new trees is tested using the
232
240
`ScratchBranch` class.
234
assert isinstance(transport, Transport), \
235
"%r is not a Transport" % transport
236
self._transport = transport
243
self.base = os.path.realpath(base)
238
244
self._make_control()
246
self.base = find_branch_root(base)
248
if base.startswith("file://"):
250
self.base = os.path.realpath(base)
251
if not isdir(self.controlfilename('.')):
252
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
253
['use "bzr init" to initialize a '
239
255
self._check_format(relax_version_check)
241
def get_store(name, compressed=True):
242
# FIXME: This approach of assuming stores are all entirely compressed
243
# or entirely uncompressed is tidy, but breaks upgrade from
244
# some existing branches where there's a mixture; we probably
245
# still want the option to look for both.
246
relpath = self._rel_controlfilename(name)
248
store = CompressedTextStore(self._transport.clone(relpath))
250
store = TextStore(self._transport.clone(relpath))
251
#if self._transport.should_cache():
252
# cache_path = os.path.join(self.cache_root, name)
253
# os.mkdir(cache_path)
254
# store = bzrlib.store.CachedStore(store, cache_path)
257
relpath = self._rel_controlfilename(name)
258
ws = WeaveStore(self._transport.clone(relpath))
259
if self._transport.should_cache():
260
ws.enable_cache = True
256
cfn = self.controlfilename
263
257
if self._branch_format == 4:
264
self.inventory_store = get_store('inventory-store')
265
self.text_store = get_store('text-store')
266
self.revision_store = get_store('revision-store')
258
self.inventory_store = ImmutableStore(cfn('inventory-store'))
259
self.text_store = ImmutableStore(cfn('text-store'))
267
260
elif self._branch_format == 5:
268
self.control_weaves = get_weave([])
269
self.weave_store = get_weave('weaves')
270
self.revision_store = get_store('revision-store', compressed=False)
261
self.control_weaves = WeaveStore(cfn([]))
262
self.weave_store = WeaveStore(cfn('weaves'))
264
# FIXME: Unify with make_control_files
265
self.control_weaves.put_empty_weave('inventory')
266
self.control_weaves.put_empty_weave('ancestry')
267
self.revision_store = ImmutableStore(cfn('revision-store'))
272
270
def __str__(self):
273
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
271
return '%s(%r)' % (self.__class__.__name__, self.base)
276
274
__repr__ = __str__
283
281
warn("branch %r was not explicitly unlocked" % self)
284
282
self._lock.unlock()
286
# TODO: It might be best to do this somewhere else,
287
# but it is nice for a Branch object to automatically
288
# cache it's information.
289
# Alternatively, we could have the Transport objects cache requests
290
# See the earlier discussion about how major objects (like Branch)
291
# should never expect their __del__ function to run.
292
if hasattr(self, 'cache_root') and self.cache_root is not None:
295
shutil.rmtree(self.cache_root)
298
self.cache_root = None
302
return self._transport.base
305
base = property(_get_base)
308
284
def lock_write(self):
309
# TODO: Upgrade locking to support using a Transport,
310
# and potentially a remote locking protocol
311
285
if self._lock_mode:
312
286
if self._lock_mode != 'w':
313
287
raise LockError("can't upgrade to a write lock from %r" %
315
289
self._lock_count += 1
317
self._lock = self._transport.lock_write(
318
self._rel_controlfilename('branch-lock'))
291
from bzrlib.lock import WriteLock
293
self._lock = WriteLock(self.controlfilename('branch-lock'))
319
294
self._lock_mode = 'w'
320
295
self._lock_count = 1
345
321
def abspath(self, name):
346
322
"""Return absolute filename for something in the branch"""
347
return self._transport.abspath(name)
323
return os.path.join(self.base, name)
349
325
def relpath(self, path):
350
326
"""Return path relative to this branch of something inside it.
352
328
Raises an error if path is not in this branch."""
353
return self._transport.relpath(path)
356
def _rel_controlfilename(self, file_or_path):
329
return _relpath(self.base, path)
331
def controlfilename(self, file_or_path):
332
"""Return location relative to branch."""
357
333
if isinstance(file_or_path, basestring):
358
334
file_or_path = [file_or_path]
359
return [bzrlib.BZRDIR] + file_or_path
361
def controlfilename(self, file_or_path):
362
"""Return location relative to branch."""
363
return self._transport.abspath(self._rel_controlfilename(file_or_path))
335
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
366
338
def controlfile(self, file_or_path, mode='r'):
374
346
Controlfiles should almost never be opened in write mode but
375
347
rather should be atomically copied and replaced using atomicfile.
379
relpath = self._rel_controlfilename(file_or_path)
380
#TODO: codecs.open() buffers linewise, so it was overloaded with
381
# a much larger buffer, do we need to do the same for getreader/getwriter?
383
return self._transport.get(relpath)
385
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
387
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
389
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
350
fn = self.controlfilename(file_or_path)
352
if mode == 'rb' or mode == 'wb':
353
return file(fn, mode)
354
elif mode == 'r' or mode == 'w':
355
# open in binary mode anyhow so there's no newline translation;
356
# codecs uses line buffering by default; don't want that.
358
return codecs.open(fn, mode + 'b', 'utf-8',
391
361
raise BzrError("invalid controlfile mode %r" % mode)
393
def put_controlfile(self, path, f, encode=True):
394
"""Write an entry as a controlfile.
396
:param path: The path to put the file, relative to the .bzr control
398
:param f: A file-like or string object whose contents should be copied.
399
:param encode: If true, encode the contents as utf-8
401
self.put_controlfiles([(path, f)], encode=encode)
403
def put_controlfiles(self, files, encode=True):
404
"""Write several entries as controlfiles.
406
:param files: A list of [(path, file)] pairs, where the path is the directory
407
underneath the bzr control directory
408
:param encode: If true, encode the contents as utf-8
412
for path, f in files:
414
if isinstance(f, basestring):
415
f = f.encode('utf-8', 'replace')
417
f = codecs.getwriter('utf-8')(f, errors='replace')
418
path = self._rel_controlfilename(path)
419
ctrl_files.append((path, f))
420
self._transport.put_multi(ctrl_files)
422
363
def _make_control(self):
423
from bzrlib.inventory import Inventory
424
from bzrlib.weavefile import write_weave_v5
425
from bzrlib.weave import Weave
427
# Create an empty inventory
364
os.mkdir(self.controlfilename([]))
365
self.controlfile('README', 'w').write(
366
"This is a Bazaar-NG control directory.\n"
367
"Do not change any files in this directory.\n")
368
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
369
for d in ('text-store', 'revision-store',
371
os.mkdir(self.controlfilename(d))
372
for f in ('revision-history',
376
self.controlfile(f, 'w').write('')
377
mutter('created control directory in ' + self.base)
429
379
# if we want per-tree root ids then this is the place to set
430
380
# them; they're not needed for now and so ommitted for
432
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
433
empty_inv = sio.getvalue()
435
bzrlib.weavefile.write_weave_v5(Weave(), sio)
436
empty_weave = sio.getvalue()
382
f = self.controlfile('inventory','w')
383
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
438
dirs = [[], 'revision-store', 'weaves']
440
"This is a Bazaar-NG control directory.\n"
441
"Do not change any files in this directory.\n"),
442
('branch-format', BZR_BRANCH_FORMAT_5),
443
('revision-history', ''),
446
('pending-merges', ''),
447
('inventory', empty_inv),
448
('inventory.weave', empty_weave),
449
('ancestry.weave', empty_weave)
451
cfn = self._rel_controlfilename
452
self._transport.mkdir_multi([cfn(d) for d in dirs])
453
self.put_controlfiles(files)
454
mutter('created control directory in ' + self._transport.base)
456
386
def _check_format(self, relax_version_check):
457
387
"""Check this branch format is supported.
514
446
That is to say, the inventory describing changes underway, that
515
447
will be committed to the next revision.
517
from cStringIO import StringIO
449
from bzrlib.atomicfile import AtomicFile
518
451
self.lock_write()
521
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
523
# Transport handles atomicity
524
self.put_controlfile('inventory', sio)
453
f = AtomicFile(self.controlfilename('inventory'), 'wb')
455
bzrlib.xml5.serializer_v5.write_inventory(inv, f)
528
462
mutter('wrote working inventory')
530
465
inventory = property(read_working_inventory, _write_inventory, None,
531
466
"""Inventory for the working copy.""")
533
469
def add(self, files, ids=None):
534
470
"""Make files versioned.
772
708
return compare_trees(old_tree, new_tree)
774
711
def get_revision_sha1(self, revision_id):
775
712
"""Hash the stored value of a revision, and return it."""
776
# In the future, revision entries will be signed. At that
777
# point, it is probably best *not* to include the signature
778
# in the revision hash. Because that lets you re-sign
779
# the revision, (add signatures/remove signatures) and still
780
# have all hash pointers stay consistent.
781
# But for now, just hash the contents.
782
713
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
716
def _get_ancestry_weave(self):
717
return self.control_weaves.get_weave('ancestry')
784
720
def get_ancestry(self, revision_id):
785
721
"""Return a list of revision-ids integrated by a revision.
787
This currently returns a list, but the ordering is not guaranteed:
790
724
if revision_id is None:
792
w = self.control_weaves.get_weave('inventory')
793
return [None] + map(w.idx_to_name,
794
w.inclusions([w.lookup(revision_id)]))
726
w = self._get_ancestry_weave()
727
return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
796
730
def get_inventory_weave(self):
797
731
return self.control_weaves.get_weave('inventory')
799
734
def get_inventory(self, revision_id):
800
735
"""Get Inventory object by hash."""
801
736
xml = self.get_inventory_xml(revision_id)
802
737
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
804
740
def get_inventory_xml(self, revision_id):
805
741
"""Get inventory XML as a file object."""
1399
1339
return gen_file_id('TREE_ROOT')
1342
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1343
"""Copy branch_from into the existing directory to_location.
1346
If not None, only revisions up to this point will be copied.
1347
The head of the new branch will be that revision. Must be a
1351
The name of a local directory that exists but is empty.
1354
The revision to copy up to
1357
A local branch to copy revisions from, related to branch_from
1359
# TODO: This could be done *much* more efficiently by just copying
1360
# all the whole weaves and revisions, rather than getting one
1361
# revision at a time.
1362
from bzrlib.merge import merge
1364
assert isinstance(branch_from, Branch)
1365
assert isinstance(to_location, basestring)
1367
br_to = Branch.initialize(to_location)
1368
mutter("copy branch from %s to %s", branch_from, br_to)
1369
if basis_branch is not None:
1370
basis_branch.push_stores(br_to)
1371
br_to.set_root_id(branch_from.get_root_id())
1372
if revision is None:
1373
revision = branch_from.last_revision()
1374
br_to.update_revisions(branch_from, stop_revision=revision)
1375
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1376
check_clean=False, ignore_zero=True)
1377
br_to.set_parent(branch_from.base)