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
self._transport = transport
250
self.base = os.path.realpath(base)
251
233
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
234
self._check_format(relax_version_check)
263
cfn = self.controlfilename
236
def get_store(name, compressed=True):
237
relpath = self._rel_controlfilename(name)
239
store = CompressedTextStore(self._transport.clone(relpath))
241
store = TextStore(self._transport.clone(relpath))
242
if self._transport.should_cache():
243
from meta_store import CachedStore
244
cache_path = os.path.join(self.cache_root, name)
246
store = CachedStore(store, cache_path)
249
relpath = self._rel_controlfilename(name)
250
ws = WeaveStore(self._transport.clone(relpath))
251
if self._transport.should_cache():
252
ws.enable_cache = True
264
255
if self._branch_format == 4:
265
self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
self.text_store = ImmutableStore(cfn('text-store'))
256
self.inventory_store = get_store('inventory-store')
257
self.text_store = get_store('text-store')
258
self.revision_store = get_store('revision-store')
267
259
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'))
260
self.control_weaves = get_weave([])
261
self.weave_store = get_weave('weaves')
262
self.revision_store = get_store('revision-store', compressed=False)
277
264
def __str__(self):
278
return '%s(%r)' % (self.__class__.__name__, self.base)
265
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
281
268
__repr__ = __str__
288
275
warn("branch %r was not explicitly unlocked" % self)
289
276
self._lock.unlock()
278
# TODO: It might be best to do this somewhere else,
279
# but it is nice for a Branch object to automatically
280
# cache it's information.
281
# Alternatively, we could have the Transport objects cache requests
282
# See the earlier discussion about how major objects (like Branch)
283
# should never expect their __del__ function to run.
284
if hasattr(self, 'cache_root') and self.cache_root is not None:
287
shutil.rmtree(self.cache_root)
290
self.cache_root = None
294
return self._transport.base
297
base = property(_get_base)
291
300
def lock_write(self):
301
# TODO: Upgrade locking to support using a Transport,
302
# and potentially a remote locking protocol
292
303
if self._lock_mode:
293
304
if self._lock_mode != 'w':
294
305
raise LockError("can't upgrade to a write lock from %r" %
296
307
self._lock_count += 1
298
from bzrlib.lock import WriteLock
300
self._lock = WriteLock(self.controlfilename('branch-lock'))
309
self._lock = self._transport.lock_write(
310
self._rel_controlfilename('branch-lock'))
301
311
self._lock_mode = 'w'
302
312
self._lock_count = 1
328
337
def abspath(self, name):
329
338
"""Return absolute filename for something in the branch"""
330
return os.path.join(self.base, name)
339
return self._transport.abspath(name)
332
341
def relpath(self, path):
333
342
"""Return path relative to this branch of something inside it.
335
344
Raises an error if path is not in this branch."""
336
return _relpath(self.base, path)
345
return self._transport.relpath(path)
348
def _rel_controlfilename(self, file_or_path):
349
if isinstance(file_or_path, basestring):
350
file_or_path = [file_or_path]
351
return [bzrlib.BZRDIR] + file_or_path
338
353
def controlfilename(self, file_or_path):
339
354
"""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)
355
return self._transport.abspath(self._rel_controlfilename(file_or_path))
345
358
def controlfile(self, file_or_path, mode='r'):
353
366
Controlfiles should almost never be opened in write mode but
354
367
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',
371
relpath = self._rel_controlfilename(file_or_path)
372
#TODO: codecs.open() buffers linewise, so it was overloaded with
373
# a much larger buffer, do we need to do the same for getreader/getwriter?
375
return self._transport.get(relpath)
377
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
379
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
381
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
368
383
raise BzrError("invalid controlfile mode %r" % mode)
385
def put_controlfile(self, path, f, encode=True):
386
"""Write an entry as a controlfile.
388
:param path: The path to put the file, relative to the .bzr control
390
:param f: A file-like or string object whose contents should be copied.
391
:param encode: If true, encode the contents as utf-8
393
self.put_controlfiles([(path, f)], encode=encode)
395
def put_controlfiles(self, files, encode=True):
396
"""Write several entries as controlfiles.
398
:param files: A list of [(path, file)] pairs, where the path is the directory
399
underneath the bzr control directory
400
:param encode: If true, encode the contents as utf-8
404
for path, f in files:
406
if isinstance(f, basestring):
407
f = f.encode('utf-8', 'replace')
409
f = codecs.getwriter('utf-8')(f, errors='replace')
410
path = self._rel_controlfilename(path)
411
ctrl_files.append((path, f))
412
self._transport.put_multi(ctrl_files)
370
414
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)
415
from bzrlib.inventory import Inventory
416
from bzrlib.weavefile import write_weave_v5
417
from bzrlib.weave import Weave
419
# Create an empty inventory
386
421
# if we want per-tree root ids then this is the place to set
387
422
# 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)
424
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
425
empty_inv = sio.getvalue()
427
bzrlib.weavefile.write_weave_v5(Weave(), sio)
428
empty_weave = sio.getvalue()
430
dirs = [[], 'revision-store', 'weaves']
432
"This is a Bazaar-NG control directory.\n"
433
"Do not change any files in this directory.\n"),
434
('branch-format', BZR_BRANCH_FORMAT_5),
435
('revision-history', ''),
438
('pending-merges', ''),
439
('inventory', empty_inv),
440
('inventory.weave', empty_weave),
441
('ancestry.weave', empty_weave)
443
cfn = self._rel_controlfilename
444
self._transport.mkdir_multi([cfn(d) for d in dirs])
445
self.put_controlfiles(files)
446
mutter('created control directory in ' + self._transport.base)
393
448
def _check_format(self, relax_version_check):
394
449
"""Check this branch format is supported.
1143
def add_pending_merge(self, revision_id):
1144
validate_revision_id(revision_id)
1190
def add_pending_merge(self, *revision_ids):
1145
1191
# TODO: Perhaps should check at this point that the
1146
1192
# history of the revision is actually present?
1193
for rev_id in revision_ids:
1194
validate_revision_id(rev_id)
1147
1196
p = self.pending_merges()
1148
if revision_id in p:
1150
p.append(revision_id)
1151
self.set_pending_merges(p)
1198
for rev_id in revision_ids:
1204
self.set_pending_merges(p)
1154
1206
def set_pending_merges(self, rev_list):
1155
from bzrlib.atomicfile import AtomicFile
1156
1207
self.lock_write()
1158
f = AtomicFile(self.controlfilename('pending-merges'))
1209
self.put_controlfile('pending-merges', '\n'.join(rev_list))