1
# Copyright (C) 2005-2011 Canonical Ltd
1
# Copyright (C) 2005 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from warnings import warn
18
22
from cStringIO import StringIO
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
23
from itertools import chain
28
config as _mod_config,
37
revision as _mod_revision,
43
from bzrlib.config import BranchConfig, TransportConfig
44
from bzrlib.tag import (
53
from bzrlib.decorators import (
58
from bzrlib.hooks import Hooks
59
from bzrlib.inter import InterObject
60
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
61
from bzrlib import registry
62
from bzrlib.symbol_versioning import (
66
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
69
class Branch(controldir.ControlComponent):
26
from bzrlib.inventory import InventoryEntry
27
import bzrlib.inventory as inventory
28
from bzrlib.trace import mutter, note
29
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
30
rename, splitpath, sha_file, appendpath,
32
import bzrlib.errors as errors
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError, UnlistableStore,
36
UnlistableBranch, NoSuchFile)
37
from bzrlib.textui import show_status
38
from bzrlib.revision import Revision
39
from bzrlib.delta import compare_trees
40
from bzrlib.tree import EmptyTree, RevisionTree
41
from bzrlib.inventory import Inventory
42
from bzrlib.store import copy_all
43
from bzrlib.store.compressed_text import CompressedTextStore
44
from bzrlib.store.text import TextStore
45
from bzrlib.store.weave import WeaveStore
46
from bzrlib.testament import Testament
47
import bzrlib.transactions as transactions
48
from bzrlib.transport import Transport, get_transport
53
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
54
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
55
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
56
## TODO: Maybe include checks for common corruption of newlines, etc?
59
# TODO: Some operations like log might retrieve the same revisions
60
# repeatedly to calculate deltas. We could perhaps have a weakref
61
# cache in memory to make this faster. In general anything can be
62
# cached in memory between lock and unlock operations.
64
def find_branch(*ignored, **ignored_too):
65
# XXX: leave this here for about one release, then remove it
66
raise NotImplementedError('find_branch() is not supported anymore, '
67
'please use one of the new branch constructors')
69
######################################################################
70
73
"""Branch holding a history of revisions.
73
Base directory/url of the branch; using control_url and
74
control_transport is more standardized.
75
:ivar hooks: An instance of BranchHooks.
76
:ivar _master_branch_cache: cached result of get_master_branch, see
76
Base directory/url of the branch.
79
# this is really an instance variable - FIXME move it there
84
def control_transport(self):
85
return self._transport
88
def user_transport(self):
89
return self.bzrdir.user_transport
91
80
def __init__(self, *ignored, **ignored_too):
92
self.tags = self._format.make_tags(self)
93
self._revision_history_cache = None
94
self._revision_id_to_revno_cache = None
95
self._partial_revision_id_to_revno_cache = {}
96
self._partial_revision_history_cache = []
97
self._tags_bytes = None
98
self._last_revision_info_cache = None
99
self._master_branch_cache = None
100
self._merge_sorted_revisions_cache = None
102
hooks = Branch.hooks['open']
106
def _open_hook(self):
107
"""Called by init to allow simpler extension of the base class."""
109
def _activate_fallback_location(self, url):
110
"""Activate the branch/repository from url as a fallback repository."""
111
for existing_fallback_repo in self.repository._fallback_repositories:
112
if existing_fallback_repo.user_url == url:
113
# This fallback is already configured. This probably only
114
# happens because BzrDir.sprout is a horrible mess. To avoid
115
# confusing _unstack we don't add this a second time.
116
mutter('duplicate activation of fallback %r on %r', url, self)
118
repo = self._get_fallback_repository(url)
119
if repo.has_same_location(self.repository):
120
raise errors.UnstackableLocationError(self.user_url, url)
121
self.repository.add_fallback_repository(repo)
123
def break_lock(self):
124
"""Break a lock if one is present from another instance.
126
Uses the ui factory to ask for confirmation if the lock may be from
129
This will probe the repository for its lock as well.
131
self.control_files.break_lock()
132
self.repository.break_lock()
133
master = self.get_master_branch()
134
if master is not None:
137
def _check_stackable_repo(self):
138
if not self.repository._format.supports_external_lookups:
139
raise errors.UnstackableRepositoryFormat(self.repository._format,
140
self.repository.base)
142
def _extend_partial_history(self, stop_index=None, stop_revision=None):
143
"""Extend the partial history to include a given index
145
If a stop_index is supplied, stop when that index has been reached.
146
If a stop_revision is supplied, stop when that revision is
147
encountered. Otherwise, stop when the beginning of history is
150
:param stop_index: The index which should be present. When it is
151
present, history extension will stop.
152
:param stop_revision: The revision id which should be present. When
153
it is encountered, history extension will stop.
155
if len(self._partial_revision_history_cache) == 0:
156
self._partial_revision_history_cache = [self.last_revision()]
157
repository._iter_for_revno(
158
self.repository, self._partial_revision_history_cache,
159
stop_index=stop_index, stop_revision=stop_revision)
160
if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
161
self._partial_revision_history_cache.pop()
163
def _get_check_refs(self):
164
"""Get the references needed for check().
168
revid = self.last_revision()
169
return [('revision-existence', revid), ('lefthand-distance', revid)]
172
def open(base, _unsupported=False, possible_transports=None):
173
"""Open the branch rooted at base.
175
For instance, if the branch is at URL/.bzr/branch,
176
Branch.open(URL) -> a Branch instance.
178
control = bzrdir.BzrDir.open(base, _unsupported,
179
possible_transports=possible_transports)
180
return control.open_branch(unsupported=_unsupported)
183
def open_from_transport(transport, name=None, _unsupported=False):
184
"""Open the branch rooted at transport"""
185
control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
186
return control.open_branch(name=name, unsupported=_unsupported)
189
def open_containing(url, possible_transports=None):
81
raise NotImplementedError('The Branch class is abstract')
84
def open_downlevel(base):
85
"""Open a branch which may be of an old format.
87
Only local branches are supported."""
88
return _Branch(get_transport(base), relax_version_check=True)
92
"""Open an existing branch, rooted at 'base' (url)"""
93
t = get_transport(base)
94
mutter("trying to open %r with transport %r", base, t)
98
def open_containing(url):
190
99
"""Open an existing branch which contains url.
192
101
This probes for a branch at url, and searches upwards from there.
194
103
Basically we keep looking up until we find the control directory or
195
104
run into the root. If there isn't one, raises NotBranchError.
196
If there is one and it is either an unrecognised format or an unsupported
197
format, UnknownFormatError or UnsupportedFormatError are raised.
198
If there is one, it is returned, along with the unused portion of url.
200
control, relpath = bzrdir.BzrDir.open_containing(url,
202
return control.open_branch(), relpath
204
def _push_should_merge_tags(self):
205
"""Should _basic_push merge this branch's tags into the target?
207
The default implementation returns False if this branch has no tags,
208
and True the rest of the time. Subclasses may override this.
210
return self.supports_tags() and self.tags.get_tag_dict()
212
def get_config(self):
213
"""Get a bzrlib.config.BranchConfig for this Branch.
215
This can then be used to get and set configuration options for the
218
:return: A bzrlib.config.BranchConfig.
220
return BranchConfig(self)
222
def _get_config(self):
223
"""Get the concrete config for just the config in this branch.
225
This is not intended for client use; see Branch.get_config for the
230
:return: An object supporting get_option and set_option.
232
raise NotImplementedError(self._get_config)
234
def _get_fallback_repository(self, url):
235
"""Get the repository we fallback to at url."""
236
url = urlutils.join(self.base, url)
237
a_branch = Branch.open(url,
238
possible_transports=[self.bzrdir.root_transport])
239
return a_branch.repository
242
def _get_tags_bytes(self):
243
"""Get the bytes of a serialised tags dict.
245
Note that not all branches support tags, nor do all use the same tags
246
logic: this method is specific to BasicTags. Other tag implementations
247
may use the same method name and behave differently, safely, because
248
of the double-dispatch via
249
format.make_tags->tags_instance->get_tags_dict.
251
:return: The bytes of the tags file.
252
:seealso: Branch._set_tags_bytes.
254
if self._tags_bytes is None:
255
self._tags_bytes = self._transport.get_bytes('tags')
256
return self._tags_bytes
258
def _get_nick(self, local=False, possible_transports=None):
259
config = self.get_config()
260
# explicit overrides master, but don't look for master if local is True
261
if not local and not config.has_explicit_nickname():
263
master = self.get_master_branch(possible_transports)
264
if master and self.user_url == master.user_url:
265
raise errors.RecursiveBind(self.user_url)
266
if master is not None:
267
# return the master branch value
269
except errors.RecursiveBind, e:
271
except errors.BzrError, e:
272
# Silently fall back to local implicit nick if the master is
274
mutter("Could not connect to bound branch, "
275
"falling back to local nick.\n " + str(e))
276
return config.get_nickname()
278
def _set_nick(self, nick):
279
self.get_config().set_user_option('nickname', nick, warn_masked=True)
281
nick = property(_get_nick, _set_nick)
284
raise NotImplementedError(self.is_locked)
286
def _lefthand_history(self, revision_id, last_rev=None,
288
if 'evil' in debug.debug_flags:
289
mutter_callsite(4, "_lefthand_history scales with history.")
290
# stop_revision must be a descendant of last_revision
291
graph = self.repository.get_graph()
292
if last_rev is not None:
293
if not graph.is_ancestor(last_rev, revision_id):
294
# our previous tip is not merged into stop_revision
295
raise errors.DivergedBranches(self, other_branch)
296
# make a new revision history from the graph
297
parents_map = graph.get_parent_map([revision_id])
298
if revision_id not in parents_map:
299
raise errors.NoSuchRevision(self, revision_id)
300
current_rev_id = revision_id
302
check_not_reserved_id = _mod_revision.check_not_reserved_id
303
# Do not include ghosts or graph origin in revision_history
304
while (current_rev_id in parents_map and
305
len(parents_map[current_rev_id]) > 0):
306
check_not_reserved_id(current_rev_id)
307
new_history.append(current_rev_id)
308
current_rev_id = parents_map[current_rev_id][0]
309
parents_map = graph.get_parent_map([current_rev_id])
310
new_history.reverse()
313
def lock_write(self, token=None):
314
"""Lock the branch for write operations.
316
:param token: A token to permit reacquiring a previously held and
318
:return: A BranchWriteLockResult.
320
raise NotImplementedError(self.lock_write)
106
t = get_transport(url)
110
except NotBranchError:
112
new_t = t.clone('..')
113
if new_t.base == t.base:
114
# reached the root, whatever that may be
115
raise NotBranchError('%s is not in a branch' % url)
119
def initialize(base):
120
"""Create a new branch, rooted at 'base' (url)"""
121
t = get_transport(base)
122
return _Branch(t, init=True)
124
def setup_caching(self, cache_root):
125
"""Subclasses that care about caching should override this, and set
126
up cached stores located under cache_root.
128
self.cache_root = cache_root
131
class _Branch(Branch):
132
"""A branch stored in the actual filesystem.
134
Note that it's "local" in the context of the filesystem; it doesn't
135
really matter if it's on an nfs/smb/afs/coda/... share, as long as
136
it's writable, and can be accessed via the normal filesystem API.
142
If _lock_mode is true, a positive count of the number of times the
146
Lock object from bzrlib.lock.
148
# We actually expect this class to be somewhat short-lived; part of its
149
# purpose is to try to isolate what bits of the branch logic are tied to
150
# filesystem access, so that in a later step, we can extricate them to
151
# a separarte ("storage") class.
155
_inventory_weave = None
157
# Map some sort of prefix into a namespace
158
# stuff like "revno:10", "revid:", etc.
159
# This should match a prefix with a function which accepts
160
REVISION_NAMESPACES = {}
162
def push_stores(self, branch_to):
163
"""Copy the content of this branches store to branch_to."""
164
if (self._branch_format != branch_to._branch_format
165
or self._branch_format != 4):
166
from bzrlib.fetch import greedy_fetch
167
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
168
self, self._branch_format, branch_to, branch_to._branch_format)
169
greedy_fetch(to_branch=branch_to, from_branch=self,
170
revision=self.last_revision())
173
store_pairs = ((self.text_store, branch_to.text_store),
174
(self.inventory_store, branch_to.inventory_store),
175
(self.revision_store, branch_to.revision_store))
177
for from_store, to_store in store_pairs:
178
copy_all(from_store, to_store)
179
except UnlistableStore:
180
raise UnlistableBranch(from_store)
182
def __init__(self, transport, init=False,
183
relax_version_check=False):
184
"""Create new branch object at a particular location.
186
transport -- A Transport object, defining how to access files.
187
(If a string, transport.transport() will be used to
188
create a Transport object)
190
init -- If True, create new control files in a previously
191
unversioned directory. If False, the branch must already
194
relax_version_check -- If true, the usual check for the branch
195
version is not applied. This is intended only for
196
upgrade/recovery type use; it's not guaranteed that
197
all operations will work on old format branches.
199
In the test suite, creation of new trees is tested using the
200
`ScratchBranch` class.
202
assert isinstance(transport, Transport), \
203
"%r is not a Transport" % transport
204
self._transport = transport
207
self._check_format(relax_version_check)
209
def get_store(name, compressed=True, prefixed=False):
210
# FIXME: This approach of assuming stores are all entirely compressed
211
# or entirely uncompressed is tidy, but breaks upgrade from
212
# some existing branches where there's a mixture; we probably
213
# still want the option to look for both.
214
relpath = self._rel_controlfilename(name)
216
store = CompressedTextStore(self._transport.clone(relpath),
219
store = TextStore(self._transport.clone(relpath),
221
#if self._transport.should_cache():
222
# cache_path = os.path.join(self.cache_root, name)
223
# os.mkdir(cache_path)
224
# store = bzrlib.store.CachedStore(store, cache_path)
226
def get_weave(name, prefixed=False):
227
relpath = self._rel_controlfilename(name)
228
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
229
if self._transport.should_cache():
230
ws.enable_cache = True
233
if self._branch_format == 4:
234
self.inventory_store = get_store('inventory-store')
235
self.text_store = get_store('text-store')
236
self.revision_store = get_store('revision-store')
237
elif self._branch_format == 5:
238
self.control_weaves = get_weave([])
239
self.weave_store = get_weave('weaves')
240
self.revision_store = get_store('revision-store', compressed=False)
241
elif self._branch_format == 6:
242
self.control_weaves = get_weave([])
243
self.weave_store = get_weave('weaves', prefixed=True)
244
self.revision_store = get_store('revision-store', compressed=False,
246
self.revision_store.register_suffix('sig')
247
self._transaction = None
250
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
257
if self._lock_mode or self._lock:
258
# XXX: This should show something every time, and be suitable for
259
# headless operation and embedding
260
warn("branch %r was not explicitly unlocked" % self)
263
# TODO: It might be best to do this somewhere else,
264
# but it is nice for a Branch object to automatically
265
# cache it's information.
266
# Alternatively, we could have the Transport objects cache requests
267
# See the earlier discussion about how major objects (like Branch)
268
# should never expect their __del__ function to run.
269
if hasattr(self, 'cache_root') and self.cache_root is not None:
272
shutil.rmtree(self.cache_root)
275
self.cache_root = None
279
return self._transport.base
282
base = property(_get_base, doc="The URL for the root of this branch.")
284
def _finish_transaction(self):
285
"""Exit the current transaction."""
286
if self._transaction is None:
287
raise errors.LockError('Branch %s is not in a transaction' %
289
transaction = self._transaction
290
self._transaction = None
293
def get_transaction(self):
294
"""Return the current active transaction.
296
If no transaction is active, this returns a passthrough object
297
for which all data is immedaitely flushed and no caching happens.
299
if self._transaction is None:
300
return transactions.PassThroughTransaction()
302
return self._transaction
304
def _set_transaction(self, new_transaction):
305
"""Set a new active transaction."""
306
if self._transaction is not None:
307
raise errors.LockError('Branch %s is in a transaction already.' %
309
self._transaction = new_transaction
311
def lock_write(self):
312
mutter("lock write: %s (%s)", self, self._lock_count)
313
# TODO: Upgrade locking to support using a Transport,
314
# and potentially a remote locking protocol
316
if self._lock_mode != 'w':
317
raise LockError("can't upgrade to a write lock from %r" %
319
self._lock_count += 1
321
self._lock = self._transport.lock_write(
322
self._rel_controlfilename('branch-lock'))
323
self._lock_mode = 'w'
325
self._set_transaction(transactions.PassThroughTransaction())
322
327
def lock_read(self):
323
"""Lock the branch for read operations.
325
:return: A bzrlib.lock.LogicalLockResult.
327
raise NotImplementedError(self.lock_read)
328
mutter("lock read: %s (%s)", self, self._lock_count)
330
assert self._lock_mode in ('r', 'w'), \
331
"invalid lock mode %r" % self._lock_mode
332
self._lock_count += 1
334
self._lock = self._transport.lock_read(
335
self._rel_controlfilename('branch-lock'))
336
self._lock_mode = 'r'
338
self._set_transaction(transactions.ReadOnlyTransaction())
339
# 5K may be excessive, but hey, its a knob.
340
self.get_transaction().set_cache_size(5000)
329
342
def unlock(self):
330
raise NotImplementedError(self.unlock)
332
def peek_lock_mode(self):
333
"""Return lock mode for the Branch: 'r', 'w' or None"""
334
raise NotImplementedError(self.peek_lock_mode)
336
def get_physical_lock_status(self):
337
raise NotImplementedError(self.get_physical_lock_status)
340
def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
341
"""Return the revision_id for a dotted revno.
343
:param revno: a tuple like (1,) or (1,1,2)
344
:param _cache_reverse: a private parameter enabling storage
345
of the reverse mapping in a top level cache. (This should
346
only be done in selective circumstances as we want to
347
avoid having the mapping cached multiple times.)
348
:return: the revision_id
349
:raises errors.NoSuchRevision: if the revno doesn't exist
351
rev_id = self._do_dotted_revno_to_revision_id(revno)
353
self._partial_revision_id_to_revno_cache[rev_id] = revno
356
def _do_dotted_revno_to_revision_id(self, revno):
357
"""Worker function for dotted_revno_to_revision_id.
359
Subclasses should override this if they wish to
360
provide a more efficient implementation.
363
return self.get_rev_id(revno[0])
364
revision_id_to_revno = self.get_revision_id_to_revno_map()
365
revision_ids = [revision_id for revision_id, this_revno
366
in revision_id_to_revno.iteritems()
367
if revno == this_revno]
368
if len(revision_ids) == 1:
369
return revision_ids[0]
371
revno_str = '.'.join(map(str, revno))
372
raise errors.NoSuchRevision(self, revno_str)
375
def revision_id_to_dotted_revno(self, revision_id):
376
"""Given a revision id, return its dotted revno.
378
:return: a tuple like (1,) or (400,1,3).
380
return self._do_revision_id_to_dotted_revno(revision_id)
382
def _do_revision_id_to_dotted_revno(self, revision_id):
383
"""Worker function for revision_id_to_revno."""
384
# Try the caches if they are loaded
385
result = self._partial_revision_id_to_revno_cache.get(revision_id)
386
if result is not None:
388
if self._revision_id_to_revno_cache:
389
result = self._revision_id_to_revno_cache.get(revision_id)
391
raise errors.NoSuchRevision(self, revision_id)
392
# Try the mainline as it's optimised
394
revno = self.revision_id_to_revno(revision_id)
396
except errors.NoSuchRevision:
397
# We need to load and use the full revno map after all
398
result = self.get_revision_id_to_revno_map().get(revision_id)
400
raise errors.NoSuchRevision(self, revision_id)
404
def get_revision_id_to_revno_map(self):
405
"""Return the revision_id => dotted revno map.
407
This will be regenerated on demand, but will be cached.
409
:return: A dictionary mapping revision_id => dotted revno.
410
This dictionary should not be modified by the caller.
412
if self._revision_id_to_revno_cache is not None:
413
mapping = self._revision_id_to_revno_cache
415
mapping = self._gen_revno_map()
416
self._cache_revision_id_to_revno(mapping)
417
# TODO: jam 20070417 Since this is being cached, should we be returning
419
# I would rather not, and instead just declare that users should not
420
# modify the return value.
423
def _gen_revno_map(self):
424
"""Create a new mapping from revision ids to dotted revnos.
426
Dotted revnos are generated based on the current tip in the revision
428
This is the worker function for get_revision_id_to_revno_map, which
429
just caches the return value.
431
:return: A dictionary mapping revision_id => dotted revno.
433
revision_id_to_revno = dict((rev_id, revno)
434
for rev_id, depth, revno, end_of_merge
435
in self.iter_merge_sorted_revisions())
436
return revision_id_to_revno
439
def iter_merge_sorted_revisions(self, start_revision_id=None,
440
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
441
"""Walk the revisions for a branch in merge sorted order.
443
Merge sorted order is the output from a merge-aware,
444
topological sort, i.e. all parents come before their
445
children going forward; the opposite for reverse.
447
:param start_revision_id: the revision_id to begin walking from.
448
If None, the branch tip is used.
449
:param stop_revision_id: the revision_id to terminate the walk
450
after. If None, the rest of history is included.
451
:param stop_rule: if stop_revision_id is not None, the precise rule
452
to use for termination:
454
* 'exclude' - leave the stop revision out of the result (default)
455
* 'include' - the stop revision is the last item in the result
456
* 'with-merges' - include the stop revision and all of its
457
merged revisions in the result
458
* 'with-merges-without-common-ancestry' - filter out revisions
459
that are in both ancestries
460
:param direction: either 'reverse' or 'forward':
462
* reverse means return the start_revision_id first, i.e.
463
start at the most recent revision and go backwards in history
464
* forward returns tuples in the opposite order to reverse.
465
Note in particular that forward does *not* do any intelligent
466
ordering w.r.t. depth as some clients of this API may like.
467
(If required, that ought to be done at higher layers.)
469
:return: an iterator over (revision_id, depth, revno, end_of_merge)
472
* revision_id: the unique id of the revision
473
* depth: How many levels of merging deep this node has been
475
* revno_sequence: This field provides a sequence of
476
revision numbers for all revisions. The format is:
477
(REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
478
branch that the revno is on. From left to right the REVNO numbers
479
are the sequence numbers within that branch of the revision.
480
* end_of_merge: When True the next node (earlier in history) is
481
part of a different merge.
483
# Note: depth and revno values are in the context of the branch so
484
# we need the full graph to get stable numbers, regardless of the
486
if self._merge_sorted_revisions_cache is None:
487
last_revision = self.last_revision()
488
known_graph = self.repository.get_known_graph_ancestry(
490
self._merge_sorted_revisions_cache = known_graph.merge_sort(
492
filtered = self._filter_merge_sorted_revisions(
493
self._merge_sorted_revisions_cache, start_revision_id,
494
stop_revision_id, stop_rule)
495
# Make sure we don't return revisions that are not part of the
496
# start_revision_id ancestry.
497
filtered = self._filter_start_non_ancestors(filtered)
498
if direction == 'reverse':
500
if direction == 'forward':
501
return reversed(list(filtered))
503
raise ValueError('invalid direction %r' % direction)
505
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
506
start_revision_id, stop_revision_id, stop_rule):
507
"""Iterate over an inclusive range of sorted revisions."""
508
rev_iter = iter(merge_sorted_revisions)
509
if start_revision_id is not None:
510
for node in rev_iter:
512
if rev_id != start_revision_id:
343
mutter("unlock: %s (%s)", self, self._lock_count)
344
if not self._lock_mode:
345
raise LockError('branch %r is not locked' % (self))
347
if self._lock_count > 1:
348
self._lock_count -= 1
350
self._finish_transaction()
353
self._lock_mode = self._lock_count = None
355
def abspath(self, name):
356
"""Return absolute filename for something in the branch
358
XXX: Robert Collins 20051017 what is this used for? why is it a branch
359
method and not a tree method.
361
return self._transport.abspath(name)
363
def _rel_controlfilename(self, file_or_path):
364
if isinstance(file_or_path, basestring):
365
file_or_path = [file_or_path]
366
return [bzrlib.BZRDIR] + file_or_path
368
def controlfilename(self, file_or_path):
369
"""Return location relative to branch."""
370
return self._transport.abspath(self._rel_controlfilename(file_or_path))
373
def controlfile(self, file_or_path, mode='r'):
374
"""Open a control file for this branch.
376
There are two classes of file in the control directory: text
377
and binary. binary files are untranslated byte streams. Text
378
control files are stored with Unix newlines and in UTF-8, even
379
if the platform or locale defaults are different.
381
Controlfiles should almost never be opened in write mode but
382
rather should be atomically copied and replaced using atomicfile.
386
relpath = self._rel_controlfilename(file_or_path)
387
#TODO: codecs.open() buffers linewise, so it was overloaded with
388
# a much larger buffer, do we need to do the same for getreader/getwriter?
390
return self._transport.get(relpath)
392
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
394
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
396
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
398
raise BzrError("invalid controlfile mode %r" % mode)
400
def put_controlfile(self, path, f, encode=True):
401
"""Write an entry as a controlfile.
403
:param path: The path to put the file, relative to the .bzr control
405
:param f: A file-like or string object whose contents should be copied.
406
:param encode: If true, encode the contents as utf-8
408
self.put_controlfiles([(path, f)], encode=encode)
410
def put_controlfiles(self, files, encode=True):
411
"""Write several entries as controlfiles.
413
:param files: A list of [(path, file)] pairs, where the path is the directory
414
underneath the bzr control directory
415
:param encode: If true, encode the contents as utf-8
419
for path, f in files:
421
if isinstance(f, basestring):
422
f = f.encode('utf-8', 'replace')
515
# The decision to include the start or not
516
# depends on the stop_rule if a stop is provided
517
# so pop this node back into the iterator
518
rev_iter = chain(iter([node]), rev_iter)
520
if stop_revision_id is None:
522
for node in rev_iter:
524
yield (rev_id, node.merge_depth, node.revno,
526
elif stop_rule == 'exclude':
527
for node in rev_iter:
529
if rev_id == stop_revision_id:
531
yield (rev_id, node.merge_depth, node.revno,
533
elif stop_rule == 'include':
534
for node in rev_iter:
536
yield (rev_id, node.merge_depth, node.revno,
538
if rev_id == stop_revision_id:
540
elif stop_rule == 'with-merges-without-common-ancestry':
541
# We want to exclude all revisions that are already part of the
542
# stop_revision_id ancestry.
543
graph = self.repository.get_graph()
544
ancestors = graph.find_unique_ancestors(start_revision_id,
546
for node in rev_iter:
548
if rev_id not in ancestors:
550
yield (rev_id, node.merge_depth, node.revno,
552
elif stop_rule == 'with-merges':
553
stop_rev = self.repository.get_revision(stop_revision_id)
554
if stop_rev.parent_ids:
555
left_parent = stop_rev.parent_ids[0]
424
f = codecs.getwriter('utf-8')(f, errors='replace')
425
path = self._rel_controlfilename(path)
426
ctrl_files.append((path, f))
427
self._transport.put_multi(ctrl_files)
429
def _make_control(self):
430
from bzrlib.inventory import Inventory
431
from bzrlib.weavefile import write_weave_v5
432
from bzrlib.weave import Weave
434
# Create an empty inventory
436
# if we want per-tree root ids then this is the place to set
437
# them; they're not needed for now and so ommitted for
439
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
440
empty_inv = sio.getvalue()
442
bzrlib.weavefile.write_weave_v5(Weave(), sio)
443
empty_weave = sio.getvalue()
445
dirs = [[], 'revision-store', 'weaves']
447
"This is a Bazaar-NG control directory.\n"
448
"Do not change any files in this directory.\n"),
449
('branch-format', BZR_BRANCH_FORMAT_6),
450
('revision-history', ''),
453
('pending-merges', ''),
454
('inventory', empty_inv),
455
('inventory.weave', empty_weave),
456
('ancestry.weave', empty_weave)
458
cfn = self._rel_controlfilename
459
self._transport.mkdir_multi([cfn(d) for d in dirs])
460
self.put_controlfiles(files)
461
mutter('created control directory in ' + self._transport.base)
463
def _check_format(self, relax_version_check):
464
"""Check this branch format is supported.
466
The format level is stored, as an integer, in
467
self._branch_format for code that needs to check it later.
469
In the future, we might need different in-memory Branch
470
classes to support downlevel branches. But not yet.
473
fmt = self.controlfile('branch-format', 'r').read()
475
raise NotBranchError(self.base)
476
mutter("got branch format %r", fmt)
477
if fmt == BZR_BRANCH_FORMAT_6:
478
self._branch_format = 6
479
elif fmt == BZR_BRANCH_FORMAT_5:
480
self._branch_format = 5
481
elif fmt == BZR_BRANCH_FORMAT_4:
482
self._branch_format = 4
484
if (not relax_version_check
485
and self._branch_format not in (5, 6)):
486
raise errors.UnsupportedFormatError(
487
'sorry, branch format %r not supported' % fmt,
488
['use a different bzr version',
489
'or remove the .bzr directory'
490
' and "bzr init" again'])
492
def get_root_id(self):
493
"""Return the id of this branches root"""
494
inv = self.read_working_inventory()
495
return inv.root.file_id
497
def set_root_id(self, file_id):
498
inv = self.read_working_inventory()
499
orig_root_id = inv.root.file_id
500
del inv._byid[inv.root.file_id]
501
inv.root.file_id = file_id
502
inv._byid[inv.root.file_id] = inv.root
505
if entry.parent_id in (None, orig_root_id):
506
entry.parent_id = inv.root.file_id
507
self._write_inventory(inv)
509
def read_working_inventory(self):
510
"""Read the working inventory."""
513
# ElementTree does its own conversion from UTF-8, so open in
515
f = self.controlfile('inventory', 'rb')
516
return bzrlib.xml5.serializer_v5.read_inventory(f)
521
def _write_inventory(self, inv):
522
"""Update the working inventory.
524
That is to say, the inventory describing changes underway, that
525
will be committed to the next revision.
527
from cStringIO import StringIO
531
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
533
# Transport handles atomicity
534
self.put_controlfile('inventory', sio)
538
mutter('wrote working inventory')
540
inventory = property(read_working_inventory, _write_inventory, None,
541
"""Inventory for the working copy.""")
543
def add(self, files, ids=None):
544
"""Make files versioned.
546
Note that the command line normally calls smart_add instead,
547
which can automatically recurse.
549
This puts the files in the Added state, so that they will be
550
recorded by the next commit.
553
List of paths to add, relative to the base of the tree.
556
If set, use these instead of automatically generated ids.
557
Must be the same length as the list of files, but may
558
contain None for ids that are to be autogenerated.
560
TODO: Perhaps have an option to add the ids even if the files do
563
TODO: Perhaps yield the ids and paths as they're added.
565
# TODO: Re-adding a file that is removed in the working copy
566
# should probably put it back with the previous ID.
567
if isinstance(files, basestring):
568
assert(ids is None or isinstance(ids, basestring))
574
ids = [None] * len(files)
576
assert(len(ids) == len(files))
580
inv = self.read_working_inventory()
581
for f,file_id in zip(files, ids):
582
if is_control_file(f):
583
raise BzrError("cannot add control file %s" % quotefn(f))
588
raise BzrError("cannot add top-level %r" % f)
590
fullpath = os.path.normpath(self.abspath(f))
593
kind = file_kind(fullpath)
595
# maybe something better?
596
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
598
if not InventoryEntry.versionable_kind(kind):
599
raise BzrError('cannot add: not a versionable file ('
600
'i.e. regular file, symlink or directory): %s' % quotefn(f))
603
file_id = gen_file_id(f)
604
inv.add_path(f, kind=kind, file_id=file_id)
606
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
608
self._write_inventory(inv)
613
def print_file(self, file, revno):
614
"""Print `file` to stdout."""
617
tree = self.revision_tree(self.get_rev_id(revno))
618
# use inventory as it was in that revision
619
file_id = tree.inventory.path2id(file)
621
raise BzrError("%r is not present in revision %s" % (file, revno))
622
tree.print_file(file_id)
627
def remove(self, files, verbose=False):
628
"""Mark nominated files for removal from the inventory.
630
This does not remove their text. This does not run on
632
TODO: Refuse to remove modified files unless --force is given?
634
TODO: Do something useful with directories.
636
TODO: Should this remove the text or not? Tough call; not
637
removing may be useful and the user can just use use rm, and
638
is the opposite of add. Removing it is consistent with most
639
other tools. Maybe an option.
641
## TODO: Normalize names
642
## TODO: Remove nested loops; better scalability
643
if isinstance(files, basestring):
649
tree = self.working_tree()
652
# do this before any modifications
656
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
657
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
659
# having remove it, it must be either ignored or unknown
660
if tree.is_ignored(f):
664
show_status(new_status, inv[fid].kind, quotefn(f))
667
self._write_inventory(inv)
671
# FIXME: this doesn't need to be a branch method
672
def set_inventory(self, new_inventory_list):
673
from bzrlib.inventory import Inventory, InventoryEntry
674
inv = Inventory(self.get_root_id())
675
for path, file_id, parent, kind in new_inventory_list:
676
name = os.path.basename(path)
679
# fixme, there should be a factory function inv,add_??
680
if kind == 'directory':
681
inv.add(inventory.InventoryDirectory(file_id, name, parent))
683
inv.add(inventory.InventoryFile(file_id, name, parent))
684
elif kind == 'symlink':
685
inv.add(inventory.InventoryLink(file_id, name, parent))
557
left_parent = _mod_revision.NULL_REVISION
558
# left_parent is the actual revision we want to stop logging at,
559
# since we want to show the merged revisions after the stop_rev too
560
reached_stop_revision_id = False
561
revision_id_whitelist = []
562
for node in rev_iter:
564
if rev_id == left_parent:
565
# reached the left parent after the stop_revision
567
if (not reached_stop_revision_id or
568
rev_id in revision_id_whitelist):
569
yield (rev_id, node.merge_depth, node.revno,
571
if reached_stop_revision_id or rev_id == stop_revision_id:
572
# only do the merged revs of rev_id from now on
573
rev = self.repository.get_revision(rev_id)
575
reached_stop_revision_id = True
576
revision_id_whitelist.extend(rev.parent_ids)
578
raise ValueError('invalid stop_rule %r' % stop_rule)
580
def _filter_start_non_ancestors(self, rev_iter):
581
# If we started from a dotted revno, we want to consider it as a tip
582
# and don't want to yield revisions that are not part of its
583
# ancestry. Given the order guaranteed by the merge sort, we will see
584
# uninteresting descendants of the first parent of our tip before the
586
first = rev_iter.next()
587
(rev_id, merge_depth, revno, end_of_merge) = first
590
# We start at a mainline revision so by definition, all others
591
# revisions in rev_iter are ancestors
592
for node in rev_iter:
597
pmap = self.repository.get_parent_map([rev_id])
598
parents = pmap.get(rev_id, [])
600
whitelist.update(parents)
602
# If there is no parents, there is nothing of interest left
604
# FIXME: It's hard to test this scenario here as this code is never
605
# called in that case. -- vila 20100322
608
for (rev_id, merge_depth, revno, end_of_merge) in rev_iter:
610
if rev_id in whitelist:
611
pmap = self.repository.get_parent_map([rev_id])
612
parents = pmap.get(rev_id, [])
613
whitelist.remove(rev_id)
614
whitelist.update(parents)
616
# We've reached the mainline, there is nothing left to
620
# A revision that is not part of the ancestry of our
623
yield (rev_id, merge_depth, revno, end_of_merge)
625
def leave_lock_in_place(self):
626
"""Tell this branch object not to release the physical lock when this
629
If lock_write doesn't return a token, then this method is not supported.
631
self.control_files.leave_in_place()
633
def dont_leave_lock_in_place(self):
634
"""Tell this branch object to release the physical lock when this
635
object is unlocked, even if it didn't originally acquire it.
637
If lock_write doesn't return a token, then this method is not supported.
639
self.control_files.dont_leave_in_place()
641
def bind(self, other):
642
"""Bind the local branch the other branch.
644
:param other: The branch to bind to
647
raise errors.UpgradeRequired(self.user_url)
649
def set_append_revisions_only(self, enabled):
650
if not self._format.supports_set_append_revisions_only():
651
raise errors.UpgradeRequired(self.user_url)
656
self.get_config().set_user_option('append_revisions_only', value,
659
def set_reference_info(self, file_id, tree_path, branch_location):
660
"""Set the branch location to use for a tree reference."""
661
raise errors.UnsupportedOperation(self.set_reference_info, self)
663
def get_reference_info(self, file_id):
664
"""Get the tree_path and branch_location for a tree reference."""
665
raise errors.UnsupportedOperation(self.get_reference_info, self)
668
def fetch(self, from_branch, last_revision=None, limit=None):
669
"""Copy revisions from from_branch into this branch.
671
:param from_branch: Where to copy from.
672
:param last_revision: What revision to stop at (None for at the end
674
:param limit: Optional rough limit of revisions to fetch
677
return InterBranch.get(from_branch, self).fetch(last_revision, limit=limit)
679
def get_bound_location(self):
680
"""Return the URL of the branch we are bound to.
682
Older format branches cannot bind, please be sure to use a metadir
687
def get_old_bound_location(self):
688
"""Return the URL of the branch we used to be bound to
690
raise errors.UpgradeRequired(self.user_url)
692
def get_commit_builder(self, parents, config=None, timestamp=None,
693
timezone=None, committer=None, revprops=None,
694
revision_id=None, lossy=False):
695
"""Obtain a CommitBuilder for this branch.
697
:param parents: Revision ids of the parents of the new revision.
698
:param config: Optional configuration to use.
699
:param timestamp: Optional timestamp recorded for commit.
700
:param timezone: Optional timezone for timestamp.
701
:param committer: Optional committer to set for commit.
702
:param revprops: Optional dictionary of revision properties.
703
:param revision_id: Optional revision id.
704
:param lossy: Whether to discard data that can not be natively
705
represented, when pushing to a foreign VCS
709
config = self.get_config()
711
return self.repository.get_commit_builder(self, parents, config,
712
timestamp, timezone, committer, revprops, revision_id,
715
def get_master_branch(self, possible_transports=None):
716
"""Return the branch we are bound to.
718
:return: Either a Branch, or None
687
raise BzrError("unknown kind %r" % kind)
688
self._write_inventory(inv)
691
"""Return all unknown files.
693
These are files in the working directory that are not versioned or
694
control files or ignored.
696
>>> b = ScratchBranch(files=['foo', 'foo~'])
697
>>> list(b.unknowns())
700
>>> list(b.unknowns())
703
>>> list(b.unknowns())
706
return self.working_tree().unknowns()
709
def append_revision(self, *revision_ids):
710
for revision_id in revision_ids:
711
mutter("add {%s} to revision-history" % revision_id)
714
rev_history = self.revision_history()
715
rev_history.extend(revision_ids)
716
self.put_controlfile('revision-history', '\n'.join(rev_history))
720
def has_revision(self, revision_id):
721
"""True if this branch has a copy of the revision.
723
This does not necessarily imply the revision is merge
724
or on the mainline."""
725
return (revision_id is None
726
or self.revision_store.has_id(revision_id))
728
def get_revision_xml_file(self, revision_id):
729
"""Return XML file object for revision object."""
730
if not revision_id or not isinstance(revision_id, basestring):
731
raise InvalidRevisionId(revision_id)
736
return self.revision_store.get(revision_id)
737
except (IndexError, KeyError):
738
raise bzrlib.errors.NoSuchRevision(self, revision_id)
743
get_revision_xml = get_revision_xml_file
745
def get_revision_xml(self, revision_id):
746
return self.get_revision_xml_file(revision_id).read()
749
def get_revision(self, revision_id):
750
"""Return the Revision object for a named revision"""
751
xml_file = self.get_revision_xml_file(revision_id)
754
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
755
except SyntaxError, e:
756
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
760
assert r.revision_id == revision_id
722
763
def get_revision_delta(self, revno):
723
764
"""Return the delta for one revision.
1010
906
That is equivalent to the number of revisions committed to
1013
return self.last_revision_info()[0]
909
return len(self.revision_history())
1016
"""Older format branches cannot bind or unbind."""
1017
raise errors.UpgradeRequired(self.user_url)
1019
912
def last_revision(self):
1020
"""Return last revision id, or NULL_REVISION."""
1021
return self.last_revision_info()[1]
1024
def last_revision_info(self):
1025
"""Return information about the last revision.
1027
:return: A tuple (revno, revision_id).
1029
if self._last_revision_info_cache is None:
1030
self._last_revision_info_cache = self._read_last_revision_info()
1031
return self._last_revision_info_cache
1033
def _read_last_revision_info(self):
1034
raise NotImplementedError(self._read_last_revision_info)
1036
@deprecated_method(deprecated_in((2, 4, 0)))
1037
def import_last_revision_info(self, source_repo, revno, revid):
1038
"""Set the last revision info, importing from another repo if necessary.
1040
:param source_repo: Source repository to optionally fetch from
1041
:param revno: Revision number of the new tip
1042
:param revid: Revision id of the new tip
1044
if not self.repository.has_same_location(source_repo):
1045
self.repository.fetch(source_repo, revision_id=revid)
1046
self.set_last_revision_info(revno, revid)
1048
def import_last_revision_info_and_tags(self, source, revno, revid,
1050
"""Set the last revision info, importing from another repo if necessary.
1052
This is used by the bound branch code to upload a revision to
1053
the master branch first before updating the tip of the local branch.
1054
Revisions referenced by source's tags are also transferred.
1056
:param source: Source branch to optionally fetch from
1057
:param revno: Revision number of the new tip
1058
:param revid: Revision id of the new tip
1059
:param lossy: Whether to discard metadata that can not be
1060
natively represented
1061
:return: Tuple with the new revision number and revision id
1062
(should only be different from the arguments when lossy=True)
1064
if not self.repository.has_same_location(source.repository):
1065
self.fetch(source, revid)
1066
self.set_last_revision_info(revno, revid)
1067
return (revno, revid)
913
"""Return last patch hash, or None if no history.
915
ph = self.revision_history()
922
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
923
"""Return a list of new revisions that would perfectly fit.
925
If self and other have not diverged, return a list of the revisions
926
present in other, but missing from self.
928
>>> from bzrlib.commit import commit
929
>>> bzrlib.trace.silent = True
930
>>> br1 = ScratchBranch()
931
>>> br2 = ScratchBranch()
932
>>> br1.missing_revisions(br2)
934
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
935
>>> br1.missing_revisions(br2)
937
>>> br2.missing_revisions(br1)
939
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
940
>>> br1.missing_revisions(br2)
942
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
943
>>> br1.missing_revisions(br2)
945
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
946
>>> br1.missing_revisions(br2)
947
Traceback (most recent call last):
948
DivergedBranches: These branches have diverged.
950
# FIXME: If the branches have diverged, but the latest
951
# revision in this branch is completely merged into the other,
952
# then we should still be able to pull.
953
self_history = self.revision_history()
954
self_len = len(self_history)
955
other_history = other.revision_history()
956
other_len = len(other_history)
957
common_index = min(self_len, other_len) -1
958
if common_index >= 0 and \
959
self_history[common_index] != other_history[common_index]:
960
raise DivergedBranches(self, other)
962
if stop_revision is None:
963
stop_revision = other_len
965
assert isinstance(stop_revision, int)
966
if stop_revision > other_len:
967
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
968
return other_history[self_len:stop_revision]
970
def update_revisions(self, other, stop_revision=None):
971
"""Pull in new perfect-fit revisions."""
972
from bzrlib.fetch import greedy_fetch
973
from bzrlib.revision import get_intervening_revisions
974
if stop_revision is None:
975
stop_revision = other.last_revision()
976
if (stop_revision is not None and
977
stop_revision in self.revision_history()):
979
greedy_fetch(to_branch=self, from_branch=other,
980
revision=stop_revision)
981
pullable_revs = self.missing_revisions(
982
other, other.revision_id_to_revno(stop_revision))
984
greedy_fetch(to_branch=self,
986
revision=pullable_revs[-1])
987
self.append_revision(*pullable_revs)
989
def commit(self, *args, **kw):
990
from bzrlib.commit import Commit
991
Commit().commit(self, *args, **kw)
1069
993
def revision_id_to_revno(self, revision_id):
1070
994
"""Given a revision id, return its revno"""
1071
if _mod_revision.is_null(revision_id):
995
if revision_id is None:
1073
997
history = self.revision_history()
1075
999
return history.index(revision_id) + 1
1076
1000
except ValueError:
1077
raise errors.NoSuchRevision(self, revision_id)
1001
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1080
1003
def get_rev_id(self, revno, history=None):
1081
1004
"""Find the revision id of the specified revno."""
1083
return _mod_revision.NULL_REVISION
1084
last_revno, last_revid = self.last_revision_info()
1085
if revno == last_revno:
1087
if revno <= 0 or revno > last_revno:
1088
raise errors.NoSuchRevision(self, revno)
1089
distance_from_last = last_revno - revno
1090
if len(self._partial_revision_history_cache) <= distance_from_last:
1091
self._extend_partial_history(distance_from_last)
1092
return self._partial_revision_history_cache[distance_from_last]
1094
def pull(self, source, overwrite=False, stop_revision=None,
1095
possible_transports=None, *args, **kwargs):
1096
"""Mirror source into this branch.
1098
This branch is considered to be 'local', having low latency.
1100
:returns: PullResult instance
1102
return InterBranch.get(source, self).pull(overwrite=overwrite,
1103
stop_revision=stop_revision,
1104
possible_transports=possible_transports, *args, **kwargs)
1106
def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1108
"""Mirror this branch into target.
1110
This branch is considered to be 'local', having low latency.
1112
return InterBranch.get(self, target).push(overwrite, stop_revision,
1113
lossy, *args, **kwargs)
1008
history = self.revision_history()
1009
elif revno <= 0 or revno > len(history):
1010
raise bzrlib.errors.NoSuchRevision(self, revno)
1011
return history[revno - 1]
1013
def revision_tree(self, revision_id):
1014
"""Return Tree for a revision on this branch.
1016
`revision_id` may be None for the null revision, in which case
1017
an `EmptyTree` is returned."""
1018
# TODO: refactor this to use an existing revision object
1019
# so we don't need to read it in twice.
1020
if revision_id == None:
1023
inv = self.get_revision_inventory(revision_id)
1024
return RevisionTree(self.weave_store, inv, revision_id)
1026
def working_tree(self):
1027
"""Return a `Tree` for the working copy."""
1028
from bzrlib.workingtree import WorkingTree
1029
# TODO: In the future, WorkingTree should utilize Transport
1030
# RobertCollins 20051003 - I don't think it should - working trees are
1031
# much more complex to keep consistent than our careful .bzr subset.
1032
# instead, we should say that working trees are local only, and optimise
1034
return WorkingTree(self.base, branch=self)
1115
1037
def basis_tree(self):
1116
"""Return `Tree` object for last revision."""
1117
return self.repository.revision_tree(self.last_revision())
1038
"""Return `Tree` object for last revision.
1040
If there are no revisions yet, return an `EmptyTree`.
1042
return self.revision_tree(self.last_revision())
1045
def rename_one(self, from_rel, to_rel):
1048
This can change the directory or the filename or both.
1052
tree = self.working_tree()
1053
inv = tree.inventory
1054
if not tree.has_filename(from_rel):
1055
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1056
if tree.has_filename(to_rel):
1057
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1059
file_id = inv.path2id(from_rel)
1061
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1063
if inv.path2id(to_rel):
1064
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1066
to_dir, to_tail = os.path.split(to_rel)
1067
to_dir_id = inv.path2id(to_dir)
1068
if to_dir_id == None and to_dir != '':
1069
raise BzrError("can't determine destination directory id for %r" % to_dir)
1071
mutter("rename_one:")
1072
mutter(" file_id {%s}" % file_id)
1073
mutter(" from_rel %r" % from_rel)
1074
mutter(" to_rel %r" % to_rel)
1075
mutter(" to_dir %r" % to_dir)
1076
mutter(" to_dir_id {%s}" % to_dir_id)
1078
inv.rename(file_id, to_dir_id, to_tail)
1080
from_abs = self.abspath(from_rel)
1081
to_abs = self.abspath(to_rel)
1083
rename(from_abs, to_abs)
1085
raise BzrError("failed to rename %r to %r: %s"
1086
% (from_abs, to_abs, e[1]),
1087
["rename rolled back"])
1089
self._write_inventory(inv)
1094
def move(self, from_paths, to_name):
1097
to_name must exist as a versioned directory.
1099
If to_name exists and is a directory, the files are moved into
1100
it, keeping their old names. If it is a directory,
1102
Note that to_name is only the last component of the new name;
1103
this doesn't change the directory.
1105
This returns a list of (from_path, to_path) pairs for each
1106
entry that is moved.
1111
## TODO: Option to move IDs only
1112
assert not isinstance(from_paths, basestring)
1113
tree = self.working_tree()
1114
inv = tree.inventory
1115
to_abs = self.abspath(to_name)
1116
if not isdir(to_abs):
1117
raise BzrError("destination %r is not a directory" % to_abs)
1118
if not tree.has_filename(to_name):
1119
raise BzrError("destination %r not in working directory" % to_abs)
1120
to_dir_id = inv.path2id(to_name)
1121
if to_dir_id == None and to_name != '':
1122
raise BzrError("destination %r is not a versioned directory" % to_name)
1123
to_dir_ie = inv[to_dir_id]
1124
if to_dir_ie.kind not in ('directory', 'root_directory'):
1125
raise BzrError("destination %r is not a directory" % to_abs)
1127
to_idpath = inv.get_idpath(to_dir_id)
1129
for f in from_paths:
1130
if not tree.has_filename(f):
1131
raise BzrError("%r does not exist in working tree" % f)
1132
f_id = inv.path2id(f)
1134
raise BzrError("%r is not versioned" % f)
1135
name_tail = splitpath(f)[-1]
1136
dest_path = appendpath(to_name, name_tail)
1137
if tree.has_filename(dest_path):
1138
raise BzrError("destination %r already exists" % dest_path)
1139
if f_id in to_idpath:
1140
raise BzrError("can't move %r to a subdirectory of itself" % f)
1142
# OK, so there's a race here, it's possible that someone will
1143
# create a file in this interval and then the rename might be
1144
# left half-done. But we should have caught most problems.
1146
for f in from_paths:
1147
name_tail = splitpath(f)[-1]
1148
dest_path = appendpath(to_name, name_tail)
1149
result.append((f, dest_path))
1150
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1152
rename(self.abspath(f), self.abspath(dest_path))
1154
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1155
["rename rolled back"])
1157
self._write_inventory(inv)
1164
def revert(self, filenames, old_tree=None, backups=True):
1165
"""Restore selected files to the versions from a previous tree.
1168
If true (default) backups are made of files before
1171
from bzrlib.errors import NotVersionedError, BzrError
1172
from bzrlib.atomicfile import AtomicFile
1173
from bzrlib.osutils import backup_file
1175
inv = self.read_working_inventory()
1176
if old_tree is None:
1177
old_tree = self.basis_tree()
1178
old_inv = old_tree.inventory
1181
for fn in filenames:
1182
file_id = inv.path2id(fn)
1184
raise NotVersionedError("not a versioned file", fn)
1185
if not old_inv.has_id(file_id):
1186
raise BzrError("file not present in old tree", fn, file_id)
1187
nids.append((fn, file_id))
1189
# TODO: Rename back if it was previously at a different location
1191
# TODO: If given a directory, restore the entire contents from
1192
# the previous version.
1194
# TODO: Make a backup to a temporary file.
1196
# TODO: If the file previously didn't exist, delete it?
1197
for fn, file_id in nids:
1200
f = AtomicFile(fn, 'wb')
1202
f.write(old_tree.get_file(file_id).read())
1208
def pending_merges(self):
1209
"""Return a list of pending merges.
1211
These are revisions that have been merged into the working
1212
directory but not yet committed.
1214
cfn = self._rel_controlfilename('pending-merges')
1215
if not self._transport.has(cfn):
1218
for l in self.controlfile('pending-merges', 'r').readlines():
1219
p.append(l.rstrip('\n'))
1223
def add_pending_merge(self, *revision_ids):
1224
# TODO: Perhaps should check at this point that the
1225
# history of the revision is actually present?
1226
p = self.pending_merges()
1228
for rev_id in revision_ids:
1234
self.set_pending_merges(p)
1236
def set_pending_merges(self, rev_list):
1239
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1119
1244
def get_parent(self):
1120
1245
"""Return the parent location of the branch.
1122
This is the default location for pull/missing. The usual
1247
This is the default location for push/pull/missing. The usual
1123
1248
pattern is that the user can override it by specifying a
1126
parent = self._get_parent_location()
1129
# This is an old-format absolute path to a local branch
1130
# turn it into a url
1131
if parent.startswith('/'):
1132
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1252
_locs = ['parent', 'pull', 'x-pull']
1255
return self.controlfile(l, 'r').read().strip('\n')
1257
if e.errno != errno.ENOENT:
1262
def set_parent(self, url):
1263
# TODO: Maybe delete old location files?
1264
from bzrlib.atomicfile import AtomicFile
1134
return urlutils.join(self.base[:-1], parent)
1135
except errors.InvalidURLJoin, e:
1136
raise errors.InaccessibleParent(parent, self.user_url)
1138
def _get_parent_location(self):
1139
raise NotImplementedError(self._get_parent_location)
1141
def _set_config_location(self, name, url, config=None,
1142
make_relative=False):
1144
config = self.get_config()
1148
url = urlutils.relative_url(self.base, url)
1149
config.set_user_option(name, url, warn_masked=True)
1151
def _get_config_location(self, name, config=None):
1153
config = self.get_config()
1154
location = config.get_user_option(name)
1159
def get_child_submit_format(self):
1160
"""Return the preferred format of submissions to this branch."""
1161
return self.get_config().get_user_option("child_submit_format")
1163
def get_submit_branch(self):
1164
"""Return the submit location of the branch.
1166
This is the default location for bundle. The usual
1167
pattern is that the user can override it by specifying a
1170
return self.get_config().get_user_option('submit_branch')
1172
def set_submit_branch(self, location):
1173
"""Return the submit location of the branch.
1175
This is the default location for bundle. The usual
1176
pattern is that the user can override it by specifying a
1179
self.get_config().set_user_option('submit_branch', location,
1182
def get_public_branch(self):
1183
"""Return the public location of the branch.
1185
This is used by merge directives.
1187
return self._get_config_location('public_branch')
1189
def set_public_branch(self, location):
1190
"""Return the submit location of the branch.
1192
This is the default location for bundle. The usual
1193
pattern is that the user can override it by specifying a
1196
self._set_config_location('public_branch', location)
1198
def get_push_location(self):
1199
"""Return the None or the location to push this branch to."""
1200
push_loc = self.get_config().get_user_option('push_location')
1203
def set_push_location(self, location):
1204
"""Set a new push location for this branch."""
1205
raise NotImplementedError(self.set_push_location)
1207
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1208
"""Run the post_change_branch_tip hooks."""
1209
hooks = Branch.hooks['post_change_branch_tip']
1212
new_revno, new_revid = self.last_revision_info()
1213
params = ChangeBranchTipParams(
1214
self, old_revno, new_revno, old_revid, new_revid)
1218
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1219
"""Run the pre_change_branch_tip hooks."""
1220
hooks = Branch.hooks['pre_change_branch_tip']
1223
old_revno, old_revid = self.last_revision_info()
1224
params = ChangeBranchTipParams(
1225
self, old_revno, new_revno, old_revid, new_revid)
1231
"""Synchronise this branch with the master branch if any.
1233
:return: None or the last_revision pivoted out during the update.
1267
f = AtomicFile(self.controlfilename('parent'))
1237
1276
def check_revno(self, revno):
1243
1282
self.check_real_revno(revno)
1245
1284
def check_real_revno(self, revno):
1247
1286
Check whether a revno corresponds to a real revision.
1248
1287
Zero (the NULL revision) is considered invalid
1250
1289
if revno < 1 or revno > self.revno():
1251
raise errors.InvalidRevisionNumber(revno)
1254
def clone(self, to_bzrdir, revision_id=None, repository_policy=None):
1255
"""Clone this branch into to_bzrdir preserving all semantic values.
1257
Most API users will want 'create_clone_on_transport', which creates a
1258
new bzrdir and branch on the fly.
1260
revision_id: if not None, the revision history in the new branch will
1261
be truncated to end with revision_id.
1263
result = to_bzrdir.create_branch()
1266
if repository_policy is not None:
1267
repository_policy.configure_branch(result)
1268
self.copy_content_into(result, revision_id=revision_id)
1274
def sprout(self, to_bzrdir, revision_id=None, repository_policy=None,
1276
"""Create a new line of development from the branch, into to_bzrdir.
1278
to_bzrdir controls the branch format.
1280
revision_id: if not None, the revision history in the new branch will
1281
be truncated to end with revision_id.
1283
if (repository_policy is not None and
1284
repository_policy.requires_stacking()):
1285
to_bzrdir._format.require_stacking(_skip_repo=True)
1286
result = to_bzrdir.create_branch(repository=repository)
1289
if repository_policy is not None:
1290
repository_policy.configure_branch(result)
1291
self.copy_content_into(result, revision_id=revision_id)
1292
master_url = self.get_bound_location()
1293
if master_url is None:
1294
result.set_parent(self.bzrdir.root_transport.base)
1296
result.set_parent(master_url)
1301
def _synchronize_history(self, destination, revision_id):
1302
"""Synchronize last revision and revision history between branches.
1304
This version is most efficient when the destination is also a
1305
BzrBranch6, but works for BzrBranch5, as long as the destination's
1306
repository contains all the lefthand ancestors of the intended
1307
last_revision. If not, set_last_revision_info will fail.
1309
:param destination: The branch to copy the history into
1310
:param revision_id: The revision-id to truncate history at. May
1311
be None to copy complete history.
1313
source_revno, source_revision_id = self.last_revision_info()
1314
if revision_id is None:
1315
revno, revision_id = source_revno, source_revision_id
1317
graph = self.repository.get_graph()
1319
revno = graph.find_distance_to_null(revision_id,
1320
[(source_revision_id, source_revno)])
1321
except errors.GhostRevisionsHaveNoRevno:
1322
# Default to 1, if we can't find anything else
1324
destination.set_last_revision_info(revno, revision_id)
1326
def copy_content_into(self, destination, revision_id=None):
1327
"""Copy the content of self into destination.
1329
revision_id: if not None, the revision history in the new branch will
1330
be truncated to end with revision_id.
1332
return InterBranch.get(self, destination).copy_content_into(
1333
revision_id=revision_id)
1335
def update_references(self, target):
1336
if not getattr(self._format, 'supports_reference_locations', False):
1338
reference_dict = self._get_all_reference_info()
1339
if len(reference_dict) == 0:
1341
old_base = self.base
1342
new_base = target.base
1343
target_reference_dict = target._get_all_reference_info()
1344
for file_id, (tree_path, branch_location) in (
1345
reference_dict.items()):
1346
branch_location = urlutils.rebase_url(branch_location,
1348
target_reference_dict.setdefault(
1349
file_id, (tree_path, branch_location))
1350
target._set_all_reference_info(target_reference_dict)
1353
def check(self, refs):
1354
"""Check consistency of the branch.
1356
In particular this checks that revisions given in the revision-history
1357
do actually match up in the revision graph, and that they're all
1358
present in the repository.
1360
Callers will typically also want to check the repository.
1362
:param refs: Calculated refs for this branch as specified by
1363
branch._get_check_refs()
1364
:return: A BranchCheckResult.
1366
result = BranchCheckResult(self)
1367
last_revno, last_revision_id = self.last_revision_info()
1368
actual_revno = refs[('lefthand-distance', last_revision_id)]
1369
if actual_revno != last_revno:
1370
result.errors.append(errors.BzrCheckError(
1371
'revno does not match len(mainline) %s != %s' % (
1372
last_revno, actual_revno)))
1373
# TODO: We should probably also check that self.revision_history
1374
# matches the repository for older branch formats.
1375
# If looking for the code that cross-checks repository parents against
1376
# the iter_reverse_revision_history output, that is now a repository
1380
def _get_checkout_format(self):
1381
"""Return the most suitable metadir for a checkout of this branch.
1382
Weaves are used if this branch's repository uses weaves.
1384
format = self.repository.bzrdir.checkout_metadir()
1385
format.set_branch_format(self._format)
1388
def create_clone_on_transport(self, to_transport, revision_id=None,
1389
stacked_on=None, create_prefix=False, use_existing_dir=False,
1391
"""Create a clone of this branch and its bzrdir.
1393
:param to_transport: The transport to clone onto.
1394
:param revision_id: The revision id to use as tip in the new branch.
1395
If None the tip is obtained from this branch.
1396
:param stacked_on: An optional URL to stack the clone on.
1397
:param create_prefix: Create any missing directories leading up to
1399
:param use_existing_dir: Use an existing directory if one exists.
1401
# XXX: Fix the bzrdir API to allow getting the branch back from the
1402
# clone call. Or something. 20090224 RBC/spiv.
1403
# XXX: Should this perhaps clone colocated branches as well,
1404
# rather than just the default branch? 20100319 JRV
1405
if revision_id is None:
1406
revision_id = self.last_revision()
1407
dir_to = self.bzrdir.clone_on_transport(to_transport,
1408
revision_id=revision_id, stacked_on=stacked_on,
1409
create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1411
return dir_to.open_branch()
1413
def create_checkout(self, to_location, revision_id=None,
1414
lightweight=False, accelerator_tree=None,
1416
"""Create a checkout of a branch.
1418
:param to_location: The url to produce the checkout at
1419
:param revision_id: The revision to check out
1420
:param lightweight: If True, produce a lightweight checkout, otherwise,
1421
produce a bound branch (heavyweight checkout)
1422
:param accelerator_tree: A tree which can be used for retrieving file
1423
contents more quickly than the revision tree, i.e. a workingtree.
1424
The revision tree will be used for cases where accelerator_tree's
1425
content is different.
1426
:param hardlink: If true, hard-link files from accelerator_tree,
1428
:return: The tree of the created checkout
1430
t = transport.get_transport(to_location)
1433
format = self._get_checkout_format()
1434
checkout = format.initialize_on_transport(t)
1435
from_branch = BranchReferenceFormat().initialize(checkout,
1438
format = self._get_checkout_format()
1439
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
1440
to_location, force_new_tree=False, format=format)
1441
checkout = checkout_branch.bzrdir
1442
checkout_branch.bind(self)
1443
# pull up to the specified revision_id to set the initial
1444
# branch tip correctly, and seed it with history.
1445
checkout_branch.pull(self, stop_revision=revision_id)
1447
tree = checkout.create_workingtree(revision_id,
1448
from_branch=from_branch,
1449
accelerator_tree=accelerator_tree,
1451
basis_tree = tree.basis_tree()
1452
basis_tree.lock_read()
1454
for path, file_id in basis_tree.iter_references():
1455
reference_parent = self.reference_parent(file_id, path)
1456
reference_parent.create_checkout(tree.abspath(path),
1457
basis_tree.get_reference_revision(file_id, path),
1464
def reconcile(self, thorough=True):
1465
"""Make sure the data stored in this branch is consistent."""
1466
from bzrlib.reconcile import BranchReconciler
1467
reconciler = BranchReconciler(self, thorough=thorough)
1468
reconciler.reconcile()
1471
def reference_parent(self, file_id, path, possible_transports=None):
1472
"""Return the parent branch for a tree-reference file_id
1474
:param file_id: The file_id of the tree reference
1475
:param path: The path of the file_id in the tree
1476
:return: A branch associated with the file_id
1478
# FIXME should provide multiple branches, based on config
1479
return Branch.open(self.bzrdir.root_transport.clone(path).base,
1480
possible_transports=possible_transports)
1482
def supports_tags(self):
1483
return self._format.supports_tags()
1485
def automatic_tag_name(self, revision_id):
1486
"""Try to automatically find the tag name for a revision.
1488
:param revision_id: Revision id of the revision.
1489
:return: A tag name or None if no tag name could be determined.
1491
for hook in Branch.hooks['automatic_tag_name']:
1492
ret = hook(self, revision_id)
1497
def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
1499
"""Ensure that revision_b is a descendant of revision_a.
1501
This is a helper function for update_revisions.
1503
:raises: DivergedBranches if revision_b has diverged from revision_a.
1504
:returns: True if revision_b is a descendant of revision_a.
1506
relation = self._revision_relations(revision_a, revision_b, graph)
1507
if relation == 'b_descends_from_a':
1509
elif relation == 'diverged':
1510
raise errors.DivergedBranches(self, other_branch)
1511
elif relation == 'a_descends_from_b':
1514
raise AssertionError("invalid relation: %r" % (relation,))
1516
def _revision_relations(self, revision_a, revision_b, graph):
1517
"""Determine the relationship between two revisions.
1519
:returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
1521
heads = graph.heads([revision_a, revision_b])
1522
if heads == set([revision_b]):
1523
return 'b_descends_from_a'
1524
elif heads == set([revision_a, revision_b]):
1525
# These branches have diverged
1527
elif heads == set([revision_a]):
1528
return 'a_descends_from_b'
1530
raise AssertionError("invalid heads: %r" % (heads,))
1532
def heads_to_fetch(self):
1533
"""Return the heads that must and that should be fetched to copy this
1534
branch into another repo.
1536
:returns: a 2-tuple of (must_fetch, if_present_fetch). must_fetch is a
1537
set of heads that must be fetched. if_present_fetch is a set of
1538
heads that must be fetched if present, but no error is necessary if
1539
they are not present.
1541
# For bzr native formats must_fetch is just the tip, and if_present_fetch
1543
must_fetch = set([self.last_revision()])
1544
if_present_fetch = set()
1545
c = self.get_config()
1546
include_tags = c.get_user_option_as_bool('branch.fetch_tags',
1550
if_present_fetch = set(self.tags.get_reverse_tag_dict())
1551
except errors.TagsNotSupported:
1553
must_fetch.discard(_mod_revision.NULL_REVISION)
1554
if_present_fetch.discard(_mod_revision.NULL_REVISION)
1555
return must_fetch, if_present_fetch
1558
class BranchFormat(controldir.ControlComponentFormat):
1559
"""An encapsulation of the initialization and open routines for a format.
1561
Formats provide three things:
1562
* An initialization routine,
1566
Formats are placed in an dict by their format string for reference
1567
during branch opening. It's not required that these be instances, they
1568
can be classes themselves with class methods - it simply depends on
1569
whether state is needed for a given format or not.
1571
Once a format is deprecated, just deprecate the initialize and open
1572
methods on the format class. Do not deprecate the object, as the
1573
object will be created every time regardless.
1290
raise InvalidRevisionNumber(revno)
1292
def sign_revision(self, revision_id, gpg_strategy):
1295
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1296
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1302
class ScratchBranch(_Branch):
1303
"""Special test class: a branch that cleans up after itself.
1305
>>> b = ScratchBranch()
1309
>>> b._transport.__del__()
1576
can_set_append_revisions_only = True
1578
def __eq__(self, other):
1579
return self.__class__ is other.__class__
1581
def __ne__(self, other):
1582
return not (self == other)
1585
def find_format(klass, a_bzrdir, name=None):
1586
"""Return the format for the branch object in a_bzrdir."""
1588
transport = a_bzrdir.get_branch_transport(None, name=name)
1589
format_string = transport.get_bytes("format")
1590
return format_registry.get(format_string)
1591
except errors.NoSuchFile:
1592
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1594
raise errors.UnknownFormatError(format=format_string, kind='branch')
1597
@deprecated_method(deprecated_in((2, 4, 0)))
1598
def get_default_format(klass):
1599
"""Return the current default format."""
1600
return format_registry.get_default()
1603
@deprecated_method(deprecated_in((2, 4, 0)))
1604
def get_formats(klass):
1605
"""Get all the known formats.
1607
Warning: This triggers a load of all lazy registered formats: do not
1608
use except when that is desireed.
1610
return format_registry._get_all()
1612
def get_reference(self, a_bzrdir, name=None):
1613
"""Get the target reference of the branch in a_bzrdir.
1615
format probing must have been completed before calling
1616
this method - it is assumed that the format of the branch
1617
in a_bzrdir is correct.
1619
:param a_bzrdir: The bzrdir to get the branch data from.
1620
:param name: Name of the colocated branch to fetch
1621
:return: None if the branch is not a reference branch.
1626
def set_reference(self, a_bzrdir, name, to_branch):
1627
"""Set the target reference of the branch in a_bzrdir.
1629
format probing must have been completed before calling
1630
this method - it is assumed that the format of the branch
1631
in a_bzrdir is correct.
1633
:param a_bzrdir: The bzrdir to set the branch reference for.
1634
:param name: Name of colocated branch to set, None for default
1635
:param to_branch: branch that the checkout is to reference
1637
raise NotImplementedError(self.set_reference)
1639
def get_format_string(self):
1640
"""Return the ASCII format string that identifies this format."""
1641
raise NotImplementedError(self.get_format_string)
1643
def get_format_description(self):
1644
"""Return the short format description for this format."""
1645
raise NotImplementedError(self.get_format_description)
1647
def _run_post_branch_init_hooks(self, a_bzrdir, name, branch):
1648
hooks = Branch.hooks['post_branch_init']
1651
params = BranchInitHookParams(self, a_bzrdir, name, branch)
1655
def initialize(self, a_bzrdir, name=None, repository=None):
1656
"""Create a branch of this format in a_bzrdir.
1658
:param name: Name of the colocated branch to create.
1660
raise NotImplementedError(self.initialize)
1662
def is_supported(self):
1663
"""Is this format supported?
1665
Supported formats can be initialized and opened.
1666
Unsupported formats may not support initialization or committing or
1667
some other features depending on the reason for not being supported.
1671
def make_tags(self, branch):
1672
"""Create a tags object for branch.
1674
This method is on BranchFormat, because BranchFormats are reflected
1675
over the wire via network_name(), whereas full Branch instances require
1676
multiple VFS method calls to operate at all.
1678
The default implementation returns a disabled-tags instance.
1680
Note that it is normal for branch to be a RemoteBranch when using tags
1683
return DisabledTags(branch)
1685
def network_name(self):
1686
"""A simple byte string uniquely identifying this format for RPC calls.
1688
MetaDir branch formats use their disk format string to identify the
1689
repository over the wire. All in one formats such as bzr < 0.8, and
1690
foreign formats like svn/git and hg should use some marker which is
1691
unique and immutable.
1693
raise NotImplementedError(self.network_name)
1695
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
1696
found_repository=None):
1697
"""Return the branch object for a_bzrdir
1699
:param a_bzrdir: A BzrDir that contains a branch.
1700
:param name: Name of colocated branch to open
1701
:param _found: a private parameter, do not use it. It is used to
1702
indicate if format probing has already be done.
1703
:param ignore_fallbacks: when set, no fallback branches will be opened
1704
(if there are any). Default is to open fallbacks.
1706
raise NotImplementedError(self.open)
1709
@deprecated_method(deprecated_in((2, 4, 0)))
1710
def register_format(klass, format):
1711
"""Register a metadir format.
1713
See MetaDirBranchFormatFactory for the ability to register a format
1714
without loading the code the format needs until it is actually used.
1716
format_registry.register(format)
1719
@deprecated_method(deprecated_in((2, 4, 0)))
1720
def set_default_format(klass, format):
1721
format_registry.set_default(format)
1723
def supports_set_append_revisions_only(self):
1724
"""True if this format supports set_append_revisions_only."""
1727
def supports_stacking(self):
1728
"""True if this format records a stacked-on branch."""
1731
def supports_leaving_lock(self):
1732
"""True if this format supports leaving locks in place."""
1733
return False # by default
1736
@deprecated_method(deprecated_in((2, 4, 0)))
1737
def unregister_format(klass, format):
1738
format_registry.remove(format)
1741
return self.get_format_description().rstrip()
1743
def supports_tags(self):
1744
"""True if this format supports tags stored in the branch"""
1745
return False # by default
1748
class MetaDirBranchFormatFactory(registry._LazyObjectGetter):
1749
"""A factory for a BranchFormat object, permitting simple lazy registration.
1314
def __init__(self, files=[], dirs=[], transport=None):
1315
"""Make a test branch.
1317
This creates a temporary directory and runs init-tree in it.
1319
If any files are listed, they are created in the working copy.
1321
if transport is None:
1322
transport = bzrlib.transport.local.ScratchTransport()
1323
super(ScratchBranch, self).__init__(transport, init=True)
1325
super(ScratchBranch, self).__init__(transport)
1328
self._transport.mkdir(d)
1331
self._transport.put(f, 'content of %s' % f)
1336
>>> orig = ScratchBranch(files=["file1", "file2"])
1337
>>> clone = orig.clone()
1338
>>> if os.name != 'nt':
1339
... os.path.samefile(orig.base, clone.base)
1341
... orig.base == clone.base
1344
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1347
from shutil import copytree
1348
from tempfile import mkdtemp
1351
copytree(self.base, base, symlinks=True)
1352
return ScratchBranch(
1353
transport=bzrlib.transport.local.ScratchTransport(base))
1751
While none of the built in BranchFormats are lazy registered yet,
1752
bzrlib.tests.test_branch.TestMetaDirBranchFormatFactory demonstrates how to
1753
use it, and the bzr-loom plugin uses it as well (see
1754
bzrlib.plugins.loom.formats).
1757
def __init__(self, format_string, module_name, member_name):
1758
"""Create a MetaDirBranchFormatFactory.
1760
:param format_string: The format string the format has.
1761
:param module_name: Module to load the format class from.
1762
:param member_name: Attribute name within the module for the format class.
1764
registry._LazyObjectGetter.__init__(self, module_name, member_name)
1765
self._format_string = format_string
1767
def get_format_string(self):
1768
"""See BranchFormat.get_format_string."""
1769
return self._format_string
1772
"""Used for network_format_registry support."""
1773
return self.get_obj()()
1776
class BranchHooks(Hooks):
1777
"""A dictionary mapping hook name to a list of callables for branch hooks.
1779
e.g. ['set_rh'] Is the list of items to be called when the
1780
set_revision_history function is invoked.
1784
"""Create the default hooks.
1786
These are all empty initially, because by default nothing should get
1789
Hooks.__init__(self, "bzrlib.branch", "Branch.hooks")
1790
self.add_hook('set_rh',
1791
"Invoked whenever the revision history has been set via "
1792
"set_revision_history. The api signature is (branch, "
1793
"revision_history), and the branch will be write-locked. "
1794
"The set_rh hook can be expensive for bzr to trigger, a better "
1795
"hook to use is Branch.post_change_branch_tip.", (0, 15))
1796
self.add_hook('open',
1797
"Called with the Branch object that has been opened after a "
1798
"branch is opened.", (1, 8))
1799
self.add_hook('post_push',
1800
"Called after a push operation completes. post_push is called "
1801
"with a bzrlib.branch.BranchPushResult object and only runs in the "
1802
"bzr client.", (0, 15))
1803
self.add_hook('post_pull',
1804
"Called after a pull operation completes. post_pull is called "
1805
"with a bzrlib.branch.PullResult object and only runs in the "
1806
"bzr client.", (0, 15))
1807
self.add_hook('pre_commit',
1808
"Called after a commit is calculated but before it is "
1809
"completed. pre_commit is called with (local, master, old_revno, "
1810
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1811
"). old_revid is NULL_REVISION for the first commit to a branch, "
1812
"tree_delta is a TreeDelta object describing changes from the "
1813
"basis revision. hooks MUST NOT modify this delta. "
1814
" future_tree is an in-memory tree obtained from "
1815
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1817
self.add_hook('post_commit',
1818
"Called in the bzr client after a commit has completed. "
1819
"post_commit is called with (local, master, old_revno, old_revid, "
1820
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1821
"commit to a branch.", (0, 15))
1822
self.add_hook('post_uncommit',
1823
"Called in the bzr client after an uncommit completes. "
1824
"post_uncommit is called with (local, master, old_revno, "
1825
"old_revid, new_revno, new_revid) where local is the local branch "
1826
"or None, master is the target branch, and an empty branch "
1827
"receives new_revno of 0, new_revid of None.", (0, 15))
1828
self.add_hook('pre_change_branch_tip',
1829
"Called in bzr client and server before a change to the tip of a "
1830
"branch is made. pre_change_branch_tip is called with a "
1831
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1832
"commit, uncommit will all trigger this hook.", (1, 6))
1833
self.add_hook('post_change_branch_tip',
1834
"Called in bzr client and server after a change to the tip of a "
1835
"branch is made. post_change_branch_tip is called with a "
1836
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1837
"commit, uncommit will all trigger this hook.", (1, 4))
1838
self.add_hook('transform_fallback_location',
1839
"Called when a stacked branch is activating its fallback "
1840
"locations. transform_fallback_location is called with (branch, "
1841
"url), and should return a new url. Returning the same url "
1842
"allows it to be used as-is, returning a different one can be "
1843
"used to cause the branch to stack on a closer copy of that "
1844
"fallback_location. Note that the branch cannot have history "
1845
"accessing methods called on it during this hook because the "
1846
"fallback locations have not been activated. When there are "
1847
"multiple hooks installed for transform_fallback_location, "
1848
"all are called with the url returned from the previous hook."
1849
"The order is however undefined.", (1, 9))
1850
self.add_hook('automatic_tag_name',
1851
"Called to determine an automatic tag name for a revision. "
1852
"automatic_tag_name is called with (branch, revision_id) and "
1853
"should return a tag name or None if no tag name could be "
1854
"determined. The first non-None tag name returned will be used.",
1856
self.add_hook('post_branch_init',
1857
"Called after new branch initialization completes. "
1858
"post_branch_init is called with a "
1859
"bzrlib.branch.BranchInitHookParams. "
1860
"Note that init, branch and checkout (both heavyweight and "
1861
"lightweight) will all trigger this hook.", (2, 2))
1862
self.add_hook('post_switch',
1863
"Called after a checkout switches branch. "
1864
"post_switch is called with a "
1865
"bzrlib.branch.SwitchHookParams.", (2, 2))
1869
# install the default hooks into the Branch class.
1870
Branch.hooks = BranchHooks()
1873
class ChangeBranchTipParams(object):
1874
"""Object holding parameters passed to `*_change_branch_tip` hooks.
1876
There are 5 fields that hooks may wish to access:
1878
:ivar branch: the branch being changed
1879
:ivar old_revno: revision number before the change
1880
:ivar new_revno: revision number after the change
1881
:ivar old_revid: revision id before the change
1882
:ivar new_revid: revision id after the change
1884
The revid fields are strings. The revno fields are integers.
1887
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1888
"""Create a group of ChangeBranchTip parameters.
1890
:param branch: The branch being changed.
1891
:param old_revno: Revision number before the change.
1892
:param new_revno: Revision number after the change.
1893
:param old_revid: Tip revision id before the change.
1894
:param new_revid: Tip revision id after the change.
1896
self.branch = branch
1897
self.old_revno = old_revno
1898
self.new_revno = new_revno
1899
self.old_revid = old_revid
1900
self.new_revid = new_revid
1902
def __eq__(self, other):
1903
return self.__dict__ == other.__dict__
1906
return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1907
self.__class__.__name__, self.branch,
1908
self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1911
class BranchInitHookParams(object):
1912
"""Object holding parameters passed to `*_branch_init` hooks.
1914
There are 4 fields that hooks may wish to access:
1916
:ivar format: the branch format
1917
:ivar bzrdir: the BzrDir where the branch will be/has been initialized
1918
:ivar name: name of colocated branch, if any (or None)
1919
:ivar branch: the branch created
1921
Note that for lightweight checkouts, the bzrdir and format fields refer to
1922
the checkout, hence they are different from the corresponding fields in
1923
branch, which refer to the original branch.
1926
def __init__(self, format, a_bzrdir, name, branch):
1927
"""Create a group of BranchInitHook parameters.
1929
:param format: the branch format
1930
:param a_bzrdir: the BzrDir where the branch will be/has been
1932
:param name: name of colocated branch, if any (or None)
1933
:param branch: the branch created
1935
Note that for lightweight checkouts, the bzrdir and format fields refer
1936
to the checkout, hence they are different from the corresponding fields
1937
in branch, which refer to the original branch.
1939
self.format = format
1940
self.bzrdir = a_bzrdir
1942
self.branch = branch
1944
def __eq__(self, other):
1945
return self.__dict__ == other.__dict__
1948
return "<%s of %s>" % (self.__class__.__name__, self.branch)
1951
class SwitchHookParams(object):
1952
"""Object holding parameters passed to `*_switch` hooks.
1954
There are 4 fields that hooks may wish to access:
1956
:ivar control_dir: BzrDir of the checkout to change
1957
:ivar to_branch: branch that the checkout is to reference
1958
:ivar force: skip the check for local commits in a heavy checkout
1959
:ivar revision_id: revision ID to switch to (or None)
1962
def __init__(self, control_dir, to_branch, force, revision_id):
1963
"""Create a group of SwitchHook parameters.
1965
:param control_dir: BzrDir of the checkout to change
1966
:param to_branch: branch that the checkout is to reference
1967
:param force: skip the check for local commits in a heavy checkout
1968
:param revision_id: revision ID to switch to (or None)
1970
self.control_dir = control_dir
1971
self.to_branch = to_branch
1973
self.revision_id = revision_id
1975
def __eq__(self, other):
1976
return self.__dict__ == other.__dict__
1979
return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
1980
self.control_dir, self.to_branch,
1984
class BranchFormatMetadir(BranchFormat):
1985
"""Common logic for meta-dir based branch formats."""
1987
def _branch_class(self):
1988
"""What class to instantiate on open calls."""
1989
raise NotImplementedError(self._branch_class)
1991
def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
1993
"""Initialize a branch in a bzrdir, with specified files
1995
:param a_bzrdir: The bzrdir to initialize the branch in
1996
:param utf8_files: The files to create as a list of
1997
(filename, content) tuples
1998
:param name: Name of colocated branch to create, if any
1999
:return: a branch in this format
2001
mutter('creating branch %r in %s', self, a_bzrdir.user_url)
2002
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
2003
control_files = lockable_files.LockableFiles(branch_transport,
2004
'lock', lockdir.LockDir)
2005
control_files.create_lock()
2006
control_files.lock_write()
2008
utf8_files += [('format', self.get_format_string())]
2009
for (filename, content) in utf8_files:
2010
branch_transport.put_bytes(
2012
mode=a_bzrdir._get_file_mode())
2014
control_files.unlock()
2015
branch = self.open(a_bzrdir, name, _found=True,
2016
found_repository=repository)
2017
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
2020
def network_name(self):
2021
"""A simple byte string uniquely identifying this format for RPC calls.
2023
Metadir branch formats use their format string.
2025
return self.get_format_string()
2027
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
2028
found_repository=None):
2029
"""See BranchFormat.open()."""
2031
format = BranchFormat.find_format(a_bzrdir, name=name)
2032
if format.__class__ != self.__class__:
2033
raise AssertionError("wrong format %r found for %r" %
2035
transport = a_bzrdir.get_branch_transport(None, name=name)
2037
control_files = lockable_files.LockableFiles(transport, 'lock',
2039
if found_repository is None:
2040
found_repository = a_bzrdir.find_repository()
2041
return self._branch_class()(_format=self,
2042
_control_files=control_files,
2045
_repository=found_repository,
2046
ignore_fallbacks=ignore_fallbacks)
2047
except errors.NoSuchFile:
2048
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
2051
super(BranchFormatMetadir, self).__init__()
2052
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2053
self._matchingbzrdir.set_branch_format(self)
2055
def supports_tags(self):
2058
def supports_leaving_lock(self):
2062
class BzrBranchFormat5(BranchFormatMetadir):
2063
"""Bzr branch format 5.
2066
- a revision-history file.
2068
- a lock dir guarding the branch itself
2069
- all of this stored in a branch/ subdirectory
2070
- works with shared repositories.
2072
This format is new in bzr 0.8.
2075
def _branch_class(self):
2078
def get_format_string(self):
2079
"""See BranchFormat.get_format_string()."""
2080
return "Bazaar-NG branch format 5\n"
2082
def get_format_description(self):
2083
"""See BranchFormat.get_format_description()."""
2084
return "Branch format 5"
2086
def initialize(self, a_bzrdir, name=None, repository=None):
2087
"""Create a branch of this format in a_bzrdir."""
2088
utf8_files = [('revision-history', ''),
2089
('branch-name', ''),
2091
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
2093
def supports_tags(self):
2097
class BzrBranchFormat6(BranchFormatMetadir):
2098
"""Branch format with last-revision and tags.
2100
Unlike previous formats, this has no explicit revision history. Instead,
2101
this just stores the last-revision, and the left-hand history leading
2102
up to there is the history.
2104
This format was introduced in bzr 0.15
2105
and became the default in 0.91.
2108
def _branch_class(self):
2111
def get_format_string(self):
2112
"""See BranchFormat.get_format_string()."""
2113
return "Bazaar Branch Format 6 (bzr 0.15)\n"
2115
def get_format_description(self):
2116
"""See BranchFormat.get_format_description()."""
2117
return "Branch format 6"
2119
def initialize(self, a_bzrdir, name=None, repository=None):
2120
"""Create a branch of this format in a_bzrdir."""
2121
utf8_files = [('last-revision', '0 null:\n'),
2122
('branch.conf', ''),
2125
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
2127
def make_tags(self, branch):
2128
"""See bzrlib.branch.BranchFormat.make_tags()."""
2129
return BasicTags(branch)
2131
def supports_set_append_revisions_only(self):
2135
class BzrBranchFormat8(BranchFormatMetadir):
2136
"""Metadir format supporting storing locations of subtree branches."""
2138
def _branch_class(self):
2141
def get_format_string(self):
2142
"""See BranchFormat.get_format_string()."""
2143
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
2145
def get_format_description(self):
2146
"""See BranchFormat.get_format_description()."""
2147
return "Branch format 8"
2149
def initialize(self, a_bzrdir, name=None, repository=None):
2150
"""Create a branch of this format in a_bzrdir."""
2151
utf8_files = [('last-revision', '0 null:\n'),
2152
('branch.conf', ''),
2156
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
2158
def make_tags(self, branch):
2159
"""See bzrlib.branch.BranchFormat.make_tags()."""
2160
return BasicTags(branch)
2162
def supports_set_append_revisions_only(self):
2165
def supports_stacking(self):
2168
supports_reference_locations = True
2171
class BzrBranchFormat7(BranchFormatMetadir):
2172
"""Branch format with last-revision, tags, and a stacked location pointer.
2174
The stacked location pointer is passed down to the repository and requires
2175
a repository format with supports_external_lookups = True.
2177
This format was introduced in bzr 1.6.
2180
def initialize(self, a_bzrdir, name=None, repository=None):
2181
"""Create a branch of this format in a_bzrdir."""
2182
utf8_files = [('last-revision', '0 null:\n'),
2183
('branch.conf', ''),
2186
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
2188
def _branch_class(self):
2191
def get_format_string(self):
2192
"""See BranchFormat.get_format_string()."""
2193
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
2195
def get_format_description(self):
2196
"""See BranchFormat.get_format_description()."""
2197
return "Branch format 7"
2199
def supports_set_append_revisions_only(self):
2202
def supports_stacking(self):
2205
def make_tags(self, branch):
2206
"""See bzrlib.branch.BranchFormat.make_tags()."""
2207
return BasicTags(branch)
2209
supports_reference_locations = False
2212
class BranchReferenceFormat(BranchFormat):
2213
"""Bzr branch reference format.
2215
Branch references are used in implementing checkouts, they
2216
act as an alias to the real branch which is at some other url.
2223
def get_format_string(self):
2224
"""See BranchFormat.get_format_string()."""
2225
return "Bazaar-NG Branch Reference Format 1\n"
2227
def get_format_description(self):
2228
"""See BranchFormat.get_format_description()."""
2229
return "Checkout reference format 1"
2231
def get_reference(self, a_bzrdir, name=None):
2232
"""See BranchFormat.get_reference()."""
2233
transport = a_bzrdir.get_branch_transport(None, name=name)
2234
return transport.get_bytes('location')
2236
def set_reference(self, a_bzrdir, name, to_branch):
2237
"""See BranchFormat.set_reference()."""
2238
transport = a_bzrdir.get_branch_transport(None, name=name)
2239
location = transport.put_bytes('location', to_branch.base)
2241
def initialize(self, a_bzrdir, name=None, target_branch=None,
2243
"""Create a branch of this format in a_bzrdir."""
2244
if target_branch is None:
2245
# this format does not implement branch itself, thus the implicit
2246
# creation contract must see it as uninitializable
2247
raise errors.UninitializableFormat(self)
2248
mutter('creating branch reference in %s', a_bzrdir.user_url)
2249
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
2250
branch_transport.put_bytes('location',
2251
target_branch.bzrdir.user_url)
2252
branch_transport.put_bytes('format', self.get_format_string())
2254
a_bzrdir, name, _found=True,
2255
possible_transports=[target_branch.bzrdir.root_transport])
2256
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
2260
super(BranchReferenceFormat, self).__init__()
2261
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2262
self._matchingbzrdir.set_branch_format(self)
2264
def _make_reference_clone_function(format, a_branch):
2265
"""Create a clone() routine for a branch dynamically."""
2266
def clone(to_bzrdir, revision_id=None,
2267
repository_policy=None):
2268
"""See Branch.clone()."""
2269
return format.initialize(to_bzrdir, target_branch=a_branch)
2270
# cannot obey revision_id limits when cloning a reference ...
2271
# FIXME RBC 20060210 either nuke revision_id for clone, or
2272
# emit some sort of warning/error to the caller ?!
2275
def open(self, a_bzrdir, name=None, _found=False, location=None,
2276
possible_transports=None, ignore_fallbacks=False,
2277
found_repository=None):
2278
"""Return the branch that the branch reference in a_bzrdir points at.
2280
:param a_bzrdir: A BzrDir that contains a branch.
2281
:param name: Name of colocated branch to open, if any
2282
:param _found: a private parameter, do not use it. It is used to
2283
indicate if format probing has already be done.
2284
:param ignore_fallbacks: when set, no fallback branches will be opened
2285
(if there are any). Default is to open fallbacks.
2286
:param location: The location of the referenced branch. If
2287
unspecified, this will be determined from the branch reference in
2289
:param possible_transports: An optional reusable transports list.
2292
format = BranchFormat.find_format(a_bzrdir, name=name)
2293
if format.__class__ != self.__class__:
2294
raise AssertionError("wrong format %r found for %r" %
2296
if location is None:
2297
location = self.get_reference(a_bzrdir, name)
2298
real_bzrdir = bzrdir.BzrDir.open(
2299
location, possible_transports=possible_transports)
2300
result = real_bzrdir.open_branch(name=name,
2301
ignore_fallbacks=ignore_fallbacks)
2302
# this changes the behaviour of result.clone to create a new reference
2303
# rather than a copy of the content of the branch.
2304
# I did not use a proxy object because that needs much more extensive
2305
# testing, and we are only changing one behaviour at the moment.
2306
# If we decide to alter more behaviours - i.e. the implicit nickname
2307
# then this should be refactored to introduce a tested proxy branch
2308
# and a subclass of that for use in overriding clone() and ....
2310
result.clone = self._make_reference_clone_function(result)
2314
class BranchFormatRegistry(controldir.ControlComponentFormatRegistry):
2315
"""Branch format registry."""
2317
def __init__(self, other_registry=None):
2318
super(BranchFormatRegistry, self).__init__(other_registry)
2319
self._default_format = None
2321
def set_default(self, format):
2322
self._default_format = format
2324
def get_default(self):
2325
return self._default_format
2328
network_format_registry = registry.FormatRegistry()
2329
"""Registry of formats indexed by their network name.
2331
The network name for a branch format is an identifier that can be used when
2332
referring to formats with smart server operations. See
2333
BranchFormat.network_name() for more detail.
2336
format_registry = BranchFormatRegistry(network_format_registry)
2339
# formats which have no format string are not discoverable
2340
# and not independently creatable, so are not registered.
2341
__format5 = BzrBranchFormat5()
2342
__format6 = BzrBranchFormat6()
2343
__format7 = BzrBranchFormat7()
2344
__format8 = BzrBranchFormat8()
2345
format_registry.register(__format5)
2346
format_registry.register(BranchReferenceFormat())
2347
format_registry.register(__format6)
2348
format_registry.register(__format7)
2349
format_registry.register(__format8)
2350
format_registry.set_default(__format7)
2353
class BranchWriteLockResult(LogicalLockResult):
2354
"""The result of write locking a branch.
2356
:ivar branch_token: The token obtained from the underlying branch lock, or
2358
:ivar unlock: A callable which will unlock the lock.
2361
def __init__(self, unlock, branch_token):
2362
LogicalLockResult.__init__(self, unlock)
2363
self.branch_token = branch_token
2366
return "BranchWriteLockResult(%s, %s)" % (self.branch_token,
2370
class BzrBranch(Branch, _RelockDebugMixin):
2371
"""A branch stored in the actual filesystem.
2373
Note that it's "local" in the context of the filesystem; it doesn't
2374
really matter if it's on an nfs/smb/afs/coda/... share, as long as
2375
it's writable, and can be accessed via the normal filesystem API.
2377
:ivar _transport: Transport for file operations on this branch's
2378
control files, typically pointing to the .bzr/branch directory.
2379
:ivar repository: Repository for this branch.
2380
:ivar base: The url of the base directory for this branch; the one
2381
containing the .bzr directory.
2382
:ivar name: Optional colocated branch name as it exists in the control
2386
def __init__(self, _format=None,
2387
_control_files=None, a_bzrdir=None, name=None,
2388
_repository=None, ignore_fallbacks=False):
2389
"""Create new branch object at a particular location."""
2390
if a_bzrdir is None:
2391
raise ValueError('a_bzrdir must be supplied')
2393
self.bzrdir = a_bzrdir
2394
self._base = self.bzrdir.transport.clone('..').base
2396
# XXX: We should be able to just do
2397
# self.base = self.bzrdir.root_transport.base
2398
# but this does not quite work yet -- mbp 20080522
2399
self._format = _format
2400
if _control_files is None:
2401
raise ValueError('BzrBranch _control_files is None')
2402
self.control_files = _control_files
2403
self._transport = _control_files._transport
2404
self.repository = _repository
2405
Branch.__init__(self)
2408
if self.name is None:
2409
return '%s(%s)' % (self.__class__.__name__, self.user_url)
2411
return '%s(%s,%s)' % (self.__class__.__name__, self.user_url,
2416
def _get_base(self):
2417
"""Returns the directory containing the control directory."""
2420
base = property(_get_base, doc="The URL for the root of this branch.")
2422
def _get_config(self):
2423
return TransportConfig(self._transport, 'branch.conf')
2425
def is_locked(self):
2426
return self.control_files.is_locked()
2428
def lock_write(self, token=None):
2429
"""Lock the branch for write operations.
2431
:param token: A token to permit reacquiring a previously held and
2433
:return: A BranchWriteLockResult.
2435
if not self.is_locked():
2436
self._note_lock('w')
2437
# All-in-one needs to always unlock/lock.
2438
repo_control = getattr(self.repository, 'control_files', None)
2439
if self.control_files == repo_control or not self.is_locked():
2440
self.repository._warn_if_deprecated(self)
2441
self.repository.lock_write()
2446
return BranchWriteLockResult(self.unlock,
2447
self.control_files.lock_write(token=token))
2450
self.repository.unlock()
2453
def lock_read(self):
2454
"""Lock the branch for read operations.
2456
:return: A bzrlib.lock.LogicalLockResult.
2458
if not self.is_locked():
2459
self._note_lock('r')
2460
# All-in-one needs to always unlock/lock.
2461
repo_control = getattr(self.repository, 'control_files', None)
2462
if self.control_files == repo_control or not self.is_locked():
2463
self.repository._warn_if_deprecated(self)
2464
self.repository.lock_read()
2469
self.control_files.lock_read()
2470
return LogicalLockResult(self.unlock)
2473
self.repository.unlock()
2476
@only_raises(errors.LockNotHeld, errors.LockBroken)
2479
self.control_files.unlock()
2481
# All-in-one needs to always unlock/lock.
2482
repo_control = getattr(self.repository, 'control_files', None)
2483
if (self.control_files == repo_control or
2484
not self.control_files.is_locked()):
2485
self.repository.unlock()
2486
if not self.control_files.is_locked():
2487
# we just released the lock
2488
self._clear_cached_state()
2490
def peek_lock_mode(self):
2491
if self.control_files._lock_count == 0:
2494
return self.control_files._lock_mode
2496
def get_physical_lock_status(self):
2497
return self.control_files.get_physical_lock_status()
2500
def print_file(self, file, revision_id):
2501
"""See Branch.print_file."""
2502
return self.repository.print_file(file, revision_id)
2505
def set_last_revision_info(self, revno, revision_id):
2506
if not revision_id or not isinstance(revision_id, basestring):
2507
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
2508
revision_id = _mod_revision.ensure_null(revision_id)
2509
old_revno, old_revid = self.last_revision_info()
2510
if self._get_append_revisions_only():
2511
self._check_history_violation(revision_id)
2512
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2513
self._write_last_revision_info(revno, revision_id)
2514
self._clear_cached_state()
2515
self._last_revision_info_cache = revno, revision_id
2516
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2518
def basis_tree(self):
2519
"""See Branch.basis_tree."""
2520
return self.repository.revision_tree(self.last_revision())
2522
def _get_parent_location(self):
2523
_locs = ['parent', 'pull', 'x-pull']
2526
return self._transport.get_bytes(l).strip('\n')
2527
except errors.NoSuchFile:
2531
def get_stacked_on_url(self):
2532
raise errors.UnstackableBranchFormat(self._format, self.user_url)
2534
def set_push_location(self, location):
2535
"""See Branch.set_push_location."""
2536
self.get_config().set_user_option(
2537
'push_location', location,
2538
store=_mod_config.STORE_LOCATION_NORECURSE)
2540
def _set_parent_location(self, url):
2542
self._transport.delete('parent')
2544
self._transport.put_bytes('parent', url + '\n',
2545
mode=self.bzrdir._get_file_mode())
2549
"""If bound, unbind"""
2550
return self.set_bound_location(None)
2553
def bind(self, other):
2554
"""Bind this branch to the branch other.
2556
This does not push or pull data between the branches, though it does
2557
check for divergence to raise an error when the branches are not
2558
either the same, or one a prefix of the other. That behaviour may not
2559
be useful, so that check may be removed in future.
2561
:param other: The branch to bind to
2564
# TODO: jam 20051230 Consider checking if the target is bound
2565
# It is debatable whether you should be able to bind to
2566
# a branch which is itself bound.
2567
# Committing is obviously forbidden,
2568
# but binding itself may not be.
2569
# Since we *have* to check at commit time, we don't
2570
# *need* to check here
2572
# we want to raise diverged if:
2573
# last_rev is not in the other_last_rev history, AND
2574
# other_last_rev is not in our history, and do it without pulling
2576
self.set_bound_location(other.base)
2578
def get_bound_location(self):
2580
return self._transport.get_bytes('bound')[:-1]
2581
except errors.NoSuchFile:
2585
def get_master_branch(self, possible_transports=None):
2586
"""Return the branch we are bound to.
2588
:return: Either a Branch, or None
2590
if self._master_branch_cache is None:
2591
self._master_branch_cache = self._get_master_branch(
2592
possible_transports)
2593
return self._master_branch_cache
2595
def _get_master_branch(self, possible_transports):
2596
bound_loc = self.get_bound_location()
2600
return Branch.open(bound_loc,
2601
possible_transports=possible_transports)
2602
except (errors.NotBranchError, errors.ConnectionError), e:
2603
raise errors.BoundBranchConnectionFailure(
2607
def set_bound_location(self, location):
2608
"""Set the target where this branch is bound to.
2610
:param location: URL to the target branch
2612
self._master_branch_cache = None
2614
self._transport.put_bytes('bound', location+'\n',
2615
mode=self.bzrdir._get_file_mode())
2618
self._transport.delete('bound')
2619
except errors.NoSuchFile:
2624
def update(self, possible_transports=None):
2625
"""Synchronise this branch with the master branch if any.
2627
:return: None or the last_revision that was pivoted out during the
2630
master = self.get_master_branch(possible_transports)
2631
if master is not None:
2632
old_tip = _mod_revision.ensure_null(self.last_revision())
2633
self.pull(master, overwrite=True)
2634
if self.repository.get_graph().is_ancestor(old_tip,
2635
_mod_revision.ensure_null(self.last_revision())):
2640
def _read_last_revision_info(self):
2641
revision_string = self._transport.get_bytes('last-revision')
2642
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2643
revision_id = cache_utf8.get_cached_utf8(revision_id)
2645
return revno, revision_id
2647
def _write_last_revision_info(self, revno, revision_id):
2648
"""Simply write out the revision id, with no checks.
2650
Use set_last_revision_info to perform this safely.
2652
Does not update the revision_history cache.
2654
revision_id = _mod_revision.ensure_null(revision_id)
2655
out_string = '%d %s\n' % (revno, revision_id)
2656
self._transport.put_bytes('last-revision', out_string,
2657
mode=self.bzrdir._get_file_mode())
2660
class FullHistoryBzrBranch(BzrBranch):
2661
"""Bzr branch which contains the full revision history."""
2664
def set_last_revision_info(self, revno, revision_id):
2665
if not revision_id or not isinstance(revision_id, basestring):
2666
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
2667
revision_id = _mod_revision.ensure_null(revision_id)
2668
# this old format stores the full history, but this api doesn't
2669
# provide it, so we must generate, and might as well check it's
2671
history = self._lefthand_history(revision_id)
2672
if len(history) != revno:
2673
raise AssertionError('%d != %d' % (len(history), revno))
2674
self._set_revision_history(history)
2676
def _read_last_revision_info(self):
2677
rh = self.revision_history()
2680
return (revno, rh[-1])
2682
return (0, _mod_revision.NULL_REVISION)
2684
@deprecated_method(deprecated_in((2, 4, 0)))
2686
def set_revision_history(self, rev_history):
2687
"""See Branch.set_revision_history."""
2688
self._set_revision_history(rev_history)
2690
def _set_revision_history(self, rev_history):
2691
if 'evil' in debug.debug_flags:
2692
mutter_callsite(3, "set_revision_history scales with history.")
2693
check_not_reserved_id = _mod_revision.check_not_reserved_id
2694
for rev_id in rev_history:
2695
check_not_reserved_id(rev_id)
2696
if Branch.hooks['post_change_branch_tip']:
2697
# Don't calculate the last_revision_info() if there are no hooks
2699
old_revno, old_revid = self.last_revision_info()
2700
if len(rev_history) == 0:
2701
revid = _mod_revision.NULL_REVISION
2703
revid = rev_history[-1]
2704
self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
2705
self._write_revision_history(rev_history)
2706
self._clear_cached_state()
2707
self._cache_revision_history(rev_history)
2708
for hook in Branch.hooks['set_rh']:
2709
hook(self, rev_history)
2710
if Branch.hooks['post_change_branch_tip']:
2711
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2713
def _write_revision_history(self, history):
2714
"""Factored out of set_revision_history.
2716
This performs the actual writing to disk.
2717
It is intended to be called by set_revision_history."""
2718
self._transport.put_bytes(
2719
'revision-history', '\n'.join(history),
2720
mode=self.bzrdir._get_file_mode())
2722
def _gen_revision_history(self):
2723
history = self._transport.get_bytes('revision-history').split('\n')
2724
if history[-1:] == ['']:
2725
# There shouldn't be a trailing newline, but just in case.
2729
def _synchronize_history(self, destination, revision_id):
2730
if not isinstance(destination, FullHistoryBzrBranch):
2731
super(BzrBranch, self)._synchronize_history(
2732
destination, revision_id)
2734
if revision_id == _mod_revision.NULL_REVISION:
2737
new_history = self.revision_history()
2738
if revision_id is not None and new_history != []:
2740
new_history = new_history[:new_history.index(revision_id) + 1]
2742
rev = self.repository.get_revision(revision_id)
2743
new_history = rev.get_history(self.repository)[1:]
2744
destination._set_revision_history(new_history)
2747
def generate_revision_history(self, revision_id, last_rev=None,
2749
"""Create a new revision history that will finish with revision_id.
2751
:param revision_id: the new tip to use.
2752
:param last_rev: The previous last_revision. If not None, then this
2753
must be a ancestory of revision_id, or DivergedBranches is raised.
2754
:param other_branch: The other branch that DivergedBranches should
2755
raise with respect to.
2757
self._set_revision_history(self._lefthand_history(revision_id,
2758
last_rev, other_branch))
2761
class BzrBranch5(FullHistoryBzrBranch):
2762
"""A format 5 branch. This supports new features over plain branches.
2764
It has support for a master_branch which is the data for bound branches.
2768
class BzrBranch8(BzrBranch):
2769
"""A branch that stores tree-reference locations."""
2771
def _open_hook(self):
2772
if self._ignore_fallbacks:
2775
url = self.get_stacked_on_url()
2776
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2777
errors.UnstackableBranchFormat):
2780
for hook in Branch.hooks['transform_fallback_location']:
2781
url = hook(self, url)
2783
hook_name = Branch.hooks.get_hook_name(hook)
2784
raise AssertionError(
2785
"'transform_fallback_location' hook %s returned "
2786
"None, not a URL." % hook_name)
2787
self._activate_fallback_location(url)
2789
def __init__(self, *args, **kwargs):
2790
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
2791
super(BzrBranch8, self).__init__(*args, **kwargs)
2792
self._last_revision_info_cache = None
2793
self._reference_info = None
2795
def _clear_cached_state(self):
2796
super(BzrBranch8, self)._clear_cached_state()
2797
self._last_revision_info_cache = None
2798
self._reference_info = None
2800
def _check_history_violation(self, revision_id):
2801
current_revid = self.last_revision()
2802
last_revision = _mod_revision.ensure_null(current_revid)
2803
if _mod_revision.is_null(last_revision):
2805
graph = self.repository.get_graph()
2806
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
2807
if lh_ancestor == current_revid:
2809
raise errors.AppendRevisionsOnlyViolation(self.user_url)
2811
def _gen_revision_history(self):
2812
"""Generate the revision history from last revision
2814
last_revno, last_revision = self.last_revision_info()
2815
self._extend_partial_history(stop_index=last_revno-1)
2816
return list(reversed(self._partial_revision_history_cache))
2819
def _set_parent_location(self, url):
2820
"""Set the parent branch"""
2821
self._set_config_location('parent_location', url, make_relative=True)
2824
def _get_parent_location(self):
2825
"""Set the parent branch"""
2826
return self._get_config_location('parent_location')
2829
def _set_all_reference_info(self, info_dict):
2830
"""Replace all reference info stored in a branch.
2832
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
2835
writer = rio.RioWriter(s)
2836
for key, (tree_path, branch_location) in info_dict.iteritems():
2837
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
2838
branch_location=branch_location)
2839
writer.write_stanza(stanza)
2840
self._transport.put_bytes('references', s.getvalue())
2841
self._reference_info = info_dict
2844
def _get_all_reference_info(self):
2845
"""Return all the reference info stored in a branch.
2847
:return: A dict of {file_id: (tree_path, branch_location)}
2849
if self._reference_info is not None:
2850
return self._reference_info
2851
rio_file = self._transport.get('references')
2853
stanzas = rio.read_stanzas(rio_file)
2854
info_dict = dict((s['file_id'], (s['tree_path'],
2855
s['branch_location'])) for s in stanzas)
2858
self._reference_info = info_dict
2861
def set_reference_info(self, file_id, tree_path, branch_location):
2862
"""Set the branch location to use for a tree reference.
2864
:param file_id: The file-id of the tree reference.
2865
:param tree_path: The path of the tree reference in the tree.
2866
:param branch_location: The location of the branch to retrieve tree
2869
info_dict = self._get_all_reference_info()
2870
info_dict[file_id] = (tree_path, branch_location)
2871
if None in (tree_path, branch_location):
2872
if tree_path is not None:
2873
raise ValueError('tree_path must be None when branch_location'
2875
if branch_location is not None:
2876
raise ValueError('branch_location must be None when tree_path'
2878
del info_dict[file_id]
2879
self._set_all_reference_info(info_dict)
2881
def get_reference_info(self, file_id):
2882
"""Get the tree_path and branch_location for a tree reference.
2884
:return: a tuple of (tree_path, branch_location)
2886
return self._get_all_reference_info().get(file_id, (None, None))
2888
def reference_parent(self, file_id, path, possible_transports=None):
2889
"""Return the parent branch for a tree-reference file_id.
2891
:param file_id: The file_id of the tree reference
2892
:param path: The path of the file_id in the tree
2893
:return: A branch associated with the file_id
2895
branch_location = self.get_reference_info(file_id)[1]
2896
if branch_location is None:
2897
return Branch.reference_parent(self, file_id, path,
2898
possible_transports)
2899
branch_location = urlutils.join(self.user_url, branch_location)
2900
return Branch.open(branch_location,
2901
possible_transports=possible_transports)
2903
def set_push_location(self, location):
2904
"""See Branch.set_push_location."""
2905
self._set_config_location('push_location', location)
2907
def set_bound_location(self, location):
2908
"""See Branch.set_push_location."""
2909
self._master_branch_cache = None
2911
config = self.get_config()
2912
if location is None:
2913
if config.get_user_option('bound') != 'True':
2916
config.set_user_option('bound', 'False', warn_masked=True)
2919
self._set_config_location('bound_location', location,
2921
config.set_user_option('bound', 'True', warn_masked=True)
2924
def _get_bound_location(self, bound):
2925
"""Return the bound location in the config file.
2927
Return None if the bound parameter does not match"""
2928
config = self.get_config()
2929
config_bound = (config.get_user_option('bound') == 'True')
2930
if config_bound != bound:
2932
return self._get_config_location('bound_location', config=config)
2934
def get_bound_location(self):
2935
"""See Branch.set_push_location."""
2936
return self._get_bound_location(True)
2938
def get_old_bound_location(self):
2939
"""See Branch.get_old_bound_location"""
2940
return self._get_bound_location(False)
2942
def get_stacked_on_url(self):
2943
# you can always ask for the URL; but you might not be able to use it
2944
# if the repo can't support stacking.
2945
## self._check_stackable_repo()
2946
# stacked_on_location is only ever defined in branch.conf, so don't
2947
# waste effort reading the whole stack of config files.
2948
config = self.get_config()._get_branch_data_config()
2949
stacked_url = self._get_config_location('stacked_on_location',
2951
if stacked_url is None:
2952
raise errors.NotStacked(self)
2955
def _get_append_revisions_only(self):
2956
return self.get_config(
2957
).get_user_option_as_bool('append_revisions_only')
2960
def get_rev_id(self, revno, history=None):
2961
"""Find the revision id of the specified revno."""
2963
return _mod_revision.NULL_REVISION
2965
last_revno, last_revision_id = self.last_revision_info()
2966
if revno <= 0 or revno > last_revno:
2967
raise errors.NoSuchRevision(self, revno)
2969
if history is not None:
2970
return history[revno - 1]
2972
index = last_revno - revno
2973
if len(self._partial_revision_history_cache) <= index:
2974
self._extend_partial_history(stop_index=index)
2975
if len(self._partial_revision_history_cache) > index:
2976
return self._partial_revision_history_cache[index]
2978
raise errors.NoSuchRevision(self, revno)
2981
def revision_id_to_revno(self, revision_id):
2982
"""Given a revision id, return its revno"""
2983
if _mod_revision.is_null(revision_id):
2986
index = self._partial_revision_history_cache.index(revision_id)
2989
self._extend_partial_history(stop_revision=revision_id)
2990
except errors.RevisionNotPresent, e:
2991
raise errors.GhostRevisionsHaveNoRevno(revision_id, e.revision_id)
2992
index = len(self._partial_revision_history_cache) - 1
2993
if self._partial_revision_history_cache[index] != revision_id:
2994
raise errors.NoSuchRevision(self, revision_id)
2995
return self.revno() - index
2998
class BzrBranch7(BzrBranch8):
2999
"""A branch with support for a fallback repository."""
3001
def set_reference_info(self, file_id, tree_path, branch_location):
3002
Branch.set_reference_info(self, file_id, tree_path, branch_location)
3004
def get_reference_info(self, file_id):
3005
Branch.get_reference_info(self, file_id)
3007
def reference_parent(self, file_id, path, possible_transports=None):
3008
return Branch.reference_parent(self, file_id, path,
3009
possible_transports)
3012
class BzrBranch6(BzrBranch7):
3013
"""See BzrBranchFormat6 for the capabilities of this branch.
3015
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
3019
def get_stacked_on_url(self):
3020
raise errors.UnstackableBranchFormat(self._format, self.user_url)
3023
1356
######################################################################
3024
# results of operations
3027
class _Result(object):
3029
def _show_tag_conficts(self, to_file):
3030
if not getattr(self, 'tag_conflicts', None):
3032
to_file.write('Conflicting tags:\n')
3033
for name, value1, value2 in self.tag_conflicts:
3034
to_file.write(' %s\n' % (name, ))
3037
class PullResult(_Result):
3038
"""Result of a Branch.pull operation.
3040
:ivar old_revno: Revision number before pull.
3041
:ivar new_revno: Revision number after pull.
3042
:ivar old_revid: Tip revision id before pull.
3043
:ivar new_revid: Tip revision id after pull.
3044
:ivar source_branch: Source (local) branch object. (read locked)
3045
:ivar master_branch: Master branch of the target, or the target if no
3047
:ivar local_branch: target branch if there is a Master, else None
3048
:ivar target_branch: Target/destination branch object. (write locked)
3049
:ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
3052
@deprecated_method(deprecated_in((2, 3, 0)))
3054
"""Return the relative change in revno.
3056
:deprecated: Use `new_revno` and `old_revno` instead.
3058
return self.new_revno - self.old_revno
3060
def report(self, to_file):
3062
if self.old_revid == self.new_revid:
3063
to_file.write('No revisions to pull.\n')
3065
to_file.write('Now on revision %d.\n' % self.new_revno)
3066
self._show_tag_conficts(to_file)
3069
class BranchPushResult(_Result):
3070
"""Result of a Branch.push operation.
3072
:ivar old_revno: Revision number (eg 10) of the target before push.
3073
:ivar new_revno: Revision number (eg 12) of the target after push.
3074
:ivar old_revid: Tip revision id (eg joe@foo.com-1234234-aoeua34) of target
3076
:ivar new_revid: Tip revision id (eg joe@foo.com-5676566-boa234a) of target
3078
:ivar source_branch: Source branch object that the push was from. This is
3079
read locked, and generally is a local (and thus low latency) branch.
3080
:ivar master_branch: If target is a bound branch, the master branch of
3081
target, or target itself. Always write locked.
3082
:ivar target_branch: The direct Branch where data is being sent (write
3084
:ivar local_branch: If the target is a bound branch this will be the
3085
target, otherwise it will be None.
3088
@deprecated_method(deprecated_in((2, 3, 0)))
3090
"""Return the relative change in revno.
3092
:deprecated: Use `new_revno` and `old_revno` instead.
3094
return self.new_revno - self.old_revno
3096
def report(self, to_file):
3097
"""Write a human-readable description of the result."""
3098
if self.old_revid == self.new_revid:
3099
note('No new revisions to push.')
3101
note('Pushed up to revision %d.' % self.new_revno)
3102
self._show_tag_conficts(to_file)
3105
class BranchCheckResult(object):
3106
"""Results of checking branch consistency.
3111
def __init__(self, branch):
3112
self.branch = branch
3115
def report_results(self, verbose):
3116
"""Report the check results via trace.note.
3118
:param verbose: Requests more detailed display of what was checked,
3121
note('checked branch %s format %s', self.branch.user_url,
3122
self.branch._format)
3123
for error in self.errors:
3124
note('found error:%s', error)
3127
class Converter5to6(object):
3128
"""Perform an in-place upgrade of format 5 to format 6"""
3130
def convert(self, branch):
3131
# Data for 5 and 6 can peacefully coexist.
3132
format = BzrBranchFormat6()
3133
new_branch = format.open(branch.bzrdir, _found=True)
3135
# Copy source data into target
3136
new_branch._write_last_revision_info(*branch.last_revision_info())
3137
new_branch.set_parent(branch.get_parent())
3138
new_branch.set_bound_location(branch.get_bound_location())
3139
new_branch.set_push_location(branch.get_push_location())
3141
# New branch has no tags by default
3142
new_branch.tags._set_tag_dict({})
3144
# Copying done; now update target format
3145
new_branch._transport.put_bytes('format',
3146
format.get_format_string(),
3147
mode=new_branch.bzrdir._get_file_mode())
3149
# Clean up old files
3150
new_branch._transport.delete('revision-history')
3152
branch.set_parent(None)
3153
except errors.NoSuchFile:
3155
branch.set_bound_location(None)
3158
class Converter6to7(object):
3159
"""Perform an in-place upgrade of format 6 to format 7"""
3161
def convert(self, branch):
3162
format = BzrBranchFormat7()
3163
branch._set_config_location('stacked_on_location', '')
3164
# update target format
3165
branch._transport.put_bytes('format', format.get_format_string())
3168
class Converter7to8(object):
3169
"""Perform an in-place upgrade of format 7 to format 8"""
3171
def convert(self, branch):
3172
format = BzrBranchFormat8()
3173
branch._transport.put_bytes('references', '')
3174
# update target format
3175
branch._transport.put_bytes('format', format.get_format_string())
3178
class InterBranch(InterObject):
3179
"""This class represents operations taking place between two branches.
3181
Its instances have methods like pull() and push() and contain
3182
references to the source and target repositories these operations
3183
can be carried out on.
3187
"""The available optimised InterBranch types."""
3190
def _get_branch_formats_to_test(klass):
3191
"""Return an iterable of format tuples for testing.
3193
:return: An iterable of (from_format, to_format) to use when testing
3194
this InterBranch class. Each InterBranch class should define this
3197
raise NotImplementedError(klass._get_branch_formats_to_test)
3200
def pull(self, overwrite=False, stop_revision=None,
3201
possible_transports=None, local=False):
3202
"""Mirror source into target branch.
3204
The target branch is considered to be 'local', having low latency.
3206
:returns: PullResult instance
3208
raise NotImplementedError(self.pull)
3211
def push(self, overwrite=False, stop_revision=None, lossy=False,
3212
_override_hook_source_branch=None):
3213
"""Mirror the source branch into the target branch.
3215
The source branch is considered to be 'local', having low latency.
3217
raise NotImplementedError(self.push)
3220
def copy_content_into(self, revision_id=None):
3221
"""Copy the content of source into target
3223
revision_id: if not None, the revision history in the new branch will
3224
be truncated to end with revision_id.
3226
raise NotImplementedError(self.copy_content_into)
3229
def fetch(self, stop_revision=None, limit=None):
3232
:param stop_revision: Last revision to fetch
3233
:param limit: Optional rough limit of revisions to fetch
3235
raise NotImplementedError(self.fetch)
3238
class GenericInterBranch(InterBranch):
3239
"""InterBranch implementation that uses public Branch functions."""
3242
def is_compatible(klass, source, target):
3243
# GenericBranch uses the public API, so always compatible
3247
def _get_branch_formats_to_test(klass):
3248
return [(format_registry.get_default(), format_registry.get_default())]
3251
def unwrap_format(klass, format):
3252
if isinstance(format, remote.RemoteBranchFormat):
3253
format._ensure_real()
3254
return format._custom_format
3258
def copy_content_into(self, revision_id=None):
3259
"""Copy the content of source into target
3261
revision_id: if not None, the revision history in the new branch will
3262
be truncated to end with revision_id.
3264
self.source.update_references(self.target)
3265
self.source._synchronize_history(self.target, revision_id)
3267
parent = self.source.get_parent()
3268
except errors.InaccessibleParent, e:
3269
mutter('parent was not accessible to copy: %s', e)
3272
self.target.set_parent(parent)
3273
if self.source._push_should_merge_tags():
3274
self.source.tags.merge_to(self.target.tags)
3277
def fetch(self, stop_revision=None, limit=None):
3278
if self.target.base == self.source.base:
3280
self.source.lock_read()
3282
fetch_spec_factory = fetch.FetchSpecFactory()
3283
fetch_spec_factory.source_branch = self.source
3284
fetch_spec_factory.source_branch_stop_revision_id = stop_revision
3285
fetch_spec_factory.source_repo = self.source.repository
3286
fetch_spec_factory.target_repo = self.target.repository
3287
fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
3288
fetch_spec_factory.limit = limit
3289
fetch_spec = fetch_spec_factory.make_fetch_spec()
3290
return self.target.repository.fetch(self.source.repository,
3291
fetch_spec=fetch_spec)
3293
self.source.unlock()
3296
def _update_revisions(self, stop_revision=None, overwrite=False,
3298
other_revno, other_last_revision = self.source.last_revision_info()
3299
stop_revno = None # unknown
3300
if stop_revision is None:
3301
stop_revision = other_last_revision
3302
if _mod_revision.is_null(stop_revision):
3303
# if there are no commits, we're done.
3305
stop_revno = other_revno
3307
# what's the current last revision, before we fetch [and change it
3309
last_rev = _mod_revision.ensure_null(self.target.last_revision())
3310
# we fetch here so that we don't process data twice in the common
3311
# case of having something to pull, and so that the check for
3312
# already merged can operate on the just fetched graph, which will
3313
# be cached in memory.
3314
self.fetch(stop_revision=stop_revision)
3315
# Check to see if one is an ancestor of the other
3318
graph = self.target.repository.get_graph()
3319
if self.target._check_if_descendant_or_diverged(
3320
stop_revision, last_rev, graph, self.source):
3321
# stop_revision is a descendant of last_rev, but we aren't
3322
# overwriting, so we're done.
3324
if stop_revno is None:
3326
graph = self.target.repository.get_graph()
3327
this_revno, this_last_revision = \
3328
self.target.last_revision_info()
3329
stop_revno = graph.find_distance_to_null(stop_revision,
3330
[(other_last_revision, other_revno),
3331
(this_last_revision, this_revno)])
3332
self.target.set_last_revision_info(stop_revno, stop_revision)
3335
def pull(self, overwrite=False, stop_revision=None,
3336
possible_transports=None, run_hooks=True,
3337
_override_hook_target=None, local=False):
3338
"""Pull from source into self, updating my master if any.
3340
:param run_hooks: Private parameter - if false, this branch
3341
is being called because it's the master of the primary branch,
3342
so it should not run its hooks.
3344
bound_location = self.target.get_bound_location()
3345
if local and not bound_location:
3346
raise errors.LocalRequiresBoundBranch()
3347
master_branch = None
3348
source_is_master = False
3350
# bound_location comes from a config file, some care has to be
3351
# taken to relate it to source.user_url
3352
normalized = urlutils.normalize_url(bound_location)
3354
relpath = self.source.user_transport.relpath(normalized)
3355
source_is_master = (relpath == '')
3356
except (errors.PathNotChild, errors.InvalidURL):
3357
source_is_master = False
3358
if not local and bound_location and not source_is_master:
3359
# not pulling from master, so we need to update master.
3360
master_branch = self.target.get_master_branch(possible_transports)
3361
master_branch.lock_write()
3364
# pull from source into master.
3365
master_branch.pull(self.source, overwrite, stop_revision,
3367
return self._pull(overwrite,
3368
stop_revision, _hook_master=master_branch,
3369
run_hooks=run_hooks,
3370
_override_hook_target=_override_hook_target,
3371
merge_tags_to_master=not source_is_master)
3374
master_branch.unlock()
3376
def push(self, overwrite=False, stop_revision=None, lossy=False,
3377
_override_hook_source_branch=None):
3378
"""See InterBranch.push.
3380
This is the basic concrete implementation of push()
3382
:param _override_hook_source_branch: If specified, run the hooks
3383
passing this Branch as the source, rather than self. This is for
3384
use of RemoteBranch, where push is delegated to the underlying
3388
raise errors.LossyPushToSameVCS(self.source, self.target)
3389
# TODO: Public option to disable running hooks - should be trivial but
3392
op = cleanup.OperationWithCleanups(self._push_with_bound_branches)
3393
op.add_cleanup(self.source.lock_read().unlock)
3394
op.add_cleanup(self.target.lock_write().unlock)
3395
return op.run(overwrite, stop_revision,
3396
_override_hook_source_branch=_override_hook_source_branch)
3398
def _basic_push(self, overwrite, stop_revision):
3399
"""Basic implementation of push without bound branches or hooks.
3401
Must be called with source read locked and target write locked.
3403
result = BranchPushResult()
3404
result.source_branch = self.source
3405
result.target_branch = self.target
3406
result.old_revno, result.old_revid = self.target.last_revision_info()
3407
self.source.update_references(self.target)
3408
if result.old_revid != stop_revision:
3409
# We assume that during 'push' this repository is closer than
3411
graph = self.source.repository.get_graph(self.target.repository)
3412
self._update_revisions(stop_revision, overwrite=overwrite,
3414
if self.source._push_should_merge_tags():
3415
result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
3417
result.new_revno, result.new_revid = self.target.last_revision_info()
3420
def _push_with_bound_branches(self, operation, overwrite, stop_revision,
3421
_override_hook_source_branch=None):
3422
"""Push from source into target, and into target's master if any.
3425
if _override_hook_source_branch:
3426
result.source_branch = _override_hook_source_branch
3427
for hook in Branch.hooks['post_push']:
3430
bound_location = self.target.get_bound_location()
3431
if bound_location and self.target.base != bound_location:
3432
# there is a master branch.
3434
# XXX: Why the second check? Is it even supported for a branch to
3435
# be bound to itself? -- mbp 20070507
3436
master_branch = self.target.get_master_branch()
3437
master_branch.lock_write()
3438
operation.add_cleanup(master_branch.unlock)
3439
# push into the master from the source branch.
3440
master_inter = InterBranch.get(self.source, master_branch)
3441
master_inter._basic_push(overwrite, stop_revision)
3442
# and push into the target branch from the source. Note that
3443
# we push from the source branch again, because it's considered
3444
# the highest bandwidth repository.
3445
result = self._basic_push(overwrite, stop_revision)
3446
result.master_branch = master_branch
3447
result.local_branch = self.target
3449
master_branch = None
3451
result = self._basic_push(overwrite, stop_revision)
3452
# TODO: Why set master_branch and local_branch if there's no
3453
# binding? Maybe cleaner to just leave them unset? -- mbp
3455
result.master_branch = self.target
3456
result.local_branch = None
3460
def _pull(self, overwrite=False, stop_revision=None,
3461
possible_transports=None, _hook_master=None, run_hooks=True,
3462
_override_hook_target=None, local=False,
3463
merge_tags_to_master=True):
3466
This function is the core worker, used by GenericInterBranch.pull to
3467
avoid duplication when pulling source->master and source->local.
3469
:param _hook_master: Private parameter - set the branch to
3470
be supplied as the master to pull hooks.
3471
:param run_hooks: Private parameter - if false, this branch
3472
is being called because it's the master of the primary branch,
3473
so it should not run its hooks.
3474
is being called because it's the master of the primary branch,
3475
so it should not run its hooks.
3476
:param _override_hook_target: Private parameter - set the branch to be
3477
supplied as the target_branch to pull hooks.
3478
:param local: Only update the local branch, and not the bound branch.
3480
# This type of branch can't be bound.
3482
raise errors.LocalRequiresBoundBranch()
3483
result = PullResult()
3484
result.source_branch = self.source
3485
if _override_hook_target is None:
3486
result.target_branch = self.target
3488
result.target_branch = _override_hook_target
3489
self.source.lock_read()
3491
# We assume that during 'pull' the target repository is closer than
3493
self.source.update_references(self.target)
3494
graph = self.target.repository.get_graph(self.source.repository)
3495
# TODO: Branch formats should have a flag that indicates
3496
# that revno's are expensive, and pull() should honor that flag.
3498
result.old_revno, result.old_revid = \
3499
self.target.last_revision_info()
3500
self._update_revisions(stop_revision, overwrite=overwrite,
3502
# TODO: The old revid should be specified when merging tags,
3503
# so a tags implementation that versions tags can only
3504
# pull in the most recent changes. -- JRV20090506
3505
result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
3506
overwrite, ignore_master=not merge_tags_to_master)
3507
result.new_revno, result.new_revid = self.target.last_revision_info()
3509
result.master_branch = _hook_master
3510
result.local_branch = result.target_branch
3512
result.master_branch = result.target_branch
3513
result.local_branch = None
3515
for hook in Branch.hooks['post_pull']:
3518
self.source.unlock()
3522
InterBranch.register_optimiser(GenericInterBranch)
1360
def is_control_file(filename):
1361
## FIXME: better check
1362
filename = os.path.normpath(filename)
1363
while filename != '':
1364
head, tail = os.path.split(filename)
1365
## mutter('check %r for control file' % ((head, tail), ))
1366
if tail == bzrlib.BZRDIR:
1368
if filename == head:
1375
def gen_file_id(name):
1376
"""Return new file id.
1378
This should probably generate proper UUIDs, but for the moment we
1379
cope with just randomness because running uuidgen every time is
1382
from binascii import hexlify
1383
from time import time
1385
# get last component
1386
idx = name.rfind('/')
1388
name = name[idx+1 : ]
1389
idx = name.rfind('\\')
1391
name = name[idx+1 : ]
1393
# make it not a hidden file
1394
name = name.lstrip('.')
1396
# remove any wierd characters; we don't escape them but rather
1397
# just pull them out
1398
name = re.sub(r'[^\w.]', '', name)
1400
s = hexlify(rand_bytes(8))
1401
return '-'.join((name, compact_date(time()), s))
1405
"""Return a new tree-root file id."""
1406
return gen_file_id('TREE_ROOT')