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
######################################################################
225
207
except UnlistableStore:
226
208
raise UnlistableBranch(from_store)
228
def __init__(self, base, init=False, find_root=True,
210
def __init__(self, transport, init=False,
229
211
relax_version_check=False):
230
212
"""Create new branch object at a particular location.
232
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)
234
218
init -- If True, create new control files in a previously
235
219
unversioned directory. If False, the branch must already
238
find_root -- If true and init is false, find the root of the
239
existing branch containing base.
241
222
relax_version_check -- If true, the usual check for the branch
242
223
version is not applied. This is intended only for
243
224
upgrade/recovery type use; it's not guaranteed that
246
227
In the test suite, creation of new trees is tested using the
247
228
`ScratchBranch` class.
230
assert isinstance(transport, Transport), \
231
"%r is not a Transport" % transport
232
self._transport = transport
250
self.base = os.path.realpath(base)
251
234
self._make_control()
253
self.base = find_branch_root(base)
255
if base.startswith("file://"):
257
self.base = os.path.realpath(base)
258
if not isdir(self.controlfilename('.')):
259
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
260
['use "bzr init" to initialize a '
262
235
self._check_format(relax_version_check)
263
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
264
256
if self._branch_format == 4:
265
self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
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')
267
260
elif self._branch_format == 5:
268
self.control_weaves = WeaveStore(cfn([]))
269
self.weave_store = WeaveStore(cfn('weaves'))
271
# FIXME: Unify with make_control_files
272
self.control_weaves.put_empty_weave('inventory')
273
self.control_weaves.put_empty_weave('ancestry')
274
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)
277
265
def __str__(self):
278
return '%s(%r)' % (self.__class__.__name__, self.base)
266
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
281
269
__repr__ = __str__
288
276
warn("branch %r was not explicitly unlocked" % self)
289
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)
291
301
def lock_write(self):
302
# TODO: Upgrade locking to support using a Transport,
303
# and potentially a remote locking protocol
292
304
if self._lock_mode:
293
305
if self._lock_mode != 'w':
294
306
raise LockError("can't upgrade to a write lock from %r" %
296
308
self._lock_count += 1
298
from bzrlib.lock import WriteLock
300
self._lock = WriteLock(self.controlfilename('branch-lock'))
310
self._lock = self._transport.lock_write(
311
self._rel_controlfilename('branch-lock'))
301
312
self._lock_mode = 'w'
302
313
self._lock_count = 1
328
338
def abspath(self, name):
329
339
"""Return absolute filename for something in the branch"""
330
return os.path.join(self.base, name)
340
return self._transport.abspath(name)
332
342
def relpath(self, path):
333
343
"""Return path relative to this branch of something inside it.
335
345
Raises an error if path is not in this branch."""
336
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
338
354
def controlfilename(self, file_or_path):
339
355
"""Return location relative to branch."""
340
if isinstance(file_or_path, basestring):
341
file_or_path = [file_or_path]
342
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
356
return self._transport.abspath(self._rel_controlfilename(file_or_path))
345
359
def controlfile(self, file_or_path, mode='r'):
353
367
Controlfiles should almost never be opened in write mode but
354
368
rather should be atomically copied and replaced using atomicfile.
357
fn = self.controlfilename(file_or_path)
359
if mode == 'rb' or mode == 'wb':
360
return file(fn, mode)
361
elif mode == 'r' or mode == 'w':
362
# open in binary mode anyhow so there's no newline translation;
363
# codecs uses line buffering by default; don't want that.
365
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")
368
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)
370
415
def _make_control(self):
371
os.mkdir(self.controlfilename([]))
372
self.controlfile('README', 'w').write(
373
"This is a Bazaar-NG control directory.\n"
374
"Do not change any files in this directory.\n")
375
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
376
for d in ('text-store', 'revision-store',
378
os.mkdir(self.controlfilename(d))
379
for f in ('revision-history',
383
self.controlfile(f, 'w').write('')
384
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
386
422
# if we want per-tree root ids then this is the place to set
387
423
# them; they're not needed for now and so ommitted for
389
f = self.controlfile('inventory','w')
390
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)
393
449
def _check_format(self, relax_version_check):
394
450
"""Check this branch format is supported.
1143
def add_pending_merge(self, revision_id):
1144
validate_revision_id(revision_id)
1189
def add_pending_merge(self, *revision_ids):
1145
1190
# TODO: Perhaps should check at this point that the
1146
1191
# history of the revision is actually present?
1192
for rev_id in revision_ids:
1193
validate_revision_id(rev_id)
1147
1195
p = self.pending_merges()
1148
if revision_id in p:
1150
p.append(revision_id)
1151
self.set_pending_merges(p)
1197
for rev_id in revision_ids:
1203
self.set_pending_merges(p)
1154
1205
def set_pending_merges(self, rev_list):
1155
from bzrlib.atomicfile import AtomicFile
1156
1206
self.lock_write()
1158
f = AtomicFile(self.controlfilename('pending-merges'))
1208
self.put_controlfile('pending-merges', '\n'.join(rev_list))