29
30
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
31
NoSuchRevision, HistoryMissing, NotBranchError,
31
32
DivergedBranches, LockError, UnlistableStore,
33
UnlistableBranch, NoSuchFile)
33
34
from bzrlib.textui import show_status
34
35
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
35
36
from bzrlib.delta import compare_trees
36
37
from bzrlib.tree import EmptyTree, RevisionTree
37
38
from bzrlib.inventory import Inventory
38
from bzrlib.weavestore import WeaveStore
39
from bzrlib.store import copy_all, ImmutableStore
39
from bzrlib.store import copy_all
40
from bzrlib.store.compressed_text import CompressedTextStore
41
from bzrlib.store.text import TextStore
42
from bzrlib.store.weave import WeaveStore
43
from bzrlib.transport import Transport, get_transport
83
86
return os.sep.join(s)
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.
89
def find_branch_root(t):
90
"""Find the branch root enclosing the transport's base.
92
t is a Transport object.
94
It is not necessary that the base of t exists.
93
96
Basically we keep looking up until we find the control directory or
94
97
run into the root. If there isn't one, raises NotBranchError.
99
f = bzrlib.osutils.normalizepath(f)
100
if not bzrlib.osutils.lexists(f):
101
raise BzrError('%r does not exist' % f)
106
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
108
head, tail = os.path.split(f)
101
if t.has(bzrlib.BZRDIR):
103
new_t = t.clone('..')
104
if new_t.base == t.base:
110
105
# reached the root, whatever that may be
111
raise NotBranchError('%s is not in a branch' % orig_f)
106
raise NotBranchError('%s is not in a branch' % orig_base)
117
110
######################################################################
215
207
except UnlistableStore:
216
208
raise UnlistableBranch(from_store)
218
def __init__(self, base, init=False, find_root=True,
210
def __init__(self, transport, init=False,
219
211
relax_version_check=False):
220
212
"""Create new branch object at a particular location.
222
base -- Base directory for the branch. May be a file:// url.
214
transport -- A Transport object, defining how to access files.
215
(If a string, transport.transport() will be used to
216
create a Transport object)
224
218
init -- If True, create new control files in a previously
225
219
unversioned directory. If False, the branch must already
228
find_root -- If true and init is false, find the root of the
229
existing branch containing base.
231
222
relax_version_check -- If true, the usual check for the branch
232
223
version is not applied. This is intended only for
233
224
upgrade/recovery type use; it's not guaranteed that
236
227
In the test suite, creation of new trees is tested using the
237
228
`ScratchBranch` class.
230
assert isinstance(transport, Transport), \
231
"%r is not a Transport" % transport
232
self._transport = transport
240
self.base = os.path.realpath(base)
241
234
self._make_control()
243
self.base = find_branch_root(base)
245
if base.startswith("file://"):
247
self.base = os.path.realpath(base)
248
if not isdir(self.controlfilename('.')):
249
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
250
['use "bzr init" to initialize a '
252
235
self._check_format(relax_version_check)
253
cfn = self.controlfilename
237
def get_store(name, compressed=True):
238
relpath = self._rel_controlfilename(name)
240
store = CompressedTextStore(self._transport.clone(relpath))
242
store = TextStore(self._transport.clone(relpath))
243
if self._transport.should_cache():
244
from meta_store import CachedStore
245
cache_path = os.path.join(self.cache_root, name)
247
store = CachedStore(store, cache_path)
250
relpath = self._rel_controlfilename(name)
251
ws = WeaveStore(self._transport.clone(relpath))
252
if self._transport.should_cache():
253
ws.enable_cache = True
254
256
if self._branch_format == 4:
255
self.inventory_store = ImmutableStore(cfn('inventory-store'))
256
self.text_store = ImmutableStore(cfn('text-store'))
257
self.inventory_store = get_store('inventory-store')
258
self.text_store = get_store('text-store')
259
self.revision_store = get_store('revision-store')
257
260
elif self._branch_format == 5:
258
self.control_weaves = WeaveStore(cfn([]))
259
self.weave_store = WeaveStore(cfn('weaves'))
261
# FIXME: Unify with make_control_files
262
self.control_weaves.put_empty_weave('inventory')
263
self.control_weaves.put_empty_weave('ancestry')
264
self.revision_store = ImmutableStore(cfn('revision-store'))
261
self.control_weaves = get_weave([])
262
self.weave_store = get_weave('weaves')
263
self.revision_store = get_store('revision-store', compressed=False)
267
265
def __str__(self):
268
return '%s(%r)' % (self.__class__.__name__, self.base)
266
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
271
269
__repr__ = __str__
278
276
warn("branch %r was not explicitly unlocked" % self)
279
277
self._lock.unlock()
279
# TODO: It might be best to do this somewhere else,
280
# but it is nice for a Branch object to automatically
281
# cache it's information.
282
# Alternatively, we could have the Transport objects cache requests
283
# See the earlier discussion about how major objects (like Branch)
284
# should never expect their __del__ function to run.
285
if hasattr(self, 'cache_root') and self.cache_root is not None:
288
shutil.rmtree(self.cache_root)
291
self.cache_root = None
295
return self._transport.base
298
base = property(_get_base)
281
301
def lock_write(self):
302
# TODO: Upgrade locking to support using a Transport,
303
# and potentially a remote locking protocol
282
304
if self._lock_mode:
283
305
if self._lock_mode != 'w':
284
306
raise LockError("can't upgrade to a write lock from %r" %
286
308
self._lock_count += 1
288
from bzrlib.lock import WriteLock
290
self._lock = WriteLock(self.controlfilename('branch-lock'))
310
self._lock = self._transport.lock_write(
311
self._rel_controlfilename('branch-lock'))
291
312
self._lock_mode = 'w'
292
313
self._lock_count = 1
318
338
def abspath(self, name):
319
339
"""Return absolute filename for something in the branch"""
320
return os.path.join(self.base, name)
340
return self._transport.abspath(name)
322
342
def relpath(self, path):
323
343
"""Return path relative to this branch of something inside it.
325
345
Raises an error if path is not in this branch."""
326
return _relpath(self.base, path)
346
return self._transport.relpath(path)
349
def _rel_controlfilename(self, file_or_path):
350
if isinstance(file_or_path, basestring):
351
file_or_path = [file_or_path]
352
return [bzrlib.BZRDIR] + file_or_path
328
354
def controlfilename(self, file_or_path):
329
355
"""Return location relative to branch."""
330
if isinstance(file_or_path, basestring):
331
file_or_path = [file_or_path]
332
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
356
return self._transport.abspath(self._rel_controlfilename(file_or_path))
335
359
def controlfile(self, file_or_path, mode='r'):
343
367
Controlfiles should almost never be opened in write mode but
344
368
rather should be atomically copied and replaced using atomicfile.
347
fn = self.controlfilename(file_or_path)
349
if mode == 'rb' or mode == 'wb':
350
return file(fn, mode)
351
elif mode == 'r' or mode == 'w':
352
# open in binary mode anyhow so there's no newline translation;
353
# codecs uses line buffering by default; don't want that.
355
return codecs.open(fn, mode + 'b', 'utf-8',
372
relpath = self._rel_controlfilename(file_or_path)
373
#TODO: codecs.open() buffers linewise, so it was overloaded with
374
# a much larger buffer, do we need to do the same for getreader/getwriter?
376
return self._transport.get(relpath)
378
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
380
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
382
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
358
384
raise BzrError("invalid controlfile mode %r" % mode)
386
def put_controlfile(self, path, f, encode=True):
387
"""Write an entry as a controlfile.
389
:param path: The path to put the file, relative to the .bzr control
391
:param f: A file-like or string object whose contents should be copied.
392
:param encode: If true, encode the contents as utf-8
394
self.put_controlfiles([(path, f)], encode=encode)
396
def put_controlfiles(self, files, encode=True):
397
"""Write several entries as controlfiles.
399
:param files: A list of [(path, file)] pairs, where the path is the directory
400
underneath the bzr control directory
401
:param encode: If true, encode the contents as utf-8
405
for path, f in files:
407
if isinstance(f, basestring):
408
f = f.encode('utf-8', 'replace')
410
f = codecs.getwriter('utf-8')(f, errors='replace')
411
path = self._rel_controlfilename(path)
412
ctrl_files.append((path, f))
413
self._transport.put_multi(ctrl_files)
360
415
def _make_control(self):
361
os.mkdir(self.controlfilename([]))
362
self.controlfile('README', 'w').write(
363
"This is a Bazaar-NG control directory.\n"
364
"Do not change any files in this directory.\n")
365
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
366
for d in ('text-store', 'revision-store',
368
os.mkdir(self.controlfilename(d))
369
for f in ('revision-history',
373
self.controlfile(f, 'w').write('')
374
mutter('created control directory in ' + self.base)
416
from bzrlib.inventory import Inventory
417
from bzrlib.weavefile import write_weave_v5
418
from bzrlib.weave import Weave
420
# Create an empty inventory
376
422
# if we want per-tree root ids then this is the place to set
377
423
# them; they're not needed for now and so ommitted for
379
f = self.controlfile('inventory','w')
380
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
425
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
426
empty_inv = sio.getvalue()
428
bzrlib.weavefile.write_weave_v5(Weave(), sio)
429
empty_weave = sio.getvalue()
431
dirs = [[], 'revision-store', 'weaves']
433
"This is a Bazaar-NG control directory.\n"
434
"Do not change any files in this directory.\n"),
435
('branch-format', BZR_BRANCH_FORMAT_5),
436
('revision-history', ''),
439
('pending-merges', ''),
440
('inventory', empty_inv),
441
('inventory.weave', empty_weave),
442
('ancestry.weave', empty_weave)
444
cfn = self._rel_controlfilename
445
self._transport.mkdir_multi([cfn(d) for d in dirs])
446
self.put_controlfiles(files)
447
mutter('created control directory in ' + self._transport.base)
383
449
def _check_format(self, relax_version_check):
384
450
"""Check this branch format is supported.
1127
def add_pending_merge(self, revision_id):
1128
validate_revision_id(revision_id)
1181
def add_pending_merge(self, *revision_ids):
1129
1182
# TODO: Perhaps should check at this point that the
1130
1183
# history of the revision is actually present?
1184
for rev_id in revision_ids:
1185
validate_revision_id(rev_id)
1131
1187
p = self.pending_merges()
1132
if revision_id in p:
1134
p.append(revision_id)
1135
self.set_pending_merges(p)
1189
for rev_id in revision_ids:
1195
self.set_pending_merges(p)
1138
1197
def set_pending_merges(self, rev_list):
1139
from bzrlib.atomicfile import AtomicFile
1140
1198
self.lock_write()
1142
f = AtomicFile(self.controlfilename('pending-merges'))
1200
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1328
1382
return gen_file_id('TREE_ROOT')
1331
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1332
"""Copy branch_from into the existing directory to_location.
1335
If not None, only revisions up to this point will be copied.
1336
The head of the new branch will be that revision. Must be a
1340
The name of a local directory that exists but is empty.
1343
The revision to copy up to
1346
A local branch to copy revisions from, related to branch_from
1348
# TODO: This could be done *much* more efficiently by just copying
1349
# all the whole weaves and revisions, rather than getting one
1350
# revision at a time.
1351
from bzrlib.merge import merge
1353
assert isinstance(branch_from, Branch)
1354
assert isinstance(to_location, basestring)
1356
br_to = Branch.initialize(to_location)
1357
mutter("copy branch from %s to %s", branch_from, br_to)
1358
if basis_branch is not None:
1359
basis_branch.push_stores(br_to)
1360
br_to.set_root_id(branch_from.get_root_id())
1361
if revision is None:
1362
revision = branch_from.last_revision()
1363
br_to.update_revisions(branch_from, stop_revision=revision)
1364
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1365
check_clean=False, ignore_zero=True)
1366
br_to.set_parent(branch_from.base)