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.
453
507
That is to say, the inventory describing changes underway, that
454
508
will be committed to the next revision.
456
from bzrlib.atomicfile import AtomicFile
510
from cStringIO import StringIO
458
511
self.lock_write()
460
f = AtomicFile(self.controlfilename('inventory'), 'wb')
462
bzrlib.xml5.serializer_v5.write_inventory(inv, f)
514
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
516
# Transport handles atomicity
517
self.put_controlfile('inventory', sio)
469
521
mutter('wrote working inventory')
472
523
inventory = property(read_working_inventory, _write_inventory, None,
473
524
"""Inventory for the working copy.""")
476
526
def add(self, files, ids=None):
477
527
"""Make files versioned.
715
756
return compare_trees(old_tree, new_tree)
718
758
def get_revision_sha1(self, revision_id):
719
759
"""Hash the stored value of a revision, and return it."""
760
# In the future, revision entries will be signed. At that
761
# point, it is probably best *not* to include the signature
762
# in the revision hash. Because that lets you re-sign
763
# the revision, (add signatures/remove signatures) and still
764
# have all hash pointers stay consistent.
765
# But for now, just hash the contents.
720
766
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
723
768
def _get_ancestry_weave(self):
724
769
return self.control_weaves.get_weave('ancestry')
727
771
def get_ancestry(self, revision_id):
728
772
"""Return a list of revision-ids integrated by a revision.
1143
def add_pending_merge(self, revision_id):
1144
validate_revision_id(revision_id)
1181
def add_pending_merge(self, *revision_ids):
1145
1182
# TODO: Perhaps should check at this point that the
1146
1183
# history of the revision is actually present?
1184
for rev_id in revision_ids:
1185
validate_revision_id(rev_id)
1147
1187
p = self.pending_merges()
1148
if revision_id in p:
1150
p.append(revision_id)
1151
self.set_pending_merges(p)
1189
for rev_id in revision_ids:
1195
self.set_pending_merges(p)
1154
1197
def set_pending_merges(self, rev_list):
1155
from bzrlib.atomicfile import AtomicFile
1156
1198
self.lock_write()
1158
f = AtomicFile(self.controlfilename('pending-merges'))
1200
self.put_controlfile('pending-merges', '\n'.join(rev_list))