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.
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)
109
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
111
head, tail = os.path.split(f)
101
if t.has(bzrlib.BZRDIR):
103
new_t = t.clone('..')
104
if new_t.base == t.base:
113
105
# reached the root, whatever that may be
114
raise NotBranchError('%s is not in a branch' % orig_f)
106
raise NotBranchError('%s is not in a branch' % orig_base)
120
110
######################################################################
218
207
except UnlistableStore:
219
208
raise UnlistableBranch(from_store)
221
def __init__(self, base, init=False, find_root=True,
210
def __init__(self, transport, init=False,
222
211
relax_version_check=False):
223
212
"""Create new branch object at a particular location.
225
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)
227
218
init -- If True, create new control files in a previously
228
219
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.
234
222
relax_version_check -- If true, the usual check for the branch
235
223
version is not applied. This is intended only for
236
224
upgrade/recovery type use; it's not guaranteed that
239
227
In the test suite, creation of new trees is tested using the
240
228
`ScratchBranch` class.
230
assert isinstance(transport, Transport), \
231
"%r is not a Transport" % transport
232
self._transport = transport
243
self.base = os.path.realpath(base)
244
234
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 '
255
235
self._check_format(relax_version_check)
256
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
257
256
if self._branch_format == 4:
258
self.inventory_store = ImmutableStore(cfn('inventory-store'))
259
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')
260
260
elif self._branch_format == 5:
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'))
261
self.control_weaves = get_weave([])
262
self.weave_store = get_weave('weaves')
263
self.revision_store = get_store('revision-store', compressed=False)
270
265
def __str__(self):
271
return '%s(%r)' % (self.__class__.__name__, self.base)
266
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
274
269
__repr__ = __str__
281
276
warn("branch %r was not explicitly unlocked" % self)
282
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)
284
301
def lock_write(self):
302
# TODO: Upgrade locking to support using a Transport,
303
# and potentially a remote locking protocol
285
304
if self._lock_mode:
286
305
if self._lock_mode != 'w':
287
306
raise LockError("can't upgrade to a write lock from %r" %
289
308
self._lock_count += 1
291
from bzrlib.lock import WriteLock
293
self._lock = WriteLock(self.controlfilename('branch-lock'))
310
self._lock = self._transport.lock_write(
311
self._rel_controlfilename('branch-lock'))
294
312
self._lock_mode = 'w'
295
313
self._lock_count = 1
321
338
def abspath(self, name):
322
339
"""Return absolute filename for something in the branch"""
323
return os.path.join(self.base, name)
340
return self._transport.abspath(name)
325
342
def relpath(self, path):
326
343
"""Return path relative to this branch of something inside it.
328
345
Raises an error if path is not in this branch."""
329
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
331
354
def controlfilename(self, file_or_path):
332
355
"""Return location relative to branch."""
333
if isinstance(file_or_path, basestring):
334
file_or_path = [file_or_path]
335
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
356
return self._transport.abspath(self._rel_controlfilename(file_or_path))
338
359
def controlfile(self, file_or_path, mode='r'):
346
367
Controlfiles should almost never be opened in write mode but
347
368
rather should be atomically copied and replaced using atomicfile.
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',
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")
361
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)
363
415
def _make_control(self):
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)
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
379
422
# if we want per-tree root ids then this is the place to set
380
423
# them; they're not needed for now and so ommitted for
382
f = self.controlfile('inventory','w')
383
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)
386
449
def _check_format(self, relax_version_check):
387
450
"""Check this branch format is supported.
1139
def add_pending_merge(self, revision_id):
1140
validate_revision_id(revision_id)
1189
def add_pending_merge(self, *revision_ids):
1141
1190
# TODO: Perhaps should check at this point that the
1142
1191
# history of the revision is actually present?
1192
for rev_id in revision_ids:
1193
validate_revision_id(rev_id)
1143
1195
p = self.pending_merges()
1144
if revision_id in p:
1146
p.append(revision_id)
1147
self.set_pending_merges(p)
1197
for rev_id in revision_ids:
1203
self.set_pending_merges(p)
1150
1205
def set_pending_merges(self, rev_list):
1151
from bzrlib.atomicfile import AtomicFile
1152
1206
self.lock_write()
1154
f = AtomicFile(self.controlfilename('pending-merges'))
1208
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1342
1392
return gen_file_id('TREE_ROOT')
1345
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1346
"""Copy branch_from into the existing directory to_location.
1349
If not None, only revisions up to this point will be copied.
1350
The head of the new branch will be that revision. Must be a
1354
The name of a local directory that exists but is empty.
1357
The revision to copy up to
1360
A local branch to copy revisions from, related to branch_from
1362
# TODO: This could be done *much* more efficiently by just copying
1363
# all the whole weaves and revisions, rather than getting one
1364
# revision at a time.
1365
from bzrlib.merge import merge
1367
assert isinstance(branch_from, Branch)
1368
assert isinstance(to_location, basestring)
1370
br_to = Branch.initialize(to_location)
1371
mutter("copy branch from %s to %s", branch_from, br_to)
1372
if basis_branch is not None:
1373
basis_branch.push_stores(br_to)
1374
br_to.set_root_id(branch_from.get_root_id())
1375
if revision is None:
1376
revision = branch_from.last_revision()
1377
br_to.update_revisions(branch_from, stop_revision=revision)
1378
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1379
check_clean=False, ignore_zero=True)
1380
br_to.set_parent(branch_from.base)