21
from warnings import warn
22
from cStringIO import StringIO
26
from bzrlib.inventory import InventoryEntry
27
import bzrlib.inventory as inventory
22
28
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
rename, splitpath, sha_file, appendpath, file_kind
26
from bzrlib.store import copy_all
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
DivergedBranches, NotBranchError, UnlistableStore, UnlistableBranch
29
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
30
rename, splitpath, sha_file, appendpath,
32
import bzrlib.errors as errors
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError, UnlistableStore,
36
UnlistableBranch, NoSuchFile)
29
37
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision, is_ancestor
38
from bzrlib.revision import Revision
31
39
from bzrlib.delta import compare_trees
32
40
from bzrlib.tree import EmptyTree, RevisionTree
41
from bzrlib.inventory import Inventory
42
from bzrlib.store import copy_all
43
from bzrlib.store.compressed_text import CompressedTextStore
44
from bzrlib.store.text import TextStore
45
from bzrlib.store.weave import WeaveStore
46
import bzrlib.transactions as transactions
47
from bzrlib.transport import Transport, get_transport
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
52
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
53
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
39
54
## TODO: Maybe include checks for common corruption of newlines, etc?
42
57
# TODO: Some operations like log might retrieve the same revisions
43
58
# repeatedly to calculate deltas. We could perhaps have a weakref
44
# cache in memory to make this faster.
59
# cache in memory to make this faster. In general anything can be
60
# cached in memory between lock and unlock operations.
46
62
def find_branch(*ignored, **ignored_too):
47
63
# XXX: leave this here for about one release, then remove it
48
64
raise NotImplementedError('find_branch() is not supported anymore, '
49
65
'please use one of the new branch constructors')
51
66
def _relpath(base, path):
52
67
"""Return path relative to base, or raise exception.
183
186
_lock_mode = None
184
187
_lock_count = None
187
def __init__(self, base, init=False, find_root=True):
189
_inventory_weave = None
191
# Map some sort of prefix into a namespace
192
# stuff like "revno:10", "revid:", etc.
193
# This should match a prefix with a function which accepts
194
REVISION_NAMESPACES = {}
196
def push_stores(self, branch_to):
197
"""Copy the content of this branches store to branch_to."""
198
if (self._branch_format != branch_to._branch_format
199
or self._branch_format != 4):
200
from bzrlib.fetch import greedy_fetch
201
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
202
self, self._branch_format, branch_to, branch_to._branch_format)
203
greedy_fetch(to_branch=branch_to, from_branch=self,
204
revision=self.last_revision())
207
store_pairs = ((self.text_store, branch_to.text_store),
208
(self.inventory_store, branch_to.inventory_store),
209
(self.revision_store, branch_to.revision_store))
211
for from_store, to_store in store_pairs:
212
copy_all(from_store, to_store)
213
except UnlistableStore:
214
raise UnlistableBranch(from_store)
216
def __init__(self, transport, init=False,
217
relax_version_check=False):
188
218
"""Create new branch object at a particular location.
190
base -- Base directory for the branch. May be a file:// url.
220
transport -- A Transport object, defining how to access files.
221
(If a string, transport.transport() will be used to
222
create a Transport object)
192
224
init -- If True, create new control files in a previously
193
225
unversioned directory. If False, the branch must already
196
find_root -- If true and init is false, find the root of the
197
existing branch containing base.
228
relax_version_check -- If true, the usual check for the branch
229
version is not applied. This is intended only for
230
upgrade/recovery type use; it's not guaranteed that
231
all operations will work on old format branches.
199
233
In the test suite, creation of new trees is tested using the
200
234
`ScratchBranch` class.
202
from bzrlib.store import ImmutableStore
236
assert isinstance(transport, Transport), \
237
"%r is not a Transport" % transport
238
self._transport = transport
204
self.base = os.path.realpath(base)
205
240
self._make_control()
207
self.base = find_branch_root(base)
209
if base.startswith("file://"):
211
self.base = os.path.realpath(base)
212
if not isdir(self.controlfilename('.')):
213
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
214
['use "bzr init" to initialize a new working tree',
215
'current bzr can only operate from top-of-tree'])
218
self.text_store = ImmutableStore(self.controlfilename('text-store'))
219
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
220
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
241
self._check_format(relax_version_check)
243
def get_store(name, compressed=True):
244
# FIXME: This approach of assuming stores are all entirely compressed
245
# or entirely uncompressed is tidy, but breaks upgrade from
246
# some existing branches where there's a mixture; we probably
247
# still want the option to look for both.
248
relpath = self._rel_controlfilename(name)
250
store = CompressedTextStore(self._transport.clone(relpath))
252
store = TextStore(self._transport.clone(relpath))
253
#if self._transport.should_cache():
254
# cache_path = os.path.join(self.cache_root, name)
255
# os.mkdir(cache_path)
256
# store = bzrlib.store.CachedStore(store, cache_path)
259
relpath = self._rel_controlfilename(name)
260
ws = WeaveStore(self._transport.clone(relpath))
261
if self._transport.should_cache():
262
ws.enable_cache = True
265
if self._branch_format == 4:
266
self.inventory_store = get_store('inventory-store')
267
self.text_store = get_store('text-store')
268
self.revision_store = get_store('revision-store')
269
elif self._branch_format == 5:
270
self.control_weaves = get_weave([])
271
self.weave_store = get_weave('weaves')
272
self.revision_store = get_store('revision-store', compressed=False)
273
self._transaction = None
223
275
def __str__(self):
224
return '%s(%r)' % (self.__class__.__name__, self.base)
276
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
227
279
__repr__ = __str__
230
282
def __del__(self):
231
283
if self._lock_mode or self._lock:
232
from bzrlib.warnings import warn
284
# XXX: This should show something every time, and be suitable for
285
# headless operation and embedding
233
286
warn("branch %r was not explicitly unlocked" % self)
234
287
self._lock.unlock()
289
# TODO: It might be best to do this somewhere else,
290
# but it is nice for a Branch object to automatically
291
# cache it's information.
292
# Alternatively, we could have the Transport objects cache requests
293
# See the earlier discussion about how major objects (like Branch)
294
# should never expect their __del__ function to run.
295
if hasattr(self, 'cache_root') and self.cache_root is not None:
298
shutil.rmtree(self.cache_root)
301
self.cache_root = None
305
return self._transport.base
308
base = property(_get_base)
310
def _finish_transaction(self):
311
"""Exit the current transaction."""
312
if self._transaction is None:
313
raise errors.LockError('Branch %s is not in a transaction' %
315
transaction = self._transaction
316
self._transaction = None
319
def get_transaction(self):
320
"""Return the current active transaction.
322
If no transaction is active, this returns a passthrough object
323
for which all data is immedaitely flushed and no caching happens.
325
if self._transaction is None:
326
return transactions.PassThroughTransaction()
328
return self._transaction
330
def _set_transaction(self, new_transaction):
331
"""Set a new active transaction."""
332
if self._transaction is not None:
333
raise errors.LockError('Branch %s is in a transaction already.' %
335
self._transaction = new_transaction
236
337
def lock_write(self):
338
# TODO: Upgrade locking to support using a Transport,
339
# and potentially a remote locking protocol
237
340
if self._lock_mode:
238
341
if self._lock_mode != 'w':
239
from bzrlib.errors import LockError
240
342
raise LockError("can't upgrade to a write lock from %r" %
242
344
self._lock_count += 1
244
from bzrlib.lock import WriteLock
246
self._lock = WriteLock(self.controlfilename('branch-lock'))
346
self._lock = self._transport.lock_write(
347
self._rel_controlfilename('branch-lock'))
247
348
self._lock_mode = 'w'
248
349
self._lock_count = 1
350
self._set_transaction(transactions.PassThroughTransaction())
251
353
def lock_read(self):
254
356
"invalid lock mode %r" % self._lock_mode
255
357
self._lock_count += 1
257
from bzrlib.lock import ReadLock
259
self._lock = ReadLock(self.controlfilename('branch-lock'))
359
self._lock = self._transport.lock_read(
360
self._rel_controlfilename('branch-lock'))
260
361
self._lock_mode = 'r'
261
362
self._lock_count = 1
363
self._set_transaction(transactions.ReadOnlyTransaction())
263
365
def unlock(self):
264
366
if not self._lock_mode:
265
from bzrlib.errors import LockError
266
367
raise LockError('branch %r is not locked' % (self))
268
369
if self._lock_count > 1:
269
370
self._lock_count -= 1
372
self._finish_transaction()
271
373
self._lock.unlock()
272
374
self._lock = None
273
375
self._lock_mode = self._lock_count = None
275
377
def abspath(self, name):
276
378
"""Return absolute filename for something in the branch"""
277
return os.path.join(self.base, name)
379
return self._transport.abspath(name)
279
381
def relpath(self, path):
280
382
"""Return path relative to this branch of something inside it.
282
384
Raises an error if path is not in this branch."""
283
return _relpath(self.base, path)
385
return self._transport.relpath(path)
388
def _rel_controlfilename(self, file_or_path):
389
if isinstance(file_or_path, basestring):
390
file_or_path = [file_or_path]
391
return [bzrlib.BZRDIR] + file_or_path
285
393
def controlfilename(self, file_or_path):
286
394
"""Return location relative to branch."""
287
if isinstance(file_or_path, basestring):
288
file_or_path = [file_or_path]
289
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
395
return self._transport.abspath(self._rel_controlfilename(file_or_path))
292
398
def controlfile(self, file_or_path, mode='r'):
300
406
Controlfiles should almost never be opened in write mode but
301
407
rather should be atomically copied and replaced using atomicfile.
304
fn = self.controlfilename(file_or_path)
306
if mode == 'rb' or mode == 'wb':
307
return file(fn, mode)
308
elif mode == 'r' or mode == 'w':
309
# open in binary mode anyhow so there's no newline translation;
310
# codecs uses line buffering by default; don't want that.
312
return codecs.open(fn, mode + 'b', 'utf-8',
411
relpath = self._rel_controlfilename(file_or_path)
412
#TODO: codecs.open() buffers linewise, so it was overloaded with
413
# a much larger buffer, do we need to do the same for getreader/getwriter?
415
return self._transport.get(relpath)
417
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
419
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
421
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
315
423
raise BzrError("invalid controlfile mode %r" % mode)
425
def put_controlfile(self, path, f, encode=True):
426
"""Write an entry as a controlfile.
428
:param path: The path to put the file, relative to the .bzr control
430
:param f: A file-like or string object whose contents should be copied.
431
:param encode: If true, encode the contents as utf-8
433
self.put_controlfiles([(path, f)], encode=encode)
435
def put_controlfiles(self, files, encode=True):
436
"""Write several entries as controlfiles.
438
:param files: A list of [(path, file)] pairs, where the path is the directory
439
underneath the bzr control directory
440
:param encode: If true, encode the contents as utf-8
444
for path, f in files:
446
if isinstance(f, basestring):
447
f = f.encode('utf-8', 'replace')
449
f = codecs.getwriter('utf-8')(f, errors='replace')
450
path = self._rel_controlfilename(path)
451
ctrl_files.append((path, f))
452
self._transport.put_multi(ctrl_files)
317
454
def _make_control(self):
318
455
from bzrlib.inventory import Inventory
456
from bzrlib.weavefile import write_weave_v5
457
from bzrlib.weave import Weave
320
os.mkdir(self.controlfilename([]))
321
self.controlfile('README', 'w').write(
322
"This is a Bazaar-NG control directory.\n"
323
"Do not change any files in this directory.\n")
324
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
325
for d in ('text-store', 'inventory-store', 'revision-store'):
326
os.mkdir(self.controlfilename(d))
327
for f in ('revision-history', 'merged-patches',
328
'pending-merged-patches', 'branch-name',
331
self.controlfile(f, 'w').write('')
332
mutter('created control directory in ' + self.base)
459
# Create an empty inventory
334
461
# if we want per-tree root ids then this is the place to set
335
462
# them; they're not needed for now and so ommitted for
337
f = self.controlfile('inventory','w')
338
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
341
def _check_format(self):
464
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
465
empty_inv = sio.getvalue()
467
bzrlib.weavefile.write_weave_v5(Weave(), sio)
468
empty_weave = sio.getvalue()
470
dirs = [[], 'revision-store', 'weaves']
472
"This is a Bazaar-NG control directory.\n"
473
"Do not change any files in this directory.\n"),
474
('branch-format', BZR_BRANCH_FORMAT_5),
475
('revision-history', ''),
478
('pending-merges', ''),
479
('inventory', empty_inv),
480
('inventory.weave', empty_weave),
481
('ancestry.weave', empty_weave)
483
cfn = self._rel_controlfilename
484
self._transport.mkdir_multi([cfn(d) for d in dirs])
485
self.put_controlfiles(files)
486
mutter('created control directory in ' + self._transport.base)
488
def _check_format(self, relax_version_check):
342
489
"""Check this branch format is supported.
344
The current tool only supports the current unstable format.
491
The format level is stored, as an integer, in
492
self._branch_format for code that needs to check it later.
346
494
In the future, we might need different in-memory Branch
347
495
classes to support downlevel branches. But not yet.
349
# This ignores newlines so that we can open branches created
350
# on Windows from Linux and so on. I think it might be better
351
# to always make all internal files in unix format.
352
fmt = self.controlfile('branch-format', 'r').read()
353
fmt = fmt.replace('\r\n', '\n')
354
if fmt != BZR_BRANCH_FORMAT:
498
fmt = self.controlfile('branch-format', 'r').read()
500
raise NotBranchError(self.base)
501
mutter("got branch format %r", fmt)
502
if fmt == BZR_BRANCH_FORMAT_5:
503
self._branch_format = 5
504
elif fmt == BZR_BRANCH_FORMAT_4:
505
self._branch_format = 4
507
if (not relax_version_check
508
and self._branch_format != 5):
355
509
raise BzrError('sorry, branch format %r not supported' % fmt,
356
510
['use a different bzr version',
357
'or remove the .bzr directory and "bzr init" again'])
511
'or remove the .bzr directory'
512
' and "bzr init" again'])
359
514
def get_root_id(self):
360
515
"""Return the id of this branches root"""
654
811
# the revision, (add signatures/remove signatures) and still
655
812
# have all hash pointers stay consistent.
656
813
# But for now, just hash the contents.
657
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
660
def get_inventory(self, inventory_id):
661
"""Get Inventory object by hash.
663
TODO: Perhaps for this and similar methods, take a revision
664
parameter which can be either an integer revno or a
666
from bzrlib.inventory import Inventory
668
f = self.get_inventory_xml_file(inventory_id)
669
return bzrlib.xml.serializer_v4.read_inventory(f)
672
def get_inventory_xml(self, inventory_id):
814
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
816
def get_ancestry(self, revision_id):
817
"""Return a list of revision-ids integrated by a revision.
819
This currently returns a list, but the ordering is not guaranteed:
822
if revision_id is None:
824
w = self.get_inventory_weave()
825
return [None] + map(w.idx_to_name,
826
w.inclusions([w.lookup(revision_id)]))
828
def get_inventory_weave(self):
829
return self.control_weaves.get_weave('inventory',
830
self.get_transaction())
832
def get_inventory(self, revision_id):
833
"""Get Inventory object by hash."""
834
xml = self.get_inventory_xml(revision_id)
835
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
837
def get_inventory_xml(self, revision_id):
673
838
"""Get inventory XML as a file object."""
674
return self.inventory_store[inventory_id]
676
get_inventory_xml_file = get_inventory_xml
679
def get_inventory_sha1(self, inventory_id):
840
assert isinstance(revision_id, basestring), type(revision_id)
841
iw = self.get_inventory_weave()
842
return iw.get_text(iw.lookup(revision_id))
844
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
846
def get_inventory_sha1(self, revision_id):
680
847
"""Return the sha1 hash of the inventory entry
682
return sha_file(self.get_inventory_xml(inventory_id))
849
return self.get_revision(revision_id).inventory_sha1
685
851
def get_revision_inventory(self, revision_id):
686
852
"""Return inventory of a past revision."""
687
# bzr 0.0.6 imposes the constraint that the inventory_id
853
# TODO: Unify this with get_inventory()
854
# bzr 0.0.6 and later imposes the constraint that the inventory_id
688
855
# must be the same as its revision, so this is trivial.
689
856
if revision_id == None:
690
from bzrlib.inventory import Inventory
691
857
return Inventory(self.get_root_id())
693
859
return self.get_inventory(revision_id)
696
861
def revision_history(self):
697
"""Return sequence of revision hashes on to this branch.
699
>>> ScratchBranch().revision_history()
862
"""Return sequence of revision hashes on to this branch."""
704
865
return [l.rstrip('\r\n') for l in
810
974
if stop_revision is None:
811
975
stop_revision = other_len
812
elif stop_revision > other_len:
813
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
977
assert isinstance(stop_revision, int)
978
if stop_revision > other_len:
979
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
815
980
return other_history[self_len:stop_revision]
818
982
def update_revisions(self, other, stop_revision=None):
819
"""Pull in all new revisions from other branch.
983
"""Pull in new perfect-fit revisions."""
821
984
from bzrlib.fetch import greedy_fetch
822
985
from bzrlib.revision import get_intervening_revisions
824
pb = bzrlib.ui.ui_factory.progress_bar()
825
pb.update('comparing histories')
826
986
if stop_revision is None:
827
other_revision = other.last_patch()
829
other_revision = other.get_rev_id(stop_revision)
830
count = greedy_fetch(self, other, other_revision, pb)[0]
832
revision_ids = self.missing_revisions(other, stop_revision)
833
except DivergedBranches, e:
835
revision_ids = get_intervening_revisions(self.last_patch(),
836
other_revision, self)
837
assert self.last_patch() not in revision_ids
838
except bzrlib.errors.NotAncestor:
839
if is_ancestor(self.last_patch(), other_revision, self):
843
self.append_revision(*revision_ids)
846
def install_revisions(self, other, revision_ids, pb):
847
if hasattr(other.revision_store, "prefetch"):
848
other.revision_store.prefetch(revision_ids)
849
if hasattr(other.inventory_store, "prefetch"):
851
for rev_id in revision_ids:
853
revision = other.get_revision(rev_id).inventory_id
854
inventory_ids.append(revision)
855
except bzrlib.errors.NoSuchRevision:
857
other.inventory_store.prefetch(inventory_ids)
860
pb = bzrlib.ui.ui_factory.progress_bar()
867
for i, rev_id in enumerate(revision_ids):
868
pb.update('fetching revision', i+1, len(revision_ids))
870
rev = other.get_revision(rev_id)
871
except bzrlib.errors.NoSuchRevision:
875
revisions.append(rev)
876
inv = other.get_inventory(str(rev.inventory_id))
877
for key, entry in inv.iter_entries():
878
if entry.text_id is None:
880
if entry.text_id not in self.text_store:
881
needed_texts.add(entry.text_id)
885
count, cp_fail = self.text_store.copy_multi(other.text_store,
887
#print "Added %d texts." % count
888
inventory_ids = [ f.inventory_id for f in revisions ]
889
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
891
#print "Added %d inventories." % count
892
revision_ids = [ f.revision_id for f in revisions]
894
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
897
assert len(cp_fail) == 0
898
return count, failures
987
stop_revision = other.last_revision()
988
greedy_fetch(to_branch=self, from_branch=other,
989
revision=stop_revision)
990
pullable_revs = self.missing_revisions(
991
other, other.revision_id_to_revno(stop_revision))
993
greedy_fetch(to_branch=self,
995
revision=pullable_revs[-1])
996
self.append_revision(*pullable_revs)
901
999
def commit(self, *args, **kw):
902
from bzrlib.commit import commit
903
commit(self, *args, **kw)
1000
from bzrlib.commit import Commit
1001
Commit().commit(self, *args, **kw)
905
1003
def revision_id_to_revno(self, revision_id):
906
1004
"""Given a revision id, return its revno"""
1005
if revision_id is None:
907
1007
history = self.revision_history()
909
1009
return history.index(revision_id) + 1
1339
1432
return gen_file_id('TREE_ROOT')
1342
def copy_branch(branch_from, to_location, revno=None, basis_branch=None):
1343
"""Copy branch_from into the existing directory to_location.
1346
If not None, only revisions up to this point will be copied.
1347
The head of the new branch will be that revision.
1350
The name of a local directory that exists but is empty.
1353
The revision to copy up to
1356
A local branch to copy revisions from, related to branch_from
1358
from bzrlib.merge import merge
1360
assert isinstance(branch_from, Branch)
1361
assert isinstance(to_location, basestring)
1363
br_to = Branch.initialize(to_location)
1364
if basis_branch is not None:
1365
copy_stores(basis_branch, br_to)
1366
br_to.set_root_id(branch_from.get_root_id())
1368
revno = branch_from.revno()
1369
br_to.update_revisions(branch_from, stop_revision=revno)
1370
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1371
check_clean=False, ignore_zero=True)
1372
br_to.set_parent(branch_from.base)
1375
def copy_stores(branch_from, branch_to):
1376
"""Copies all entries from branch stores to another branch's stores.
1378
store_pairs = ((branch_from.text_store, branch_to.text_store),
1379
(branch_from.inventory_store, branch_to.inventory_store),
1380
(branch_from.revision_store, branch_to.revision_store))
1382
for from_store, to_store in store_pairs:
1383
copy_all(from_store, to_store)
1384
except UnlistableStore:
1385
raise UnlistableBranch(from_store)