1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005-2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from cStringIO import StringIO
22
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
sha_file, appendpath, file_kind
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
from bzrlib.textui import show_status
29
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
31
from bzrlib.delta import compare_trees
32
from bzrlib.tree import EmptyTree, RevisionTree
34
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
## TODO: Maybe include checks for common corruption of newlines, etc?
38
# TODO: Some operations like log might retrieve the same revisions
39
# repeatedly to calculate deltas. We could perhaps have a weakref
40
# cache in memory to make this faster.
43
def find_branch(f, **args):
44
if f and (f.startswith('http://') or f.startswith('https://')):
46
return remotebranch.RemoteBranch(f, **args)
48
return Branch(f, **args)
51
def find_cached_branch(f, cache_root, **args):
52
from remotebranch import RemoteBranch
53
br = find_branch(f, **args)
54
def cacheify(br, store_name):
55
from meta_store import CachedStore
56
cache_path = os.path.join(cache_root, store_name)
58
new_store = CachedStore(getattr(br, store_name), cache_path)
59
setattr(br, store_name, new_store)
61
if isinstance(br, RemoteBranch):
62
cacheify(br, 'inventory_store')
63
cacheify(br, 'text_store')
64
cacheify(br, 'revision_store')
68
def _relpath(base, path):
69
"""Return path relative to base, or raise exception.
71
The path may be either an absolute path or a path relative to the
72
current working directory.
74
Lifted out of Branch.relpath for ease of testing.
76
os.path.commonprefix (python2.4) has a bad bug that it works just
77
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
78
avoids that problem."""
79
rp = os.path.abspath(path)
83
while len(head) >= len(base):
86
head, tail = os.path.split(head)
90
from errors import NotBranchError
91
raise NotBranchError("path %r is not within branch %r" % (rp, base))
96
def find_branch_root(f=None):
97
"""Find the branch root enclosing f, or pwd.
99
f may be a filename or a URL.
101
It is not necessary that f exists.
103
Basically we keep looking up until we find the control directory or
104
run into the root."""
107
elif hasattr(os.path, 'realpath'):
108
f = os.path.realpath(f)
110
f = os.path.abspath(f)
111
if not os.path.exists(f):
112
raise BzrError('%r does not exist' % f)
118
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
120
head, tail = os.path.split(f)
122
# reached the root, whatever that may be
123
raise BzrError('%r is not in a branch' % orig_f)
126
class DivergedBranches(Exception):
127
def __init__(self, branch1, branch2):
128
self.branch1 = branch1
129
self.branch2 = branch2
130
Exception.__init__(self, "These branches have diverged.")
133
######################################################################
136
class Branch(object):
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
23
from itertools import chain
27
config as _mod_config,
34
revision as _mod_revision,
42
from bzrlib.config import BranchConfig, TransportConfig
43
from bzrlib.repofmt.pack_repo import RepositoryFormatKnitPack5RichRoot
44
from bzrlib.tag import (
50
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
51
from bzrlib.hooks import HookPoint, Hooks
52
from bzrlib.inter import InterObject
53
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
54
from bzrlib import registry
55
from bzrlib.symbol_versioning import (
59
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
63
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
64
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
67
class Branch(bzrdir.ControlComponent):
137
68
"""Branch holding a history of revisions.
140
Base directory of the branch.
146
If _lock_mode is true, a positive count of the number of times the
150
Lock object from bzrlib.lock.
71
Base directory/url of the branch; using control_url and
72
control_transport is more standardized.
74
hooks: An instance of BranchHooks.
76
# this is really an instance variable - FIXME move it there
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 __init__(self, base, init=False, find_root=True):
163
"""Create new branch object at a particular location.
165
base -- Base directory for the branch.
167
init -- If True, create new control files in a previously
168
unversioned directory. If False, the branch must already
171
find_root -- If true and init is false, find the root of the
172
existing branch containing base.
174
In the test suite, creation of new trees is tested using the
175
`ScratchBranch` class.
177
from bzrlib.store import ImmutableStore
179
self.base = os.path.realpath(base)
182
self.base = find_branch_root(base)
184
self.base = os.path.realpath(base)
185
if not isdir(self.controlfilename('.')):
186
from errors import NotBranchError
187
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
188
['use "bzr init" to initialize a new working tree',
189
'current bzr can only operate from top-of-tree'])
192
self.text_store = ImmutableStore(self.controlfilename('text-store'))
193
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
194
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
198
return '%s(%r)' % (self.__class__.__name__, self.base)
205
if self._lock_mode or self._lock:
206
from warnings import warn
207
warn("branch %r was not explicitly unlocked" % self)
212
def lock_write(self):
214
if self._lock_mode != 'w':
215
from errors import LockError
216
raise LockError("can't upgrade to a write lock from %r" %
218
self._lock_count += 1
220
from bzrlib.lock import WriteLock
222
self._lock = WriteLock(self.controlfilename('branch-lock'))
223
self._lock_mode = 'w'
81
def control_transport(self):
82
return self._transport
85
def user_transport(self):
86
return self.bzrdir.user_transport
88
def __init__(self, *ignored, **ignored_too):
89
self.tags = self._format.make_tags(self)
90
self._revision_history_cache = None
91
self._revision_id_to_revno_cache = None
92
self._partial_revision_id_to_revno_cache = {}
93
self._partial_revision_history_cache = []
94
self._last_revision_info_cache = None
95
self._merge_sorted_revisions_cache = None
97
hooks = Branch.hooks['open']
101
def _open_hook(self):
102
"""Called by init to allow simpler extension of the base class."""
104
def _activate_fallback_location(self, url):
105
"""Activate the branch/repository from url as a fallback repository."""
106
repo = self._get_fallback_repository(url)
107
if repo.has_same_location(self.repository):
108
raise errors.UnstackableLocationError(self.user_url, url)
109
self.repository.add_fallback_repository(repo)
111
def break_lock(self):
112
"""Break a lock if one is present from another instance.
114
Uses the ui factory to ask for confirmation if the lock may be from
117
This will probe the repository for its lock as well.
119
self.control_files.break_lock()
120
self.repository.break_lock()
121
master = self.get_master_branch()
122
if master is not None:
125
def _check_stackable_repo(self):
126
if not self.repository._format.supports_external_lookups:
127
raise errors.UnstackableRepositoryFormat(self.repository._format,
128
self.repository.base)
130
def _extend_partial_history(self, stop_index=None, stop_revision=None):
131
"""Extend the partial history to include a given index
133
If a stop_index is supplied, stop when that index has been reached.
134
If a stop_revision is supplied, stop when that revision is
135
encountered. Otherwise, stop when the beginning of history is
138
:param stop_index: The index which should be present. When it is
139
present, history extension will stop.
140
:param stop_revision: The revision id which should be present. When
141
it is encountered, history extension will stop.
143
if len(self._partial_revision_history_cache) == 0:
144
self._partial_revision_history_cache = [self.last_revision()]
145
repository._iter_for_revno(
146
self.repository, self._partial_revision_history_cache,
147
stop_index=stop_index, stop_revision=stop_revision)
148
if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
149
self._partial_revision_history_cache.pop()
151
def _get_check_refs(self):
152
"""Get the references needed for check().
156
revid = self.last_revision()
157
return [('revision-existence', revid), ('lefthand-distance', revid)]
160
def open(base, _unsupported=False, possible_transports=None):
161
"""Open the branch rooted at base.
163
For instance, if the branch is at URL/.bzr/branch,
164
Branch.open(URL) -> a Branch instance.
166
control = bzrdir.BzrDir.open(base, _unsupported,
167
possible_transports=possible_transports)
168
return control.open_branch(unsupported=_unsupported)
171
def open_from_transport(transport, name=None, _unsupported=False):
172
"""Open the branch rooted at transport"""
173
control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
174
return control.open_branch(name=name, unsupported=_unsupported)
177
def open_containing(url, possible_transports=None):
178
"""Open an existing branch which contains url.
180
This probes for a branch at url, and searches upwards from there.
182
Basically we keep looking up until we find the control directory or
183
run into the root. If there isn't one, raises NotBranchError.
184
If there is one and it is either an unrecognised format or an unsupported
185
format, UnknownFormatError or UnsupportedFormatError are raised.
186
If there is one, it is returned, along with the unused portion of url.
188
control, relpath = bzrdir.BzrDir.open_containing(url,
190
return control.open_branch(), relpath
192
def _push_should_merge_tags(self):
193
"""Should _basic_push merge this branch's tags into the target?
195
The default implementation returns False if this branch has no tags,
196
and True the rest of the time. Subclasses may override this.
198
return self.supports_tags() and self.tags.get_tag_dict()
200
def get_config(self):
201
"""Get a bzrlib.config.BranchConfig for this Branch.
203
This can then be used to get and set configuration options for the
206
:return: A bzrlib.config.BranchConfig.
208
return BranchConfig(self)
210
def _get_config(self):
211
"""Get the concrete config for just the config in this branch.
213
This is not intended for client use; see Branch.get_config for the
218
:return: An object supporting get_option and set_option.
220
raise NotImplementedError(self._get_config)
222
def _get_fallback_repository(self, url):
223
"""Get the repository we fallback to at url."""
224
url = urlutils.join(self.base, url)
225
a_branch = Branch.open(url,
226
possible_transports=[self.bzrdir.root_transport])
227
return a_branch.repository
229
def _get_tags_bytes(self):
230
"""Get the bytes of a serialised tags dict.
232
Note that not all branches support tags, nor do all use the same tags
233
logic: this method is specific to BasicTags. Other tag implementations
234
may use the same method name and behave differently, safely, because
235
of the double-dispatch via
236
format.make_tags->tags_instance->get_tags_dict.
238
:return: The bytes of the tags file.
239
:seealso: Branch._set_tags_bytes.
241
return self._transport.get_bytes('tags')
243
def _get_nick(self, local=False, possible_transports=None):
244
config = self.get_config()
245
# explicit overrides master, but don't look for master if local is True
246
if not local and not config.has_explicit_nickname():
248
master = self.get_master_branch(possible_transports)
249
if master and self.user_url == master.user_url:
250
raise errors.RecursiveBind(self.user_url)
251
if master is not None:
252
# return the master branch value
254
except errors.RecursiveBind, e:
256
except errors.BzrError, e:
257
# Silently fall back to local implicit nick if the master is
259
mutter("Could not connect to bound branch, "
260
"falling back to local nick.\n " + str(e))
261
return config.get_nickname()
263
def _set_nick(self, nick):
264
self.get_config().set_user_option('nickname', nick, warn_masked=True)
266
nick = property(_get_nick, _set_nick)
269
raise NotImplementedError(self.is_locked)
271
def _lefthand_history(self, revision_id, last_rev=None,
273
if 'evil' in debug.debug_flags:
274
mutter_callsite(4, "_lefthand_history scales with history.")
275
# stop_revision must be a descendant of last_revision
276
graph = self.repository.get_graph()
277
if last_rev is not None:
278
if not graph.is_ancestor(last_rev, revision_id):
279
# our previous tip is not merged into stop_revision
280
raise errors.DivergedBranches(self, other_branch)
281
# make a new revision history from the graph
282
parents_map = graph.get_parent_map([revision_id])
283
if revision_id not in parents_map:
284
raise errors.NoSuchRevision(self, revision_id)
285
current_rev_id = revision_id
287
check_not_reserved_id = _mod_revision.check_not_reserved_id
288
# Do not include ghosts or graph origin in revision_history
289
while (current_rev_id in parents_map and
290
len(parents_map[current_rev_id]) > 0):
291
check_not_reserved_id(current_rev_id)
292
new_history.append(current_rev_id)
293
current_rev_id = parents_map[current_rev_id][0]
294
parents_map = graph.get_parent_map([current_rev_id])
295
new_history.reverse()
298
def lock_write(self, token=None):
299
"""Lock the branch for write operations.
301
:param token: A token to permit reacquiring a previously held and
303
:return: A BranchWriteLockResult.
305
raise NotImplementedError(self.lock_write)
228
307
def lock_read(self):
230
assert self._lock_mode in ('r', 'w'), \
231
"invalid lock mode %r" % self._lock_mode
232
self._lock_count += 1
234
from bzrlib.lock import ReadLock
236
self._lock = ReadLock(self.controlfilename('branch-lock'))
237
self._lock_mode = 'r'
308
"""Lock the branch for read operations.
310
:return: A bzrlib.lock.LogicalLockResult.
312
raise NotImplementedError(self.lock_read)
242
314
def unlock(self):
243
if not self._lock_mode:
244
from errors import LockError
245
raise LockError('branch %r is not locked' % (self))
247
if self._lock_count > 1:
248
self._lock_count -= 1
252
self._lock_mode = self._lock_count = None
255
def abspath(self, name):
256
"""Return absolute filename for something in the branch"""
257
return os.path.join(self.base, name)
260
def relpath(self, path):
261
"""Return path relative to this branch of something inside it.
263
Raises an error if path is not in this branch."""
264
return _relpath(self.base, path)
267
def controlfilename(self, file_or_path):
268
"""Return location relative to branch."""
269
if isinstance(file_or_path, basestring):
270
file_or_path = [file_or_path]
271
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
274
def controlfile(self, file_or_path, mode='r'):
275
"""Open a control file for this branch.
277
There are two classes of file in the control directory: text
278
and binary. binary files are untranslated byte streams. Text
279
control files are stored with Unix newlines and in UTF-8, even
280
if the platform or locale defaults are different.
282
Controlfiles should almost never be opened in write mode but
283
rather should be atomically copied and replaced using atomicfile.
286
fn = self.controlfilename(file_or_path)
288
if mode == 'rb' or mode == 'wb':
289
return file(fn, mode)
290
elif mode == 'r' or mode == 'w':
291
# open in binary mode anyhow so there's no newline translation;
292
# codecs uses line buffering by default; don't want that.
294
return codecs.open(fn, mode + 'b', 'utf-8',
297
raise BzrError("invalid controlfile mode %r" % mode)
301
def _make_control(self):
302
from bzrlib.inventory import Inventory
303
from bzrlib.xml import pack_xml
305
os.mkdir(self.controlfilename([]))
306
self.controlfile('README', 'w').write(
307
"This is a Bazaar-NG control directory.\n"
308
"Do not change any files in this directory.\n")
309
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
310
for d in ('text-store', 'inventory-store', 'revision-store'):
311
os.mkdir(self.controlfilename(d))
312
for f in ('revision-history', 'merged-patches',
313
'pending-merged-patches', 'branch-name',
316
self.controlfile(f, 'w').write('')
317
mutter('created control directory in ' + self.base)
319
# if we want per-tree root ids then this is the place to set
320
# them; they're not needed for now and so ommitted for
322
pack_xml(Inventory(), self.controlfile('inventory','w'))
325
def _check_format(self):
326
"""Check this branch format is supported.
328
The current tool only supports the current unstable format.
330
In the future, we might need different in-memory Branch
331
classes to support downlevel branches. But not yet.
333
# This ignores newlines so that we can open branches created
334
# on Windows from Linux and so on. I think it might be better
335
# to always make all internal files in unix format.
336
fmt = self.controlfile('branch-format', 'r').read()
337
fmt.replace('\r\n', '')
338
if fmt != BZR_BRANCH_FORMAT:
339
raise BzrError('sorry, branch format %r not supported' % fmt,
340
['use a different bzr version',
341
'or remove the .bzr directory and "bzr init" again'])
343
def get_root_id(self):
344
"""Return the id of this branches root"""
345
inv = self.read_working_inventory()
346
return inv.root.file_id
348
def set_root_id(self, file_id):
349
inv = self.read_working_inventory()
350
orig_root_id = inv.root.file_id
351
del inv._byid[inv.root.file_id]
352
inv.root.file_id = file_id
353
inv._byid[inv.root.file_id] = inv.root
356
if entry.parent_id in (None, orig_root_id):
357
entry.parent_id = inv.root.file_id
358
self._write_inventory(inv)
360
def read_working_inventory(self):
361
"""Read the working inventory."""
362
from bzrlib.inventory import Inventory
363
from bzrlib.xml import unpack_xml
364
from time import time
368
# ElementTree does its own conversion from UTF-8, so open in
370
inv = unpack_xml(Inventory,
371
self.controlfile('inventory', 'rb'))
372
mutter("loaded inventory of %d items in %f"
373
% (len(inv), time() - before))
379
def _write_inventory(self, inv):
380
"""Update the working inventory.
382
That is to say, the inventory describing changes underway, that
383
will be committed to the next revision.
385
from bzrlib.atomicfile import AtomicFile
386
from bzrlib.xml import pack_xml
390
f = AtomicFile(self.controlfilename('inventory'), 'wb')
399
mutter('wrote working inventory')
402
inventory = property(read_working_inventory, _write_inventory, None,
403
"""Inventory for the working copy.""")
406
def add(self, files, verbose=False, ids=None):
407
"""Make files versioned.
409
Note that the command line normally calls smart_add instead.
411
This puts the files in the Added state, so that they will be
412
recorded by the next commit.
415
List of paths to add, relative to the base of the tree.
418
If set, use these instead of automatically generated ids.
419
Must be the same length as the list of files, but may
420
contain None for ids that are to be autogenerated.
422
TODO: Perhaps have an option to add the ids even if the files do
425
TODO: Perhaps return the ids of the files? But then again it
426
is easy to retrieve them if they're needed.
428
TODO: Adding a directory should optionally recurse down and
429
add all non-ignored children. Perhaps do that in a
432
# TODO: Re-adding a file that is removed in the working copy
433
# should probably put it back with the previous ID.
434
if isinstance(files, basestring):
435
assert(ids is None or isinstance(ids, basestring))
441
ids = [None] * len(files)
443
assert(len(ids) == len(files))
447
inv = self.read_working_inventory()
448
for f,file_id in zip(files, ids):
449
if is_control_file(f):
450
raise BzrError("cannot add control file %s" % quotefn(f))
455
raise BzrError("cannot add top-level %r" % f)
457
fullpath = os.path.normpath(self.abspath(f))
460
kind = file_kind(fullpath)
462
# maybe something better?
463
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
465
if kind != 'file' and kind != 'directory':
466
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
469
file_id = gen_file_id(f)
470
inv.add_path(f, kind=kind, file_id=file_id)
473
print 'added', quotefn(f)
475
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
477
self._write_inventory(inv)
482
def print_file(self, file, revno):
483
"""Print `file` to stdout."""
486
tree = self.revision_tree(self.lookup_revision(revno))
487
# use inventory as it was in that revision
488
file_id = tree.inventory.path2id(file)
490
raise BzrError("%r is not present in revision %s" % (file, revno))
491
tree.print_file(file_id)
496
def remove(self, files, verbose=False):
497
"""Mark nominated files for removal from the inventory.
499
This does not remove their text. This does not run on
501
TODO: Refuse to remove modified files unless --force is given?
503
TODO: Do something useful with directories.
505
TODO: Should this remove the text or not? Tough call; not
506
removing may be useful and the user can just use use rm, and
507
is the opposite of add. Removing it is consistent with most
508
other tools. Maybe an option.
510
## TODO: Normalize names
511
## TODO: Remove nested loops; better scalability
512
if isinstance(files, basestring):
518
tree = self.working_tree()
521
# do this before any modifications
525
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
526
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
528
# having remove it, it must be either ignored or unknown
529
if tree.is_ignored(f):
533
show_status(new_status, inv[fid].kind, quotefn(f))
536
self._write_inventory(inv)
541
# FIXME: this doesn't need to be a branch method
542
def set_inventory(self, new_inventory_list):
543
from bzrlib.inventory import Inventory, InventoryEntry
544
inv = Inventory(self.get_root_id())
545
for path, file_id, parent, kind in new_inventory_list:
546
name = os.path.basename(path)
549
inv.add(InventoryEntry(file_id, name, kind, parent))
550
self._write_inventory(inv)
554
"""Return all unknown files.
556
These are files in the working directory that are not versioned or
557
control files or ignored.
559
>>> b = ScratchBranch(files=['foo', 'foo~'])
560
>>> list(b.unknowns())
563
>>> list(b.unknowns())
566
>>> list(b.unknowns())
569
return self.working_tree().unknowns()
572
def append_revision(self, *revision_ids):
573
from bzrlib.atomicfile import AtomicFile
575
for revision_id in revision_ids:
576
mutter("add {%s} to revision-history" % revision_id)
578
rev_history = self.revision_history()
579
rev_history.extend(revision_ids)
581
f = AtomicFile(self.controlfilename('revision-history'))
583
for rev_id in rev_history:
590
def get_revision_xml(self, revision_id):
591
"""Return XML file object for revision object."""
592
if not revision_id or not isinstance(revision_id, basestring):
593
raise InvalidRevisionId(revision_id)
598
return self.revision_store[revision_id]
600
raise bzrlib.errors.NoSuchRevision(self, revision_id)
605
def get_revision(self, revision_id):
606
"""Return the Revision object for a named revision"""
607
xml_file = self.get_revision_xml(revision_id)
610
r = unpack_xml(Revision, xml_file)
611
except SyntaxError, e:
612
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
616
assert r.revision_id == revision_id
315
raise NotImplementedError(self.unlock)
317
def peek_lock_mode(self):
318
"""Return lock mode for the Branch: 'r', 'w' or None"""
319
raise NotImplementedError(self.peek_lock_mode)
321
def get_physical_lock_status(self):
322
raise NotImplementedError(self.get_physical_lock_status)
325
def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
326
"""Return the revision_id for a dotted revno.
328
:param revno: a tuple like (1,) or (1,1,2)
329
:param _cache_reverse: a private parameter enabling storage
330
of the reverse mapping in a top level cache. (This should
331
only be done in selective circumstances as we want to
332
avoid having the mapping cached multiple times.)
333
:return: the revision_id
334
:raises errors.NoSuchRevision: if the revno doesn't exist
336
rev_id = self._do_dotted_revno_to_revision_id(revno)
338
self._partial_revision_id_to_revno_cache[rev_id] = revno
341
def _do_dotted_revno_to_revision_id(self, revno):
342
"""Worker function for dotted_revno_to_revision_id.
344
Subclasses should override this if they wish to
345
provide a more efficient implementation.
348
return self.get_rev_id(revno[0])
349
revision_id_to_revno = self.get_revision_id_to_revno_map()
350
revision_ids = [revision_id for revision_id, this_revno
351
in revision_id_to_revno.iteritems()
352
if revno == this_revno]
353
if len(revision_ids) == 1:
354
return revision_ids[0]
356
revno_str = '.'.join(map(str, revno))
357
raise errors.NoSuchRevision(self, revno_str)
360
def revision_id_to_dotted_revno(self, revision_id):
361
"""Given a revision id, return its dotted revno.
363
:return: a tuple like (1,) or (400,1,3).
365
return self._do_revision_id_to_dotted_revno(revision_id)
367
def _do_revision_id_to_dotted_revno(self, revision_id):
368
"""Worker function for revision_id_to_revno."""
369
# Try the caches if they are loaded
370
result = self._partial_revision_id_to_revno_cache.get(revision_id)
371
if result is not None:
373
if self._revision_id_to_revno_cache:
374
result = self._revision_id_to_revno_cache.get(revision_id)
376
raise errors.NoSuchRevision(self, revision_id)
377
# Try the mainline as it's optimised
379
revno = self.revision_id_to_revno(revision_id)
381
except errors.NoSuchRevision:
382
# We need to load and use the full revno map after all
383
result = self.get_revision_id_to_revno_map().get(revision_id)
385
raise errors.NoSuchRevision(self, revision_id)
389
def get_revision_id_to_revno_map(self):
390
"""Return the revision_id => dotted revno map.
392
This will be regenerated on demand, but will be cached.
394
:return: A dictionary mapping revision_id => dotted revno.
395
This dictionary should not be modified by the caller.
397
if self._revision_id_to_revno_cache is not None:
398
mapping = self._revision_id_to_revno_cache
400
mapping = self._gen_revno_map()
401
self._cache_revision_id_to_revno(mapping)
402
# TODO: jam 20070417 Since this is being cached, should we be returning
404
# I would rather not, and instead just declare that users should not
405
# modify the return value.
408
def _gen_revno_map(self):
409
"""Create a new mapping from revision ids to dotted revnos.
411
Dotted revnos are generated based on the current tip in the revision
413
This is the worker function for get_revision_id_to_revno_map, which
414
just caches the return value.
416
:return: A dictionary mapping revision_id => dotted revno.
418
revision_id_to_revno = dict((rev_id, revno)
419
for rev_id, depth, revno, end_of_merge
420
in self.iter_merge_sorted_revisions())
421
return revision_id_to_revno
424
def iter_merge_sorted_revisions(self, start_revision_id=None,
425
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
426
"""Walk the revisions for a branch in merge sorted order.
428
Merge sorted order is the output from a merge-aware,
429
topological sort, i.e. all parents come before their
430
children going forward; the opposite for reverse.
432
:param start_revision_id: the revision_id to begin walking from.
433
If None, the branch tip is used.
434
:param stop_revision_id: the revision_id to terminate the walk
435
after. If None, the rest of history is included.
436
:param stop_rule: if stop_revision_id is not None, the precise rule
437
to use for termination:
438
* 'exclude' - leave the stop revision out of the result (default)
439
* 'include' - the stop revision is the last item in the result
440
* 'with-merges' - include the stop revision and all of its
441
merged revisions in the result
442
* 'with-merges-without-common-ancestry' - filter out revisions
443
that are in both ancestries
444
:param direction: either 'reverse' or 'forward':
445
* reverse means return the start_revision_id first, i.e.
446
start at the most recent revision and go backwards in history
447
* forward returns tuples in the opposite order to reverse.
448
Note in particular that forward does *not* do any intelligent
449
ordering w.r.t. depth as some clients of this API may like.
450
(If required, that ought to be done at higher layers.)
452
:return: an iterator over (revision_id, depth, revno, end_of_merge)
455
* revision_id: the unique id of the revision
456
* depth: How many levels of merging deep this node has been
458
* revno_sequence: This field provides a sequence of
459
revision numbers for all revisions. The format is:
460
(REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
461
branch that the revno is on. From left to right the REVNO numbers
462
are the sequence numbers within that branch of the revision.
463
* end_of_merge: When True the next node (earlier in history) is
464
part of a different merge.
466
# Note: depth and revno values are in the context of the branch so
467
# we need the full graph to get stable numbers, regardless of the
469
if self._merge_sorted_revisions_cache is None:
470
last_revision = self.last_revision()
471
known_graph = self.repository.get_known_graph_ancestry(
473
self._merge_sorted_revisions_cache = known_graph.merge_sort(
475
filtered = self._filter_merge_sorted_revisions(
476
self._merge_sorted_revisions_cache, start_revision_id,
477
stop_revision_id, stop_rule)
478
# Make sure we don't return revisions that are not part of the
479
# start_revision_id ancestry.
480
filtered = self._filter_start_non_ancestors(filtered)
481
if direction == 'reverse':
483
if direction == 'forward':
484
return reversed(list(filtered))
486
raise ValueError('invalid direction %r' % direction)
488
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
489
start_revision_id, stop_revision_id, stop_rule):
490
"""Iterate over an inclusive range of sorted revisions."""
491
rev_iter = iter(merge_sorted_revisions)
492
if start_revision_id is not None:
493
for node in rev_iter:
494
rev_id = node.key[-1]
495
if rev_id != start_revision_id:
498
# The decision to include the start or not
499
# depends on the stop_rule if a stop is provided
500
# so pop this node back into the iterator
501
rev_iter = chain(iter([node]), rev_iter)
503
if stop_revision_id is None:
505
for node in rev_iter:
506
rev_id = node.key[-1]
507
yield (rev_id, node.merge_depth, node.revno,
509
elif stop_rule == 'exclude':
510
for node in rev_iter:
511
rev_id = node.key[-1]
512
if rev_id == stop_revision_id:
514
yield (rev_id, node.merge_depth, node.revno,
516
elif stop_rule == 'include':
517
for node in rev_iter:
518
rev_id = node.key[-1]
519
yield (rev_id, node.merge_depth, node.revno,
521
if rev_id == stop_revision_id:
523
elif stop_rule == 'with-merges-without-common-ancestry':
524
# We want to exclude all revisions that are already part of the
525
# stop_revision_id ancestry.
526
graph = self.repository.get_graph()
527
ancestors = graph.find_unique_ancestors(start_revision_id,
529
for node in rev_iter:
530
rev_id = node.key[-1]
531
if rev_id not in ancestors:
533
yield (rev_id, node.merge_depth, node.revno,
535
elif stop_rule == 'with-merges':
536
stop_rev = self.repository.get_revision(stop_revision_id)
537
if stop_rev.parent_ids:
538
left_parent = stop_rev.parent_ids[0]
540
left_parent = _mod_revision.NULL_REVISION
541
# left_parent is the actual revision we want to stop logging at,
542
# since we want to show the merged revisions after the stop_rev too
543
reached_stop_revision_id = False
544
revision_id_whitelist = []
545
for node in rev_iter:
546
rev_id = node.key[-1]
547
if rev_id == left_parent:
548
# reached the left parent after the stop_revision
550
if (not reached_stop_revision_id or
551
rev_id in revision_id_whitelist):
552
yield (rev_id, node.merge_depth, node.revno,
554
if reached_stop_revision_id or rev_id == stop_revision_id:
555
# only do the merged revs of rev_id from now on
556
rev = self.repository.get_revision(rev_id)
558
reached_stop_revision_id = True
559
revision_id_whitelist.extend(rev.parent_ids)
561
raise ValueError('invalid stop_rule %r' % stop_rule)
563
def _filter_start_non_ancestors(self, rev_iter):
564
# If we started from a dotted revno, we want to consider it as a tip
565
# and don't want to yield revisions that are not part of its
566
# ancestry. Given the order guaranteed by the merge sort, we will see
567
# uninteresting descendants of the first parent of our tip before the
569
first = rev_iter.next()
570
(rev_id, merge_depth, revno, end_of_merge) = first
573
# We start at a mainline revision so by definition, all others
574
# revisions in rev_iter are ancestors
575
for node in rev_iter:
580
pmap = self.repository.get_parent_map([rev_id])
581
parents = pmap.get(rev_id, [])
583
whitelist.update(parents)
585
# If there is no parents, there is nothing of interest left
587
# FIXME: It's hard to test this scenario here as this code is never
588
# called in that case. -- vila 20100322
591
for (rev_id, merge_depth, revno, end_of_merge) in rev_iter:
593
if rev_id in whitelist:
594
pmap = self.repository.get_parent_map([rev_id])
595
parents = pmap.get(rev_id, [])
596
whitelist.remove(rev_id)
597
whitelist.update(parents)
599
# We've reached the mainline, there is nothing left to
603
# A revision that is not part of the ancestry of our
606
yield (rev_id, merge_depth, revno, end_of_merge)
608
def leave_lock_in_place(self):
609
"""Tell this branch object not to release the physical lock when this
612
If lock_write doesn't return a token, then this method is not supported.
614
self.control_files.leave_in_place()
616
def dont_leave_lock_in_place(self):
617
"""Tell this branch object to release the physical lock when this
618
object is unlocked, even if it didn't originally acquire it.
620
If lock_write doesn't return a token, then this method is not supported.
622
self.control_files.dont_leave_in_place()
624
def bind(self, other):
625
"""Bind the local branch the other branch.
627
:param other: The branch to bind to
630
raise errors.UpgradeRequired(self.user_url)
632
def set_append_revisions_only(self, enabled):
633
if not self._format.supports_set_append_revisions_only():
634
raise errors.UpgradeRequired(self.user_url)
639
self.get_config().set_user_option('append_revisions_only', value,
642
def set_reference_info(self, file_id, tree_path, branch_location):
643
"""Set the branch location to use for a tree reference."""
644
raise errors.UnsupportedOperation(self.set_reference_info, self)
646
def get_reference_info(self, file_id):
647
"""Get the tree_path and branch_location for a tree reference."""
648
raise errors.UnsupportedOperation(self.get_reference_info, self)
651
def fetch(self, from_branch, last_revision=None, pb=None):
652
"""Copy revisions from from_branch into this branch.
654
:param from_branch: Where to copy from.
655
:param last_revision: What revision to stop at (None for at the end
657
:param pb: An optional progress bar to use.
660
if self.base == from_branch.base:
663
symbol_versioning.warn(
664
symbol_versioning.deprecated_in((1, 14, 0))
665
% "pb parameter to fetch()")
666
from_branch.lock_read()
668
if last_revision is None:
669
last_revision = from_branch.last_revision()
670
last_revision = _mod_revision.ensure_null(last_revision)
671
return self.repository.fetch(from_branch.repository,
672
revision_id=last_revision,
677
def get_bound_location(self):
678
"""Return the URL of the branch we are bound to.
680
Older format branches cannot bind, please be sure to use a metadir
685
def get_old_bound_location(self):
686
"""Return the URL of the branch we used to be bound to
688
raise errors.UpgradeRequired(self.user_url)
690
def get_commit_builder(self, parents, config=None, timestamp=None,
691
timezone=None, committer=None, revprops=None,
693
"""Obtain a CommitBuilder for this branch.
695
:param parents: Revision ids of the parents of the new revision.
696
:param config: Optional configuration to use.
697
:param timestamp: Optional timestamp recorded for commit.
698
:param timezone: Optional timezone for timestamp.
699
:param committer: Optional committer to set for commit.
700
:param revprops: Optional dictionary of revision properties.
701
:param revision_id: Optional revision id.
705
config = self.get_config()
707
return self.repository.get_commit_builder(self, parents, config,
708
timestamp, timezone, committer, revprops, revision_id)
710
def get_master_branch(self, possible_transports=None):
711
"""Return the branch we are bound to.
713
:return: Either a Branch, or None
620
717
def get_revision_delta(self, revno):
621
718
"""Return the delta for one revision.
792
992
common_index = min(self_len, other_len) -1
793
993
if common_index >= 0 and \
794
994
self_history[common_index] != other_history[common_index]:
795
raise DivergedBranches(self, other)
995
raise errors.DivergedBranches(self, other)
797
997
if stop_revision is None:
798
998
stop_revision = other_len
799
elif stop_revision > other_len:
800
raise NoSuchRevision(self, stop_revision)
1000
if stop_revision > other_len:
1001
raise errors.NoSuchRevision(self, stop_revision)
802
1002
return other_history[self_len:stop_revision]
805
def update_revisions(self, other, stop_revision=None):
806
"""Pull in all new revisions from other branch.
808
>>> from bzrlib.commit import commit
809
>>> bzrlib.trace.silent = True
810
>>> br1 = ScratchBranch(files=['foo', 'bar'])
813
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
814
>>> br2 = ScratchBranch()
815
>>> br2.update_revisions(br1)
819
>>> br2.revision_history()
821
>>> br2.update_revisions(br1)
825
>>> br1.text_store.total_size() == br2.text_store.total_size()
828
from bzrlib.progress import ProgressBar
832
pb.update('comparing histories')
833
revision_ids = self.missing_revisions(other, stop_revision)
835
if hasattr(other.revision_store, "prefetch"):
836
other.revision_store.prefetch(revision_ids)
837
if hasattr(other.inventory_store, "prefetch"):
838
inventory_ids = [other.get_revision(r).inventory_id
839
for r in revision_ids]
840
other.inventory_store.prefetch(inventory_ids)
845
for rev_id in revision_ids:
847
pb.update('fetching revision', i, len(revision_ids))
848
rev = other.get_revision(rev_id)
849
revisions.append(rev)
850
inv = other.get_inventory(str(rev.inventory_id))
851
for key, entry in inv.iter_entries():
852
if entry.text_id is None:
854
if entry.text_id not in self.text_store:
855
needed_texts.add(entry.text_id)
859
count = self.text_store.copy_multi(other.text_store, needed_texts)
860
print "Added %d texts." % count
861
inventory_ids = [ f.inventory_id for f in revisions ]
862
count = self.inventory_store.copy_multi(other.inventory_store,
864
print "Added %d inventories." % count
865
revision_ids = [ f.revision_id for f in revisions]
866
count = self.revision_store.copy_multi(other.revision_store,
868
for revision_id in revision_ids:
869
self.append_revision(revision_id)
870
print "Added %d revisions." % count
873
def commit(self, *args, **kw):
874
from bzrlib.commit import commit
875
commit(self, *args, **kw)
878
def lookup_revision(self, revision):
879
"""Return the revision identifier for a given revision information."""
880
revno, info = self.get_revision_info(revision)
883
def get_revision_info(self, revision):
884
"""Return (revno, revision id) for revision identifier.
886
revision can be an integer, in which case it is assumed to be revno (though
887
this will translate negative values into positive ones)
888
revision can also be a string, in which case it is parsed for something like
889
'date:' or 'revid:' etc.
894
try:# Convert to int if possible
895
revision = int(revision)
898
revs = self.revision_history()
899
if isinstance(revision, int):
902
# Mabye we should do this first, but we don't need it if revision == 0
904
revno = len(revs) + revision + 1
907
elif isinstance(revision, basestring):
908
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
909
if revision.startswith(prefix):
910
revno = func(self, revs, revision)
913
raise BzrError('No namespace registered for string: %r' % revision)
915
if revno is None or revno <= 0 or revno > len(revs):
916
raise BzrError("no such revision %s" % revision)
917
return revno, revs[revno-1]
919
def _namespace_revno(self, revs, revision):
920
"""Lookup a revision by revision number"""
921
assert revision.startswith('revno:')
923
return int(revision[6:])
926
REVISION_NAMESPACES['revno:'] = _namespace_revno
928
def _namespace_revid(self, revs, revision):
929
assert revision.startswith('revid:')
931
return revs.index(revision[6:]) + 1
934
REVISION_NAMESPACES['revid:'] = _namespace_revid
936
def _namespace_last(self, revs, revision):
937
assert revision.startswith('last:')
939
offset = int(revision[5:])
944
raise BzrError('You must supply a positive value for --revision last:XXX')
945
return len(revs) - offset + 1
946
REVISION_NAMESPACES['last:'] = _namespace_last
948
def _namespace_tag(self, revs, revision):
949
assert revision.startswith('tag:')
950
raise BzrError('tag: namespace registered, but not implemented.')
951
REVISION_NAMESPACES['tag:'] = _namespace_tag
953
def _namespace_date(self, revs, revision):
954
assert revision.startswith('date:')
956
# Spec for date revisions:
958
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
959
# it can also start with a '+/-/='. '+' says match the first
960
# entry after the given date. '-' is match the first entry before the date
961
# '=' is match the first entry after, but still on the given date.
963
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
964
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
965
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
966
# May 13th, 2005 at 0:00
968
# So the proper way of saying 'give me all entries for today' is:
969
# -r {date:+today}:{date:-tomorrow}
970
# The default is '=' when not supplied
973
if val[:1] in ('+', '-', '='):
974
match_style = val[:1]
977
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
978
if val.lower() == 'yesterday':
979
dt = today - datetime.timedelta(days=1)
980
elif val.lower() == 'today':
982
elif val.lower() == 'tomorrow':
983
dt = today + datetime.timedelta(days=1)
986
# This should be done outside the function to avoid recompiling it.
987
_date_re = re.compile(
988
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
990
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
992
m = _date_re.match(val)
993
if not m or (not m.group('date') and not m.group('time')):
994
raise BzrError('Invalid revision date %r' % revision)
997
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
999
year, month, day = today.year, today.month, today.day
1001
hour = int(m.group('hour'))
1002
minute = int(m.group('minute'))
1003
if m.group('second'):
1004
second = int(m.group('second'))
1008
hour, minute, second = 0,0,0
1010
dt = datetime.datetime(year=year, month=month, day=day,
1011
hour=hour, minute=minute, second=second)
1015
if match_style == '-':
1017
elif match_style == '=':
1018
last = dt + datetime.timedelta(days=1)
1021
for i in range(len(revs)-1, -1, -1):
1022
r = self.get_revision(revs[i])
1023
# TODO: Handle timezone.
1024
dt = datetime.datetime.fromtimestamp(r.timestamp)
1025
if first >= dt and (last is None or dt >= last):
1028
for i in range(len(revs)):
1029
r = self.get_revision(revs[i])
1030
# TODO: Handle timezone.
1031
dt = datetime.datetime.fromtimestamp(r.timestamp)
1032
if first <= dt and (last is None or dt <= last):
1034
REVISION_NAMESPACES['date:'] = _namespace_date
1036
def revision_tree(self, revision_id):
1037
"""Return Tree for a revision on this branch.
1039
`revision_id` may be None for the null revision, in which case
1040
an `EmptyTree` is returned."""
1041
# TODO: refactor this to use an existing revision object
1042
# so we don't need to read it in twice.
1043
if revision_id == None:
1046
inv = self.get_revision_inventory(revision_id)
1047
return RevisionTree(self.text_store, inv)
1050
def working_tree(self):
1051
"""Return a `Tree` for the working copy."""
1052
from workingtree import WorkingTree
1053
return WorkingTree(self.base, self.read_working_inventory())
1004
def update_revisions(self, other, stop_revision=None, overwrite=False,
1006
"""Pull in new perfect-fit revisions.
1008
:param other: Another Branch to pull from
1009
:param stop_revision: Updated until the given revision
1010
:param overwrite: Always set the branch pointer, rather than checking
1011
to see if it is a proper descendant.
1012
:param graph: A Graph object that can be used to query history
1013
information. This can be None.
1016
return InterBranch.get(other, self).update_revisions(stop_revision,
1019
def import_last_revision_info(self, source_repo, revno, revid):
1020
"""Set the last revision info, importing from another repo if necessary.
1022
This is used by the bound branch code to upload a revision to
1023
the master branch first before updating the tip of the local branch.
1025
:param source_repo: Source repository to optionally fetch from
1026
:param revno: Revision number of the new tip
1027
:param revid: Revision id of the new tip
1029
if not self.repository.has_same_location(source_repo):
1030
self.repository.fetch(source_repo, revision_id=revid)
1031
self.set_last_revision_info(revno, revid)
1033
def revision_id_to_revno(self, revision_id):
1034
"""Given a revision id, return its revno"""
1035
if _mod_revision.is_null(revision_id):
1037
history = self.revision_history()
1039
return history.index(revision_id) + 1
1041
raise errors.NoSuchRevision(self, revision_id)
1044
def get_rev_id(self, revno, history=None):
1045
"""Find the revision id of the specified revno."""
1047
return _mod_revision.NULL_REVISION
1048
last_revno, last_revid = self.last_revision_info()
1049
if revno == last_revno:
1051
if revno <= 0 or revno > last_revno:
1052
raise errors.NoSuchRevision(self, revno)
1053
distance_from_last = last_revno - revno
1054
if len(self._partial_revision_history_cache) <= distance_from_last:
1055
self._extend_partial_history(distance_from_last)
1056
return self._partial_revision_history_cache[distance_from_last]
1058
def pull(self, source, overwrite=False, stop_revision=None,
1059
possible_transports=None, *args, **kwargs):
1060
"""Mirror source into this branch.
1062
This branch is considered to be 'local', having low latency.
1064
:returns: PullResult instance
1066
return InterBranch.get(source, self).pull(overwrite=overwrite,
1067
stop_revision=stop_revision,
1068
possible_transports=possible_transports, *args, **kwargs)
1070
def push(self, target, overwrite=False, stop_revision=None, *args,
1072
"""Mirror this branch into target.
1074
This branch is considered to be 'local', having low latency.
1076
return InterBranch.get(self, target).push(overwrite, stop_revision,
1079
def lossy_push(self, target, stop_revision=None):
1080
"""Push deltas into another branch.
1082
:note: This does not, like push, retain the revision ids from
1083
the source branch and will, rather than adding bzr-specific
1084
metadata, push only those semantics of the revision that can be
1085
natively represented by this branch' VCS.
1087
:param target: Target branch
1088
:param stop_revision: Revision to push, defaults to last revision.
1089
:return: BranchPushResult with an extra member revidmap:
1090
A dictionary mapping revision ids from the target branch
1091
to new revision ids in the target branch, for each
1092
revision that was pushed.
1094
inter = InterBranch.get(self, target)
1095
lossy_push = getattr(inter, "lossy_push", None)
1096
if lossy_push is None:
1097
raise errors.LossyPushToSameVCS(self, target)
1098
return lossy_push(stop_revision)
1056
1100
def basis_tree(self):
1057
"""Return `Tree` object for last revision.
1059
If there are no revisions yet, return an `EmptyTree`.
1061
r = self.last_patch()
1065
return RevisionTree(self.text_store, self.get_revision_inventory(r))
1069
def rename_one(self, from_rel, to_rel):
1072
This can change the directory or the filename or both.
1076
tree = self.working_tree()
1077
inv = tree.inventory
1078
if not tree.has_filename(from_rel):
1079
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1080
if tree.has_filename(to_rel):
1081
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1083
file_id = inv.path2id(from_rel)
1085
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1087
if inv.path2id(to_rel):
1088
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1090
to_dir, to_tail = os.path.split(to_rel)
1091
to_dir_id = inv.path2id(to_dir)
1092
if to_dir_id == None and to_dir != '':
1093
raise BzrError("can't determine destination directory id for %r" % to_dir)
1095
mutter("rename_one:")
1096
mutter(" file_id {%s}" % file_id)
1097
mutter(" from_rel %r" % from_rel)
1098
mutter(" to_rel %r" % to_rel)
1099
mutter(" to_dir %r" % to_dir)
1100
mutter(" to_dir_id {%s}" % to_dir_id)
1102
inv.rename(file_id, to_dir_id, to_tail)
1104
print "%s => %s" % (from_rel, to_rel)
1106
from_abs = self.abspath(from_rel)
1107
to_abs = self.abspath(to_rel)
1109
os.rename(from_abs, to_abs)
1111
raise BzrError("failed to rename %r to %r: %s"
1112
% (from_abs, to_abs, e[1]),
1113
["rename rolled back"])
1115
self._write_inventory(inv)
1120
def move(self, from_paths, to_name):
1123
to_name must exist as a versioned directory.
1125
If to_name exists and is a directory, the files are moved into
1126
it, keeping their old names. If it is a directory,
1128
Note that to_name is only the last component of the new name;
1129
this doesn't change the directory.
1133
## TODO: Option to move IDs only
1134
assert not isinstance(from_paths, basestring)
1135
tree = self.working_tree()
1136
inv = tree.inventory
1137
to_abs = self.abspath(to_name)
1138
if not isdir(to_abs):
1139
raise BzrError("destination %r is not a directory" % to_abs)
1140
if not tree.has_filename(to_name):
1141
raise BzrError("destination %r not in working directory" % to_abs)
1142
to_dir_id = inv.path2id(to_name)
1143
if to_dir_id == None and to_name != '':
1144
raise BzrError("destination %r is not a versioned directory" % to_name)
1145
to_dir_ie = inv[to_dir_id]
1146
if to_dir_ie.kind not in ('directory', 'root_directory'):
1147
raise BzrError("destination %r is not a directory" % to_abs)
1149
to_idpath = inv.get_idpath(to_dir_id)
1151
for f in from_paths:
1152
if not tree.has_filename(f):
1153
raise BzrError("%r does not exist in working tree" % f)
1154
f_id = inv.path2id(f)
1156
raise BzrError("%r is not versioned" % f)
1157
name_tail = splitpath(f)[-1]
1158
dest_path = appendpath(to_name, name_tail)
1159
if tree.has_filename(dest_path):
1160
raise BzrError("destination %r already exists" % dest_path)
1161
if f_id in to_idpath:
1162
raise BzrError("can't move %r to a subdirectory of itself" % f)
1164
# OK, so there's a race here, it's possible that someone will
1165
# create a file in this interval and then the rename might be
1166
# left half-done. But we should have caught most problems.
1168
for f in from_paths:
1169
name_tail = splitpath(f)[-1]
1170
dest_path = appendpath(to_name, name_tail)
1171
print "%s => %s" % (f, dest_path)
1172
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1174
os.rename(self.abspath(f), self.abspath(dest_path))
1176
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1177
["rename rolled back"])
1179
self._write_inventory(inv)
1184
def revert(self, filenames, old_tree=None, backups=True):
1185
"""Restore selected files to the versions from a previous tree.
1188
If true (default) backups are made of files before
1191
from bzrlib.errors import NotVersionedError, BzrError
1192
from bzrlib.atomicfile import AtomicFile
1193
from bzrlib.osutils import backup_file
1195
inv = self.read_working_inventory()
1196
if old_tree is None:
1197
old_tree = self.basis_tree()
1198
old_inv = old_tree.inventory
1201
for fn in filenames:
1202
file_id = inv.path2id(fn)
1204
raise NotVersionedError("not a versioned file", fn)
1205
if not old_inv.has_id(file_id):
1206
raise BzrError("file not present in old tree", fn, file_id)
1207
nids.append((fn, file_id))
1209
# TODO: Rename back if it was previously at a different location
1211
# TODO: If given a directory, restore the entire contents from
1212
# the previous version.
1214
# TODO: Make a backup to a temporary file.
1216
# TODO: If the file previously didn't exist, delete it?
1217
for fn, file_id in nids:
1220
f = AtomicFile(fn, 'wb')
1222
f.write(old_tree.get_file(file_id).read())
1228
def pending_merges(self):
1229
"""Return a list of pending merges.
1231
These are revisions that have been merged into the working
1232
directory but not yet committed.
1234
cfn = self.controlfilename('pending-merges')
1235
if not os.path.exists(cfn):
1238
for l in self.controlfile('pending-merges', 'r').readlines():
1239
p.append(l.rstrip('\n'))
1243
def add_pending_merge(self, revision_id):
1244
from bzrlib.revision import validate_revision_id
1246
validate_revision_id(revision_id)
1248
p = self.pending_merges()
1249
if revision_id in p:
1251
p.append(revision_id)
1252
self.set_pending_merges(p)
1255
def set_pending_merges(self, rev_list):
1256
from bzrlib.atomicfile import AtomicFile
1259
f = AtomicFile(self.controlfilename('pending-merges'))
1271
class ScratchBranch(Branch):
1272
"""Special test class: a branch that cleans up after itself.
1274
>>> b = ScratchBranch()
1101
"""Return `Tree` object for last revision."""
1102
return self.repository.revision_tree(self.last_revision())
1104
def get_parent(self):
1105
"""Return the parent location of the branch.
1107
This is the default location for pull/missing. The usual
1108
pattern is that the user can override it by specifying a
1111
parent = self._get_parent_location()
1114
# This is an old-format absolute path to a local branch
1115
# turn it into a url
1116
if parent.startswith('/'):
1117
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1119
return urlutils.join(self.base[:-1], parent)
1120
except errors.InvalidURLJoin, e:
1121
raise errors.InaccessibleParent(parent, self.user_url)
1123
def _get_parent_location(self):
1124
raise NotImplementedError(self._get_parent_location)
1126
def _set_config_location(self, name, url, config=None,
1127
make_relative=False):
1129
config = self.get_config()
1133
url = urlutils.relative_url(self.base, url)
1134
config.set_user_option(name, url, warn_masked=True)
1136
def _get_config_location(self, name, config=None):
1138
config = self.get_config()
1139
location = config.get_user_option(name)
1144
def get_child_submit_format(self):
1145
"""Return the preferred format of submissions to this branch."""
1146
return self.get_config().get_user_option("child_submit_format")
1148
def get_submit_branch(self):
1149
"""Return the submit location of the branch.
1151
This is the default location for bundle. The usual
1152
pattern is that the user can override it by specifying a
1155
return self.get_config().get_user_option('submit_branch')
1157
def set_submit_branch(self, location):
1158
"""Return the submit location of the branch.
1160
This is the default location for bundle. The usual
1161
pattern is that the user can override it by specifying a
1164
self.get_config().set_user_option('submit_branch', location,
1167
def get_public_branch(self):
1168
"""Return the public location of the branch.
1170
This is used by merge directives.
1172
return self._get_config_location('public_branch')
1174
def set_public_branch(self, location):
1175
"""Return the submit location of the branch.
1177
This is the default location for bundle. The usual
1178
pattern is that the user can override it by specifying a
1181
self._set_config_location('public_branch', location)
1183
def get_push_location(self):
1184
"""Return the None or the location to push this branch to."""
1185
push_loc = self.get_config().get_user_option('push_location')
1188
def set_push_location(self, location):
1189
"""Set a new push location for this branch."""
1190
raise NotImplementedError(self.set_push_location)
1192
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1193
"""Run the post_change_branch_tip hooks."""
1194
hooks = Branch.hooks['post_change_branch_tip']
1197
new_revno, new_revid = self.last_revision_info()
1198
params = ChangeBranchTipParams(
1199
self, old_revno, new_revno, old_revid, new_revid)
1203
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1204
"""Run the pre_change_branch_tip hooks."""
1205
hooks = Branch.hooks['pre_change_branch_tip']
1208
old_revno, old_revid = self.last_revision_info()
1209
params = ChangeBranchTipParams(
1210
self, old_revno, new_revno, old_revid, new_revid)
1216
"""Synchronise this branch with the master branch if any.
1218
:return: None or the last_revision pivoted out during the update.
1222
def check_revno(self, revno):
1224
Check whether a revno corresponds to any revision.
1225
Zero (the NULL revision) is considered valid.
1228
self.check_real_revno(revno)
1230
def check_real_revno(self, revno):
1232
Check whether a revno corresponds to a real revision.
1233
Zero (the NULL revision) is considered invalid
1235
if revno < 1 or revno > self.revno():
1236
raise errors.InvalidRevisionNumber(revno)
1239
def clone(self, to_bzrdir, revision_id=None, repository_policy=None):
1240
"""Clone this branch into to_bzrdir preserving all semantic values.
1242
Most API users will want 'create_clone_on_transport', which creates a
1243
new bzrdir and branch on the fly.
1245
revision_id: if not None, the revision history in the new branch will
1246
be truncated to end with revision_id.
1248
result = to_bzrdir.create_branch()
1251
if repository_policy is not None:
1252
repository_policy.configure_branch(result)
1253
self.copy_content_into(result, revision_id=revision_id)
1259
def sprout(self, to_bzrdir, revision_id=None, repository_policy=None):
1260
"""Create a new line of development from the branch, into to_bzrdir.
1262
to_bzrdir controls the branch format.
1264
revision_id: if not None, the revision history in the new branch will
1265
be truncated to end with revision_id.
1267
if (repository_policy is not None and
1268
repository_policy.requires_stacking()):
1269
to_bzrdir._format.require_stacking(_skip_repo=True)
1270
result = to_bzrdir.create_branch()
1273
if repository_policy is not None:
1274
repository_policy.configure_branch(result)
1275
self.copy_content_into(result, revision_id=revision_id)
1276
result.set_parent(self.bzrdir.root_transport.base)
1281
def _synchronize_history(self, destination, revision_id):
1282
"""Synchronize last revision and revision history between branches.
1284
This version is most efficient when the destination is also a
1285
BzrBranch6, but works for BzrBranch5, as long as the destination's
1286
repository contains all the lefthand ancestors of the intended
1287
last_revision. If not, set_last_revision_info will fail.
1289
:param destination: The branch to copy the history into
1290
:param revision_id: The revision-id to truncate history at. May
1291
be None to copy complete history.
1293
source_revno, source_revision_id = self.last_revision_info()
1294
if revision_id is None:
1295
revno, revision_id = source_revno, source_revision_id
1297
graph = self.repository.get_graph()
1299
revno = graph.find_distance_to_null(revision_id,
1300
[(source_revision_id, source_revno)])
1301
except errors.GhostRevisionsHaveNoRevno:
1302
# Default to 1, if we can't find anything else
1304
destination.set_last_revision_info(revno, revision_id)
1306
def copy_content_into(self, destination, revision_id=None):
1307
"""Copy the content of self into destination.
1309
revision_id: if not None, the revision history in the new branch will
1310
be truncated to end with revision_id.
1312
return InterBranch.get(self, destination).copy_content_into(
1313
revision_id=revision_id)
1315
def update_references(self, target):
1316
if not getattr(self._format, 'supports_reference_locations', False):
1318
reference_dict = self._get_all_reference_info()
1319
if len(reference_dict) == 0:
1321
old_base = self.base
1322
new_base = target.base
1323
target_reference_dict = target._get_all_reference_info()
1324
for file_id, (tree_path, branch_location) in (
1325
reference_dict.items()):
1326
branch_location = urlutils.rebase_url(branch_location,
1328
target_reference_dict.setdefault(
1329
file_id, (tree_path, branch_location))
1330
target._set_all_reference_info(target_reference_dict)
1333
def check(self, refs):
1334
"""Check consistency of the branch.
1336
In particular this checks that revisions given in the revision-history
1337
do actually match up in the revision graph, and that they're all
1338
present in the repository.
1340
Callers will typically also want to check the repository.
1342
:param refs: Calculated refs for this branch as specified by
1343
branch._get_check_refs()
1344
:return: A BranchCheckResult.
1346
result = BranchCheckResult(self)
1347
last_revno, last_revision_id = self.last_revision_info()
1348
actual_revno = refs[('lefthand-distance', last_revision_id)]
1349
if actual_revno != last_revno:
1350
result.errors.append(errors.BzrCheckError(
1351
'revno does not match len(mainline) %s != %s' % (
1352
last_revno, actual_revno)))
1353
# TODO: We should probably also check that self.revision_history
1354
# matches the repository for older branch formats.
1355
# If looking for the code that cross-checks repository parents against
1356
# the iter_reverse_revision_history output, that is now a repository
1360
def _get_checkout_format(self):
1361
"""Return the most suitable metadir for a checkout of this branch.
1362
Weaves are used if this branch's repository uses weaves.
1364
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
1365
from bzrlib.repofmt import weaverepo
1366
format = bzrdir.BzrDirMetaFormat1()
1367
format.repository_format = weaverepo.RepositoryFormat7()
1369
format = self.repository.bzrdir.checkout_metadir()
1370
format.set_branch_format(self._format)
1373
def create_clone_on_transport(self, to_transport, revision_id=None,
1374
stacked_on=None, create_prefix=False, use_existing_dir=False):
1375
"""Create a clone of this branch and its bzrdir.
1377
:param to_transport: The transport to clone onto.
1378
:param revision_id: The revision id to use as tip in the new branch.
1379
If None the tip is obtained from this branch.
1380
:param stacked_on: An optional URL to stack the clone on.
1381
:param create_prefix: Create any missing directories leading up to
1383
:param use_existing_dir: Use an existing directory if one exists.
1385
# XXX: Fix the bzrdir API to allow getting the branch back from the
1386
# clone call. Or something. 20090224 RBC/spiv.
1387
# XXX: Should this perhaps clone colocated branches as well,
1388
# rather than just the default branch? 20100319 JRV
1389
if revision_id is None:
1390
revision_id = self.last_revision()
1391
dir_to = self.bzrdir.clone_on_transport(to_transport,
1392
revision_id=revision_id, stacked_on=stacked_on,
1393
create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1394
return dir_to.open_branch()
1396
def create_checkout(self, to_location, revision_id=None,
1397
lightweight=False, accelerator_tree=None,
1399
"""Create a checkout of a branch.
1401
:param to_location: The url to produce the checkout at
1402
:param revision_id: The revision to check out
1403
:param lightweight: If True, produce a lightweight checkout, otherwise,
1404
produce a bound branch (heavyweight checkout)
1405
:param accelerator_tree: A tree which can be used for retrieving file
1406
contents more quickly than the revision tree, i.e. a workingtree.
1407
The revision tree will be used for cases where accelerator_tree's
1408
content is different.
1409
:param hardlink: If true, hard-link files from accelerator_tree,
1411
:return: The tree of the created checkout
1413
t = transport.get_transport(to_location)
1416
format = self._get_checkout_format()
1417
checkout = format.initialize_on_transport(t)
1418
from_branch = BranchReferenceFormat().initialize(checkout,
1421
format = self._get_checkout_format()
1422
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
1423
to_location, force_new_tree=False, format=format)
1424
checkout = checkout_branch.bzrdir
1425
checkout_branch.bind(self)
1426
# pull up to the specified revision_id to set the initial
1427
# branch tip correctly, and seed it with history.
1428
checkout_branch.pull(self, stop_revision=revision_id)
1430
tree = checkout.create_workingtree(revision_id,
1431
from_branch=from_branch,
1432
accelerator_tree=accelerator_tree,
1434
basis_tree = tree.basis_tree()
1435
basis_tree.lock_read()
1437
for path, file_id in basis_tree.iter_references():
1438
reference_parent = self.reference_parent(file_id, path)
1439
reference_parent.create_checkout(tree.abspath(path),
1440
basis_tree.get_reference_revision(file_id, path),
1447
def reconcile(self, thorough=True):
1448
"""Make sure the data stored in this branch is consistent."""
1449
from bzrlib.reconcile import BranchReconciler
1450
reconciler = BranchReconciler(self, thorough=thorough)
1451
reconciler.reconcile()
1454
def reference_parent(self, file_id, path, possible_transports=None):
1455
"""Return the parent branch for a tree-reference file_id
1456
:param file_id: The file_id of the tree reference
1457
:param path: The path of the file_id in the tree
1458
:return: A branch associated with the file_id
1460
# FIXME should provide multiple branches, based on config
1461
return Branch.open(self.bzrdir.root_transport.clone(path).base,
1462
possible_transports=possible_transports)
1464
def supports_tags(self):
1465
return self._format.supports_tags()
1467
def automatic_tag_name(self, revision_id):
1468
"""Try to automatically find the tag name for a revision.
1470
:param revision_id: Revision id of the revision.
1471
:return: A tag name or None if no tag name could be determined.
1473
for hook in Branch.hooks['automatic_tag_name']:
1474
ret = hook(self, revision_id)
1479
def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
1481
"""Ensure that revision_b is a descendant of revision_a.
1483
This is a helper function for update_revisions.
1485
:raises: DivergedBranches if revision_b has diverged from revision_a.
1486
:returns: True if revision_b is a descendant of revision_a.
1488
relation = self._revision_relations(revision_a, revision_b, graph)
1489
if relation == 'b_descends_from_a':
1491
elif relation == 'diverged':
1492
raise errors.DivergedBranches(self, other_branch)
1493
elif relation == 'a_descends_from_b':
1496
raise AssertionError("invalid relation: %r" % (relation,))
1498
def _revision_relations(self, revision_a, revision_b, graph):
1499
"""Determine the relationship between two revisions.
1501
:returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
1503
heads = graph.heads([revision_a, revision_b])
1504
if heads == set([revision_b]):
1505
return 'b_descends_from_a'
1506
elif heads == set([revision_a, revision_b]):
1507
# These branches have diverged
1509
elif heads == set([revision_a]):
1510
return 'a_descends_from_b'
1512
raise AssertionError("invalid heads: %r" % (heads,))
1515
class BranchFormat(object):
1516
"""An encapsulation of the initialization and open routines for a format.
1518
Formats provide three things:
1519
* An initialization routine,
1523
Formats are placed in an dict by their format string for reference
1524
during branch opening. Its not required that these be instances, they
1525
can be classes themselves with class methods - it simply depends on
1526
whether state is needed for a given format or not.
1528
Once a format is deprecated, just deprecate the initialize and open
1529
methods on the format class. Do not deprecate the object, as the
1530
object will be created every time regardless.
1282
def __init__(self, files=[], dirs=[], base=None):
1283
"""Make a test branch.
1285
This creates a temporary directory and runs init-tree in it.
1287
If any files are listed, they are created in the working copy.
1289
from tempfile import mkdtemp
1294
Branch.__init__(self, base, init=init)
1296
os.mkdir(self.abspath(d))
1299
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
1304
>>> orig = ScratchBranch(files=["file1", "file2"])
1305
>>> clone = orig.clone()
1306
>>> os.path.samefile(orig.base, clone.base)
1308
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1311
from shutil import copytree
1312
from tempfile import mkdtemp
1315
copytree(self.base, base, symlinks=True)
1316
return ScratchBranch(base=base)
1322
"""Destroy the test branch, removing the scratch directory."""
1323
from shutil import rmtree
1326
mutter("delete ScratchBranch %s" % self.base)
1329
# Work around for shutil.rmtree failing on Windows when
1330
# readonly files are encountered
1331
mutter("hit exception in destroying ScratchBranch: %s" % e)
1332
for root, dirs, files in os.walk(self.base, topdown=False):
1334
os.chmod(os.path.join(root, name), 0700)
1533
_default_format = None
1534
"""The default format used for new branches."""
1537
"""The known formats."""
1539
can_set_append_revisions_only = True
1541
def __eq__(self, other):
1542
return self.__class__ is other.__class__
1544
def __ne__(self, other):
1545
return not (self == other)
1548
def find_format(klass, a_bzrdir, name=None):
1549
"""Return the format for the branch object in a_bzrdir."""
1551
transport = a_bzrdir.get_branch_transport(None, name=name)
1552
format_string = transport.get_bytes("format")
1553
format = klass._formats[format_string]
1554
if isinstance(format, MetaDirBranchFormatFactory):
1557
except errors.NoSuchFile:
1558
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1560
raise errors.UnknownFormatError(format=format_string, kind='branch')
1563
def get_default_format(klass):
1564
"""Return the current default format."""
1565
return klass._default_format
1568
def get_formats(klass):
1569
"""Get all the known formats.
1571
Warning: This triggers a load of all lazy registered formats: do not
1572
use except when that is desireed.
1575
for fmt in klass._formats.values():
1576
if isinstance(fmt, MetaDirBranchFormatFactory):
1581
def get_reference(self, a_bzrdir, name=None):
1582
"""Get the target reference of the branch in a_bzrdir.
1584
format probing must have been completed before calling
1585
this method - it is assumed that the format of the branch
1586
in a_bzrdir is correct.
1588
:param a_bzrdir: The bzrdir to get the branch data from.
1589
:param name: Name of the colocated branch to fetch
1590
:return: None if the branch is not a reference branch.
1595
def set_reference(self, a_bzrdir, name, to_branch):
1596
"""Set the target reference of the branch in a_bzrdir.
1598
format probing must have been completed before calling
1599
this method - it is assumed that the format of the branch
1600
in a_bzrdir is correct.
1602
:param a_bzrdir: The bzrdir to set the branch reference for.
1603
:param name: Name of colocated branch to set, None for default
1604
:param to_branch: branch that the checkout is to reference
1606
raise NotImplementedError(self.set_reference)
1608
def get_format_string(self):
1609
"""Return the ASCII format string that identifies this format."""
1610
raise NotImplementedError(self.get_format_string)
1612
def get_format_description(self):
1613
"""Return the short format description for this format."""
1614
raise NotImplementedError(self.get_format_description)
1616
def _run_post_branch_init_hooks(self, a_bzrdir, name, branch):
1617
hooks = Branch.hooks['post_branch_init']
1620
params = BranchInitHookParams(self, a_bzrdir, name, branch)
1624
def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
1625
lock_type='metadir', set_format=True):
1626
"""Initialize a branch in a bzrdir, with specified files
1628
:param a_bzrdir: The bzrdir to initialize the branch in
1629
:param utf8_files: The files to create as a list of
1630
(filename, content) tuples
1631
:param name: Name of colocated branch to create, if any
1632
:param set_format: If True, set the format with
1633
self.get_format_string. (BzrBranch4 has its format set
1635
:return: a branch in this format
1637
mutter('creating branch %r in %s', self, a_bzrdir.user_url)
1638
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
1640
'metadir': ('lock', lockdir.LockDir),
1641
'branch4': ('branch-lock', lockable_files.TransportLock),
1643
lock_name, lock_class = lock_map[lock_type]
1644
control_files = lockable_files.LockableFiles(branch_transport,
1645
lock_name, lock_class)
1646
control_files.create_lock()
1648
control_files.lock_write()
1649
except errors.LockContention:
1650
if lock_type != 'branch4':
1656
utf8_files += [('format', self.get_format_string())]
1658
for (filename, content) in utf8_files:
1659
branch_transport.put_bytes(
1661
mode=a_bzrdir._get_file_mode())
1664
control_files.unlock()
1665
branch = self.open(a_bzrdir, name, _found=True)
1666
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
1669
def initialize(self, a_bzrdir, name=None):
1670
"""Create a branch of this format in a_bzrdir.
1672
:param name: Name of the colocated branch to create.
1674
raise NotImplementedError(self.initialize)
1676
def is_supported(self):
1677
"""Is this format supported?
1679
Supported formats can be initialized and opened.
1680
Unsupported formats may not support initialization or committing or
1681
some other features depending on the reason for not being supported.
1685
def make_tags(self, branch):
1686
"""Create a tags object for branch.
1688
This method is on BranchFormat, because BranchFormats are reflected
1689
over the wire via network_name(), whereas full Branch instances require
1690
multiple VFS method calls to operate at all.
1692
The default implementation returns a disabled-tags instance.
1694
Note that it is normal for branch to be a RemoteBranch when using tags
1697
return DisabledTags(branch)
1699
def network_name(self):
1700
"""A simple byte string uniquely identifying this format for RPC calls.
1702
MetaDir branch formats use their disk format string to identify the
1703
repository over the wire. All in one formats such as bzr < 0.8, and
1704
foreign formats like svn/git and hg should use some marker which is
1705
unique and immutable.
1707
raise NotImplementedError(self.network_name)
1709
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1710
"""Return the branch object for a_bzrdir
1712
:param a_bzrdir: A BzrDir that contains a branch.
1713
:param name: Name of colocated branch to open
1714
:param _found: a private parameter, do not use it. It is used to
1715
indicate if format probing has already be done.
1716
:param ignore_fallbacks: when set, no fallback branches will be opened
1717
(if there are any). Default is to open fallbacks.
1719
raise NotImplementedError(self.open)
1722
def register_format(klass, format):
1723
"""Register a metadir format.
1725
See MetaDirBranchFormatFactory for the ability to register a format
1726
without loading the code the format needs until it is actually used.
1728
klass._formats[format.get_format_string()] = format
1729
# Metadir formats have a network name of their format string, and get
1730
# registered as factories.
1731
if isinstance(format, MetaDirBranchFormatFactory):
1732
network_format_registry.register(format.get_format_string(), format)
1734
network_format_registry.register(format.get_format_string(),
1738
def set_default_format(klass, format):
1739
klass._default_format = format
1741
def supports_set_append_revisions_only(self):
1742
"""True if this format supports set_append_revisions_only."""
1745
def supports_stacking(self):
1746
"""True if this format records a stacked-on branch."""
1750
def unregister_format(klass, format):
1751
del klass._formats[format.get_format_string()]
1754
return self.get_format_description().rstrip()
1756
def supports_tags(self):
1757
"""True if this format supports tags stored in the branch"""
1758
return False # by default
1761
class MetaDirBranchFormatFactory(registry._LazyObjectGetter):
1762
"""A factory for a BranchFormat object, permitting simple lazy registration.
1764
While none of the built in BranchFormats are lazy registered yet,
1765
bzrlib.tests.test_branch.TestMetaDirBranchFormatFactory demonstrates how to
1766
use it, and the bzr-loom plugin uses it as well (see
1767
bzrlib.plugins.loom.formats).
1770
def __init__(self, format_string, module_name, member_name):
1771
"""Create a MetaDirBranchFormatFactory.
1773
:param format_string: The format string the format has.
1774
:param module_name: Module to load the format class from.
1775
:param member_name: Attribute name within the module for the format class.
1777
registry._LazyObjectGetter.__init__(self, module_name, member_name)
1778
self._format_string = format_string
1780
def get_format_string(self):
1781
"""See BranchFormat.get_format_string."""
1782
return self._format_string
1785
"""Used for network_format_registry support."""
1786
return self.get_obj()()
1789
class BranchHooks(Hooks):
1790
"""A dictionary mapping hook name to a list of callables for branch hooks.
1792
e.g. ['set_rh'] Is the list of items to be called when the
1793
set_revision_history function is invoked.
1797
"""Create the default hooks.
1799
These are all empty initially, because by default nothing should get
1802
Hooks.__init__(self)
1803
self.create_hook(HookPoint('set_rh',
1804
"Invoked whenever the revision history has been set via "
1805
"set_revision_history. The api signature is (branch, "
1806
"revision_history), and the branch will be write-locked. "
1807
"The set_rh hook can be expensive for bzr to trigger, a better "
1808
"hook to use is Branch.post_change_branch_tip.", (0, 15), None))
1809
self.create_hook(HookPoint('open',
1810
"Called with the Branch object that has been opened after a "
1811
"branch is opened.", (1, 8), None))
1812
self.create_hook(HookPoint('post_push',
1813
"Called after a push operation completes. post_push is called "
1814
"with a bzrlib.branch.BranchPushResult object and only runs in the "
1815
"bzr client.", (0, 15), None))
1816
self.create_hook(HookPoint('post_pull',
1817
"Called after a pull operation completes. post_pull is called "
1818
"with a bzrlib.branch.PullResult object and only runs in the "
1819
"bzr client.", (0, 15), None))
1820
self.create_hook(HookPoint('pre_commit',
1821
"Called after a commit is calculated but before it is is "
1822
"completed. pre_commit is called with (local, master, old_revno, "
1823
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1824
"). old_revid is NULL_REVISION for the first commit to a branch, "
1825
"tree_delta is a TreeDelta object describing changes from the "
1826
"basis revision. hooks MUST NOT modify this delta. "
1827
" future_tree is an in-memory tree obtained from "
1828
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1829
"tree.", (0,91), None))
1830
self.create_hook(HookPoint('post_commit',
1831
"Called in the bzr client after a commit has completed. "
1832
"post_commit is called with (local, master, old_revno, old_revid, "
1833
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1834
"commit to a branch.", (0, 15), None))
1835
self.create_hook(HookPoint('post_uncommit',
1836
"Called in the bzr client after an uncommit completes. "
1837
"post_uncommit is called with (local, master, old_revno, "
1838
"old_revid, new_revno, new_revid) where local is the local branch "
1839
"or None, master is the target branch, and an empty branch "
1840
"receives new_revno of 0, new_revid of None.", (0, 15), None))
1841
self.create_hook(HookPoint('pre_change_branch_tip',
1842
"Called in bzr client and server before a change to the tip of a "
1843
"branch is made. pre_change_branch_tip is called with a "
1844
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1845
"commit, uncommit will all trigger this hook.", (1, 6), None))
1846
self.create_hook(HookPoint('post_change_branch_tip',
1847
"Called in bzr client and server after a change to the tip of a "
1848
"branch is made. post_change_branch_tip is called with a "
1849
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1850
"commit, uncommit will all trigger this hook.", (1, 4), None))
1851
self.create_hook(HookPoint('transform_fallback_location',
1852
"Called when a stacked branch is activating its fallback "
1853
"locations. transform_fallback_location is called with (branch, "
1854
"url), and should return a new url. Returning the same url "
1855
"allows it to be used as-is, returning a different one can be "
1856
"used to cause the branch to stack on a closer copy of that "
1857
"fallback_location. Note that the branch cannot have history "
1858
"accessing methods called on it during this hook because the "
1859
"fallback locations have not been activated. When there are "
1860
"multiple hooks installed for transform_fallback_location, "
1861
"all are called with the url returned from the previous hook."
1862
"The order is however undefined.", (1, 9), None))
1863
self.create_hook(HookPoint('automatic_tag_name',
1864
"Called to determine an automatic tag name for a revision. "
1865
"automatic_tag_name is called with (branch, revision_id) and "
1866
"should return a tag name or None if no tag name could be "
1867
"determined. The first non-None tag name returned will be used.",
1869
self.create_hook(HookPoint('post_branch_init',
1870
"Called after new branch initialization completes. "
1871
"post_branch_init is called with a "
1872
"bzrlib.branch.BranchInitHookParams. "
1873
"Note that init, branch and checkout (both heavyweight and "
1874
"lightweight) will all trigger this hook.", (2, 2), None))
1875
self.create_hook(HookPoint('post_switch',
1876
"Called after a checkout switches branch. "
1877
"post_switch is called with a "
1878
"bzrlib.branch.SwitchHookParams.", (2, 2), None))
1882
# install the default hooks into the Branch class.
1883
Branch.hooks = BranchHooks()
1886
class ChangeBranchTipParams(object):
1887
"""Object holding parameters passed to *_change_branch_tip hooks.
1889
There are 5 fields that hooks may wish to access:
1891
:ivar branch: the branch being changed
1892
:ivar old_revno: revision number before the change
1893
:ivar new_revno: revision number after the change
1894
:ivar old_revid: revision id before the change
1895
:ivar new_revid: revision id after the change
1897
The revid fields are strings. The revno fields are integers.
1900
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1901
"""Create a group of ChangeBranchTip parameters.
1903
:param branch: The branch being changed.
1904
:param old_revno: Revision number before the change.
1905
:param new_revno: Revision number after the change.
1906
:param old_revid: Tip revision id before the change.
1907
:param new_revid: Tip revision id after the change.
1909
self.branch = branch
1910
self.old_revno = old_revno
1911
self.new_revno = new_revno
1912
self.old_revid = old_revid
1913
self.new_revid = new_revid
1915
def __eq__(self, other):
1916
return self.__dict__ == other.__dict__
1919
return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1920
self.__class__.__name__, self.branch,
1921
self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1924
class BranchInitHookParams(object):
1925
"""Object holding parameters passed to *_branch_init hooks.
1927
There are 4 fields that hooks may wish to access:
1929
:ivar format: the branch format
1930
:ivar bzrdir: the BzrDir where the branch will be/has been initialized
1931
:ivar name: name of colocated branch, if any (or None)
1932
:ivar branch: the branch created
1934
Note that for lightweight checkouts, the bzrdir and format fields refer to
1935
the checkout, hence they are different from the corresponding fields in
1936
branch, which refer to the original branch.
1939
def __init__(self, format, a_bzrdir, name, branch):
1940
"""Create a group of BranchInitHook parameters.
1942
:param format: the branch format
1943
:param a_bzrdir: the BzrDir where the branch will be/has been
1945
:param name: name of colocated branch, if any (or None)
1946
:param branch: the branch created
1948
Note that for lightweight checkouts, the bzrdir and format fields refer
1949
to the checkout, hence they are different from the corresponding fields
1950
in branch, which refer to the original branch.
1952
self.format = format
1953
self.bzrdir = a_bzrdir
1955
self.branch = branch
1957
def __eq__(self, other):
1958
return self.__dict__ == other.__dict__
1961
return "<%s of %s>" % (self.__class__.__name__, self.branch)
1964
class SwitchHookParams(object):
1965
"""Object holding parameters passed to *_switch hooks.
1967
There are 4 fields that hooks may wish to access:
1969
:ivar control_dir: BzrDir of the checkout to change
1970
:ivar to_branch: branch that the checkout is to reference
1971
:ivar force: skip the check for local commits in a heavy checkout
1972
:ivar revision_id: revision ID to switch to (or None)
1975
def __init__(self, control_dir, to_branch, force, revision_id):
1976
"""Create a group of SwitchHook parameters.
1978
:param control_dir: BzrDir of the checkout to change
1979
:param to_branch: branch that the checkout is to reference
1980
:param force: skip the check for local commits in a heavy checkout
1981
:param revision_id: revision ID to switch to (or None)
1983
self.control_dir = control_dir
1984
self.to_branch = to_branch
1986
self.revision_id = revision_id
1988
def __eq__(self, other):
1989
return self.__dict__ == other.__dict__
1992
return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
1993
self.control_dir, self.to_branch,
1997
class BzrBranchFormat4(BranchFormat):
1998
"""Bzr branch format 4.
2001
- a revision-history file.
2002
- a branch-lock lock file [ to be shared with the bzrdir ]
2005
def get_format_description(self):
2006
"""See BranchFormat.get_format_description()."""
2007
return "Branch format 4"
2009
def initialize(self, a_bzrdir, name=None):
2010
"""Create a branch of this format in a_bzrdir."""
2011
utf8_files = [('revision-history', ''),
2012
('branch-name', ''),
2014
return self._initialize_helper(a_bzrdir, utf8_files, name=name,
2015
lock_type='branch4', set_format=False)
2018
super(BzrBranchFormat4, self).__init__()
2019
self._matchingbzrdir = bzrdir.BzrDirFormat6()
2021
def network_name(self):
2022
"""The network name for this format is the control dirs disk label."""
2023
return self._matchingbzrdir.get_format_string()
2025
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
2026
"""See BranchFormat.open()."""
2028
# we are being called directly and must probe.
2029
raise NotImplementedError
2030
return BzrBranch(_format=self,
2031
_control_files=a_bzrdir._control_files,
2034
_repository=a_bzrdir.open_repository())
2037
return "Bazaar-NG branch format 4"
2040
class BranchFormatMetadir(BranchFormat):
2041
"""Common logic for meta-dir based branch formats."""
2043
def _branch_class(self):
2044
"""What class to instantiate on open calls."""
2045
raise NotImplementedError(self._branch_class)
2047
def network_name(self):
2048
"""A simple byte string uniquely identifying this format for RPC calls.
2050
Metadir branch formats use their format string.
2052
return self.get_format_string()
2054
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
2055
"""See BranchFormat.open()."""
2057
format = BranchFormat.find_format(a_bzrdir, name=name)
2058
if format.__class__ != self.__class__:
2059
raise AssertionError("wrong format %r found for %r" %
2061
transport = a_bzrdir.get_branch_transport(None, name=name)
2063
control_files = lockable_files.LockableFiles(transport, 'lock',
2065
return self._branch_class()(_format=self,
2066
_control_files=control_files,
2069
_repository=a_bzrdir.find_repository(),
2070
ignore_fallbacks=ignore_fallbacks)
2071
except errors.NoSuchFile:
2072
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
2075
super(BranchFormatMetadir, self).__init__()
2076
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2077
self._matchingbzrdir.set_branch_format(self)
2079
def supports_tags(self):
2083
class BzrBranchFormat5(BranchFormatMetadir):
2084
"""Bzr branch format 5.
2087
- a revision-history file.
2089
- a lock dir guarding the branch itself
2090
- all of this stored in a branch/ subdirectory
2091
- works with shared repositories.
2093
This format is new in bzr 0.8.
2096
def _branch_class(self):
2099
def get_format_string(self):
2100
"""See BranchFormat.get_format_string()."""
2101
return "Bazaar-NG branch format 5\n"
2103
def get_format_description(self):
2104
"""See BranchFormat.get_format_description()."""
2105
return "Branch format 5"
2107
def initialize(self, a_bzrdir, name=None):
2108
"""Create a branch of this format in a_bzrdir."""
2109
utf8_files = [('revision-history', ''),
2110
('branch-name', ''),
2112
return self._initialize_helper(a_bzrdir, utf8_files, name)
2114
def supports_tags(self):
2118
class BzrBranchFormat6(BranchFormatMetadir):
2119
"""Branch format with last-revision and tags.
2121
Unlike previous formats, this has no explicit revision history. Instead,
2122
this just stores the last-revision, and the left-hand history leading
2123
up to there is the history.
2125
This format was introduced in bzr 0.15
2126
and became the default in 0.91.
2129
def _branch_class(self):
2132
def get_format_string(self):
2133
"""See BranchFormat.get_format_string()."""
2134
return "Bazaar Branch Format 6 (bzr 0.15)\n"
2136
def get_format_description(self):
2137
"""See BranchFormat.get_format_description()."""
2138
return "Branch format 6"
2140
def initialize(self, a_bzrdir, name=None):
2141
"""Create a branch of this format in a_bzrdir."""
2142
utf8_files = [('last-revision', '0 null:\n'),
2143
('branch.conf', ''),
2146
return self._initialize_helper(a_bzrdir, utf8_files, name)
2148
def make_tags(self, branch):
2149
"""See bzrlib.branch.BranchFormat.make_tags()."""
2150
return BasicTags(branch)
2152
def supports_set_append_revisions_only(self):
2156
class BzrBranchFormat8(BranchFormatMetadir):
2157
"""Metadir format supporting storing locations of subtree branches."""
2159
def _branch_class(self):
2162
def get_format_string(self):
2163
"""See BranchFormat.get_format_string()."""
2164
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
2166
def get_format_description(self):
2167
"""See BranchFormat.get_format_description()."""
2168
return "Branch format 8"
2170
def initialize(self, a_bzrdir, name=None):
2171
"""Create a branch of this format in a_bzrdir."""
2172
utf8_files = [('last-revision', '0 null:\n'),
2173
('branch.conf', ''),
2177
return self._initialize_helper(a_bzrdir, utf8_files, name)
2180
super(BzrBranchFormat8, self).__init__()
2181
self._matchingbzrdir.repository_format = \
2182
RepositoryFormatKnitPack5RichRoot()
2184
def make_tags(self, branch):
2185
"""See bzrlib.branch.BranchFormat.make_tags()."""
2186
return BasicTags(branch)
2188
def supports_set_append_revisions_only(self):
2191
def supports_stacking(self):
2194
supports_reference_locations = True
2197
class BzrBranchFormat7(BzrBranchFormat8):
2198
"""Branch format with last-revision, tags, and a stacked location pointer.
2200
The stacked location pointer is passed down to the repository and requires
2201
a repository format with supports_external_lookups = True.
2203
This format was introduced in bzr 1.6.
2206
def initialize(self, a_bzrdir, name=None):
2207
"""Create a branch of this format in a_bzrdir."""
2208
utf8_files = [('last-revision', '0 null:\n'),
2209
('branch.conf', ''),
2212
return self._initialize_helper(a_bzrdir, utf8_files, name)
2214
def _branch_class(self):
2217
def get_format_string(self):
2218
"""See BranchFormat.get_format_string()."""
2219
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
2221
def get_format_description(self):
2222
"""See BranchFormat.get_format_description()."""
2223
return "Branch format 7"
2225
def supports_set_append_revisions_only(self):
2228
supports_reference_locations = False
2231
class BranchReferenceFormat(BranchFormat):
2232
"""Bzr branch reference format.
2234
Branch references are used in implementing checkouts, they
2235
act as an alias to the real branch which is at some other url.
2242
def get_format_string(self):
2243
"""See BranchFormat.get_format_string()."""
2244
return "Bazaar-NG Branch Reference Format 1\n"
2246
def get_format_description(self):
2247
"""See BranchFormat.get_format_description()."""
2248
return "Checkout reference format 1"
2250
def get_reference(self, a_bzrdir, name=None):
2251
"""See BranchFormat.get_reference()."""
2252
transport = a_bzrdir.get_branch_transport(None, name=name)
2253
return transport.get_bytes('location')
2255
def set_reference(self, a_bzrdir, name, to_branch):
2256
"""See BranchFormat.set_reference()."""
2257
transport = a_bzrdir.get_branch_transport(None, name=name)
2258
location = transport.put_bytes('location', to_branch.base)
2260
def initialize(self, a_bzrdir, name=None, target_branch=None):
2261
"""Create a branch of this format in a_bzrdir."""
2262
if target_branch is None:
2263
# this format does not implement branch itself, thus the implicit
2264
# creation contract must see it as uninitializable
2265
raise errors.UninitializableFormat(self)
2266
mutter('creating branch reference in %s', a_bzrdir.user_url)
2267
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
2268
branch_transport.put_bytes('location',
2269
target_branch.bzrdir.user_url)
2270
branch_transport.put_bytes('format', self.get_format_string())
2272
a_bzrdir, name, _found=True,
2273
possible_transports=[target_branch.bzrdir.root_transport])
2274
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
2278
super(BranchReferenceFormat, self).__init__()
2279
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2280
self._matchingbzrdir.set_branch_format(self)
2282
def _make_reference_clone_function(format, a_branch):
2283
"""Create a clone() routine for a branch dynamically."""
2284
def clone(to_bzrdir, revision_id=None,
2285
repository_policy=None):
2286
"""See Branch.clone()."""
2287
return format.initialize(to_bzrdir, target_branch=a_branch)
2288
# cannot obey revision_id limits when cloning a reference ...
2289
# FIXME RBC 20060210 either nuke revision_id for clone, or
2290
# emit some sort of warning/error to the caller ?!
2293
def open(self, a_bzrdir, name=None, _found=False, location=None,
2294
possible_transports=None, ignore_fallbacks=False):
2295
"""Return the branch that the branch reference in a_bzrdir points at.
2297
:param a_bzrdir: A BzrDir that contains a branch.
2298
:param name: Name of colocated branch to open, if any
2299
:param _found: a private parameter, do not use it. It is used to
2300
indicate if format probing has already be done.
2301
:param ignore_fallbacks: when set, no fallback branches will be opened
2302
(if there are any). Default is to open fallbacks.
2303
:param location: The location of the referenced branch. If
2304
unspecified, this will be determined from the branch reference in
2306
:param possible_transports: An optional reusable transports list.
2309
format = BranchFormat.find_format(a_bzrdir, name=name)
2310
if format.__class__ != self.__class__:
2311
raise AssertionError("wrong format %r found for %r" %
2313
if location is None:
2314
location = self.get_reference(a_bzrdir, name)
2315
real_bzrdir = bzrdir.BzrDir.open(
2316
location, possible_transports=possible_transports)
2317
result = real_bzrdir.open_branch(name=name,
2318
ignore_fallbacks=ignore_fallbacks)
2319
# this changes the behaviour of result.clone to create a new reference
2320
# rather than a copy of the content of the branch.
2321
# I did not use a proxy object because that needs much more extensive
2322
# testing, and we are only changing one behaviour at the moment.
2323
# If we decide to alter more behaviours - i.e. the implicit nickname
2324
# then this should be refactored to introduce a tested proxy branch
2325
# and a subclass of that for use in overriding clone() and ....
2327
result.clone = self._make_reference_clone_function(result)
2331
network_format_registry = registry.FormatRegistry()
2332
"""Registry of formats indexed by their network name.
2334
The network name for a branch format is an identifier that can be used when
2335
referring to formats with smart server operations. See
2336
BranchFormat.network_name() for more detail.
2340
# formats which have no format string are not discoverable
2341
# and not independently creatable, so are not registered.
2342
__format5 = BzrBranchFormat5()
2343
__format6 = BzrBranchFormat6()
2344
__format7 = BzrBranchFormat7()
2345
__format8 = BzrBranchFormat8()
2346
BranchFormat.register_format(__format5)
2347
BranchFormat.register_format(BranchReferenceFormat())
2348
BranchFormat.register_format(__format6)
2349
BranchFormat.register_format(__format7)
2350
BranchFormat.register_format(__format8)
2351
BranchFormat.set_default_format(__format7)
2352
_legacy_formats = [BzrBranchFormat4(),
2354
network_format_registry.register(
2355
_legacy_formats[0].network_name(), _legacy_formats[0].__class__)
2358
class BranchWriteLockResult(LogicalLockResult):
2359
"""The result of write locking a branch.
2361
:ivar branch_token: The token obtained from the underlying branch lock, or
2363
:ivar unlock: A callable which will unlock the lock.
2366
def __init__(self, unlock, branch_token):
2367
LogicalLockResult.__init__(self, unlock)
2368
self.branch_token = branch_token
2371
return "BranchWriteLockResult(%s, %s)" % (self.branch_token,
2375
class BzrBranch(Branch, _RelockDebugMixin):
2376
"""A branch stored in the actual filesystem.
2378
Note that it's "local" in the context of the filesystem; it doesn't
2379
really matter if it's on an nfs/smb/afs/coda/... share, as long as
2380
it's writable, and can be accessed via the normal filesystem API.
2382
:ivar _transport: Transport for file operations on this branch's
2383
control files, typically pointing to the .bzr/branch directory.
2384
:ivar repository: Repository for this branch.
2385
:ivar base: The url of the base directory for this branch; the one
2386
containing the .bzr directory.
2387
:ivar name: Optional colocated branch name as it exists in the control
2391
def __init__(self, _format=None,
2392
_control_files=None, a_bzrdir=None, name=None,
2393
_repository=None, ignore_fallbacks=False):
2394
"""Create new branch object at a particular location."""
2395
if a_bzrdir is None:
2396
raise ValueError('a_bzrdir must be supplied')
2398
self.bzrdir = a_bzrdir
2399
self._base = self.bzrdir.transport.clone('..').base
2401
# XXX: We should be able to just do
2402
# self.base = self.bzrdir.root_transport.base
2403
# but this does not quite work yet -- mbp 20080522
2404
self._format = _format
2405
if _control_files is None:
2406
raise ValueError('BzrBranch _control_files is None')
2407
self.control_files = _control_files
2408
self._transport = _control_files._transport
2409
self.repository = _repository
2410
Branch.__init__(self)
2413
if self.name is None:
2414
return '%s(%s)' % (self.__class__.__name__, self.user_url)
2416
return '%s(%s,%s)' % (self.__class__.__name__, self.user_url,
2421
def _get_base(self):
2422
"""Returns the directory containing the control directory."""
2425
base = property(_get_base, doc="The URL for the root of this branch.")
2427
def _get_config(self):
2428
return TransportConfig(self._transport, 'branch.conf')
2430
def is_locked(self):
2431
return self.control_files.is_locked()
2433
def lock_write(self, token=None):
2434
"""Lock the branch for write operations.
2436
:param token: A token to permit reacquiring a previously held and
2438
:return: A BranchWriteLockResult.
2440
if not self.is_locked():
2441
self._note_lock('w')
2442
# All-in-one needs to always unlock/lock.
2443
repo_control = getattr(self.repository, 'control_files', None)
2444
if self.control_files == repo_control or not self.is_locked():
2445
self.repository._warn_if_deprecated(self)
2446
self.repository.lock_write()
2451
return BranchWriteLockResult(self.unlock,
2452
self.control_files.lock_write(token=token))
2455
self.repository.unlock()
2458
def lock_read(self):
2459
"""Lock the branch for read operations.
2461
:return: A bzrlib.lock.LogicalLockResult.
2463
if not self.is_locked():
2464
self._note_lock('r')
2465
# All-in-one needs to always unlock/lock.
2466
repo_control = getattr(self.repository, 'control_files', None)
2467
if self.control_files == repo_control or not self.is_locked():
2468
self.repository._warn_if_deprecated(self)
2469
self.repository.lock_read()
2474
self.control_files.lock_read()
2475
return LogicalLockResult(self.unlock)
2478
self.repository.unlock()
2481
@only_raises(errors.LockNotHeld, errors.LockBroken)
2484
self.control_files.unlock()
2486
# All-in-one needs to always unlock/lock.
2487
repo_control = getattr(self.repository, 'control_files', None)
2488
if (self.control_files == repo_control or
2489
not self.control_files.is_locked()):
2490
self.repository.unlock()
2491
if not self.control_files.is_locked():
2492
# we just released the lock
2493
self._clear_cached_state()
2495
def peek_lock_mode(self):
2496
if self.control_files._lock_count == 0:
2499
return self.control_files._lock_mode
2501
def get_physical_lock_status(self):
2502
return self.control_files.get_physical_lock_status()
2505
def print_file(self, file, revision_id):
2506
"""See Branch.print_file."""
2507
return self.repository.print_file(file, revision_id)
2509
def _write_revision_history(self, history):
2510
"""Factored out of set_revision_history.
2512
This performs the actual writing to disk.
2513
It is intended to be called by BzrBranch5.set_revision_history."""
2514
self._transport.put_bytes(
2515
'revision-history', '\n'.join(history),
2516
mode=self.bzrdir._get_file_mode())
2519
def set_revision_history(self, rev_history):
2520
"""See Branch.set_revision_history."""
2521
if 'evil' in debug.debug_flags:
2522
mutter_callsite(3, "set_revision_history scales with history.")
2523
check_not_reserved_id = _mod_revision.check_not_reserved_id
2524
for rev_id in rev_history:
2525
check_not_reserved_id(rev_id)
2526
if Branch.hooks['post_change_branch_tip']:
2527
# Don't calculate the last_revision_info() if there are no hooks
2529
old_revno, old_revid = self.last_revision_info()
2530
if len(rev_history) == 0:
2531
revid = _mod_revision.NULL_REVISION
2533
revid = rev_history[-1]
2534
self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
2535
self._write_revision_history(rev_history)
2536
self._clear_cached_state()
2537
self._cache_revision_history(rev_history)
2538
for hook in Branch.hooks['set_rh']:
2539
hook(self, rev_history)
2540
if Branch.hooks['post_change_branch_tip']:
2541
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2543
def _synchronize_history(self, destination, revision_id):
2544
"""Synchronize last revision and revision history between branches.
2546
This version is most efficient when the destination is also a
2547
BzrBranch5, but works for BzrBranch6 as long as the revision
2548
history is the true lefthand parent history, and all of the revisions
2549
are in the destination's repository. If not, set_revision_history
2552
:param destination: The branch to copy the history into
2553
:param revision_id: The revision-id to truncate history at. May
2554
be None to copy complete history.
2556
if not isinstance(destination._format, BzrBranchFormat5):
2557
super(BzrBranch, self)._synchronize_history(
2558
destination, revision_id)
2560
if revision_id == _mod_revision.NULL_REVISION:
2563
new_history = self.revision_history()
2564
if revision_id is not None and new_history != []:
2566
new_history = new_history[:new_history.index(revision_id) + 1]
2568
rev = self.repository.get_revision(revision_id)
2569
new_history = rev.get_history(self.repository)[1:]
2570
destination.set_revision_history(new_history)
2573
def set_last_revision_info(self, revno, revision_id):
2574
"""Set the last revision of this branch.
2576
The caller is responsible for checking that the revno is correct
2577
for this revision id.
2579
It may be possible to set the branch last revision to an id not
2580
present in the repository. However, branches can also be
2581
configured to check constraints on history, in which case this may not
2584
revision_id = _mod_revision.ensure_null(revision_id)
2585
# this old format stores the full history, but this api doesn't
2586
# provide it, so we must generate, and might as well check it's
2588
history = self._lefthand_history(revision_id)
2589
if len(history) != revno:
2590
raise AssertionError('%d != %d' % (len(history), revno))
2591
self.set_revision_history(history)
2593
def _gen_revision_history(self):
2594
history = self._transport.get_bytes('revision-history').split('\n')
2595
if history[-1:] == ['']:
2596
# There shouldn't be a trailing newline, but just in case.
2601
def generate_revision_history(self, revision_id, last_rev=None,
2603
"""Create a new revision history that will finish with revision_id.
2605
:param revision_id: the new tip to use.
2606
:param last_rev: The previous last_revision. If not None, then this
2607
must be a ancestory of revision_id, or DivergedBranches is raised.
2608
:param other_branch: The other branch that DivergedBranches should
2609
raise with respect to.
2611
self.set_revision_history(self._lefthand_history(revision_id,
2612
last_rev, other_branch))
2614
def basis_tree(self):
2615
"""See Branch.basis_tree."""
2616
return self.repository.revision_tree(self.last_revision())
2618
def _get_parent_location(self):
2619
_locs = ['parent', 'pull', 'x-pull']
2622
return self._transport.get_bytes(l).strip('\n')
2623
except errors.NoSuchFile:
2627
def _basic_push(self, target, overwrite, stop_revision):
2628
"""Basic implementation of push without bound branches or hooks.
2630
Must be called with source read locked and target write locked.
2632
result = BranchPushResult()
2633
result.source_branch = self
2634
result.target_branch = target
2635
result.old_revno, result.old_revid = target.last_revision_info()
2636
self.update_references(target)
2637
if result.old_revid != self.last_revision():
2638
# We assume that during 'push' this repository is closer than
2640
graph = self.repository.get_graph(target.repository)
2641
target.update_revisions(self, stop_revision,
2642
overwrite=overwrite, graph=graph)
2643
if self._push_should_merge_tags():
2644
result.tag_conflicts = self.tags.merge_to(target.tags,
2646
result.new_revno, result.new_revid = target.last_revision_info()
2649
def get_stacked_on_url(self):
2650
raise errors.UnstackableBranchFormat(self._format, self.user_url)
2652
def set_push_location(self, location):
2653
"""See Branch.set_push_location."""
2654
self.get_config().set_user_option(
2655
'push_location', location,
2656
store=_mod_config.STORE_LOCATION_NORECURSE)
2658
def _set_parent_location(self, url):
2660
self._transport.delete('parent')
2662
self._transport.put_bytes('parent', url + '\n',
2663
mode=self.bzrdir._get_file_mode())
2666
class BzrBranch5(BzrBranch):
2667
"""A format 5 branch. This supports new features over plain branches.
2669
It has support for a master_branch which is the data for bound branches.
2672
def get_bound_location(self):
2674
return self._transport.get_bytes('bound')[:-1]
2675
except errors.NoSuchFile:
2679
def get_master_branch(self, possible_transports=None):
2680
"""Return the branch we are bound to.
2682
:return: Either a Branch, or None
2684
This could memoise the branch, but if thats done
2685
it must be revalidated on each new lock.
2686
So for now we just don't memoise it.
2687
# RBC 20060304 review this decision.
2689
bound_loc = self.get_bound_location()
2693
return Branch.open(bound_loc,
2694
possible_transports=possible_transports)
2695
except (errors.NotBranchError, errors.ConnectionError), e:
2696
raise errors.BoundBranchConnectionFailure(
2700
def set_bound_location(self, location):
2701
"""Set the target where this branch is bound to.
2703
:param location: URL to the target branch
2706
self._transport.put_bytes('bound', location+'\n',
2707
mode=self.bzrdir._get_file_mode())
2710
self._transport.delete('bound')
2711
except errors.NoSuchFile:
2716
def bind(self, other):
2717
"""Bind this branch to the branch other.
2719
This does not push or pull data between the branches, though it does
2720
check for divergence to raise an error when the branches are not
2721
either the same, or one a prefix of the other. That behaviour may not
2722
be useful, so that check may be removed in future.
2724
:param other: The branch to bind to
2727
# TODO: jam 20051230 Consider checking if the target is bound
2728
# It is debatable whether you should be able to bind to
2729
# a branch which is itself bound.
2730
# Committing is obviously forbidden,
2731
# but binding itself may not be.
2732
# Since we *have* to check at commit time, we don't
2733
# *need* to check here
2735
# we want to raise diverged if:
2736
# last_rev is not in the other_last_rev history, AND
2737
# other_last_rev is not in our history, and do it without pulling
2739
self.set_bound_location(other.base)
2743
"""If bound, unbind"""
2744
return self.set_bound_location(None)
2747
def update(self, possible_transports=None):
2748
"""Synchronise this branch with the master branch if any.
2750
:return: None or the last_revision that was pivoted out during the
2753
master = self.get_master_branch(possible_transports)
2754
if master is not None:
2755
old_tip = _mod_revision.ensure_null(self.last_revision())
2756
self.pull(master, overwrite=True)
2757
if self.repository.get_graph().is_ancestor(old_tip,
2758
_mod_revision.ensure_null(self.last_revision())):
2764
class BzrBranch8(BzrBranch5):
2765
"""A branch that stores tree-reference locations."""
2767
def _open_hook(self):
2768
if self._ignore_fallbacks:
2771
url = self.get_stacked_on_url()
2772
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2773
errors.UnstackableBranchFormat):
2776
for hook in Branch.hooks['transform_fallback_location']:
2777
url = hook(self, url)
2779
hook_name = Branch.hooks.get_hook_name(hook)
2780
raise AssertionError(
2781
"'transform_fallback_location' hook %s returned "
2782
"None, not a URL." % hook_name)
2783
self._activate_fallback_location(url)
2785
def __init__(self, *args, **kwargs):
2786
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
2787
super(BzrBranch8, self).__init__(*args, **kwargs)
2788
self._last_revision_info_cache = None
2789
self._reference_info = None
2791
def _clear_cached_state(self):
2792
super(BzrBranch8, self)._clear_cached_state()
2793
self._last_revision_info_cache = None
2794
self._reference_info = None
2796
def _last_revision_info(self):
2797
revision_string = self._transport.get_bytes('last-revision')
2798
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2799
revision_id = cache_utf8.get_cached_utf8(revision_id)
2801
return revno, revision_id
2803
def _write_last_revision_info(self, revno, revision_id):
2804
"""Simply write out the revision id, with no checks.
2806
Use set_last_revision_info to perform this safely.
2808
Does not update the revision_history cache.
2809
Intended to be called by set_last_revision_info and
2810
_write_revision_history.
2812
revision_id = _mod_revision.ensure_null(revision_id)
2813
out_string = '%d %s\n' % (revno, revision_id)
2814
self._transport.put_bytes('last-revision', out_string,
2815
mode=self.bzrdir._get_file_mode())
2818
def set_last_revision_info(self, revno, revision_id):
2819
revision_id = _mod_revision.ensure_null(revision_id)
2820
old_revno, old_revid = self.last_revision_info()
2821
if self._get_append_revisions_only():
2822
self._check_history_violation(revision_id)
2823
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2824
self._write_last_revision_info(revno, revision_id)
2825
self._clear_cached_state()
2826
self._last_revision_info_cache = revno, revision_id
2827
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2829
def _synchronize_history(self, destination, revision_id):
2830
"""Synchronize last revision and revision history between branches.
2832
:see: Branch._synchronize_history
2834
# XXX: The base Branch has a fast implementation of this method based
2835
# on set_last_revision_info, but BzrBranch/BzrBranch5 have a slower one
2836
# that uses set_revision_history. This class inherits from BzrBranch5,
2837
# but wants the fast implementation, so it calls
2838
# Branch._synchronize_history directly.
2839
Branch._synchronize_history(self, destination, revision_id)
2841
def _check_history_violation(self, revision_id):
2842
last_revision = _mod_revision.ensure_null(self.last_revision())
2843
if _mod_revision.is_null(last_revision):
2845
if last_revision not in self._lefthand_history(revision_id):
2846
raise errors.AppendRevisionsOnlyViolation(self.user_url)
2848
def _gen_revision_history(self):
2849
"""Generate the revision history from last revision
2851
last_revno, last_revision = self.last_revision_info()
2852
self._extend_partial_history(stop_index=last_revno-1)
2853
return list(reversed(self._partial_revision_history_cache))
2855
def _write_revision_history(self, history):
2856
"""Factored out of set_revision_history.
2858
This performs the actual writing to disk, with format-specific checks.
2859
It is intended to be called by BzrBranch5.set_revision_history.
2861
if len(history) == 0:
2862
last_revision = 'null:'
2864
if history != self._lefthand_history(history[-1]):
2865
raise errors.NotLefthandHistory(history)
2866
last_revision = history[-1]
2867
if self._get_append_revisions_only():
2868
self._check_history_violation(last_revision)
2869
self._write_last_revision_info(len(history), last_revision)
2872
def _set_parent_location(self, url):
2873
"""Set the parent branch"""
2874
self._set_config_location('parent_location', url, make_relative=True)
2877
def _get_parent_location(self):
2878
"""Set the parent branch"""
2879
return self._get_config_location('parent_location')
2882
def _set_all_reference_info(self, info_dict):
2883
"""Replace all reference info stored in a branch.
2885
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
2888
writer = rio.RioWriter(s)
2889
for key, (tree_path, branch_location) in info_dict.iteritems():
2890
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
2891
branch_location=branch_location)
2892
writer.write_stanza(stanza)
2893
self._transport.put_bytes('references', s.getvalue())
2894
self._reference_info = info_dict
2897
def _get_all_reference_info(self):
2898
"""Return all the reference info stored in a branch.
2900
:return: A dict of {file_id: (tree_path, branch_location)}
2902
if self._reference_info is not None:
2903
return self._reference_info
2904
rio_file = self._transport.get('references')
2906
stanzas = rio.read_stanzas(rio_file)
2907
info_dict = dict((s['file_id'], (s['tree_path'],
2908
s['branch_location'])) for s in stanzas)
2911
self._reference_info = info_dict
2914
def set_reference_info(self, file_id, tree_path, branch_location):
2915
"""Set the branch location to use for a tree reference.
2917
:param file_id: The file-id of the tree reference.
2918
:param tree_path: The path of the tree reference in the tree.
2919
:param branch_location: The location of the branch to retrieve tree
2922
info_dict = self._get_all_reference_info()
2923
info_dict[file_id] = (tree_path, branch_location)
2924
if None in (tree_path, branch_location):
2925
if tree_path is not None:
2926
raise ValueError('tree_path must be None when branch_location'
2928
if branch_location is not None:
2929
raise ValueError('branch_location must be None when tree_path'
2931
del info_dict[file_id]
2932
self._set_all_reference_info(info_dict)
2934
def get_reference_info(self, file_id):
2935
"""Get the tree_path and branch_location for a tree reference.
2937
:return: a tuple of (tree_path, branch_location)
2939
return self._get_all_reference_info().get(file_id, (None, None))
2941
def reference_parent(self, file_id, path, possible_transports=None):
2942
"""Return the parent branch for a tree-reference file_id.
2944
:param file_id: The file_id of the tree reference
2945
:param path: The path of the file_id in the tree
2946
:return: A branch associated with the file_id
2948
branch_location = self.get_reference_info(file_id)[1]
2949
if branch_location is None:
2950
return Branch.reference_parent(self, file_id, path,
2951
possible_transports)
2952
branch_location = urlutils.join(self.user_url, branch_location)
2953
return Branch.open(branch_location,
2954
possible_transports=possible_transports)
2956
def set_push_location(self, location):
2957
"""See Branch.set_push_location."""
2958
self._set_config_location('push_location', location)
2960
def set_bound_location(self, location):
2961
"""See Branch.set_push_location."""
2963
config = self.get_config()
2964
if location is None:
2965
if config.get_user_option('bound') != 'True':
2968
config.set_user_option('bound', 'False', warn_masked=True)
2971
self._set_config_location('bound_location', location,
2973
config.set_user_option('bound', 'True', warn_masked=True)
2976
def _get_bound_location(self, bound):
2977
"""Return the bound location in the config file.
2979
Return None if the bound parameter does not match"""
2980
config = self.get_config()
2981
config_bound = (config.get_user_option('bound') == 'True')
2982
if config_bound != bound:
2984
return self._get_config_location('bound_location', config=config)
2986
def get_bound_location(self):
2987
"""See Branch.set_push_location."""
2988
return self._get_bound_location(True)
2990
def get_old_bound_location(self):
2991
"""See Branch.get_old_bound_location"""
2992
return self._get_bound_location(False)
2994
def get_stacked_on_url(self):
2995
# you can always ask for the URL; but you might not be able to use it
2996
# if the repo can't support stacking.
2997
## self._check_stackable_repo()
2998
stacked_url = self._get_config_location('stacked_on_location')
2999
if stacked_url is None:
3000
raise errors.NotStacked(self)
3003
def _get_append_revisions_only(self):
3004
return self.get_config(
3005
).get_user_option_as_bool('append_revisions_only')
3008
def generate_revision_history(self, revision_id, last_rev=None,
3010
"""See BzrBranch5.generate_revision_history"""
3011
history = self._lefthand_history(revision_id, last_rev, other_branch)
3012
revno = len(history)
3013
self.set_last_revision_info(revno, revision_id)
3016
def get_rev_id(self, revno, history=None):
3017
"""Find the revision id of the specified revno."""
3019
return _mod_revision.NULL_REVISION
3021
last_revno, last_revision_id = self.last_revision_info()
3022
if revno <= 0 or revno > last_revno:
3023
raise errors.NoSuchRevision(self, revno)
3025
if history is not None:
3026
return history[revno - 1]
3028
index = last_revno - revno
3029
if len(self._partial_revision_history_cache) <= index:
3030
self._extend_partial_history(stop_index=index)
3031
if len(self._partial_revision_history_cache) > index:
3032
return self._partial_revision_history_cache[index]
3034
raise errors.NoSuchRevision(self, revno)
3037
def revision_id_to_revno(self, revision_id):
3038
"""Given a revision id, return its revno"""
3039
if _mod_revision.is_null(revision_id):
3042
index = self._partial_revision_history_cache.index(revision_id)
3044
self._extend_partial_history(stop_revision=revision_id)
3045
index = len(self._partial_revision_history_cache) - 1
3046
if self._partial_revision_history_cache[index] != revision_id:
3047
raise errors.NoSuchRevision(self, revision_id)
3048
return self.revno() - index
3051
class BzrBranch7(BzrBranch8):
3052
"""A branch with support for a fallback repository."""
3054
def set_reference_info(self, file_id, tree_path, branch_location):
3055
Branch.set_reference_info(self, file_id, tree_path, branch_location)
3057
def get_reference_info(self, file_id):
3058
Branch.get_reference_info(self, file_id)
3060
def reference_parent(self, file_id, path, possible_transports=None):
3061
return Branch.reference_parent(self, file_id, path,
3062
possible_transports)
3065
class BzrBranch6(BzrBranch7):
3066
"""See BzrBranchFormat6 for the capabilities of this branch.
3068
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
3072
def get_stacked_on_url(self):
3073
raise errors.UnstackableBranchFormat(self._format, self.user_url)
1340
3076
######################################################################
1344
def is_control_file(filename):
1345
## FIXME: better check
1346
filename = os.path.normpath(filename)
1347
while filename != '':
1348
head, tail = os.path.split(filename)
1349
## mutter('check %r for control file' % ((head, tail), ))
1350
if tail == bzrlib.BZRDIR:
1352
if filename == head:
1359
def gen_file_id(name):
1360
"""Return new file id.
1362
This should probably generate proper UUIDs, but for the moment we
1363
cope with just randomness because running uuidgen every time is
1366
from binascii import hexlify
1367
from time import time
1369
# get last component
1370
idx = name.rfind('/')
1372
name = name[idx+1 : ]
1373
idx = name.rfind('\\')
1375
name = name[idx+1 : ]
1377
# make it not a hidden file
1378
name = name.lstrip('.')
1380
# remove any wierd characters; we don't escape them but rather
1381
# just pull them out
1382
name = re.sub(r'[^\w.]', '', name)
1384
s = hexlify(rand_bytes(8))
1385
return '-'.join((name, compact_date(time()), s))
1389
"""Return a new tree-root file id."""
1390
return gen_file_id('TREE_ROOT')
3077
# results of operations
3080
class _Result(object):
3082
def _show_tag_conficts(self, to_file):
3083
if not getattr(self, 'tag_conflicts', None):
3085
to_file.write('Conflicting tags:\n')
3086
for name, value1, value2 in self.tag_conflicts:
3087
to_file.write(' %s\n' % (name, ))
3090
class PullResult(_Result):
3091
"""Result of a Branch.pull operation.
3093
:ivar old_revno: Revision number before pull.
3094
:ivar new_revno: Revision number after pull.
3095
:ivar old_revid: Tip revision id before pull.
3096
:ivar new_revid: Tip revision id after pull.
3097
:ivar source_branch: Source (local) branch object. (read locked)
3098
:ivar master_branch: Master branch of the target, or the target if no
3100
:ivar local_branch: target branch if there is a Master, else None
3101
:ivar target_branch: Target/destination branch object. (write locked)
3102
:ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
3106
# DEPRECATED: pull used to return the change in revno
3107
return self.new_revno - self.old_revno
3109
def report(self, to_file):
3111
if self.old_revid == self.new_revid:
3112
to_file.write('No revisions to pull.\n')
3114
to_file.write('Now on revision %d.\n' % self.new_revno)
3115
self._show_tag_conficts(to_file)
3118
class BranchPushResult(_Result):
3119
"""Result of a Branch.push operation.
3121
:ivar old_revno: Revision number (eg 10) of the target before push.
3122
:ivar new_revno: Revision number (eg 12) of the target after push.
3123
:ivar old_revid: Tip revision id (eg joe@foo.com-1234234-aoeua34) of target
3125
:ivar new_revid: Tip revision id (eg joe@foo.com-5676566-boa234a) of target
3127
:ivar source_branch: Source branch object that the push was from. This is
3128
read locked, and generally is a local (and thus low latency) branch.
3129
:ivar master_branch: If target is a bound branch, the master branch of
3130
target, or target itself. Always write locked.
3131
:ivar target_branch: The direct Branch where data is being sent (write
3133
:ivar local_branch: If the target is a bound branch this will be the
3134
target, otherwise it will be None.
3138
# DEPRECATED: push used to return the change in revno
3139
return self.new_revno - self.old_revno
3141
def report(self, to_file):
3142
"""Write a human-readable description of the result."""
3143
if self.old_revid == self.new_revid:
3144
note('No new revisions to push.')
3146
note('Pushed up to revision %d.' % self.new_revno)
3147
self._show_tag_conficts(to_file)
3150
class BranchCheckResult(object):
3151
"""Results of checking branch consistency.
3156
def __init__(self, branch):
3157
self.branch = branch
3160
def report_results(self, verbose):
3161
"""Report the check results via trace.note.
3163
:param verbose: Requests more detailed display of what was checked,
3166
note('checked branch %s format %s', self.branch.user_url,
3167
self.branch._format)
3168
for error in self.errors:
3169
note('found error:%s', error)
3172
class Converter5to6(object):
3173
"""Perform an in-place upgrade of format 5 to format 6"""
3175
def convert(self, branch):
3176
# Data for 5 and 6 can peacefully coexist.
3177
format = BzrBranchFormat6()
3178
new_branch = format.open(branch.bzrdir, _found=True)
3180
# Copy source data into target
3181
new_branch._write_last_revision_info(*branch.last_revision_info())
3182
new_branch.set_parent(branch.get_parent())
3183
new_branch.set_bound_location(branch.get_bound_location())
3184
new_branch.set_push_location(branch.get_push_location())
3186
# New branch has no tags by default
3187
new_branch.tags._set_tag_dict({})
3189
# Copying done; now update target format
3190
new_branch._transport.put_bytes('format',
3191
format.get_format_string(),
3192
mode=new_branch.bzrdir._get_file_mode())
3194
# Clean up old files
3195
new_branch._transport.delete('revision-history')
3197
branch.set_parent(None)
3198
except errors.NoSuchFile:
3200
branch.set_bound_location(None)
3203
class Converter6to7(object):
3204
"""Perform an in-place upgrade of format 6 to format 7"""
3206
def convert(self, branch):
3207
format = BzrBranchFormat7()
3208
branch._set_config_location('stacked_on_location', '')
3209
# update target format
3210
branch._transport.put_bytes('format', format.get_format_string())
3213
class Converter7to8(object):
3214
"""Perform an in-place upgrade of format 6 to format 7"""
3216
def convert(self, branch):
3217
format = BzrBranchFormat8()
3218
branch._transport.put_bytes('references', '')
3219
# update target format
3220
branch._transport.put_bytes('format', format.get_format_string())
3223
def _run_with_write_locked_target(target, callable, *args, **kwargs):
3224
"""Run ``callable(*args, **kwargs)``, write-locking target for the
3227
_run_with_write_locked_target will attempt to release the lock it acquires.
3229
If an exception is raised by callable, then that exception *will* be
3230
propagated, even if the unlock attempt raises its own error. Thus
3231
_run_with_write_locked_target should be preferred to simply doing::
3235
return callable(*args, **kwargs)
3240
# This is very similar to bzrlib.decorators.needs_write_lock. Perhaps they
3241
# should share code?
3244
result = callable(*args, **kwargs)
3246
exc_info = sys.exc_info()
3250
raise exc_info[0], exc_info[1], exc_info[2]
3256
class InterBranch(InterObject):
3257
"""This class represents operations taking place between two branches.
3259
Its instances have methods like pull() and push() and contain
3260
references to the source and target repositories these operations
3261
can be carried out on.
3265
"""The available optimised InterBranch types."""
3268
def _get_branch_formats_to_test(klass):
3269
"""Return an iterable of format tuples for testing.
3271
:return: An iterable of (from_format, to_format) to use when testing
3272
this InterBranch class. Each InterBranch class should define this
3275
raise NotImplementedError(klass._get_branch_formats_to_test)
3278
def pull(self, overwrite=False, stop_revision=None,
3279
possible_transports=None, local=False):
3280
"""Mirror source into target branch.
3282
The target branch is considered to be 'local', having low latency.
3284
:returns: PullResult instance
3286
raise NotImplementedError(self.pull)
3289
def update_revisions(self, stop_revision=None, overwrite=False,
3291
"""Pull in new perfect-fit revisions.
3293
:param stop_revision: Updated until the given revision
3294
:param overwrite: Always set the branch pointer, rather than checking
3295
to see if it is a proper descendant.
3296
:param graph: A Graph object that can be used to query history
3297
information. This can be None.
3300
raise NotImplementedError(self.update_revisions)
3303
def push(self, overwrite=False, stop_revision=None,
3304
_override_hook_source_branch=None):
3305
"""Mirror the source branch into the target branch.
3307
The source branch is considered to be 'local', having low latency.
3309
raise NotImplementedError(self.push)
3312
def copy_content_into(self, revision_id=None):
3313
"""Copy the content of source into target
3315
revision_id: if not None, the revision history in the new branch will
3316
be truncated to end with revision_id.
3318
raise NotImplementedError(self.copy_content_into)
3321
class GenericInterBranch(InterBranch):
3322
"""InterBranch implementation that uses public Branch functions."""
3325
def is_compatible(klass, source, target):
3326
# GenericBranch uses the public API, so always compatible
3330
def _get_branch_formats_to_test(klass):
3331
return [(BranchFormat._default_format, BranchFormat._default_format)]
3334
def unwrap_format(klass, format):
3335
if isinstance(format, remote.RemoteBranchFormat):
3336
format._ensure_real()
3337
return format._custom_format
3341
def copy_content_into(self, revision_id=None):
3342
"""Copy the content of source into target
3344
revision_id: if not None, the revision history in the new branch will
3345
be truncated to end with revision_id.
3347
self.source.update_references(self.target)
3348
self.source._synchronize_history(self.target, revision_id)
3350
parent = self.source.get_parent()
3351
except errors.InaccessibleParent, e:
3352
mutter('parent was not accessible to copy: %s', e)
3355
self.target.set_parent(parent)
3356
if self.source._push_should_merge_tags():
3357
self.source.tags.merge_to(self.target.tags)
3360
def update_revisions(self, stop_revision=None, overwrite=False,
3362
"""See InterBranch.update_revisions()."""
3363
other_revno, other_last_revision = self.source.last_revision_info()
3364
stop_revno = None # unknown
3365
if stop_revision is None:
3366
stop_revision = other_last_revision
3367
if _mod_revision.is_null(stop_revision):
3368
# if there are no commits, we're done.
3370
stop_revno = other_revno
3372
# what's the current last revision, before we fetch [and change it
3374
last_rev = _mod_revision.ensure_null(self.target.last_revision())
3375
# we fetch here so that we don't process data twice in the common
3376
# case of having something to pull, and so that the check for
3377
# already merged can operate on the just fetched graph, which will
3378
# be cached in memory.
3379
self.target.fetch(self.source, stop_revision)
3380
# Check to see if one is an ancestor of the other
3383
graph = self.target.repository.get_graph()
3384
if self.target._check_if_descendant_or_diverged(
3385
stop_revision, last_rev, graph, self.source):
3386
# stop_revision is a descendant of last_rev, but we aren't
3387
# overwriting, so we're done.
3389
if stop_revno is None:
3391
graph = self.target.repository.get_graph()
3392
this_revno, this_last_revision = \
3393
self.target.last_revision_info()
3394
stop_revno = graph.find_distance_to_null(stop_revision,
3395
[(other_last_revision, other_revno),
3396
(this_last_revision, this_revno)])
3397
self.target.set_last_revision_info(stop_revno, stop_revision)
3400
def pull(self, overwrite=False, stop_revision=None,
3401
possible_transports=None, run_hooks=True,
3402
_override_hook_target=None, local=False):
3403
"""Pull from source into self, updating my master if any.
3405
:param run_hooks: Private parameter - if false, this branch
3406
is being called because it's the master of the primary branch,
3407
so it should not run its hooks.
3409
bound_location = self.target.get_bound_location()
3410
if local and not bound_location:
3411
raise errors.LocalRequiresBoundBranch()
3412
master_branch = None
3413
if not local and bound_location and self.source.user_url != bound_location:
3414
# not pulling from master, so we need to update master.
3415
master_branch = self.target.get_master_branch(possible_transports)
3416
master_branch.lock_write()
3419
# pull from source into master.
3420
master_branch.pull(self.source, overwrite, stop_revision,
3422
return self._pull(overwrite,
3423
stop_revision, _hook_master=master_branch,
3424
run_hooks=run_hooks,
3425
_override_hook_target=_override_hook_target)
3428
master_branch.unlock()
3430
def push(self, overwrite=False, stop_revision=None,
3431
_override_hook_source_branch=None):
3432
"""See InterBranch.push.
3434
This is the basic concrete implementation of push()
3436
:param _override_hook_source_branch: If specified, run
3437
the hooks passing this Branch as the source, rather than self.
3438
This is for use of RemoteBranch, where push is delegated to the
3439
underlying vfs-based Branch.
3441
# TODO: Public option to disable running hooks - should be trivial but
3443
self.source.lock_read()
3445
return _run_with_write_locked_target(
3446
self.target, self._push_with_bound_branches, overwrite,
3448
_override_hook_source_branch=_override_hook_source_branch)
3450
self.source.unlock()
3452
def _push_with_bound_branches(self, overwrite, stop_revision,
3453
_override_hook_source_branch=None):
3454
"""Push from source into target, and into target's master if any.
3457
if _override_hook_source_branch:
3458
result.source_branch = _override_hook_source_branch
3459
for hook in Branch.hooks['post_push']:
3462
bound_location = self.target.get_bound_location()
3463
if bound_location and self.target.base != bound_location:
3464
# there is a master branch.
3466
# XXX: Why the second check? Is it even supported for a branch to
3467
# be bound to itself? -- mbp 20070507
3468
master_branch = self.target.get_master_branch()
3469
master_branch.lock_write()
3471
# push into the master from the source branch.
3472
self.source._basic_push(master_branch, overwrite, stop_revision)
3473
# and push into the target branch from the source. Note that we
3474
# push from the source branch again, because its considered the
3475
# highest bandwidth repository.
3476
result = self.source._basic_push(self.target, overwrite,
3478
result.master_branch = master_branch
3479
result.local_branch = self.target
3483
master_branch.unlock()
3486
result = self.source._basic_push(self.target, overwrite,
3488
# TODO: Why set master_branch and local_branch if there's no
3489
# binding? Maybe cleaner to just leave them unset? -- mbp
3491
result.master_branch = self.target
3492
result.local_branch = None
3496
def _pull(self, overwrite=False, stop_revision=None,
3497
possible_transports=None, _hook_master=None, run_hooks=True,
3498
_override_hook_target=None, local=False):
3501
This function is the core worker, used by GenericInterBranch.pull to
3502
avoid duplication when pulling source->master and source->local.
3504
:param _hook_master: Private parameter - set the branch to
3505
be supplied as the master to pull hooks.
3506
:param run_hooks: Private parameter - if false, this branch
3507
is being called because it's the master of the primary branch,
3508
so it should not run its hooks.
3509
:param _override_hook_target: Private parameter - set the branch to be
3510
supplied as the target_branch to pull hooks.
3511
:param local: Only update the local branch, and not the bound branch.
3513
# This type of branch can't be bound.
3515
raise errors.LocalRequiresBoundBranch()
3516
result = PullResult()
3517
result.source_branch = self.source
3518
if _override_hook_target is None:
3519
result.target_branch = self.target
3521
result.target_branch = _override_hook_target
3522
self.source.lock_read()
3524
# We assume that during 'pull' the target repository is closer than
3526
self.source.update_references(self.target)
3527
graph = self.target.repository.get_graph(self.source.repository)
3528
# TODO: Branch formats should have a flag that indicates
3529
# that revno's are expensive, and pull() should honor that flag.
3531
result.old_revno, result.old_revid = \
3532
self.target.last_revision_info()
3533
self.target.update_revisions(self.source, stop_revision,
3534
overwrite=overwrite, graph=graph)
3535
# TODO: The old revid should be specified when merging tags,
3536
# so a tags implementation that versions tags can only
3537
# pull in the most recent changes. -- JRV20090506
3538
result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
3540
result.new_revno, result.new_revid = self.target.last_revision_info()
3542
result.master_branch = _hook_master
3543
result.local_branch = result.target_branch
3545
result.master_branch = result.target_branch
3546
result.local_branch = None
3548
for hook in Branch.hooks['post_pull']:
3551
self.source.unlock()
3555
InterBranch.register_optimiser(GenericInterBranch)