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 deprecated_nonce, deprecated_passed
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)
171
def initialize(base):
172
"""Create a new branch, rooted at 'base' (url)
174
This will call the current default initializer with base
175
as the only parameter.
177
return Branch._default_initializer(safe_unicode(base))
180
def get_default_initializer():
181
"""Return the initializer being used for new branches."""
182
return Branch._default_initializer
185
def set_default_initializer(initializer):
186
"""Set the initializer to be used for new branches."""
187
Branch._default_initializer = staticmethod(initializer)
189
def setup_caching(self, cache_root):
190
"""Subclasses that care about caching should override this, and set
191
up cached stores located under cache_root.
193
self.cache_root = cache_root
196
cfg = self.tree_config()
197
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
199
def _set_nick(self, nick):
200
cfg = self.tree_config()
201
cfg.set_option(nick, "nickname")
202
assert cfg.get_option("nickname") == nick
204
nick = property(_get_nick, _set_nick)
206
def push_stores(self, branch_to):
207
"""Copy the content of this branches store to branch_to."""
208
raise NotImplementedError('push_stores is abstract')
210
def get_transaction(self):
211
"""Return the current active transaction.
213
If no transaction is active, this returns a passthrough object
214
for which all data is immediately flushed and no caching happens.
216
raise NotImplementedError('get_transaction is abstract')
218
def lock_write(self):
219
raise NotImplementedError('lock_write is abstract')
222
raise NotImplementedError('lock_read is abstract')
225
raise NotImplementedError('unlock is abstract')
227
def abspath(self, name):
228
"""Return absolute filename for something in the branch
230
XXX: Robert Collins 20051017 what is this used for? why is it a branch
231
method and not a tree method.
233
raise NotImplementedError('abspath is abstract')
235
def controlfilename(self, file_or_path):
236
"""Return location relative to branch."""
237
raise NotImplementedError('controlfilename is abstract')
239
def controlfile(self, file_or_path, mode='r'):
240
"""Open a control file for this branch.
242
There are two classes of file in the control directory: text
243
and binary. binary files are untranslated byte streams. Text
244
control files are stored with Unix newlines and in UTF-8, even
245
if the platform or locale defaults are different.
247
Controlfiles should almost never be opened in write mode but
248
rather should be atomically copied and replaced using atomicfile.
250
raise NotImplementedError('controlfile is abstract')
252
def put_controlfile(self, path, f, encode=True):
253
"""Write an entry as a controlfile.
255
:param path: The path to put the file, relative to the .bzr control
257
:param f: A file-like or string object whose contents should be copied.
258
:param encode: If true, encode the contents as utf-8
260
raise NotImplementedError('put_controlfile is abstract')
262
def put_controlfiles(self, files, encode=True):
263
"""Write several entries as controlfiles.
265
:param files: A list of [(path, file)] pairs, where the path is the directory
266
underneath the bzr control directory
267
:param encode: If true, encode the contents as utf-8
269
raise NotImplementedError('put_controlfiles is abstract')
271
def get_root_id(self):
272
"""Return the id of this branches root"""
273
raise NotImplementedError('get_root_id is abstract')
275
def set_root_id(self, file_id):
276
raise NotImplementedError('set_root_id is abstract')
278
def print_file(self, file, revision_id):
279
"""Print `file` to stdout."""
280
raise NotImplementedError('print_file is abstract')
282
def append_revision(self, *revision_ids):
283
raise NotImplementedError('append_revision is abstract')
285
def set_revision_history(self, rev_history):
286
raise NotImplementedError('set_revision_history is abstract')
288
def has_revision(self, revision_id):
289
"""True if this branch has a copy of the revision.
291
This does not necessarily imply the revision is merge
292
or on the mainline."""
293
raise NotImplementedError('has_revision is abstract')
295
def get_revision_xml(self, revision_id):
296
raise NotImplementedError('get_revision_xml is abstract')
298
def get_revision(self, revision_id):
299
"""Return the Revision object for a named revision"""
300
raise NotImplementedError('get_revision is abstract')
302
def get_revision_delta(self, revno):
303
"""Return the delta for one revision.
305
The delta is relative to its mainline predecessor, or the
306
empty tree for revision 1.
308
assert isinstance(revno, int)
309
rh = self.revision_history()
310
if not (1 <= revno <= len(rh)):
311
raise InvalidRevisionNumber(revno)
313
# revno is 1-based; list is 0-based
315
new_tree = self.revision_tree(rh[revno-1])
317
old_tree = EmptyTree()
319
old_tree = self.revision_tree(rh[revno-2])
321
return compare_trees(old_tree, new_tree)
323
def get_revision_sha1(self, revision_id):
324
"""Hash the stored value of a revision, and return it."""
325
raise NotImplementedError('get_revision_sha1 is abstract')
327
def get_ancestry(self, revision_id):
328
"""Return a list of revision-ids integrated by a revision.
330
This currently returns a list, but the ordering is not guaranteed:
333
raise NotImplementedError('get_ancestry is abstract')
335
def get_inventory(self, revision_id):
336
"""Get Inventory object by hash."""
337
raise NotImplementedError('get_inventory is abstract')
339
def get_inventory_xml(self, revision_id):
340
"""Get inventory XML as a file object."""
341
raise NotImplementedError('get_inventory_xml is abstract')
343
def get_inventory_sha1(self, revision_id):
344
"""Return the sha1 hash of the inventory entry."""
345
raise NotImplementedError('get_inventory_sha1 is abstract')
347
def get_revision_inventory(self, revision_id):
348
"""Return inventory of a past revision."""
349
raise NotImplementedError('get_revision_inventory is abstract')
351
def revision_history(self):
352
"""Return sequence of revision hashes on to this branch."""
353
raise NotImplementedError('revision_history is abstract')
356
"""Return current revision number for this branch.
358
That is equivalent to the number of revisions committed to
361
return len(self.revision_history())
363
def last_revision(self):
364
"""Return last patch hash, or None if no history."""
365
ph = self.revision_history()
371
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
372
"""Return a list of new revisions that would perfectly fit.
374
If self and other have not diverged, return a list of the revisions
375
present in other, but missing from self.
377
>>> from bzrlib.commit import commit
378
>>> bzrlib.trace.silent = True
379
>>> br1 = ScratchBranch()
380
>>> br2 = ScratchBranch()
381
>>> br1.missing_revisions(br2)
383
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
384
>>> br1.missing_revisions(br2)
386
>>> br2.missing_revisions(br1)
388
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
389
>>> br1.missing_revisions(br2)
391
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
392
>>> br1.missing_revisions(br2)
394
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
395
>>> br1.missing_revisions(br2)
396
Traceback (most recent call last):
397
DivergedBranches: These branches have diverged. Try merge.
399
self_history = self.revision_history()
400
self_len = len(self_history)
401
other_history = other.revision_history()
402
other_len = len(other_history)
403
common_index = min(self_len, other_len) -1
404
if common_index >= 0 and \
405
self_history[common_index] != other_history[common_index]:
406
raise DivergedBranches(self, other)
408
if stop_revision is None:
409
stop_revision = other_len
411
assert isinstance(stop_revision, int)
412
if stop_revision > other_len:
413
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
414
return other_history[self_len:stop_revision]
416
def update_revisions(self, other, stop_revision=None):
417
"""Pull in new perfect-fit revisions."""
418
raise NotImplementedError('update_revisions is abstract')
420
def pullable_revisions(self, other, stop_revision):
421
raise NotImplementedError('pullable_revisions is abstract')
423
def revision_id_to_revno(self, revision_id):
424
"""Given a revision id, return its revno"""
425
if revision_id is None:
427
history = self.revision_history()
429
return history.index(revision_id) + 1
431
raise bzrlib.errors.NoSuchRevision(self, revision_id)
433
def get_rev_id(self, revno, history=None):
434
"""Find the revision id of the specified revno."""
438
history = self.revision_history()
439
elif revno <= 0 or revno > len(history):
440
raise bzrlib.errors.NoSuchRevision(self, revno)
441
return history[revno - 1]
443
def revision_tree(self, revision_id):
444
"""Return Tree for a revision on this branch.
446
`revision_id` may be None for the null revision, in which case
447
an `EmptyTree` is returned."""
448
raise NotImplementedError('revision_tree is abstract')
450
def working_tree(self):
451
"""Return a `Tree` for the working copy if this is a local branch."""
452
raise NotImplementedError('working_tree is abstract')
454
def pull(self, source, overwrite=False):
455
raise NotImplementedError('pull is abstract')
457
def basis_tree(self):
458
"""Return `Tree` object for last revision.
460
If there are no revisions yet, return an `EmptyTree`.
462
return self.revision_tree(self.last_revision())
464
def rename_one(self, from_rel, to_rel):
467
This can change the directory or the filename or both.
469
raise NotImplementedError('rename_one is abstract')
471
def move(self, from_paths, to_name):
474
to_name must exist as a versioned directory.
476
If to_name exists and is a directory, the files are moved into
477
it, keeping their old names. If it is a directory,
479
Note that to_name is only the last component of the new name;
480
this doesn't change the directory.
482
This returns a list of (from_path, to_path) pairs for each
485
raise NotImplementedError('move is abstract')
487
def get_parent(self):
488
"""Return the parent location of the branch.
490
This is the default location for push/pull/missing. The usual
491
pattern is that the user can override it by specifying a
494
raise NotImplementedError('get_parent is abstract')
496
def get_push_location(self):
497
"""Return the None or the location to push this branch to."""
498
raise NotImplementedError('get_push_location is abstract')
500
def set_push_location(self, location):
501
"""Set a new push location for this branch."""
502
raise NotImplementedError('set_push_location is abstract')
504
def set_parent(self, url):
505
raise NotImplementedError('set_parent is abstract')
507
def check_revno(self, revno):
509
Check whether a revno corresponds to any revision.
510
Zero (the NULL revision) is considered valid.
513
self.check_real_revno(revno)
515
def check_real_revno(self, revno):
517
Check whether a revno corresponds to a real revision.
518
Zero (the NULL revision) is considered invalid
520
if revno < 1 or revno > self.revno():
521
raise InvalidRevisionNumber(revno)
523
def sign_revision(self, revision_id, gpg_strategy):
524
raise NotImplementedError('sign_revision is abstract')
526
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
527
raise NotImplementedError('store_revision_signature is abstract')
529
def fileid_involved_between_revs(self, from_revid, to_revid):
530
""" This function returns the file_id(s) involved in the
531
changes between the from_revid revision and the to_revid
534
raise NotImplementedError('fileid_involved_between_revs is abstract')
536
def fileid_involved(self, last_revid=None):
537
""" This function returns the file_id(s) involved in the
538
changes up to the revision last_revid
539
If no parametr is passed, then all file_id[s] present in the
540
repository are returned
542
raise NotImplementedError('fileid_involved is abstract')
544
def fileid_involved_by_set(self, changes):
545
""" This function returns the file_id(s) involved in the
546
changes present in the set 'changes'
548
raise NotImplementedError('fileid_involved_by_set is abstract')
550
def fileid_involved_between_revs(self, from_revid, to_revid):
551
""" This function returns the file_id(s) involved in the
552
changes between the from_revid revision and the to_revid
555
raise NotImplementedError('fileid_involved_between_revs is abstract')
557
def fileid_involved(self, last_revid=None):
558
""" This function returns the file_id(s) involved in the
559
changes up to the revision last_revid
560
If no parametr is passed, then all file_id[s] present in the
561
repository are returned
563
raise NotImplementedError('fileid_involved is abstract')
565
def fileid_involved_by_set(self, changes):
566
""" This function returns the file_id(s) involved in the
567
changes present in the set 'changes'
569
raise NotImplementedError('fileid_involved_by_set is abstract')
571
class BzrBranchFormat(object):
572
"""An encapsulation of the initialization and open routines for a format.
574
Formats provide three things:
575
* An initialization routine,
579
Formats are placed in an dict by their format string for reference
580
during branch opening. Its not required that these be instances, they
581
can be classes themselves with class methods - it simply depends on
582
whether state is needed for a given format or not.
584
Once a format is deprecated, just deprecate the initialize and open
585
methods on the format class. Do not deprecate the object, as the
586
object will be created every time regardless.
590
"""The known formats."""
593
def find_format(klass, transport):
594
"""Return the format registered for URL."""
596
format_string = transport.get(".bzr/branch-format").read()
597
return klass._formats[format_string]
599
raise NotBranchError(path=transport.base)
601
raise errors.UnknownFormatError(format_string)
603
def get_format_string(self):
604
"""Return the ASCII format string that identifies this format."""
605
raise NotImplementedError(self.get_format_string)
607
def _find_modes(self, t):
608
"""Determine the appropriate modes for files and directories.
610
FIXME: When this merges into, or from storage,
611
this code becomes delgatable to a LockableFiles instance.
613
For now its cribbed and returns (dir_mode, file_mode)
617
except errors.TransportNotPossible:
621
dir_mode = st.st_mode & 07777
622
# Remove the sticky and execute bits for files
623
file_mode = dir_mode & ~07111
624
if not BzrBranch._set_dir_mode:
626
if not BzrBranch._set_file_mode:
628
return dir_mode, file_mode
630
def initialize(self, url):
631
"""Create a branch of this format at url and return an open branch."""
632
t = get_transport(url)
633
from bzrlib.inventory import Inventory
634
from bzrlib.weavefile import write_weave_v5
635
from bzrlib.weave import Weave
637
# Create an empty inventory
639
# if we want per-tree root ids then this is the place to set
640
# them; they're not needed for now and so ommitted for
642
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
643
empty_inv = sio.getvalue()
645
bzrlib.weavefile.write_weave_v5(Weave(), sio)
646
empty_weave = sio.getvalue()
648
# Since we don't have a .bzr directory, inherit the
649
# mode from the root directory
650
dir_mode, file_mode = self._find_modes(t)
652
t.mkdir('.bzr', mode=dir_mode)
653
control = t.clone('.bzr')
654
dirs = ['revision-store', 'weaves']
656
StringIO("This is a Bazaar-NG control directory.\n"
657
"Do not change any files in this directory.\n")),
658
('branch-format', StringIO(self.get_format_string())),
659
('revision-history', StringIO('')),
660
('branch-name', StringIO('')),
661
('branch-lock', StringIO('')),
662
('pending-merges', StringIO('')),
663
('inventory', StringIO(empty_inv)),
664
('inventory.weave', StringIO(empty_weave)),
666
control.mkdir_multi(dirs, mode=dir_mode)
667
control.put_multi(files, mode=file_mode)
668
mutter('created control directory in ' + t.base)
669
return BzrBranch(t, format=self)
671
def is_supported(self):
672
"""Is this format supported?
674
Supported formats can be initialized and opened.
675
Unsupported formats may not support initialization or committing or
676
some other features depending on the reason for not being supported.
680
def open(self, transport):
681
"""Fill out the data in branch for the branch at url."""
682
return BzrBranch(transport, format=self)
685
def register_format(klass, format):
686
klass._formats[format.get_format_string()] = format
689
def unregister_format(klass, format):
690
assert klass._formats[format.get_format_string()] is format
691
del klass._formats[format.get_format_string()]
694
class BzrBranchFormat4(BzrBranchFormat):
695
"""Bzr branch format 4.
699
- TextStores for texts, inventories,revisions.
701
This format is deprecated: it indexes texts using a text it which is
702
removed in format 5; write support for this format has been removed.
705
def get_format_string(self):
706
"""See BzrBranchFormat.get_format_string()."""
707
return BZR_BRANCH_FORMAT_4
709
def initialize(self, url):
710
"""Format 4 branches cannot be created."""
711
raise UninitializableFormat(self)
713
def is_supported(self):
714
"""Format 4 is not supported.
716
It is not supported because the model changed from 4 to 5 and the
717
conversion logic is expensive - so doing it on the fly was not
723
class BzrBranchFormat5(BzrBranchFormat):
724
"""Bzr branch format 5.
727
- weaves for file texts and inventory
729
- TextStores for revisions and signatures.
732
def get_format_string(self):
733
"""See BzrBranchFormat.get_format_string()."""
734
return BZR_BRANCH_FORMAT_5
737
class BzrBranchFormat6(BzrBranchFormat):
738
"""Bzr branch format 6.
741
- weaves for file texts and inventory
742
- hash subdirectory based stores.
743
- TextStores for revisions and signatures.
746
def get_format_string(self):
747
"""See BzrBranchFormat.get_format_string()."""
748
return BZR_BRANCH_FORMAT_6
751
BzrBranchFormat.register_format(BzrBranchFormat4())
752
BzrBranchFormat.register_format(BzrBranchFormat5())
753
BzrBranchFormat.register_format(BzrBranchFormat6())
755
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
756
# make sure that ancestry.weave is deleted (it is never used, but
757
# used to be created)
760
class BzrBranch(Branch):
761
"""A branch stored in the actual filesystem.
763
Note that it's "local" in the context of the filesystem; it doesn't
764
really matter if it's on an nfs/smb/afs/coda/... share, as long as
765
it's writable, and can be accessed via the normal filesystem API.
771
If _lock_mode is true, a positive count of the number of times the
775
Lock object from bzrlib.lock.
777
# We actually expect this class to be somewhat short-lived; part of its
778
# purpose is to try to isolate what bits of the branch logic are tied to
779
# filesystem access, so that in a later step, we can extricate them to
780
# a separarte ("storage") class.
784
_inventory_weave = None
785
# If set to False (by a plugin, etc) BzrBranch will not set the
786
# mode on created files or directories
787
_set_file_mode = True
790
# Map some sort of prefix into a namespace
791
# stuff like "revno:10", "revid:", etc.
792
# This should match a prefix with a function which accepts
793
REVISION_NAMESPACES = {}
795
def push_stores(self, branch_to):
796
"""See Branch.push_stores."""
797
if (not isinstance(self._branch_format, BzrBranchFormat4) or
798
self._branch_format != branch_to._branch_format):
799
from bzrlib.fetch import greedy_fetch
800
mutter("Using fetch logic to push between %s(%s) and %s(%s)",
801
self, self._branch_format, branch_to, branch_to._branch_format)
802
greedy_fetch(to_branch=branch_to, from_branch=self,
803
revision=self.last_revision())
806
# format 4 to format 4 logic only.
807
store_pairs = ((self.text_store, branch_to.text_store),
808
(self.inventory_store, branch_to.inventory_store),
809
(self.revision_store, branch_to.revision_store))
811
for from_store, to_store in store_pairs:
812
copy_all(from_store, to_store)
813
except UnlistableStore:
814
raise UnlistableBranch(from_store)
816
def __init__(self, transport, init=deprecated_nonce,
817
relax_version_check=deprecated_nonce, format=None):
818
"""Create new branch object at a particular location.
820
transport -- A Transport object, defining how to access files.
822
init -- If True, create new control files in a previously
823
unversioned directory. If False, the branch must already
826
relax_version_check -- If true, the usual check for the branch
827
version is not applied. This is intended only for
828
upgrade/recovery type use; it's not guaranteed that
829
all operations will work on old format branches.
831
In the test suite, creation of new trees is tested using the
832
`ScratchBranch` class.
834
assert isinstance(transport, Transport), \
835
"%r is not a Transport" % transport
836
self._transport = transport
837
if deprecated_passed(init):
838
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
839
"deprecated as of bzr 0.8. Please use Branch.initialize().",
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 "
851
"open() method.", DeprecationWarning)
852
if (not relax_version_check
853
and not self._branch_format.is_supported()):
854
raise errors.UnsupportedFormatError(
855
'sorry, branch format %r not supported' % fmt,
856
['use a different bzr version',
857
'or remove the .bzr directory'
858
' and "bzr init" again'])
860
def get_store(name, compressed=True, prefixed=False):
861
relpath = self._rel_controlfilename(safe_unicode(name))
862
store = TextStore(self._transport.clone(relpath),
863
dir_mode=self._dir_mode,
864
file_mode=self._file_mode,
866
compressed=compressed)
869
def get_weave(name, prefixed=False):
870
relpath = self._rel_controlfilename(unicode(name))
871
ws = WeaveStore(self._transport.clone(relpath),
873
dir_mode=self._dir_mode,
874
file_mode=self._file_mode)
875
if self._transport.should_cache():
876
ws.enable_cache = True
879
if isinstance(self._branch_format, BzrBranchFormat4):
880
self.inventory_store = get_store('inventory-store')
881
self.text_store = get_store('text-store')
882
self.revision_store = get_store('revision-store')
883
elif isinstance(self._branch_format, BzrBranchFormat5):
884
self.control_weaves = get_weave(u'')
885
self.weave_store = get_weave(u'weaves')
886
self.revision_store = get_store(u'revision-store', compressed=False)
887
elif isinstance(self._branch_format, BzrBranchFormat6):
888
self.control_weaves = get_weave(u'')
889
self.weave_store = get_weave(u'weaves', prefixed=True)
890
self.revision_store = get_store(u'revision-store', compressed=False,
892
self.revision_store.register_suffix('sig')
893
self._transaction = None
896
def _initialize(base):
897
"""Create a bzr branch in the latest format."""
898
return BzrBranchFormat6().initialize(base)
901
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
906
if self._lock_mode or self._lock:
907
# XXX: This should show something every time, and be suitable for
908
# headless operation and embedding
909
warn("branch %r was not explicitly unlocked" % self)
912
# TODO: It might be best to do this somewhere else,
913
# but it is nice for a Branch object to automatically
914
# cache it's information.
915
# Alternatively, we could have the Transport objects cache requests
916
# See the earlier discussion about how major objects (like Branch)
917
# should never expect their __del__ function to run.
918
if hasattr(self, 'cache_root') and self.cache_root is not None:
920
shutil.rmtree(self.cache_root)
923
self.cache_root = None
927
return self._transport.base
930
base = property(_get_base, doc="The URL for the root of this branch.")
932
def _finish_transaction(self):
933
"""Exit the current transaction."""
934
if self._transaction is None:
935
raise errors.LockError('Branch %s is not in a transaction' %
937
transaction = self._transaction
938
self._transaction = None
941
def get_transaction(self):
942
"""See Branch.get_transaction."""
943
if self._transaction is None:
944
return transactions.PassThroughTransaction()
946
return self._transaction
948
def _set_transaction(self, new_transaction):
949
"""Set a new active transaction."""
950
if self._transaction is not None:
951
raise errors.LockError('Branch %s is in a transaction already.' %
953
self._transaction = new_transaction
955
def lock_write(self):
956
#mutter("lock write: %s (%s)", self, self._lock_count)
957
# TODO: Upgrade locking to support using a Transport,
958
# and potentially a remote locking protocol
960
if self._lock_mode != 'w':
961
raise LockError("can't upgrade to a write lock from %r" %
963
self._lock_count += 1
965
self._lock = self._transport.lock_write(
966
self._rel_controlfilename('branch-lock'))
967
self._lock_mode = 'w'
969
self._set_transaction(transactions.PassThroughTransaction())
972
#mutter("lock read: %s (%s)", self, self._lock_count)
974
assert self._lock_mode in ('r', 'w'), \
975
"invalid lock mode %r" % self._lock_mode
976
self._lock_count += 1
978
self._lock = self._transport.lock_read(
979
self._rel_controlfilename('branch-lock'))
980
self._lock_mode = 'r'
982
self._set_transaction(transactions.ReadOnlyTransaction())
983
# 5K may be excessive, but hey, its a knob.
984
self.get_transaction().set_cache_size(5000)
987
#mutter("unlock: %s (%s)", self, self._lock_count)
988
if not self._lock_mode:
989
raise LockError('branch %r is not locked' % (self))
991
if self._lock_count > 1:
992
self._lock_count -= 1
994
self._finish_transaction()
997
self._lock_mode = self._lock_count = None
999
def abspath(self, name):
1000
"""See Branch.abspath."""
1001
return self._transport.abspath(name)
1003
def _rel_controlfilename(self, file_or_path):
1004
if not isinstance(file_or_path, basestring):
1005
file_or_path = u'/'.join(file_or_path)
1006
if file_or_path == '':
1007
return bzrlib.BZRDIR
1008
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
1010
def controlfilename(self, file_or_path):
1011
"""See Branch.controlfilename."""
1012
return self._transport.abspath(self._rel_controlfilename(file_or_path))
1014
def controlfile(self, file_or_path, mode='r'):
1015
"""See Branch.controlfile."""
1018
relpath = self._rel_controlfilename(file_or_path)
1019
#TODO: codecs.open() buffers linewise, so it was overloaded with
1020
# a much larger buffer, do we need to do the same for getreader/getwriter?
1022
return self._transport.get(relpath)
1024
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
1026
# XXX: Do we really want errors='replace'? Perhaps it should be
1027
# an error, or at least reported, if there's incorrectly-encoded
1028
# data inside a file.
1029
# <https://launchpad.net/products/bzr/+bug/3823>
1030
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
1032
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
1034
raise BzrError("invalid controlfile mode %r" % mode)
1036
def put_controlfile(self, path, f, encode=True):
1037
"""See Branch.put_controlfile."""
1038
self.put_controlfiles([(path, f)], encode=encode)
1040
def put_controlfiles(self, files, encode=True):
1041
"""See Branch.put_controlfiles."""
1044
for path, f in files:
1046
if isinstance(f, basestring):
1047
f = f.encode('utf-8', 'replace')
1049
f = codecs.getwriter('utf-8')(f, errors='replace')
1050
path = self._rel_controlfilename(path)
1051
ctrl_files.append((path, f))
1052
self._transport.put_multi(ctrl_files, mode=self._file_mode)
1054
def _find_modes(self, path=None):
1055
"""Determine the appropriate modes for files and directories."""
1058
path = self._rel_controlfilename('')
1059
st = self._transport.stat(path)
1060
except errors.TransportNotPossible:
1061
self._dir_mode = 0755
1062
self._file_mode = 0644
1064
self._dir_mode = st.st_mode & 07777
1065
# Remove the sticky and execute bits for files
1066
self._file_mode = self._dir_mode & ~07111
1067
if not self._set_dir_mode:
1068
self._dir_mode = None
1069
if not self._set_file_mode:
1070
self._file_mode = None
1072
def _check_format(self, format):
1073
"""Identify the branch format if needed.
1075
The format is stored as a reference to the format object in
1076
self._branch_format for code that needs to check it later.
1078
The format parameter is either None or the branch format class
1079
used to open this branch.
1082
format = BzrBranchFormat.find_format(self._transport)
1083
self._branch_format = format
1084
mutter("got branch format %s", self._branch_format)
1087
def get_root_id(self):
1088
"""See Branch.get_root_id."""
1089
inv = self.get_inventory(self.last_revision())
1090
return inv.root.file_id
1093
def print_file(self, file, revision_id):
1094
"""See Branch.print_file."""
1095
tree = self.revision_tree(revision_id)
1096
# use inventory as it was in that revision
1097
file_id = tree.inventory.path2id(file)
1100
revno = self.revision_id_to_revno(revision_id)
1101
except errors.NoSuchRevision:
1102
# TODO: This should not be BzrError,
1103
# but NoSuchFile doesn't fit either
1104
raise BzrError('%r is not present in revision %s'
1105
% (file, revision_id))
1107
raise BzrError('%r is not present in revision %s'
1109
tree.print_file(file_id)
1112
def append_revision(self, *revision_ids):
1113
"""See Branch.append_revision."""
1114
for revision_id in revision_ids:
1115
mutter("add {%s} to revision-history" % revision_id)
1116
rev_history = self.revision_history()
1117
rev_history.extend(revision_ids)
1118
self.set_revision_history(rev_history)
1121
def set_revision_history(self, rev_history):
1122
"""See Branch.set_revision_history."""
1123
old_revision = self.last_revision()
1124
new_revision = rev_history[-1]
1125
self.put_controlfile('revision-history', '\n'.join(rev_history))
1127
self.working_tree().set_last_revision(new_revision, old_revision)
1128
except NoWorkingTree:
1129
mutter('Unable to set_last_revision without a working tree.')
1131
def has_revision(self, revision_id):
1132
"""See Branch.has_revision."""
1133
return (revision_id is None
1134
or self.revision_store.has_id(revision_id))
1137
def _get_revision_xml_file(self, revision_id):
1138
if not revision_id or not isinstance(revision_id, basestring):
1139
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1141
return self.revision_store.get(revision_id)
1142
except (IndexError, KeyError):
1143
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1145
def get_revision_xml(self, revision_id):
1146
"""See Branch.get_revision_xml."""
1147
return self._get_revision_xml_file(revision_id).read()
1149
def get_revision(self, revision_id):
1150
"""See Branch.get_revision."""
1151
xml_file = self._get_revision_xml_file(revision_id)
1154
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1155
except SyntaxError, e:
1156
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1160
assert r.revision_id == revision_id
1163
def get_revision_sha1(self, revision_id):
1164
"""See Branch.get_revision_sha1."""
1165
# In the future, revision entries will be signed. At that
1166
# point, it is probably best *not* to include the signature
1167
# in the revision hash. Because that lets you re-sign
1168
# the revision, (add signatures/remove signatures) and still
1169
# have all hash pointers stay consistent.
1170
# But for now, just hash the contents.
1171
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1173
def get_ancestry(self, revision_id):
1174
"""See Branch.get_ancestry."""
1175
if revision_id is None:
1177
w = self._get_inventory_weave()
1178
return [None] + map(w.idx_to_name,
1179
w.inclusions([w.lookup(revision_id)]))
1181
def _get_inventory_weave(self):
1182
return self.control_weaves.get_weave('inventory',
1183
self.get_transaction())
1185
def get_inventory(self, revision_id):
1186
"""See Branch.get_inventory."""
1187
xml = self.get_inventory_xml(revision_id)
1188
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1190
def get_inventory_xml(self, revision_id):
1191
"""See Branch.get_inventory_xml."""
1193
assert isinstance(revision_id, basestring), type(revision_id)
1194
iw = self._get_inventory_weave()
1195
return iw.get_text(iw.lookup(revision_id))
1197
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1199
def get_inventory_sha1(self, revision_id):
1200
"""See Branch.get_inventory_sha1."""
1201
return self.get_revision(revision_id).inventory_sha1
1203
def get_revision_inventory(self, revision_id):
1204
"""See Branch.get_revision_inventory."""
1205
# TODO: Unify this with get_inventory()
1206
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1207
# must be the same as its revision, so this is trivial.
1208
if revision_id == None:
1209
# This does not make sense: if there is no revision,
1210
# then it is the current tree inventory surely ?!
1211
# and thus get_root_id() is something that looks at the last
1212
# commit on the branch, and the get_root_id is an inventory check.
1213
raise NotImplementedError
1214
# return Inventory(self.get_root_id())
1216
return self.get_inventory(revision_id)
1219
def revision_history(self):
1220
"""See Branch.revision_history."""
1221
transaction = self.get_transaction()
1222
history = transaction.map.find_revision_history()
1223
if history is not None:
1224
mutter("cache hit for revision-history in %s", self)
1225
return list(history)
1226
history = [l.rstrip('\r\n') for l in
1227
self.controlfile('revision-history', 'r').readlines()]
1228
transaction.map.add_revision_history(history)
1229
# this call is disabled because revision_history is
1230
# not really an object yet, and the transaction is for objects.
1231
# transaction.register_clean(history, precious=True)
1232
return list(history)
1234
def update_revisions(self, other, stop_revision=None):
1235
"""See Branch.update_revisions."""
1236
from bzrlib.fetch import greedy_fetch
1237
if stop_revision is None:
1238
stop_revision = other.last_revision()
1239
### Should this be checking is_ancestor instead of revision_history?
1240
if (stop_revision is not None and
1241
stop_revision in self.revision_history()):
1243
greedy_fetch(to_branch=self, from_branch=other,
1244
revision=stop_revision)
1245
pullable_revs = self.pullable_revisions(other, stop_revision)
1246
if len(pullable_revs) > 0:
1247
self.append_revision(*pullable_revs)
1249
def pullable_revisions(self, other, stop_revision):
1250
"""See Branch.pullable_revisions."""
1251
other_revno = other.revision_id_to_revno(stop_revision)
1253
return self.missing_revisions(other, other_revno)
1254
except DivergedBranches, e:
1256
pullable_revs = get_intervening_revisions(self.last_revision(),
1257
stop_revision, self)
1258
assert self.last_revision() not in pullable_revs
1259
return pullable_revs
1260
except bzrlib.errors.NotAncestor:
1261
if is_ancestor(self.last_revision(), stop_revision, self):
1266
def revision_tree(self, revision_id):
1267
"""See Branch.revision_tree."""
1268
# TODO: refactor this to use an existing revision object
1269
# so we don't need to read it in twice.
1270
if revision_id == None or revision_id == NULL_REVISION:
1273
inv = self.get_revision_inventory(revision_id)
1274
return RevisionTree(self, inv, revision_id)
1276
def basis_tree(self):
1277
"""See Branch.basis_tree."""
1279
revision_id = self.revision_history()[-1]
1280
xml = self.working_tree().read_basis_inventory(revision_id)
1281
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1282
return RevisionTree(self, inv, revision_id)
1283
except (IndexError, NoSuchFile, NoWorkingTree), e:
1284
return self.revision_tree(self.last_revision())
1286
def working_tree(self):
1287
"""See Branch.working_tree."""
1288
from bzrlib.workingtree import WorkingTree
1289
if self._transport.base.find('://') != -1:
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
Branch.initialize(transport.base)
1506
super(ScratchBranch, self).__init__(transport)
1508
super(ScratchBranch, self).__init__(transport)
1511
self._transport.mkdir(d)
1514
self._transport.put(f, 'content of %s' % f)
1519
>>> orig = ScratchBranch(files=["file1", "file2"])
1520
>>> clone = orig.clone()
1521
>>> if os.name != 'nt':
1522
... os.path.samefile(orig.base, clone.base)
1524
... orig.base == clone.base
1527
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1530
from shutil import copytree
1531
from bzrlib.osutils import mkdtemp
1534
copytree(self.base, base, symlinks=True)
1535
return ScratchBranch(
1536
transport=bzrlib.transport.local.ScratchTransport(base))
1539
######################################################################
1543
def is_control_file(filename):
1544
## FIXME: better check
1545
filename = normpath(filename)
1546
while filename != '':
1547
head, tail = os.path.split(filename)
1548
## mutter('check %r for control file' % ((head, tail),))
1549
if tail == bzrlib.BZRDIR:
1551
if filename == head: