164
166
nick = property(_get_nick, _set_nick)
167
class _Branch(Branch):
168
def push_stores(self, branch_to):
169
"""Copy the content of this branches store to branch_to."""
170
raise NotImplementedError('push_stores is abstract')
172
def get_transaction(self):
173
"""Return the current active transaction.
175
If no transaction is active, this returns a passthrough object
176
for which all data is immediately flushed and no caching happens.
178
raise NotImplementedError('get_transaction is abstract')
180
def lock_write(self):
181
raise NotImplementedError('lock_write is abstract')
184
raise NotImplementedError('lock_read is abstract')
187
raise NotImplementedError('unlock is abstract')
189
def abspath(self, name):
190
"""Return absolute filename for something in the branch
192
XXX: Robert Collins 20051017 what is this used for? why is it a branch
193
method and not a tree method.
195
raise NotImplementedError('abspath is abstract')
197
def get_root_id(self):
198
"""Return the id of this branches root"""
199
raise NotImplementedError('get_root_id is abstract')
201
def print_file(self, file, revno):
202
"""Print `file` to stdout."""
203
raise NotImplementedError('print_file is abstract')
205
def append_revision(self, *revision_ids):
206
raise NotImplementedError('append_revision is abstract')
208
def set_revision_history(self, rev_history):
209
raise NotImplementedError('set_revision_history is abstract')
211
def get_revision_delta(self, revno):
212
"""Return the delta for one revision.
214
The delta is relative to its mainline predecessor, or the
215
empty tree for revision 1.
217
assert isinstance(revno, int)
218
rh = self.revision_history()
219
if not (1 <= revno <= len(rh)):
220
raise InvalidRevisionNumber(revno)
222
# revno is 1-based; list is 0-based
224
new_tree = self.storage.revision_tree(rh[revno-1])
226
old_tree = EmptyTree()
228
old_tree = self.storage.revision_tree(rh[revno-2])
230
return compare_trees(old_tree, new_tree)
232
def get_ancestry(self, revision_id):
233
"""Return a list of revision-ids integrated by a revision.
235
This currently returns a list, but the ordering is not guaranteed:
238
raise NotImplementedError('get_ancestry is abstract')
240
def get_inventory(self, revision_id):
241
"""Get Inventory object by hash."""
242
raise NotImplementedError('get_inventory is abstract')
244
def get_inventory_xml(self, revision_id):
245
"""Get inventory XML as a file object."""
246
raise NotImplementedError('get_inventory_xml is abstract')
248
def get_inventory_sha1(self, revision_id):
249
"""Return the sha1 hash of the inventory entry."""
250
raise NotImplementedError('get_inventory_sha1 is abstract')
252
def get_revision_inventory(self, revision_id):
253
"""Return inventory of a past revision."""
254
raise NotImplementedError('get_revision_inventory is abstract')
256
def revision_history(self):
257
"""Return sequence of revision hashes on to this branch."""
258
raise NotImplementedError('revision_history is abstract')
261
"""Return current revision number for this branch.
263
That is equivalent to the number of revisions committed to
266
return len(self.revision_history())
268
def last_revision(self):
269
"""Return last patch hash, or None if no history."""
270
ph = self.revision_history()
276
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
277
"""Return a list of new revisions that would perfectly fit.
279
If self and other have not diverged, return a list of the revisions
280
present in other, but missing from self.
282
>>> from bzrlib.commit import commit
283
>>> bzrlib.trace.silent = True
284
>>> br1 = ScratchBranch()
285
>>> br2 = ScratchBranch()
286
>>> br1.missing_revisions(br2)
288
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
289
>>> br1.missing_revisions(br2)
291
>>> br2.missing_revisions(br1)
293
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
294
>>> br1.missing_revisions(br2)
296
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
297
>>> br1.missing_revisions(br2)
299
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
300
>>> br1.missing_revisions(br2)
301
Traceback (most recent call last):
302
DivergedBranches: These branches have diverged.
304
self_history = self.revision_history()
305
self_len = len(self_history)
306
other_history = other.revision_history()
307
other_len = len(other_history)
308
common_index = min(self_len, other_len) -1
309
if common_index >= 0 and \
310
self_history[common_index] != other_history[common_index]:
311
raise DivergedBranches(self, other)
313
if stop_revision is None:
314
stop_revision = other_len
316
assert isinstance(stop_revision, int)
317
if stop_revision > other_len:
318
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
319
return other_history[self_len:stop_revision]
321
def update_revisions(self, other, stop_revision=None):
322
"""Pull in new perfect-fit revisions."""
323
raise NotImplementedError('update_revisions is abstract')
325
def pullable_revisions(self, other, stop_revision):
326
raise NotImplementedError('pullable_revisions is abstract')
328
def revision_id_to_revno(self, revision_id):
329
"""Given a revision id, return its revno"""
330
if revision_id is None:
332
history = self.revision_history()
334
return history.index(revision_id) + 1
336
raise bzrlib.errors.NoSuchRevision(self, revision_id)
338
def get_rev_id(self, revno, history=None):
339
"""Find the revision id of the specified revno."""
343
history = self.revision_history()
344
elif revno <= 0 or revno > len(history):
345
raise bzrlib.errors.NoSuchRevision(self, revno)
346
return history[revno - 1]
348
def working_tree(self):
349
"""Return a `Tree` for the working copy if this is a local branch."""
350
raise NotImplementedError('working_tree is abstract')
352
def pull(self, source, overwrite=False):
353
raise NotImplementedError('pull is abstract')
355
def basis_tree(self):
356
"""Return `Tree` object for last revision.
358
If there are no revisions yet, return an `EmptyTree`.
360
return self.storage.revision_tree(self.last_revision())
362
def rename_one(self, from_rel, to_rel):
365
This can change the directory or the filename or both.
367
raise NotImplementedError('rename_one is abstract')
369
def move(self, from_paths, to_name):
372
to_name must exist as a versioned directory.
374
If to_name exists and is a directory, the files are moved into
375
it, keeping their old names. If it is a directory,
377
Note that to_name is only the last component of the new name;
378
this doesn't change the directory.
380
This returns a list of (from_path, to_path) pairs for each
383
raise NotImplementedError('move is abstract')
385
def get_parent(self):
386
"""Return the parent location of the branch.
388
This is the default location for push/pull/missing. The usual
389
pattern is that the user can override it by specifying a
392
raise NotImplementedError('get_parent is abstract')
394
def get_push_location(self):
395
"""Return the None or the location to push this branch to."""
396
raise NotImplementedError('get_push_location is abstract')
398
def set_push_location(self, location):
399
"""Set a new push location for this branch."""
400
raise NotImplementedError('set_push_location is abstract')
402
def set_parent(self, url):
403
raise NotImplementedError('set_parent is abstract')
405
def check_revno(self, revno):
407
Check whether a revno corresponds to any revision.
408
Zero (the NULL revision) is considered valid.
411
self.check_real_revno(revno)
413
def check_real_revno(self, revno):
415
Check whether a revno corresponds to a real revision.
416
Zero (the NULL revision) is considered invalid
418
if revno < 1 or revno > self.revno():
419
raise InvalidRevisionNumber(revno)
421
def sign_revision(self, revision_id, gpg_strategy):
422
raise NotImplementedError('sign_revision is abstract')
424
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
425
raise NotImplementedError('store_revision_signature is abstract')
427
class BzrBranch(Branch):
168
428
"""A branch stored in the actual filesystem.
170
430
Note that it's "local" in the context of the filesystem; it doesn't
171
431
really matter if it's on an nfs/smb/afs/coda/... share, as long as
172
432
it's writable, and can be accessed via the normal filesystem API.
438
If _lock_mode is true, a positive count of the number of times the
442
Lock object from bzrlib.lock.
175
444
# We actually expect this class to be somewhat short-lived; part of its
176
445
# purpose is to try to isolate what bits of the branch logic are tied to
177
446
# filesystem access, so that in a later step, we can extricate them to
178
447
# a separarte ("storage") class.
179
451
_inventory_weave = None
181
453
# Map some sort of prefix into a namespace
347
639
self.storage.unlock()
348
640
self.control_files.unlock()
351
def set_root_id(self, file_id):
352
inv = self.working_tree().read_working_inventory()
353
orig_root_id = inv.root.file_id
354
del inv._byid[inv.root.file_id]
355
inv.root.file_id = file_id
356
inv._byid[inv.root.file_id] = inv.root
359
if entry.parent_id in (None, orig_root_id):
360
entry.parent_id = inv.root.file_id
361
self._write_inventory(inv)
364
def _write_inventory(self, inv):
365
"""Update the working inventory.
367
That is to say, the inventory describing changes underway, that
368
will be committed to the next revision.
370
from cStringIO import StringIO
372
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
374
# Transport handles atomicity
375
self.control_files.put_utf8('inventory', sio)
377
mutter('wrote working inventory')
380
def add(self, files, ids=None):
381
"""Make files versioned.
383
Note that the command line normally calls smart_add instead,
384
which can automatically recurse.
386
This puts the files in the Added state, so that they will be
387
recorded by the next commit.
390
List of paths to add, relative to the base of the tree.
393
If set, use these instead of automatically generated ids.
394
Must be the same length as the list of files, but may
395
contain None for ids that are to be autogenerated.
397
TODO: Perhaps have an option to add the ids even if the files do
400
TODO: Perhaps yield the ids and paths as they're added.
402
# TODO: Re-adding a file that is removed in the working copy
403
# should probably put it back with the previous ID.
404
if isinstance(files, basestring):
405
assert(ids is None or isinstance(ids, basestring))
411
ids = [None] * len(files)
413
assert(len(ids) == len(files))
415
inv = self.working_tree().read_working_inventory()
416
for f,file_id in zip(files, ids):
417
if is_control_file(f):
418
raise BzrError("cannot add control file %s" % quotefn(f))
423
raise BzrError("cannot add top-level %r" % f)
425
fullpath = os.path.normpath(self.abspath(f))
428
kind = file_kind(fullpath)
430
# maybe something better?
431
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
433
if not InventoryEntry.versionable_kind(kind):
434
raise BzrError('cannot add: not a versionable file ('
435
'i.e. regular file, symlink or directory): %s' % quotefn(f))
438
file_id = gen_file_id(f)
439
inv.add_path(f, kind=kind, file_id=file_id)
441
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
443
self._write_inventory(inv)
446
643
def print_file(self, file, revno):
447
"""Print `file` to stdout."""
644
"""See Branch.print_file."""
448
645
return self.storage.print_file(file, self.get_rev_id(revno))
451
"""Return all unknown files.
453
These are files in the working directory that are not versioned or
454
control files or ignored.
456
>>> from bzrlib.workingtree import WorkingTree
457
>>> b = ScratchBranch(files=['foo', 'foo~'])
458
>>> map(str, b.unknowns())
461
>>> list(b.unknowns())
463
>>> WorkingTree(b.base, b).remove('foo')
464
>>> list(b.unknowns())
467
return self.working_tree().unknowns()
469
647
@needs_write_lock
470
648
def append_revision(self, *revision_ids):
649
"""See Branch.append_revision."""
471
650
for revision_id in revision_ids:
472
651
mutter("add {%s} to revision-history" % revision_id)
473
652
rev_history = self.revision_history()
477
656
@needs_write_lock
478
657
def set_revision_history(self, rev_history):
658
"""See Branch.set_revision_history."""
479
659
self.control_files.put_utf8(
480
660
'revision-history', '\n'.join(rev_history))
482
def get_revision_delta(self, revno):
483
"""Return the delta for one revision.
485
The delta is relative to its mainline predecessor, or the
486
empty tree for revision 1.
488
assert isinstance(revno, int)
489
rh = self.revision_history()
490
if not (1 <= revno <= len(rh)):
491
raise InvalidRevisionNumber(revno)
493
# revno is 1-based; list is 0-based
495
new_tree = self.storage.revision_tree(rh[revno-1])
497
old_tree = EmptyTree()
499
old_tree = self.storage.revision_tree(rh[revno-2])
501
return compare_trees(old_tree, new_tree)
503
662
def get_ancestry(self, revision_id):
504
"""Return a list of revision-ids integrated by a revision.
506
This currently returns a list, but the ordering is not guaranteed:
663
"""See Branch.get_ancestry."""
509
664
if revision_id is None:
511
666
w = self.storage.get_inventory_weave()
512
667
return [None] + map(w.idx_to_name,
513
668
w.inclusions([w.lookup(revision_id)]))
670
def _get_inventory_weave(self):
671
return self.storage.control_weaves.get_weave('inventory',
672
self.get_transaction())
674
def get_inventory(self, revision_id):
675
"""See Branch.get_inventory."""
676
xml = self.get_inventory_xml(revision_id)
677
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
679
def get_inventory_xml(self, revision_id):
680
"""See Branch.get_inventory_xml."""
682
assert isinstance(revision_id, basestring), type(revision_id)
683
iw = self._get_inventory_weave()
684
return iw.get_text(iw.lookup(revision_id))
686
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
688
def get_inventory_sha1(self, revision_id):
689
"""See Branch.get_inventory_sha1."""
690
return self.get_revision(revision_id).inventory_sha1
692
def get_revision_inventory(self, revision_id):
693
"""See Branch.get_revision_inventory."""
694
# TODO: Unify this with get_inventory()
695
# bzr 0.0.6 and later imposes the constraint that the inventory_id
696
# must be the same as its revision, so this is trivial.
697
if revision_id == None:
698
# This does not make sense: if there is no revision,
699
# then it is the current tree inventory surely ?!
700
# and thus get_root_id() is something that looks at the last
701
# commit on the branch, and the get_root_id is an inventory check.
702
raise NotImplementedError
703
# return Inventory(self.get_root_id())
705
return self.get_inventory(revision_id)
516
708
def revision_history(self):
517
"""Return sequence of revision hashes on to this branch."""
709
"""See Branch.revision_history."""
518
710
# FIXME are transactions bound to control files ? RBC 20051121
519
711
transaction = self.get_transaction()
520
712
history = transaction.map.find_revision_history()
529
721
# transaction.register_clean(history, precious=True)
530
722
return list(history)
533
"""Return current revision number for this branch.
535
That is equivalent to the number of revisions committed to
538
return len(self.revision_history())
540
def last_revision(self):
541
"""Return last patch hash, or None if no history.
543
ph = self.revision_history()
549
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
550
"""Return a list of new revisions that would perfectly fit.
552
If self and other have not diverged, return a list of the revisions
553
present in other, but missing from self.
555
>>> from bzrlib.commit import commit
556
>>> bzrlib.trace.silent = True
557
>>> br1 = ScratchBranch()
558
>>> br2 = ScratchBranch()
559
>>> br1.missing_revisions(br2)
561
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
562
>>> br1.missing_revisions(br2)
564
>>> br2.missing_revisions(br1)
566
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
567
>>> br1.missing_revisions(br2)
569
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
570
>>> br1.missing_revisions(br2)
572
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
573
>>> br1.missing_revisions(br2)
574
Traceback (most recent call last):
575
DivergedBranches: These branches have diverged.
577
self_history = self.revision_history()
578
self_len = len(self_history)
579
other_history = other.revision_history()
580
other_len = len(other_history)
581
common_index = min(self_len, other_len) -1
582
if common_index >= 0 and \
583
self_history[common_index] != other_history[common_index]:
584
raise DivergedBranches(self, other)
586
if stop_revision is None:
587
stop_revision = other_len
589
assert isinstance(stop_revision, int)
590
if stop_revision > other_len:
591
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
592
return other_history[self_len:stop_revision]
594
724
def update_revisions(self, other, stop_revision=None):
595
"""Pull in new perfect-fit revisions."""
725
"""See Branch.update_revisions."""
596
726
from bzrlib.fetch import greedy_fetch
597
727
if stop_revision is None:
598
728
stop_revision = other.last_revision()
626
def commit(self, *args, **kw):
627
from bzrlib.commit import Commit
628
Commit().commit(self, *args, **kw)
630
def revision_id_to_revno(self, revision_id):
631
"""Given a revision id, return its revno"""
632
if revision_id is None:
634
history = self.revision_history()
636
return history.index(revision_id) + 1
638
raise bzrlib.errors.NoSuchRevision(self, revision_id)
640
def get_rev_id(self, revno, history=None):
641
"""Find the revision id of the specified revno."""
645
history = self.revision_history()
646
elif revno <= 0 or revno > len(history):
647
raise bzrlib.errors.NoSuchRevision(self, revno)
648
return history[revno - 1]
650
757
def working_tree(self):
651
"""Return a `Tree` for the working copy."""
758
"""See Branch.working_tree."""
652
759
from bzrlib.workingtree import WorkingTree
653
# TODO: In the future, perhaps WorkingTree should utilize Transport
654
# RobertCollins 20051003 - I don't think it should - working trees are
655
# much more complex to keep consistent than our careful .bzr subset.
656
# instead, we should say that working trees are local only, and optimise
658
760
if self.base.find('://') != -1:
659
761
raise NoWorkingTree(self.base)
660
762
return WorkingTree(self.base, branch=self)
662
764
@needs_write_lock
663
765
def pull(self, source, overwrite=False):
766
"""See Branch.pull."""
664
767
source.lock_read()
769
old_count = len(self.revision_history())
667
771
self.update_revisions(source)
668
772
except DivergedBranches:
669
773
if not overwrite:
671
775
self.set_revision_history(source.revision_history())
776
new_count = len(self.revision_history())
777
return new_count - old_count
675
def basis_tree(self):
676
"""Return `Tree` object for last revision.
678
If there are no revisions yet, return an `EmptyTree`.
680
return self.storage.revision_tree(self.last_revision())
683
def rename_one(self, from_rel, to_rel):
686
This can change the directory or the filename or both.
688
tree = self.working_tree()
690
if not tree.has_filename(from_rel):
691
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
692
if tree.has_filename(to_rel):
693
raise BzrError("can't rename: new working file %r already exists" % to_rel)
695
file_id = inv.path2id(from_rel)
697
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
699
if inv.path2id(to_rel):
700
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
702
to_dir, to_tail = os.path.split(to_rel)
703
to_dir_id = inv.path2id(to_dir)
704
if to_dir_id == None and to_dir != '':
705
raise BzrError("can't determine destination directory id for %r" % to_dir)
707
mutter("rename_one:")
708
mutter(" file_id {%s}" % file_id)
709
mutter(" from_rel %r" % from_rel)
710
mutter(" to_rel %r" % to_rel)
711
mutter(" to_dir %r" % to_dir)
712
mutter(" to_dir_id {%s}" % to_dir_id)
714
inv.rename(file_id, to_dir_id, to_tail)
716
from_abs = self.abspath(from_rel)
717
to_abs = self.abspath(to_rel)
719
rename(from_abs, to_abs)
721
raise BzrError("failed to rename %r to %r: %s"
722
% (from_abs, to_abs, e[1]),
723
["rename rolled back"])
725
self._write_inventory(inv)
728
def move(self, from_paths, to_name):
731
to_name must exist as a versioned directory.
733
If to_name exists and is a directory, the files are moved into
734
it, keeping their old names. If it is a directory,
736
Note that to_name is only the last component of the new name;
737
this doesn't change the directory.
739
This returns a list of (from_path, to_path) pairs for each
743
## TODO: Option to move IDs only
744
assert not isinstance(from_paths, basestring)
745
tree = self.working_tree()
747
to_abs = self.abspath(to_name)
748
if not isdir(to_abs):
749
raise BzrError("destination %r is not a directory" % to_abs)
750
if not tree.has_filename(to_name):
751
raise BzrError("destination %r not in working directory" % to_abs)
752
to_dir_id = inv.path2id(to_name)
753
if to_dir_id == None and to_name != '':
754
raise BzrError("destination %r is not a versioned directory" % to_name)
755
to_dir_ie = inv[to_dir_id]
756
if to_dir_ie.kind not in ('directory', 'root_directory'):
757
raise BzrError("destination %r is not a directory" % to_abs)
759
to_idpath = inv.get_idpath(to_dir_id)
762
if not tree.has_filename(f):
763
raise BzrError("%r does not exist in working tree" % f)
764
f_id = inv.path2id(f)
766
raise BzrError("%r is not versioned" % f)
767
name_tail = splitpath(f)[-1]
768
dest_path = appendpath(to_name, name_tail)
769
if tree.has_filename(dest_path):
770
raise BzrError("destination %r already exists" % dest_path)
771
if f_id in to_idpath:
772
raise BzrError("can't move %r to a subdirectory of itself" % f)
774
# OK, so there's a race here, it's possible that someone will
775
# create a file in this interval and then the rename might be
776
# left half-done. But we should have caught most problems.
779
name_tail = splitpath(f)[-1]
780
dest_path = appendpath(to_name, name_tail)
781
result.append((f, dest_path))
782
inv.rename(inv.path2id(f), to_dir_id, name_tail)
784
rename(self.abspath(f), self.abspath(dest_path))
786
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
787
["rename rolled back"])
789
self._write_inventory(inv)
793
def revert(self, filenames, old_tree=None, backups=True):
794
"""Restore selected files to the versions from a previous tree.
797
If true (default) backups are made of files before
800
from bzrlib.atomicfile import AtomicFile
801
from bzrlib.osutils import backup_file
803
inv = self.working_tree().read_working_inventory()
805
old_tree = self.basis_tree()
806
old_inv = old_tree.inventory
810
file_id = inv.path2id(fn)
812
raise NotVersionedError(path=fn)
813
if not old_inv.has_id(file_id):
814
raise BzrError("file not present in old tree", fn, file_id)
815
nids.append((fn, file_id))
817
# TODO: Rename back if it was previously at a different location
819
# TODO: If given a directory, restore the entire contents from
820
# the previous version.
822
# TODO: Make a backup to a temporary file.
824
# TODO: If the file previously didn't exist, delete it?
825
for fn, file_id in nids:
828
f = AtomicFile(fn, 'wb')
830
f.write(old_tree.get_file(file_id).read())
836
def pending_merges(self):
837
"""Return a list of pending merges.
839
These are revisions that have been merged into the working
840
directory but not yet committed.
842
cfn = self.control_files._rel_controlfilename('pending-merges')
843
if not self.control_files._transport.has(cfn):
846
for l in self.control_files.controlfile(
847
'pending-merges', 'r').readlines():
848
p.append(l.rstrip('\n'))
852
def add_pending_merge(self, *revision_ids):
853
# TODO: Perhaps should check at this point that the
854
# history of the revision is actually present?
855
p = self.pending_merges()
857
for rev_id in revision_ids:
863
self.set_pending_merges(p)
866
def set_pending_merges(self, rev_list):
867
self.control_files.put_utf8(
868
'pending-merges', '\n'.join(rev_list))
870
781
def get_parent(self):
871
"""Return the parent location of the branch.
873
This is the default location for push/pull/missing. The usual
874
pattern is that the user can override it by specifying a
782
"""See Branch.get_parent."""
878
784
_locs = ['parent', 'pull', 'x-pull']
909
816
def tree_config(self):
910
817
return TreeConfig(self)
912
def check_revno(self, revno):
914
Check whether a revno corresponds to any revision.
915
Zero (the NULL revision) is considered valid.
918
self.check_real_revno(revno)
920
def check_real_revno(self, revno):
922
Check whether a revno corresponds to a real revision.
923
Zero (the NULL revision) is considered invalid
925
if revno < 1 or revno > self.revno():
926
raise InvalidRevisionNumber(revno)
928
def _finish_transaction(self):
929
"""Exit the current transaction."""
930
return self.control_files._finish_transaction()
932
def get_transaction(self):
933
"""Return the current active transaction.
935
If no transaction is active, this returns a passthrough object
936
for which all data is immediately flushed and no caching happens.
938
# this is an explicit function so that we can do tricky stuff
939
# when the storage in rev_storage is elsewhere.
940
# we probably need to hook the two 'lock a location' and
941
# 'have a transaction' together more delicately, so that
942
# we can have two locks (branch and storage) and one transaction
943
# ... and finishing the transaction unlocks both, but unlocking
944
# does not. - RBC 20051121
945
return self.control_files.get_transaction()
947
def _set_transaction(self, transaction):
948
"""Set a new active transaction."""
949
return self.control_files._set_transaction(transaction)
951
class ScratchBranch(_Branch):
819
def sign_revision(self, revision_id, gpg_strategy):
820
"""See Branch.sign_revision."""
821
plaintext = Testament.from_revision(self, revision_id).as_short_text()
822
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
825
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
826
"""See Branch.store_revision_signature."""
827
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
831
class ScratchBranch(BzrBranch):
952
832
"""Special test class: a branch that cleans up after itself.
954
834
>>> b = ScratchBranch()