1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from copy import deepcopy
19
from cStringIO import StringIO
24
from unittest import TestSuite
25
from warnings import warn
26
import xml.sax.saxutils
30
from bzrlib.config import TreeConfig
31
from bzrlib.delta import compare_trees
32
import bzrlib.errors as errors
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError,
36
UninitializableFormat,
38
UnlistableBranch, NoSuchFile, NotVersionedError,
40
import bzrlib.inventory as inventory
41
from bzrlib.inventory import Inventory
42
from bzrlib.osutils import (isdir, quotefn,
43
rename, splitpath, sha_file,
44
file_kind, abspath, normpath, pathjoin,
47
from bzrlib.textui import show_status
48
from bzrlib.trace import mutter, note
49
from bzrlib.tree import EmptyTree, RevisionTree
50
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
52
from bzrlib.store import copy_all
53
from bzrlib.store.text import TextStore
54
from bzrlib.store.weave import WeaveStore
55
from bzrlib.symbol_versioning import *
56
from bzrlib.testament import Testament
57
import bzrlib.transactions as transactions
58
from bzrlib.transport import Transport, get_transport
63
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
64
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
65
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
66
## TODO: Maybe include checks for common corruption of newlines, etc?
69
# TODO: Some operations like log might retrieve the same revisions
70
# repeatedly to calculate deltas. We could perhaps have a weakref
71
# cache in memory to make this faster. In general anything can be
72
# cached in memory between lock and unlock operations.
74
def find_branch(*ignored, **ignored_too):
75
# XXX: leave this here for about one release, then remove it
76
raise NotImplementedError('find_branch() is not supported anymore, '
77
'please use one of the new branch constructors')
80
def needs_read_lock(unbound):
81
"""Decorate unbound to take out and release a read lock."""
82
def decorated(self, *args, **kwargs):
85
return unbound(self, *args, **kwargs)
91
def needs_write_lock(unbound):
92
"""Decorate unbound to take out and release a write lock."""
93
def decorated(self, *args, **kwargs):
96
return unbound(self, *args, **kwargs)
101
######################################################################
104
class Branch(object):
105
"""Branch holding a history of revisions.
108
Base directory/url of the branch.
110
# this is really an instance variable - FIXME move it there
114
_default_initializer = None
115
"""The default initializer for making new branches."""
117
def __init__(self, *ignored, **ignored_too):
118
raise NotImplementedError('The Branch class is abstract')
121
def open_downlevel(base):
122
"""Open a branch which may be of an old format."""
123
return Branch.open(base, _unsupported=True)
126
def open(base, _unsupported=False):
127
"""Open an existing branch, rooted at 'base' (url)
129
_unsupported is a private parameter to the Branch class.
131
t = get_transport(base)
132
mutter("trying to open %r with transport %r", base, t)
133
format = BzrBranchFormat.find_format(t)
134
if not _unsupported and not format.is_supported():
135
# see open_downlevel to open legacy branches.
136
raise errors.UnsupportedFormatError(
137
'sorry, branch format %s not supported' % format,
138
['use a different bzr version',
139
'or remove the .bzr directory'
140
' and "bzr init" again'])
141
return format.open(t)
144
def open_containing(url):
145
"""Open an existing branch which contains url.
147
This probes for a branch at url, and searches upwards from there.
149
Basically we keep looking up until we find the control directory or
150
run into the root. If there isn't one, raises NotBranchError.
151
If there is one and it is either an unrecognised format or an unsupported
152
format, UnknownFormatError or UnsupportedFormatError are raised.
153
If there is one, it is returned, along with the unused portion of url.
155
t = get_transport(url)
156
# this gets the normalised url back. I.e. '.' -> the full path.
160
format = BzrBranchFormat.find_format(t)
161
return format.open(t), t.relpath(url)
162
except NotBranchError, e:
163
mutter('not a branch in: %r %s', t.base, e)
164
new_t = t.clone('..')
165
if new_t.base == t.base:
166
# reached the root, whatever that may be
167
raise NotBranchError(path=url)
172
"""Create a new Branch at the url 'bzr'.
174
This will call the current default initializer with base
175
as the only parameter.
177
return Branch._default_initializer(safe_unicode(base))
180
@deprecated_function(zero_eight)
181
def initialize(base):
182
"""Create a new working tree and branch, rooted at 'base' (url)
184
# imported here to prevent scope creep as this is going.
185
from bzrlib.workingtree import WorkingTree
186
return WorkingTree.create_standalone(safe_unicode(base)).branch
189
def get_default_initializer():
190
"""Return the initializer being used for new branches."""
191
return Branch._default_initializer
194
def set_default_initializer(initializer):
195
"""Set the initializer to be used for new branches."""
196
Branch._default_initializer = staticmethod(initializer)
198
def setup_caching(self, cache_root):
199
"""Subclasses that care about caching should override this, and set
200
up cached stores located under cache_root.
202
self.cache_root = cache_root
205
cfg = self.tree_config()
206
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
208
def _set_nick(self, nick):
209
cfg = self.tree_config()
210
cfg.set_option(nick, "nickname")
211
assert cfg.get_option("nickname") == nick
213
nick = property(_get_nick, _set_nick)
215
def push_stores(self, branch_to):
216
"""Copy the content of this branches store to branch_to."""
217
raise NotImplementedError('push_stores is abstract')
219
def get_transaction(self):
220
"""Return the current active transaction.
222
If no transaction is active, this returns a passthrough object
223
for which all data is immediately flushed and no caching happens.
225
raise NotImplementedError('get_transaction is abstract')
227
def lock_write(self):
228
raise NotImplementedError('lock_write is abstract')
231
raise NotImplementedError('lock_read is abstract')
234
raise NotImplementedError('unlock is abstract')
236
def abspath(self, name):
237
"""Return absolute filename for something in the branch
239
XXX: Robert Collins 20051017 what is this used for? why is it a branch
240
method and not a tree method.
242
raise NotImplementedError('abspath is abstract')
244
def controlfilename(self, file_or_path):
245
"""Return location relative to branch."""
246
raise NotImplementedError('controlfilename is abstract')
248
def controlfile(self, file_or_path, mode='r'):
249
"""Open a control file for this branch.
251
There are two classes of file in the control directory: text
252
and binary. binary files are untranslated byte streams. Text
253
control files are stored with Unix newlines and in UTF-8, even
254
if the platform or locale defaults are different.
256
Controlfiles should almost never be opened in write mode but
257
rather should be atomically copied and replaced using atomicfile.
259
raise NotImplementedError('controlfile is abstract')
261
def put_controlfile(self, path, f, encode=True):
262
"""Write an entry as a controlfile.
264
:param path: The path to put the file, relative to the .bzr control
266
:param f: A file-like or string object whose contents should be copied.
267
:param encode: If true, encode the contents as utf-8
269
raise NotImplementedError('put_controlfile is abstract')
271
def put_controlfiles(self, files, encode=True):
272
"""Write several entries as controlfiles.
274
:param files: A list of [(path, file)] pairs, where the path is the directory
275
underneath the bzr control directory
276
:param encode: If true, encode the contents as utf-8
278
raise NotImplementedError('put_controlfiles is abstract')
280
def get_root_id(self):
281
"""Return the id of this branches root"""
282
raise NotImplementedError('get_root_id is abstract')
284
def set_root_id(self, file_id):
285
raise NotImplementedError('set_root_id is abstract')
287
def print_file(self, file, revision_id):
288
"""Print `file` to stdout."""
289
raise NotImplementedError('print_file is abstract')
291
def append_revision(self, *revision_ids):
292
raise NotImplementedError('append_revision is abstract')
294
def set_revision_history(self, rev_history):
295
raise NotImplementedError('set_revision_history is abstract')
297
def has_revision(self, revision_id):
298
"""True if this branch has a copy of the revision.
300
This does not necessarily imply the revision is merge
301
or on the mainline."""
302
raise NotImplementedError('has_revision is abstract')
304
def get_revision_xml(self, revision_id):
305
raise NotImplementedError('get_revision_xml is abstract')
307
def get_revision(self, revision_id):
308
"""Return the Revision object for a named revision"""
309
raise NotImplementedError('get_revision is abstract')
311
def get_revision_delta(self, revno):
312
"""Return the delta for one revision.
314
The delta is relative to its mainline predecessor, or the
315
empty tree for revision 1.
317
assert isinstance(revno, int)
318
rh = self.revision_history()
319
if not (1 <= revno <= len(rh)):
320
raise InvalidRevisionNumber(revno)
322
# revno is 1-based; list is 0-based
324
new_tree = self.revision_tree(rh[revno-1])
326
old_tree = EmptyTree()
328
old_tree = self.revision_tree(rh[revno-2])
330
return compare_trees(old_tree, new_tree)
332
def get_revision_sha1(self, revision_id):
333
"""Hash the stored value of a revision, and return it."""
334
raise NotImplementedError('get_revision_sha1 is abstract')
336
def get_ancestry(self, revision_id):
337
"""Return a list of revision-ids integrated by a revision.
339
This currently returns a list, but the ordering is not guaranteed:
342
raise NotImplementedError('get_ancestry is abstract')
344
def get_inventory(self, revision_id):
345
"""Get Inventory object by hash."""
346
raise NotImplementedError('get_inventory is abstract')
348
def get_inventory_xml(self, revision_id):
349
"""Get inventory XML as a file object."""
350
raise NotImplementedError('get_inventory_xml is abstract')
352
def get_inventory_sha1(self, revision_id):
353
"""Return the sha1 hash of the inventory entry."""
354
raise NotImplementedError('get_inventory_sha1 is abstract')
356
def get_revision_inventory(self, revision_id):
357
"""Return inventory of a past revision."""
358
raise NotImplementedError('get_revision_inventory is abstract')
360
def revision_history(self):
361
"""Return sequence of revision hashes on to this branch."""
362
raise NotImplementedError('revision_history is abstract')
365
"""Return current revision number for this branch.
367
That is equivalent to the number of revisions committed to
370
return len(self.revision_history())
372
def last_revision(self):
373
"""Return last patch hash, or None if no history."""
374
ph = self.revision_history()
380
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
381
"""Return a list of new revisions that would perfectly fit.
383
If self and other have not diverged, return a list of the revisions
384
present in other, but missing from self.
386
>>> bzrlib.trace.silent = True
387
>>> br1 = ScratchBranch()
388
>>> br2 = ScratchBranch()
389
>>> br1.missing_revisions(br2)
391
>>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
392
>>> br1.missing_revisions(br2)
394
>>> br2.missing_revisions(br1)
396
>>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
397
>>> br1.missing_revisions(br2)
399
>>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
400
>>> br1.missing_revisions(br2)
402
>>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
403
>>> br1.missing_revisions(br2)
404
Traceback (most recent call last):
405
DivergedBranches: These branches have diverged. Try merge.
407
self_history = self.revision_history()
408
self_len = len(self_history)
409
other_history = other.revision_history()
410
other_len = len(other_history)
411
common_index = min(self_len, other_len) -1
412
if common_index >= 0 and \
413
self_history[common_index] != other_history[common_index]:
414
raise DivergedBranches(self, other)
416
if stop_revision is None:
417
stop_revision = other_len
419
assert isinstance(stop_revision, int)
420
if stop_revision > other_len:
421
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
422
return other_history[self_len:stop_revision]
424
def update_revisions(self, other, stop_revision=None):
425
"""Pull in new perfect-fit revisions."""
426
raise NotImplementedError('update_revisions is abstract')
428
def pullable_revisions(self, other, stop_revision):
429
raise NotImplementedError('pullable_revisions is abstract')
431
def revision_id_to_revno(self, revision_id):
432
"""Given a revision id, return its revno"""
433
if revision_id is None:
435
history = self.revision_history()
437
return history.index(revision_id) + 1
439
raise bzrlib.errors.NoSuchRevision(self, revision_id)
441
def get_rev_id(self, revno, history=None):
442
"""Find the revision id of the specified revno."""
446
history = self.revision_history()
447
elif revno <= 0 or revno > len(history):
448
raise bzrlib.errors.NoSuchRevision(self, revno)
449
return history[revno - 1]
451
def revision_tree(self, revision_id):
452
"""Return Tree for a revision on this branch.
454
`revision_id` may be None for the null revision, in which case
455
an `EmptyTree` is returned."""
456
raise NotImplementedError('revision_tree is abstract')
458
def working_tree(self):
459
"""Return a `Tree` for the working copy if this is a local branch."""
460
raise NotImplementedError('working_tree is abstract')
462
def pull(self, source, overwrite=False):
463
raise NotImplementedError('pull is abstract')
465
def basis_tree(self):
466
"""Return `Tree` object for last revision.
468
If there are no revisions yet, return an `EmptyTree`.
470
return self.revision_tree(self.last_revision())
472
def rename_one(self, from_rel, to_rel):
475
This can change the directory or the filename or both.
477
raise NotImplementedError('rename_one is abstract')
479
def move(self, from_paths, to_name):
482
to_name must exist as a versioned directory.
484
If to_name exists and is a directory, the files are moved into
485
it, keeping their old names. If it is a directory,
487
Note that to_name is only the last component of the new name;
488
this doesn't change the directory.
490
This returns a list of (from_path, to_path) pairs for each
493
raise NotImplementedError('move is abstract')
495
def get_parent(self):
496
"""Return the parent location of the branch.
498
This is the default location for push/pull/missing. The usual
499
pattern is that the user can override it by specifying a
502
raise NotImplementedError('get_parent is abstract')
504
def get_push_location(self):
505
"""Return the None or the location to push this branch to."""
506
raise NotImplementedError('get_push_location is abstract')
508
def set_push_location(self, location):
509
"""Set a new push location for this branch."""
510
raise NotImplementedError('set_push_location is abstract')
512
def set_parent(self, url):
513
raise NotImplementedError('set_parent is abstract')
515
def check_revno(self, revno):
517
Check whether a revno corresponds to any revision.
518
Zero (the NULL revision) is considered valid.
521
self.check_real_revno(revno)
523
def check_real_revno(self, revno):
525
Check whether a revno corresponds to a real revision.
526
Zero (the NULL revision) is considered invalid
528
if revno < 1 or revno > self.revno():
529
raise InvalidRevisionNumber(revno)
531
def sign_revision(self, revision_id, gpg_strategy):
532
raise NotImplementedError('sign_revision is abstract')
534
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
535
raise NotImplementedError('store_revision_signature is abstract')
537
def fileid_involved_between_revs(self, from_revid, to_revid):
538
""" This function returns the file_id(s) involved in the
539
changes between the from_revid revision and the to_revid
542
raise NotImplementedError('fileid_involved_between_revs is abstract')
544
def fileid_involved(self, last_revid=None):
545
""" This function returns the file_id(s) involved in the
546
changes up to the revision last_revid
547
If no parametr is passed, then all file_id[s] present in the
548
repository are returned
550
raise NotImplementedError('fileid_involved is abstract')
552
def fileid_involved_by_set(self, changes):
553
""" This function returns the file_id(s) involved in the
554
changes present in the set 'changes'
556
raise NotImplementedError('fileid_involved_by_set is abstract')
558
def fileid_involved_between_revs(self, from_revid, to_revid):
559
""" This function returns the file_id(s) involved in the
560
changes between the from_revid revision and the to_revid
563
raise NotImplementedError('fileid_involved_between_revs is abstract')
565
def fileid_involved(self, last_revid=None):
566
""" This function returns the file_id(s) involved in the
567
changes up to the revision last_revid
568
If no parametr is passed, then all file_id[s] present in the
569
repository are returned
571
raise NotImplementedError('fileid_involved is abstract')
573
def fileid_involved_by_set(self, changes):
574
""" This function returns the file_id(s) involved in the
575
changes present in the set 'changes'
577
raise NotImplementedError('fileid_involved_by_set is abstract')
579
class BzrBranchFormat(object):
580
"""An encapsulation of the initialization and open routines for a format.
582
Formats provide three things:
583
* An initialization routine,
587
Formats are placed in an dict by their format string for reference
588
during branch opening. Its not required that these be instances, they
589
can be classes themselves with class methods - it simply depends on
590
whether state is needed for a given format or not.
592
Once a format is deprecated, just deprecate the initialize and open
593
methods on the format class. Do not deprecate the object, as the
594
object will be created every time regardless.
598
"""The known formats."""
601
def find_format(klass, transport):
602
"""Return the format registered for URL."""
604
format_string = transport.get(".bzr/branch-format").read()
605
return klass._formats[format_string]
607
raise NotBranchError(path=transport.base)
609
raise errors.UnknownFormatError(format_string)
611
def get_format_string(self):
612
"""Return the ASCII format string that identifies this format."""
613
raise NotImplementedError(self.get_format_string)
615
def _find_modes(self, t):
616
"""Determine the appropriate modes for files and directories.
618
FIXME: When this merges into, or from storage,
619
this code becomes delgatable to a LockableFiles instance.
621
For now its cribbed and returns (dir_mode, file_mode)
625
except errors.TransportNotPossible:
629
dir_mode = st.st_mode & 07777
630
# Remove the sticky and execute bits for files
631
file_mode = dir_mode & ~07111
632
if not BzrBranch._set_dir_mode:
634
if not BzrBranch._set_file_mode:
636
return dir_mode, file_mode
638
def initialize(self, url):
639
"""Create a branch of this format at url and return an open branch."""
640
t = get_transport(url)
641
from bzrlib.weavefile import write_weave_v5
642
from bzrlib.weave import Weave
644
# Create an empty weave
646
bzrlib.weavefile.write_weave_v5(Weave(), sio)
647
empty_weave = sio.getvalue()
649
# Since we don't have a .bzr directory, inherit the
650
# mode from the root directory
651
dir_mode, file_mode = self._find_modes(t)
653
t.mkdir('.bzr', mode=dir_mode)
654
control = t.clone('.bzr')
655
dirs = ['revision-store', 'weaves']
657
StringIO("This is a Bazaar-NG control directory.\n"
658
"Do not change any files in this directory.\n")),
659
('branch-format', StringIO(self.get_format_string())),
660
('revision-history', StringIO('')),
661
('branch-name', StringIO('')),
662
('branch-lock', StringIO('')),
663
('inventory.weave', StringIO(empty_weave)),
665
control.mkdir_multi(dirs, mode=dir_mode)
666
control.put_multi(files, mode=file_mode)
667
mutter('created control directory in ' + t.base)
668
return BzrBranch(t, format=self)
670
def is_supported(self):
671
"""Is this format supported?
673
Supported formats can be initialized and opened.
674
Unsupported formats may not support initialization or committing or
675
some other features depending on the reason for not being supported.
679
def open(self, transport):
680
"""Fill out the data in branch for the branch at url."""
681
return BzrBranch(transport, format=self)
684
def register_format(klass, format):
685
klass._formats[format.get_format_string()] = format
688
def unregister_format(klass, format):
689
assert klass._formats[format.get_format_string()] is format
690
del klass._formats[format.get_format_string()]
693
class BzrBranchFormat4(BzrBranchFormat):
694
"""Bzr branch format 4.
698
- TextStores for texts, inventories,revisions.
700
This format is deprecated: it indexes texts using a text it which is
701
removed in format 5; write support for this format has been removed.
704
def get_format_string(self):
705
"""See BzrBranchFormat.get_format_string()."""
706
return BZR_BRANCH_FORMAT_4
708
def initialize(self, url):
709
"""Format 4 branches cannot be created."""
710
raise UninitializableFormat(self)
712
def is_supported(self):
713
"""Format 4 is not supported.
715
It is not supported because the model changed from 4 to 5 and the
716
conversion logic is expensive - so doing it on the fly was not
722
class BzrBranchFormat5(BzrBranchFormat):
723
"""Bzr branch format 5.
726
- weaves for file texts and inventory
728
- TextStores for revisions and signatures.
731
def get_format_string(self):
732
"""See BzrBranchFormat.get_format_string()."""
733
return BZR_BRANCH_FORMAT_5
736
class BzrBranchFormat6(BzrBranchFormat):
737
"""Bzr branch format 6.
740
- weaves for file texts and inventory
741
- hash subdirectory based stores.
742
- TextStores for revisions and signatures.
745
def get_format_string(self):
746
"""See BzrBranchFormat.get_format_string()."""
747
return BZR_BRANCH_FORMAT_6
750
BzrBranchFormat.register_format(BzrBranchFormat4())
751
BzrBranchFormat.register_format(BzrBranchFormat5())
752
BzrBranchFormat.register_format(BzrBranchFormat6())
754
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
755
# make sure that ancestry.weave is deleted (it is never used, but
756
# used to be created)
759
class BzrBranch(Branch):
760
"""A branch stored in the actual filesystem.
762
Note that it's "local" in the context of the filesystem; it doesn't
763
really matter if it's on an nfs/smb/afs/coda/... share, as long as
764
it's writable, and can be accessed via the normal filesystem API.
770
If _lock_mode is true, a positive count of the number of times the
774
Lock object from bzrlib.lock.
776
# We actually expect this class to be somewhat short-lived; part of its
777
# purpose is to try to isolate what bits of the branch logic are tied to
778
# filesystem access, so that in a later step, we can extricate them to
779
# a separarte ("storage") class.
783
_inventory_weave = None
784
# If set to False (by a plugin, etc) BzrBranch will not set the
785
# mode on created files or directories
786
_set_file_mode = True
789
# Map some sort of prefix into a namespace
790
# stuff like "revno:10", "revid:", etc.
791
# This should match a prefix with a function which accepts
792
REVISION_NAMESPACES = {}
794
def push_stores(self, branch_to):
795
"""See Branch.push_stores."""
796
if (not isinstance(self._branch_format, BzrBranchFormat4) or
797
self._branch_format != branch_to._branch_format):
798
from bzrlib.fetch import greedy_fetch
799
mutter("Using fetch logic to push between %s(%s) and %s(%s)",
800
self, self._branch_format, branch_to, branch_to._branch_format)
801
greedy_fetch(to_branch=branch_to, from_branch=self,
802
revision=self.last_revision())
805
# format 4 to format 4 logic only.
806
store_pairs = ((self.text_store, branch_to.text_store),
807
(self.inventory_store, branch_to.inventory_store),
808
(self.revision_store, branch_to.revision_store))
810
for from_store, to_store in store_pairs:
811
copy_all(from_store, to_store)
812
except UnlistableStore:
813
raise UnlistableBranch(from_store)
815
def __init__(self, transport, init=deprecated_nonce,
816
relax_version_check=deprecated_nonce, format=None):
817
"""Create new branch object at a particular location.
819
transport -- A Transport object, defining how to access files.
821
init -- If True, create new control files in a previously
822
unversioned directory. If False, the branch must already
825
relax_version_check -- If true, the usual check for the branch
826
version is not applied. This is intended only for
827
upgrade/recovery type use; it's not guaranteed that
828
all operations will work on old format branches.
830
In the test suite, creation of new trees is tested using the
831
`ScratchBranch` class.
833
assert isinstance(transport, Transport), \
834
"%r is not a Transport" % transport
835
self._transport = transport
836
if deprecated_passed(init):
837
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
838
"deprecated as of bzr 0.8. Please use Branch.create().",
842
# this is slower than before deprecation, oh well never mind.
844
self._initialize(transport.base)
846
self._check_format(format)
847
if deprecated_passed(relax_version_check):
848
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
849
"relax_version_check parameter is deprecated as of bzr 0.8. "
850
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
854
if (not relax_version_check
855
and not self._branch_format.is_supported()):
856
raise errors.UnsupportedFormatError(
857
'sorry, branch format %r not supported' % fmt,
858
['use a different bzr version',
859
'or remove the .bzr directory'
860
' and "bzr init" again'])
862
def get_store(name, compressed=True, prefixed=False):
863
relpath = self._rel_controlfilename(safe_unicode(name))
864
store = TextStore(self._transport.clone(relpath),
865
dir_mode=self._dir_mode,
866
file_mode=self._file_mode,
868
compressed=compressed)
871
def get_weave(name, prefixed=False):
872
relpath = self._rel_controlfilename(unicode(name))
873
ws = WeaveStore(self._transport.clone(relpath),
875
dir_mode=self._dir_mode,
876
file_mode=self._file_mode)
877
if self._transport.should_cache():
878
ws.enable_cache = True
881
if isinstance(self._branch_format, BzrBranchFormat4):
882
self.inventory_store = get_store('inventory-store')
883
self.text_store = get_store('text-store')
884
self.revision_store = get_store('revision-store')
885
elif isinstance(self._branch_format, BzrBranchFormat5):
886
self.control_weaves = get_weave(u'')
887
self.weave_store = get_weave(u'weaves')
888
self.revision_store = get_store(u'revision-store', compressed=False)
889
elif isinstance(self._branch_format, BzrBranchFormat6):
890
self.control_weaves = get_weave(u'')
891
self.weave_store = get_weave(u'weaves', prefixed=True)
892
self.revision_store = get_store(u'revision-store', compressed=False,
894
self.revision_store.register_suffix('sig')
895
self._transaction = None
898
def _initialize(base):
899
"""Create a bzr branch in the latest format."""
900
return BzrBranchFormat6().initialize(base)
903
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
908
if self._lock_mode or self._lock:
909
# XXX: This should show something every time, and be suitable for
910
# headless operation and embedding
911
warn("branch %r was not explicitly unlocked" % self)
914
# TODO: It might be best to do this somewhere else,
915
# but it is nice for a Branch object to automatically
916
# cache it's information.
917
# Alternatively, we could have the Transport objects cache requests
918
# See the earlier discussion about how major objects (like Branch)
919
# should never expect their __del__ function to run.
920
if hasattr(self, 'cache_root') and self.cache_root is not None:
922
shutil.rmtree(self.cache_root)
925
self.cache_root = None
929
return self._transport.base
932
base = property(_get_base, doc="The URL for the root of this branch.")
934
def _finish_transaction(self):
935
"""Exit the current transaction."""
936
if self._transaction is None:
937
raise errors.LockError('Branch %s is not in a transaction' %
939
transaction = self._transaction
940
self._transaction = None
943
def get_transaction(self):
944
"""See Branch.get_transaction."""
945
if self._transaction is None:
946
return transactions.PassThroughTransaction()
948
return self._transaction
950
def _set_transaction(self, new_transaction):
951
"""Set a new active transaction."""
952
if self._transaction is not None:
953
raise errors.LockError('Branch %s is in a transaction already.' %
955
self._transaction = new_transaction
957
def lock_write(self):
958
#mutter("lock write: %s (%s)", self, self._lock_count)
959
# TODO: Upgrade locking to support using a Transport,
960
# and potentially a remote locking protocol
962
if self._lock_mode != 'w':
963
raise LockError("can't upgrade to a write lock from %r" %
965
self._lock_count += 1
967
self._lock = self._transport.lock_write(
968
self._rel_controlfilename('branch-lock'))
969
self._lock_mode = 'w'
971
self._set_transaction(transactions.PassThroughTransaction())
974
#mutter("lock read: %s (%s)", self, self._lock_count)
976
assert self._lock_mode in ('r', 'w'), \
977
"invalid lock mode %r" % self._lock_mode
978
self._lock_count += 1
980
self._lock = self._transport.lock_read(
981
self._rel_controlfilename('branch-lock'))
982
self._lock_mode = 'r'
984
self._set_transaction(transactions.ReadOnlyTransaction())
985
# 5K may be excessive, but hey, its a knob.
986
self.get_transaction().set_cache_size(5000)
989
#mutter("unlock: %s (%s)", self, self._lock_count)
990
if not self._lock_mode:
991
raise LockError('branch %r is not locked' % (self))
993
if self._lock_count > 1:
994
self._lock_count -= 1
996
self._finish_transaction()
999
self._lock_mode = self._lock_count = None
1001
def abspath(self, name):
1002
"""See Branch.abspath."""
1003
return self._transport.abspath(name)
1005
def _rel_controlfilename(self, file_or_path):
1006
if not isinstance(file_or_path, basestring):
1007
file_or_path = u'/'.join(file_or_path)
1008
if file_or_path == '':
1009
return bzrlib.BZRDIR
1010
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
1012
def controlfilename(self, file_or_path):
1013
"""See Branch.controlfilename."""
1014
return self._transport.abspath(self._rel_controlfilename(file_or_path))
1016
def controlfile(self, file_or_path, mode='r'):
1017
"""See Branch.controlfile."""
1020
relpath = self._rel_controlfilename(file_or_path)
1021
#TODO: codecs.open() buffers linewise, so it was overloaded with
1022
# a much larger buffer, do we need to do the same for getreader/getwriter?
1024
return self._transport.get(relpath)
1026
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
1028
# XXX: Do we really want errors='replace'? Perhaps it should be
1029
# an error, or at least reported, if there's incorrectly-encoded
1030
# data inside a file.
1031
# <https://launchpad.net/products/bzr/+bug/3823>
1032
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
1034
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
1036
raise BzrError("invalid controlfile mode %r" % mode)
1038
def put_controlfile(self, path, f, encode=True):
1039
"""See Branch.put_controlfile."""
1040
self.put_controlfiles([(path, f)], encode=encode)
1042
def put_controlfiles(self, files, encode=True):
1043
"""See Branch.put_controlfiles."""
1046
for path, f in files:
1048
if isinstance(f, basestring):
1049
f = StringIO(f.encode('utf-8', 'replace'))
1051
f = codecs.getwriter('utf-8')(f, errors='replace')
1052
path = self._rel_controlfilename(path)
1053
ctrl_files.append((path, f))
1054
self._transport.put_multi(ctrl_files, mode=self._file_mode)
1056
def _find_modes(self, path=None):
1057
"""Determine the appropriate modes for files and directories."""
1060
path = self._rel_controlfilename('')
1061
st = self._transport.stat(path)
1062
except errors.TransportNotPossible:
1063
self._dir_mode = 0755
1064
self._file_mode = 0644
1066
self._dir_mode = st.st_mode & 07777
1067
# Remove the sticky and execute bits for files
1068
self._file_mode = self._dir_mode & ~07111
1069
if not self._set_dir_mode:
1070
self._dir_mode = None
1071
if not self._set_file_mode:
1072
self._file_mode = None
1074
def _check_format(self, format):
1075
"""Identify the branch format if needed.
1077
The format is stored as a reference to the format object in
1078
self._branch_format for code that needs to check it later.
1080
The format parameter is either None or the branch format class
1081
used to open this branch.
1084
format = BzrBranchFormat.find_format(self._transport)
1085
self._branch_format = format
1086
mutter("got branch format %s", self._branch_format)
1089
def get_root_id(self):
1090
"""See Branch.get_root_id."""
1091
inv = self.get_inventory(self.last_revision())
1092
return inv.root.file_id
1095
def print_file(self, file, revision_id):
1096
"""See Branch.print_file."""
1097
tree = self.revision_tree(revision_id)
1098
# use inventory as it was in that revision
1099
file_id = tree.inventory.path2id(file)
1102
revno = self.revision_id_to_revno(revision_id)
1103
except errors.NoSuchRevision:
1104
# TODO: This should not be BzrError,
1105
# but NoSuchFile doesn't fit either
1106
raise BzrError('%r is not present in revision %s'
1107
% (file, revision_id))
1109
raise BzrError('%r is not present in revision %s'
1111
tree.print_file(file_id)
1114
def append_revision(self, *revision_ids):
1115
"""See Branch.append_revision."""
1116
for revision_id in revision_ids:
1117
mutter("add {%s} to revision-history" % revision_id)
1118
rev_history = self.revision_history()
1119
rev_history.extend(revision_ids)
1120
self.set_revision_history(rev_history)
1123
def set_revision_history(self, rev_history):
1124
"""See Branch.set_revision_history."""
1125
old_revision = self.last_revision()
1126
new_revision = rev_history[-1]
1127
self.put_controlfile('revision-history', '\n'.join(rev_history))
1129
def has_revision(self, revision_id):
1130
"""See Branch.has_revision."""
1131
return (revision_id is None
1132
or self.revision_store.has_id(revision_id))
1135
def _get_revision_xml_file(self, revision_id):
1136
if not revision_id or not isinstance(revision_id, basestring):
1137
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1139
return self.revision_store.get(revision_id)
1140
except (IndexError, KeyError):
1141
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1143
def get_revision_xml(self, revision_id):
1144
"""See Branch.get_revision_xml."""
1145
return self._get_revision_xml_file(revision_id).read()
1147
def get_revision(self, revision_id):
1148
"""See Branch.get_revision."""
1149
xml_file = self._get_revision_xml_file(revision_id)
1152
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1153
except SyntaxError, e:
1154
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1158
assert r.revision_id == revision_id
1161
def get_revision_sha1(self, revision_id):
1162
"""See Branch.get_revision_sha1."""
1163
# In the future, revision entries will be signed. At that
1164
# point, it is probably best *not* to include the signature
1165
# in the revision hash. Because that lets you re-sign
1166
# the revision, (add signatures/remove signatures) and still
1167
# have all hash pointers stay consistent.
1168
# But for now, just hash the contents.
1169
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1171
def get_ancestry(self, revision_id):
1172
"""See Branch.get_ancestry."""
1173
if revision_id is None:
1175
w = self._get_inventory_weave()
1176
return [None] + map(w.idx_to_name,
1177
w.inclusions([w.lookup(revision_id)]))
1179
def _get_inventory_weave(self):
1180
return self.control_weaves.get_weave('inventory',
1181
self.get_transaction())
1183
def get_inventory(self, revision_id):
1184
"""See Branch.get_inventory."""
1185
xml = self.get_inventory_xml(revision_id)
1186
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1188
def get_inventory_xml(self, revision_id):
1189
"""See Branch.get_inventory_xml."""
1191
assert isinstance(revision_id, basestring), type(revision_id)
1192
iw = self._get_inventory_weave()
1193
return iw.get_text(iw.lookup(revision_id))
1195
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1197
def get_inventory_sha1(self, revision_id):
1198
"""See Branch.get_inventory_sha1."""
1199
return self.get_revision(revision_id).inventory_sha1
1201
def get_revision_inventory(self, revision_id):
1202
"""See Branch.get_revision_inventory."""
1203
# TODO: Unify this with get_inventory()
1204
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1205
# must be the same as its revision, so this is trivial.
1206
if revision_id == None:
1207
# This does not make sense: if there is no revision,
1208
# then it is the current tree inventory surely ?!
1209
# and thus get_root_id() is something that looks at the last
1210
# commit on the branch, and the get_root_id is an inventory check.
1211
raise NotImplementedError
1212
# return Inventory(self.get_root_id())
1214
return self.get_inventory(revision_id)
1217
def revision_history(self):
1218
"""See Branch.revision_history."""
1219
transaction = self.get_transaction()
1220
history = transaction.map.find_revision_history()
1221
if history is not None:
1222
mutter("cache hit for revision-history in %s", self)
1223
return list(history)
1224
history = [l.rstrip('\r\n') for l in
1225
self.controlfile('revision-history', 'r').readlines()]
1226
transaction.map.add_revision_history(history)
1227
# this call is disabled because revision_history is
1228
# not really an object yet, and the transaction is for objects.
1229
# transaction.register_clean(history, precious=True)
1230
return list(history)
1232
def update_revisions(self, other, stop_revision=None):
1233
"""See Branch.update_revisions."""
1234
from bzrlib.fetch import greedy_fetch
1235
if stop_revision is None:
1236
stop_revision = other.last_revision()
1237
### Should this be checking is_ancestor instead of revision_history?
1238
if (stop_revision is not None and
1239
stop_revision in self.revision_history()):
1241
greedy_fetch(to_branch=self, from_branch=other,
1242
revision=stop_revision)
1243
pullable_revs = self.pullable_revisions(other, stop_revision)
1244
if len(pullable_revs) > 0:
1245
self.append_revision(*pullable_revs)
1247
def pullable_revisions(self, other, stop_revision):
1248
"""See Branch.pullable_revisions."""
1249
other_revno = other.revision_id_to_revno(stop_revision)
1251
return self.missing_revisions(other, other_revno)
1252
except DivergedBranches, e:
1254
pullable_revs = get_intervening_revisions(self.last_revision(),
1255
stop_revision, self)
1256
assert self.last_revision() not in pullable_revs
1257
return pullable_revs
1258
except bzrlib.errors.NotAncestor:
1259
if is_ancestor(self.last_revision(), stop_revision, self):
1264
def revision_tree(self, revision_id):
1265
"""See Branch.revision_tree."""
1266
# TODO: refactor this to use an existing revision object
1267
# so we don't need to read it in twice.
1268
if revision_id == None or revision_id == NULL_REVISION:
1271
inv = self.get_revision_inventory(revision_id)
1272
return RevisionTree(self, inv, revision_id)
1274
def basis_tree(self):
1275
"""See Branch.basis_tree."""
1277
revision_id = self.revision_history()[-1]
1278
xml = self.working_tree().read_basis_inventory(revision_id)
1279
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1280
return RevisionTree(self, inv, revision_id)
1281
except (IndexError, NoSuchFile, NoWorkingTree), e:
1282
return self.revision_tree(self.last_revision())
1284
def working_tree(self):
1285
"""See Branch.working_tree."""
1286
from bzrlib.workingtree import WorkingTree
1287
from bzrlib.transport.local import LocalTransport
1288
if (self._transport.base.find('://') != -1 or
1289
not isinstance(self._transport, LocalTransport)):
1290
raise NoWorkingTree(self.base)
1291
return WorkingTree(self.base, branch=self)
1294
def pull(self, source, overwrite=False):
1295
"""See Branch.pull."""
1298
old_count = len(self.revision_history())
1300
self.update_revisions(source)
1301
except DivergedBranches:
1305
self.set_revision_history(source.revision_history())
1306
new_count = len(self.revision_history())
1307
return new_count - old_count
1311
def get_parent(self):
1312
"""See Branch.get_parent."""
1314
_locs = ['parent', 'pull', 'x-pull']
1317
return self.controlfile(l, 'r').read().strip('\n')
1322
def get_push_location(self):
1323
"""See Branch.get_push_location."""
1324
config = bzrlib.config.BranchConfig(self)
1325
push_loc = config.get_user_option('push_location')
1328
def set_push_location(self, location):
1329
"""See Branch.set_push_location."""
1330
config = bzrlib.config.LocationConfig(self.base)
1331
config.set_user_option('push_location', location)
1334
def set_parent(self, url):
1335
"""See Branch.set_parent."""
1336
# TODO: Maybe delete old location files?
1337
from bzrlib.atomicfile import AtomicFile
1338
f = AtomicFile(self.controlfilename('parent'))
1345
def tree_config(self):
1346
return TreeConfig(self)
1348
def sign_revision(self, revision_id, gpg_strategy):
1349
"""See Branch.sign_revision."""
1350
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1351
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1354
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1355
"""See Branch.store_revision_signature."""
1356
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1359
def fileid_involved_between_revs(self, from_revid, to_revid):
1360
"""Find file_id(s) which are involved in the changes between revisions.
1362
This determines the set of revisions which are involved, and then
1363
finds all file ids affected by those revisions.
1365
# TODO: jam 20060119 This code assumes that w.inclusions will
1366
# always be correct. But because of the presence of ghosts
1367
# it is possible to be wrong.
1368
# One specific example from Robert Collins:
1369
# Two branches, with revisions ABC, and AD
1370
# C is a ghost merge of D.
1371
# Inclusions doesn't recognize D as an ancestor.
1372
# If D is ever merged in the future, the weave
1373
# won't be fixed, because AD never saw revision C
1374
# to cause a conflict which would force a reweave.
1375
w = self._get_inventory_weave()
1376
from_set = set(w.inclusions([w.lookup(from_revid)]))
1377
to_set = set(w.inclusions([w.lookup(to_revid)]))
1378
included = to_set.difference(from_set)
1379
changed = map(w.idx_to_name, included)
1380
return self._fileid_involved_by_set(changed)
1382
def fileid_involved(self, last_revid=None):
1383
"""Find all file_ids modified in the ancestry of last_revid.
1385
:param last_revid: If None, last_revision() will be used.
1387
w = self._get_inventory_weave()
1389
changed = set(w._names)
1391
included = w.inclusions([w.lookup(last_revid)])
1392
changed = map(w.idx_to_name, included)
1393
return self._fileid_involved_by_set(changed)
1395
def fileid_involved_by_set(self, changes):
1396
"""Find all file_ids modified by the set of revisions passed in.
1398
:param changes: A set() of revision ids
1400
# TODO: jam 20060119 This line does *nothing*, remove it.
1401
# or better yet, change _fileid_involved_by_set so
1402
# that it takes the inventory weave, rather than
1403
# pulling it out by itself.
1404
w = self._get_inventory_weave()
1405
return self._fileid_involved_by_set(changes)
1407
def _fileid_involved_by_set(self, changes):
1408
"""Find the set of file-ids affected by the set of revisions.
1410
:param changes: A set() of revision ids.
1411
:return: A set() of file ids.
1413
This peaks at the Weave, interpreting each line, looking to
1414
see if it mentions one of the revisions. And if so, includes
1415
the file id mentioned.
1416
This expects both the Weave format, and the serialization
1417
to have a single line per file/directory, and to have
1418
fileid="" and revision="" on that line.
1420
assert (isinstance(self._branch_format, BzrBranchFormat5) or
1421
isinstance(self._branch_format, BzrBranchFormat6)), \
1422
"fileid_involved only supported for branches which store inventory as xml"
1424
w = self._get_inventory_weave()
1426
for line in w._weave:
1428
# it is ugly, but it is due to the weave structure
1429
if not isinstance(line, basestring): continue
1431
start = line.find('file_id="')+9
1432
if start < 9: continue
1433
end = line.find('"', start)
1435
file_id = xml.sax.saxutils.unescape(line[start:end])
1437
# check if file_id is already present
1438
if file_id in file_ids: continue
1440
start = line.find('revision="')+10
1441
if start < 10: continue
1442
end = line.find('"', start)
1444
revision_id = xml.sax.saxutils.unescape(line[start:end])
1446
if revision_id in changes:
1447
file_ids.add(file_id)
1452
Branch.set_default_initializer(BzrBranch._initialize)
1455
class BranchTestProviderAdapter(object):
1456
"""A tool to generate a suite testing multiple branch formats at once.
1458
This is done by copying the test once for each transport and injecting
1459
the transport_server, transport_readonly_server, and branch_format
1460
classes into each copy. Each copy is also given a new id() to make it
1464
def __init__(self, transport_server, transport_readonly_server, formats):
1465
self._transport_server = transport_server
1466
self._transport_readonly_server = transport_readonly_server
1467
self._formats = formats
1469
def adapt(self, test):
1470
result = TestSuite()
1471
for format in self._formats:
1472
new_test = deepcopy(test)
1473
new_test.transport_server = self._transport_server
1474
new_test.transport_readonly_server = self._transport_readonly_server
1475
new_test.branch_format = format
1476
def make_new_test_id():
1477
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1478
return lambda: new_id
1479
new_test.id = make_new_test_id()
1480
result.addTest(new_test)
1484
class ScratchBranch(BzrBranch):
1485
"""Special test class: a branch that cleans up after itself.
1487
>>> b = ScratchBranch()
1491
>>> b._transport.__del__()
1496
def __init__(self, files=[], dirs=[], transport=None):
1497
"""Make a test branch.
1499
This creates a temporary directory and runs init-tree in it.
1501
If any files are listed, they are created in the working copy.
1503
if transport is None:
1504
transport = bzrlib.transport.local.ScratchTransport()
1505
# local import for scope restriction
1506
from bzrlib.workingtree import WorkingTree
1507
WorkingTree.create_standalone(transport.base)
1508
super(ScratchBranch, self).__init__(transport)
1510
super(ScratchBranch, self).__init__(transport)
1513
self._transport.mkdir(d)
1516
self._transport.put(f, 'content of %s' % f)
1521
>>> orig = ScratchBranch(files=["file1", "file2"])
1522
>>> clone = orig.clone()
1523
>>> if os.name != 'nt':
1524
... os.path.samefile(orig.base, clone.base)
1526
... orig.base == clone.base
1529
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1532
from shutil import copytree
1533
from bzrlib.osutils import mkdtemp
1536
copytree(self.base, base, symlinks=True)
1537
return ScratchBranch(
1538
transport=bzrlib.transport.local.ScratchTransport(base))
1541
######################################################################
1545
def is_control_file(filename):
1546
## FIXME: better check
1547
filename = normpath(filename)
1548
while filename != '':
1549
head, tail = os.path.split(filename)
1550
## mutter('check %r for control file' % ((head, tail),))
1551
if tail == bzrlib.BZRDIR:
1553
if filename == head: