137
77
"""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.
80
Base directory/url of the branch.
82
hooks: An instance of BranchHooks.
84
# 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)
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.base, 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()
152
def open(base, _unsupported=False, possible_transports=None):
153
"""Open the branch rooted at base.
155
For instance, if the branch is at URL/.bzr/branch,
156
Branch.open(URL) -> a Branch instance.
158
control = bzrdir.BzrDir.open(base, _unsupported,
159
possible_transports=possible_transports)
160
return control.open_branch(_unsupported)
163
def open_from_transport(transport, _unsupported=False):
164
"""Open the branch rooted at transport"""
165
control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
166
return control.open_branch(_unsupported)
169
def open_containing(url, possible_transports=None):
170
"""Open an existing branch which contains url.
172
This probes for a branch at url, and searches upwards from there.
174
Basically we keep looking up until we find the control directory or
175
run into the root. If there isn't one, raises NotBranchError.
176
If there is one and it is either an unrecognised format or an unsupported
177
format, UnknownFormatError or UnsupportedFormatError are raised.
178
If there is one, it is returned, along with the unused portion of url.
180
control, relpath = bzrdir.BzrDir.open_containing(url,
182
return control.open_branch(), relpath
184
def _push_should_merge_tags(self):
185
"""Should _basic_push merge this branch's tags into the target?
187
The default implementation returns False if this branch has no tags,
188
and True the rest of the time. Subclasses may override this.
190
return self.supports_tags() and self.tags.get_tag_dict()
192
def get_config(self):
193
return BranchConfig(self)
195
def _get_config(self):
196
"""Get the concrete config for just the config in this branch.
198
This is not intended for client use; see Branch.get_config for the
203
:return: An object supporting get_option and set_option.
205
raise NotImplementedError(self._get_config)
207
def _get_fallback_repository(self, url):
208
"""Get the repository we fallback to at url."""
209
url = urlutils.join(self.base, url)
210
a_bzrdir = bzrdir.BzrDir.open(url,
211
possible_transports=[self.bzrdir.root_transport])
212
return a_bzrdir.open_branch().repository
214
def _get_tags_bytes(self):
215
"""Get the bytes of a serialised tags dict.
217
Note that not all branches support tags, nor do all use the same tags
218
logic: this method is specific to BasicTags. Other tag implementations
219
may use the same method name and behave differently, safely, because
220
of the double-dispatch via
221
format.make_tags->tags_instance->get_tags_dict.
223
:return: The bytes of the tags file.
224
:seealso: Branch._set_tags_bytes.
226
return self._transport.get_bytes('tags')
228
def _get_nick(self, local=False, possible_transports=None):
229
config = self.get_config()
230
# explicit overrides master, but don't look for master if local is True
231
if not local and not config.has_explicit_nickname():
233
master = self.get_master_branch(possible_transports)
234
if master is not None:
235
# return the master branch value
237
except errors.BzrError, e:
238
# Silently fall back to local implicit nick if the master is
240
mutter("Could not connect to bound branch, "
241
"falling back to local nick.\n " + str(e))
242
return config.get_nickname()
244
def _set_nick(self, nick):
245
self.get_config().set_user_option('nickname', nick, warn_masked=True)
247
nick = property(_get_nick, _set_nick)
250
raise NotImplementedError(self.is_locked)
252
def _lefthand_history(self, revision_id, last_rev=None,
254
if 'evil' in debug.debug_flags:
255
mutter_callsite(4, "_lefthand_history scales with history.")
256
# stop_revision must be a descendant of last_revision
257
graph = self.repository.get_graph()
258
if last_rev is not None:
259
if not graph.is_ancestor(last_rev, revision_id):
260
# our previous tip is not merged into stop_revision
261
raise errors.DivergedBranches(self, other_branch)
262
# make a new revision history from the graph
263
parents_map = graph.get_parent_map([revision_id])
264
if revision_id not in parents_map:
265
raise errors.NoSuchRevision(self, revision_id)
266
current_rev_id = revision_id
268
check_not_reserved_id = _mod_revision.check_not_reserved_id
269
# Do not include ghosts or graph origin in revision_history
270
while (current_rev_id in parents_map and
271
len(parents_map[current_rev_id]) > 0):
272
check_not_reserved_id(current_rev_id)
273
new_history.append(current_rev_id)
274
current_rev_id = parents_map[current_rev_id][0]
275
parents_map = graph.get_parent_map([current_rev_id])
276
new_history.reverse()
212
279
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'
280
raise NotImplementedError(self.lock_write)
228
282
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'
283
raise NotImplementedError(self.lock_read)
242
285
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
286
raise NotImplementedError(self.unlock)
288
def peek_lock_mode(self):
289
"""Return lock mode for the Branch: 'r', 'w' or None"""
290
raise NotImplementedError(self.peek_lock_mode)
292
def get_physical_lock_status(self):
293
raise NotImplementedError(self.get_physical_lock_status)
296
def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
297
"""Return the revision_id for a dotted revno.
299
:param revno: a tuple like (1,) or (1,1,2)
300
:param _cache_reverse: a private parameter enabling storage
301
of the reverse mapping in a top level cache. (This should
302
only be done in selective circumstances as we want to
303
avoid having the mapping cached multiple times.)
304
:return: the revision_id
305
:raises errors.NoSuchRevision: if the revno doesn't exist
307
rev_id = self._do_dotted_revno_to_revision_id(revno)
309
self._partial_revision_id_to_revno_cache[rev_id] = revno
312
def _do_dotted_revno_to_revision_id(self, revno):
313
"""Worker function for dotted_revno_to_revision_id.
315
Subclasses should override this if they wish to
316
provide a more efficient implementation.
319
return self.get_rev_id(revno[0])
320
revision_id_to_revno = self.get_revision_id_to_revno_map()
321
revision_ids = [revision_id for revision_id, this_revno
322
in revision_id_to_revno.iteritems()
323
if revno == this_revno]
324
if len(revision_ids) == 1:
325
return revision_ids[0]
327
revno_str = '.'.join(map(str, revno))
328
raise errors.NoSuchRevision(self, revno_str)
331
def revision_id_to_dotted_revno(self, revision_id):
332
"""Given a revision id, return its dotted revno.
334
:return: a tuple like (1,) or (400,1,3).
336
return self._do_revision_id_to_dotted_revno(revision_id)
338
def _do_revision_id_to_dotted_revno(self, revision_id):
339
"""Worker function for revision_id_to_revno."""
340
# Try the caches if they are loaded
341
result = self._partial_revision_id_to_revno_cache.get(revision_id)
342
if result is not None:
344
if self._revision_id_to_revno_cache:
345
result = self._revision_id_to_revno_cache.get(revision_id)
347
raise errors.NoSuchRevision(self, revision_id)
348
# Try the mainline as it's optimised
350
revno = self.revision_id_to_revno(revision_id)
352
except errors.NoSuchRevision:
353
# We need to load and use the full revno map after all
354
result = self.get_revision_id_to_revno_map().get(revision_id)
356
raise errors.NoSuchRevision(self, revision_id)
360
def get_revision_id_to_revno_map(self):
361
"""Return the revision_id => dotted revno map.
363
This will be regenerated on demand, but will be cached.
365
:return: A dictionary mapping revision_id => dotted revno.
366
This dictionary should not be modified by the caller.
368
if self._revision_id_to_revno_cache is not None:
369
mapping = self._revision_id_to_revno_cache
371
mapping = self._gen_revno_map()
372
self._cache_revision_id_to_revno(mapping)
373
# TODO: jam 20070417 Since this is being cached, should we be returning
375
# I would rather not, and instead just declare that users should not
376
# modify the return value.
379
def _gen_revno_map(self):
380
"""Create a new mapping from revision ids to dotted revnos.
382
Dotted revnos are generated based on the current tip in the revision
384
This is the worker function for get_revision_id_to_revno_map, which
385
just caches the return value.
387
:return: A dictionary mapping revision_id => dotted revno.
389
revision_id_to_revno = dict((rev_id, revno)
390
for rev_id, depth, revno, end_of_merge
391
in self.iter_merge_sorted_revisions())
392
return revision_id_to_revno
395
def iter_merge_sorted_revisions(self, start_revision_id=None,
396
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
397
"""Walk the revisions for a branch in merge sorted order.
399
Merge sorted order is the output from a merge-aware,
400
topological sort, i.e. all parents come before their
401
children going forward; the opposite for reverse.
403
:param start_revision_id: the revision_id to begin walking from.
404
If None, the branch tip is used.
405
:param stop_revision_id: the revision_id to terminate the walk
406
after. If None, the rest of history is included.
407
:param stop_rule: if stop_revision_id is not None, the precise rule
408
to use for termination:
409
* 'exclude' - leave the stop revision out of the result (default)
410
* 'include' - the stop revision is the last item in the result
411
* 'with-merges' - include the stop revision and all of its
412
merged revisions in the result
413
:param direction: either 'reverse' or 'forward':
414
* reverse means return the start_revision_id first, i.e.
415
start at the most recent revision and go backwards in history
416
* forward returns tuples in the opposite order to reverse.
417
Note in particular that forward does *not* do any intelligent
418
ordering w.r.t. depth as some clients of this API may like.
419
(If required, that ought to be done at higher layers.)
421
:return: an iterator over (revision_id, depth, revno, end_of_merge)
424
* revision_id: the unique id of the revision
425
* depth: How many levels of merging deep this node has been
427
* revno_sequence: This field provides a sequence of
428
revision numbers for all revisions. The format is:
429
(REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
430
branch that the revno is on. From left to right the REVNO numbers
431
are the sequence numbers within that branch of the revision.
432
* end_of_merge: When True the next node (earlier in history) is
433
part of a different merge.
435
# Note: depth and revno values are in the context of the branch so
436
# we need the full graph to get stable numbers, regardless of the
438
if self._merge_sorted_revisions_cache is None:
439
last_revision = self.last_revision()
440
graph = self.repository.get_graph()
441
parent_map = dict(((key, value) for key, value in
442
graph.iter_ancestry([last_revision]) if value is not None))
443
revision_graph = repository._strip_NULL_ghosts(parent_map)
444
revs = tsort.merge_sort(revision_graph, last_revision, None,
446
# Drop the sequence # before caching
447
self._merge_sorted_revisions_cache = [r[1:] for r in revs]
449
filtered = self._filter_merge_sorted_revisions(
450
self._merge_sorted_revisions_cache, start_revision_id,
451
stop_revision_id, stop_rule)
452
if direction == 'reverse':
454
if direction == 'forward':
455
return reversed(list(filtered))
457
raise ValueError('invalid direction %r' % direction)
459
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
460
start_revision_id, stop_revision_id, stop_rule):
461
"""Iterate over an inclusive range of sorted revisions."""
462
rev_iter = iter(merge_sorted_revisions)
463
if start_revision_id is not None:
464
for rev_id, depth, revno, end_of_merge in rev_iter:
465
if rev_id != start_revision_id:
468
# The decision to include the start or not
469
# depends on the stop_rule if a stop is provided
471
iter([(rev_id, depth, revno, end_of_merge)]),
474
if stop_revision_id is None:
475
for rev_id, depth, revno, end_of_merge in rev_iter:
476
yield rev_id, depth, revno, end_of_merge
477
elif stop_rule == 'exclude':
478
for rev_id, depth, revno, end_of_merge in rev_iter:
479
if rev_id == stop_revision_id:
481
yield rev_id, depth, revno, end_of_merge
482
elif stop_rule == 'include':
483
for rev_id, depth, revno, end_of_merge in rev_iter:
484
yield rev_id, depth, revno, end_of_merge
485
if rev_id == stop_revision_id:
487
elif stop_rule == 'with-merges':
488
stop_rev = self.repository.get_revision(stop_revision_id)
489
if stop_rev.parent_ids:
490
left_parent = stop_rev.parent_ids[0]
492
left_parent = _mod_revision.NULL_REVISION
493
for rev_id, depth, revno, end_of_merge in rev_iter:
494
if rev_id == left_parent:
496
yield rev_id, depth, revno, end_of_merge
498
raise ValueError('invalid stop_rule %r' % stop_rule)
500
def leave_lock_in_place(self):
501
"""Tell this branch object not to release the physical lock when this
504
If lock_write doesn't return a token, then this method is not supported.
506
self.control_files.leave_in_place()
508
def dont_leave_lock_in_place(self):
509
"""Tell this branch object to release the physical lock when this
510
object is unlocked, even if it didn't originally acquire it.
512
If lock_write doesn't return a token, then this method is not supported.
514
self.control_files.dont_leave_in_place()
516
def bind(self, other):
517
"""Bind the local branch the other branch.
519
:param other: The branch to bind to
522
raise errors.UpgradeRequired(self.base)
524
def set_append_revisions_only(self, enabled):
525
if not self._format.supports_set_append_revisions_only():
526
raise errors.UpgradeRequired(self.base)
531
self.get_config().set_user_option('append_revisions_only', value,
534
def set_reference_info(self, file_id, tree_path, branch_location):
535
"""Set the branch location to use for a tree reference."""
536
raise errors.UnsupportedOperation(self.set_reference_info, self)
538
def get_reference_info(self, file_id):
539
"""Get the tree_path and branch_location for a tree reference."""
540
raise errors.UnsupportedOperation(self.get_reference_info, self)
543
def fetch(self, from_branch, last_revision=None, pb=None):
544
"""Copy revisions from from_branch into this branch.
546
:param from_branch: Where to copy from.
547
:param last_revision: What revision to stop at (None for at the end
549
:param pb: An optional progress bar to use.
552
if self.base == from_branch.base:
555
symbol_versioning.warn(
556
symbol_versioning.deprecated_in((1, 14, 0))
557
% "pb parameter to fetch()")
558
from_branch.lock_read()
560
if last_revision is None:
561
last_revision = from_branch.last_revision()
562
last_revision = _mod_revision.ensure_null(last_revision)
563
return self.repository.fetch(from_branch.repository,
564
revision_id=last_revision,
569
def get_bound_location(self):
570
"""Return the URL of the branch we are bound to.
572
Older format branches cannot bind, please be sure to use a metadir
577
def get_old_bound_location(self):
578
"""Return the URL of the branch we used to be bound to
580
raise errors.UpgradeRequired(self.base)
582
def get_commit_builder(self, parents, config=None, timestamp=None,
583
timezone=None, committer=None, revprops=None,
585
"""Obtain a CommitBuilder for this branch.
587
:param parents: Revision ids of the parents of the new revision.
588
:param config: Optional configuration to use.
589
:param timestamp: Optional timestamp recorded for commit.
590
:param timezone: Optional timezone for timestamp.
591
:param committer: Optional committer to set for commit.
592
:param revprops: Optional dictionary of revision properties.
593
:param revision_id: Optional revision id.
597
config = self.get_config()
599
return self.repository.get_commit_builder(self, parents, config,
600
timestamp, timezone, committer, revprops, revision_id)
602
def get_master_branch(self, possible_transports=None):
603
"""Return the branch we are bound to.
605
:return: Either a Branch, or None
620
609
def get_revision_delta(self, revno):
621
610
"""Return the delta for one revision.
792
813
common_index = min(self_len, other_len) -1
793
814
if common_index >= 0 and \
794
815
self_history[common_index] != other_history[common_index]:
795
raise DivergedBranches(self, other)
816
raise errors.DivergedBranches(self, other)
797
818
if stop_revision is None:
798
819
stop_revision = other_len
799
elif stop_revision > other_len:
800
raise NoSuchRevision(self, stop_revision)
821
if stop_revision > other_len:
822
raise errors.NoSuchRevision(self, stop_revision)
802
823
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)
826
def update_revisions(self, other, stop_revision=None, overwrite=False,
828
"""Pull in new perfect-fit revisions.
830
:param other: Another Branch to pull from
831
:param stop_revision: Updated until the given revision
832
:param overwrite: Always set the branch pointer, rather than checking
833
to see if it is a proper descendant.
834
:param graph: A Graph object that can be used to query history
835
information. This can be None.
838
return InterBranch.get(other, self).update_revisions(stop_revision,
841
def import_last_revision_info(self, source_repo, revno, revid):
842
"""Set the last revision info, importing from another repo if necessary.
844
This is used by the bound branch code to upload a revision to
845
the master branch first before updating the tip of the local branch.
847
:param source_repo: Source repository to optionally fetch from
848
:param revno: Revision number of the new tip
849
:param revid: Revision id of the new tip
851
if not self.repository.has_same_location(source_repo):
852
self.repository.fetch(source_repo, revision_id=revid)
853
self.set_last_revision_info(revno, revid)
855
def revision_id_to_revno(self, revision_id):
856
"""Given a revision id, return its revno"""
857
if _mod_revision.is_null(revision_id):
859
history = self.revision_history()
861
return history.index(revision_id) + 1
896
862
except ValueError:
863
raise errors.NoSuchRevision(self, revision_id)
866
def get_rev_id(self, revno, history=None):
867
"""Find the revision id of the specified revno."""
869
return _mod_revision.NULL_REVISION
870
last_revno, last_revid = self.last_revision_info()
871
if revno == last_revno:
873
if revno <= 0 or revno > last_revno:
874
raise errors.NoSuchRevision(self, revno)
875
distance_from_last = last_revno - revno
876
if len(self._partial_revision_history_cache) <= distance_from_last:
877
self._extend_partial_history(distance_from_last)
878
return self._partial_revision_history_cache[distance_from_last]
881
def pull(self, source, overwrite=False, stop_revision=None,
882
possible_transports=None, *args, **kwargs):
883
"""Mirror source into this branch.
885
This branch is considered to be 'local', having low latency.
887
:returns: PullResult instance
889
return InterBranch.get(source, self).pull(overwrite=overwrite,
890
stop_revision=stop_revision,
891
possible_transports=possible_transports, *args, **kwargs)
893
def push(self, target, overwrite=False, stop_revision=None, *args,
895
"""Mirror this branch into target.
897
This branch is considered to be 'local', having low latency.
899
return InterBranch.get(self, target).push(overwrite, stop_revision,
902
def lossy_push(self, target, stop_revision=None):
903
"""Push deltas into another branch.
905
:note: This does not, like push, retain the revision ids from
906
the source branch and will, rather than adding bzr-specific
907
metadata, push only those semantics of the revision that can be
908
natively represented by this branch' VCS.
910
:param target: Target branch
911
:param stop_revision: Revision to push, defaults to last revision.
912
:return: BranchPushResult with an extra member revidmap:
913
A dictionary mapping revision ids from the target branch
914
to new revision ids in the target branch, for each
915
revision that was pushed.
917
inter = InterBranch.get(self, target)
918
lossy_push = getattr(inter, "lossy_push", None)
919
if lossy_push is None:
920
raise errors.LossyPushToSameVCS(self, target)
921
return lossy_push(stop_revision)
923
def basis_tree(self):
924
"""Return `Tree` object for last revision."""
925
return self.repository.revision_tree(self.last_revision())
927
def get_parent(self):
928
"""Return the parent location of the branch.
930
This is the default location for pull/missing. The usual
931
pattern is that the user can override it by specifying a
934
parent = self._get_parent_location()
937
# This is an old-format absolute path to a local branch
939
if parent.startswith('/'):
940
parent = urlutils.local_path_to_url(parent.decode('utf8'))
942
return urlutils.join(self.base[:-1], parent)
943
except errors.InvalidURLJoin, e:
944
raise errors.InaccessibleParent(parent, self.base)
946
def _get_parent_location(self):
947
raise NotImplementedError(self._get_parent_location)
949
def _set_config_location(self, name, url, config=None,
950
make_relative=False):
952
config = self.get_config()
956
url = urlutils.relative_url(self.base, url)
957
config.set_user_option(name, url, warn_masked=True)
959
def _get_config_location(self, name, config=None):
961
config = self.get_config()
962
location = config.get_user_option(name)
967
def get_child_submit_format(self):
968
"""Return the preferred format of submissions to this branch."""
969
return self.get_config().get_user_option("child_submit_format")
971
def get_submit_branch(self):
972
"""Return the submit location of the branch.
974
This is the default location for bundle. The usual
975
pattern is that the user can override it by specifying a
978
return self.get_config().get_user_option('submit_branch')
980
def set_submit_branch(self, location):
981
"""Return the submit location of the branch.
983
This is the default location for bundle. The usual
984
pattern is that the user can override it by specifying a
987
self.get_config().set_user_option('submit_branch', location,
990
def get_public_branch(self):
991
"""Return the public location of the branch.
993
This is used by merge directives.
995
return self._get_config_location('public_branch')
997
def set_public_branch(self, location):
998
"""Return the submit location of the branch.
1000
This is the default location for bundle. The usual
1001
pattern is that the user can override it by specifying a
1004
self._set_config_location('public_branch', location)
1006
def get_push_location(self):
1007
"""Return the None or the location to push this branch to."""
1008
push_loc = self.get_config().get_user_option('push_location')
1011
def set_push_location(self, location):
1012
"""Set a new push location for this branch."""
1013
raise NotImplementedError(self.set_push_location)
1015
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1016
"""Run the post_change_branch_tip hooks."""
1017
hooks = Branch.hooks['post_change_branch_tip']
1020
new_revno, new_revid = self.last_revision_info()
1021
params = ChangeBranchTipParams(
1022
self, old_revno, new_revno, old_revid, new_revid)
1026
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1027
"""Run the pre_change_branch_tip hooks."""
1028
hooks = Branch.hooks['pre_change_branch_tip']
1031
old_revno, old_revid = self.last_revision_info()
1032
params = ChangeBranchTipParams(
1033
self, old_revno, new_revno, old_revid, new_revid)
1037
except errors.TipChangeRejected:
1040
exc_info = sys.exc_info()
1041
hook_name = Branch.hooks.get_hook_name(hook)
1042
raise errors.HookFailed(
1043
'pre_change_branch_tip', hook_name, exc_info)
1047
"""Synchronise this branch with the master branch if any.
1049
:return: None or the last_revision pivoted out during the update.
1053
def check_revno(self, revno):
1055
Check whether a revno corresponds to any revision.
1056
Zero (the NULL revision) is considered valid.
1059
self.check_real_revno(revno)
1061
def check_real_revno(self, revno):
1063
Check whether a revno corresponds to a real revision.
1064
Zero (the NULL revision) is considered invalid
1066
if revno < 1 or revno > self.revno():
1067
raise errors.InvalidRevisionNumber(revno)
1070
def clone(self, to_bzrdir, revision_id=None, repository_policy=None):
1071
"""Clone this branch into to_bzrdir preserving all semantic values.
1073
Most API users will want 'create_clone_on_transport', which creates a
1074
new bzrdir and branch on the fly.
1076
revision_id: if not None, the revision history in the new branch will
1077
be truncated to end with revision_id.
1079
result = to_bzrdir.create_branch()
1082
if repository_policy is not None:
1083
repository_policy.configure_branch(result)
1084
self.copy_content_into(result, revision_id=revision_id)
1090
def sprout(self, to_bzrdir, revision_id=None, repository_policy=None):
1091
"""Create a new line of development from the branch, into to_bzrdir.
1093
to_bzrdir controls the branch format.
1095
revision_id: if not None, the revision history in the new branch will
1096
be truncated to end with revision_id.
1098
result = to_bzrdir.create_branch()
1101
if repository_policy is not None:
1102
repository_policy.configure_branch(result)
1103
self.copy_content_into(result, revision_id=revision_id)
1104
result.set_parent(self.bzrdir.root_transport.base)
1109
def _synchronize_history(self, destination, revision_id):
1110
"""Synchronize last revision and revision history between branches.
1112
This version is most efficient when the destination is also a
1113
BzrBranch6, but works for BzrBranch5, as long as the destination's
1114
repository contains all the lefthand ancestors of the intended
1115
last_revision. If not, set_last_revision_info will fail.
1117
:param destination: The branch to copy the history into
1118
:param revision_id: The revision-id to truncate history at. May
1119
be None to copy complete history.
1121
source_revno, source_revision_id = self.last_revision_info()
1122
if revision_id is None:
1123
revno, revision_id = source_revno, source_revision_id
1125
graph = self.repository.get_graph()
1127
revno = graph.find_distance_to_null(revision_id,
1128
[(source_revision_id, source_revno)])
1129
except errors.GhostRevisionsHaveNoRevno:
1130
# Default to 1, if we can't find anything else
1132
destination.set_last_revision_info(revno, revision_id)
1135
def copy_content_into(self, destination, revision_id=None):
1136
"""Copy the content of self into destination.
1138
revision_id: if not None, the revision history in the new branch will
1139
be truncated to end with revision_id.
1141
self.update_references(destination)
1142
self._synchronize_history(destination, revision_id)
1144
parent = self.get_parent()
1145
except errors.InaccessibleParent, e:
1146
mutter('parent was not accessible to copy: %s', e)
1149
destination.set_parent(parent)
1150
if self._push_should_merge_tags():
1151
self.tags.merge_to(destination.tags)
1153
def update_references(self, target):
1154
if not getattr(self._format, 'supports_reference_locations', False):
1156
reference_dict = self._get_all_reference_info()
1157
if len(reference_dict) == 0:
1159
old_base = self.base
1160
new_base = target.base
1161
target_reference_dict = target._get_all_reference_info()
1162
for file_id, (tree_path, branch_location) in (
1163
reference_dict.items()):
1164
branch_location = urlutils.rebase_url(branch_location,
1166
target_reference_dict.setdefault(
1167
file_id, (tree_path, branch_location))
1168
target._set_all_reference_info(target_reference_dict)
1172
"""Check consistency of the branch.
1174
In particular this checks that revisions given in the revision-history
1175
do actually match up in the revision graph, and that they're all
1176
present in the repository.
1178
Callers will typically also want to check the repository.
1180
:return: A BranchCheckResult.
1182
ret = BranchCheckResult(self)
1183
mainline_parent_id = None
1184
last_revno, last_revision_id = self.last_revision_info()
1185
real_rev_history = []
1187
for revid in self.repository.iter_reverse_revision_history(
1189
real_rev_history.append(revid)
1190
except errors.RevisionNotPresent:
1191
ret.ghosts_in_mainline = True
1193
ret.ghosts_in_mainline = False
1194
real_rev_history.reverse()
1195
if len(real_rev_history) != last_revno:
1196
raise errors.BzrCheckError('revno does not match len(mainline)'
1197
' %s != %s' % (last_revno, len(real_rev_history)))
1198
# TODO: We should probably also check that real_rev_history actually
1199
# matches self.revision_history()
1200
for revision_id in real_rev_history:
1202
revision = self.repository.get_revision(revision_id)
1203
except errors.NoSuchRevision, e:
1204
raise errors.BzrCheckError("mainline revision {%s} not in repository"
1206
# In general the first entry on the revision history has no parents.
1207
# But it's not illegal for it to have parents listed; this can happen
1208
# in imports from Arch when the parents weren't reachable.
1209
if mainline_parent_id is not None:
1210
if mainline_parent_id not in revision.parent_ids:
1211
raise errors.BzrCheckError("previous revision {%s} not listed among "
1213
% (mainline_parent_id, revision_id))
1214
mainline_parent_id = revision_id
1217
def _get_checkout_format(self):
1218
"""Return the most suitable metadir for a checkout of this branch.
1219
Weaves are used if this branch's repository uses weaves.
1221
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
1222
from bzrlib.repofmt import weaverepo
1223
format = bzrdir.BzrDirMetaFormat1()
1224
format.repository_format = weaverepo.RepositoryFormat7()
1226
format = self.repository.bzrdir.checkout_metadir()
1227
format.set_branch_format(self._format)
1230
def create_clone_on_transport(self, to_transport, revision_id=None,
1231
stacked_on=None, create_prefix=False, use_existing_dir=False):
1232
"""Create a clone of this branch and its bzrdir.
1234
:param to_transport: The transport to clone onto.
1235
:param revision_id: The revision id to use as tip in the new branch.
1236
If None the tip is obtained from this branch.
1237
:param stacked_on: An optional URL to stack the clone on.
1238
:param create_prefix: Create any missing directories leading up to
1240
:param use_existing_dir: Use an existing directory if one exists.
1242
# XXX: Fix the bzrdir API to allow getting the branch back from the
1243
# clone call. Or something. 20090224 RBC/spiv.
1244
if revision_id is None:
1245
revision_id = self.last_revision()
1247
dir_to = self.bzrdir.clone_on_transport(to_transport,
1248
revision_id=revision_id, stacked_on=stacked_on,
1249
create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1250
except errors.FileExists:
1251
if not use_existing_dir:
1253
except errors.NoSuchFile:
1254
if not create_prefix:
1256
return dir_to.open_branch()
1258
def create_checkout(self, to_location, revision_id=None,
1259
lightweight=False, accelerator_tree=None,
1261
"""Create a checkout of a branch.
1263
:param to_location: The url to produce the checkout at
1264
:param revision_id: The revision to check out
1265
:param lightweight: If True, produce a lightweight checkout, otherwise,
1266
produce a bound branch (heavyweight checkout)
1267
:param accelerator_tree: A tree which can be used for retrieving file
1268
contents more quickly than the revision tree, i.e. a workingtree.
1269
The revision tree will be used for cases where accelerator_tree's
1270
content is different.
1271
:param hardlink: If true, hard-link files from accelerator_tree,
1273
:return: The tree of the created checkout
1275
t = transport.get_transport(to_location)
1278
format = self._get_checkout_format()
1279
checkout = format.initialize_on_transport(t)
1280
from_branch = BranchReferenceFormat().initialize(checkout, self)
1282
format = self._get_checkout_format()
1283
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
1284
to_location, force_new_tree=False, format=format)
1285
checkout = checkout_branch.bzrdir
1286
checkout_branch.bind(self)
1287
# pull up to the specified revision_id to set the initial
1288
# branch tip correctly, and seed it with history.
1289
checkout_branch.pull(self, stop_revision=revision_id)
1291
tree = checkout.create_workingtree(revision_id,
1292
from_branch=from_branch,
1293
accelerator_tree=accelerator_tree,
1295
basis_tree = tree.basis_tree()
1296
basis_tree.lock_read()
1298
for path, file_id in basis_tree.iter_references():
1299
reference_parent = self.reference_parent(file_id, path)
1300
reference_parent.create_checkout(tree.abspath(path),
1301
basis_tree.get_reference_revision(file_id, path),
1308
def reconcile(self, thorough=True):
1309
"""Make sure the data stored in this branch is consistent."""
1310
from bzrlib.reconcile import BranchReconciler
1311
reconciler = BranchReconciler(self, thorough=thorough)
1312
reconciler.reconcile()
1315
def reference_parent(self, file_id, path, possible_transports=None):
1316
"""Return the parent branch for a tree-reference file_id
1317
:param file_id: The file_id of the tree reference
1318
:param path: The path of the file_id in the tree
1319
:return: A branch associated with the file_id
1321
# FIXME should provide multiple branches, based on config
1322
return Branch.open(self.bzrdir.root_transport.clone(path).base,
1323
possible_transports=possible_transports)
1325
def supports_tags(self):
1326
return self._format.supports_tags()
1328
def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
1330
"""Ensure that revision_b is a descendant of revision_a.
1332
This is a helper function for update_revisions.
1334
:raises: DivergedBranches if revision_b has diverged from revision_a.
1335
:returns: True if revision_b is a descendant of revision_a.
1337
relation = self._revision_relations(revision_a, revision_b, graph)
1338
if relation == 'b_descends_from_a':
1340
elif relation == 'diverged':
1341
raise errors.DivergedBranches(self, other_branch)
1342
elif relation == 'a_descends_from_b':
1345
raise AssertionError("invalid relation: %r" % (relation,))
1347
def _revision_relations(self, revision_a, revision_b, graph):
1348
"""Determine the relationship between two revisions.
1350
:returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
1352
heads = graph.heads([revision_a, revision_b])
1353
if heads == set([revision_b]):
1354
return 'b_descends_from_a'
1355
elif heads == set([revision_a, revision_b]):
1356
# These branches have diverged
1358
elif heads == set([revision_a]):
1359
return 'a_descends_from_b'
1361
raise AssertionError("invalid heads: %r" % (heads,))
1364
class BranchFormat(object):
1365
"""An encapsulation of the initialization and open routines for a format.
1367
Formats provide three things:
1368
* An initialization routine,
1372
Formats are placed in an dict by their format string for reference
1373
during branch opening. Its not required that these be instances, they
1374
can be classes themselves with class methods - it simply depends on
1375
whether state is needed for a given format or not.
1377
Once a format is deprecated, just deprecate the initialize and open
1378
methods on the format class. Do not deprecate the object, as the
1379
object will be created every time regardless.
1382
_default_format = None
1383
"""The default format used for new branches."""
1386
"""The known formats."""
1388
can_set_append_revisions_only = True
1390
def __eq__(self, other):
1391
return self.__class__ is other.__class__
1393
def __ne__(self, other):
1394
return not (self == other)
1397
def find_format(klass, a_bzrdir):
1398
"""Return the format for the branch object in a_bzrdir."""
1400
transport = a_bzrdir.get_branch_transport(None)
1401
format_string = transport.get("format").read()
1402
return klass._formats[format_string]
1403
except errors.NoSuchFile:
1404
raise errors.NotBranchError(path=transport.base)
1406
raise errors.UnknownFormatError(format=format_string, kind='branch')
1409
def get_default_format(klass):
1410
"""Return the current default format."""
1411
return klass._default_format
1413
def get_reference(self, a_bzrdir):
1414
"""Get the target reference of the branch in a_bzrdir.
1416
format probing must have been completed before calling
1417
this method - it is assumed that the format of the branch
1418
in a_bzrdir is correct.
1420
:param a_bzrdir: The bzrdir to get the branch data from.
1421
:return: None if the branch is not a reference branch.
1426
def set_reference(self, a_bzrdir, to_branch):
1427
"""Set the target reference of the branch in a_bzrdir.
1429
format probing must have been completed before calling
1430
this method - it is assumed that the format of the branch
1431
in a_bzrdir is correct.
1433
:param a_bzrdir: The bzrdir to set the branch reference for.
1434
:param to_branch: branch that the checkout is to reference
1436
raise NotImplementedError(self.set_reference)
1438
def get_format_string(self):
1439
"""Return the ASCII format string that identifies this format."""
1440
raise NotImplementedError(self.get_format_string)
1442
def get_format_description(self):
1443
"""Return the short format description for this format."""
1444
raise NotImplementedError(self.get_format_description)
1446
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
1448
"""Initialize a branch in a bzrdir, with specified files
1450
:param a_bzrdir: The bzrdir to initialize the branch in
1451
:param utf8_files: The files to create as a list of
1452
(filename, content) tuples
1453
:param set_format: If True, set the format with
1454
self.get_format_string. (BzrBranch4 has its format set
1456
:return: a branch in this format
1458
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
1459
branch_transport = a_bzrdir.get_branch_transport(self)
1461
'metadir': ('lock', lockdir.LockDir),
1462
'branch4': ('branch-lock', lockable_files.TransportLock),
1464
lock_name, lock_class = lock_map[lock_type]
1465
control_files = lockable_files.LockableFiles(branch_transport,
1466
lock_name, lock_class)
1467
control_files.create_lock()
1469
control_files.lock_write()
1470
except errors.LockContention:
1471
if lock_type != 'branch4':
1477
utf8_files += [('format', self.get_format_string())]
1479
for (filename, content) in utf8_files:
1480
branch_transport.put_bytes(
1482
mode=a_bzrdir._get_file_mode())
1485
control_files.unlock()
1486
return self.open(a_bzrdir, _found=True)
1488
def initialize(self, a_bzrdir):
1489
"""Create a branch of this format in a_bzrdir."""
1490
raise NotImplementedError(self.initialize)
1492
def is_supported(self):
1493
"""Is this format supported?
1495
Supported formats can be initialized and opened.
1496
Unsupported formats may not support initialization or committing or
1497
some other features depending on the reason for not being supported.
1501
def make_tags(self, branch):
1502
"""Create a tags object for branch.
1504
This method is on BranchFormat, because BranchFormats are reflected
1505
over the wire via network_name(), whereas full Branch instances require
1506
multiple VFS method calls to operate at all.
1508
The default implementation returns a disabled-tags instance.
1510
Note that it is normal for branch to be a RemoteBranch when using tags
1513
return DisabledTags(branch)
1515
def network_name(self):
1516
"""A simple byte string uniquely identifying this format for RPC calls.
1518
MetaDir branch formats use their disk format string to identify the
1519
repository over the wire. All in one formats such as bzr < 0.8, and
1520
foreign formats like svn/git and hg should use some marker which is
1521
unique and immutable.
1523
raise NotImplementedError(self.network_name)
1525
def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
1526
"""Return the branch object for a_bzrdir
1528
:param a_bzrdir: A BzrDir that contains a branch.
1529
:param _found: a private parameter, do not use it. It is used to
1530
indicate if format probing has already be done.
1531
:param ignore_fallbacks: when set, no fallback branches will be opened
1532
(if there are any). Default is to open fallbacks.
1534
raise NotImplementedError(self.open)
1537
def register_format(klass, format):
1538
"""Register a metadir format."""
1539
klass._formats[format.get_format_string()] = format
1540
# Metadir formats have a network name of their format string, and get
1541
# registered as class factories.
1542
network_format_registry.register(format.get_format_string(), format.__class__)
1545
def set_default_format(klass, format):
1546
klass._default_format = format
1548
def supports_set_append_revisions_only(self):
1549
"""True if this format supports set_append_revisions_only."""
1552
def supports_stacking(self):
1553
"""True if this format records a stacked-on branch."""
1557
def unregister_format(klass, format):
1558
del klass._formats[format.get_format_string()]
1561
return self.get_format_description().rstrip()
1563
def supports_tags(self):
1564
"""True if this format supports tags stored in the branch"""
1565
return False # by default
1568
class BranchHooks(Hooks):
1569
"""A dictionary mapping hook name to a list of callables for branch hooks.
1571
e.g. ['set_rh'] Is the list of items to be called when the
1572
set_revision_history function is invoked.
1576
"""Create the default hooks.
1578
These are all empty initially, because by default nothing should get
1581
Hooks.__init__(self)
1582
self.create_hook(HookPoint('set_rh',
1583
"Invoked whenever the revision history has been set via "
1584
"set_revision_history. The api signature is (branch, "
1585
"revision_history), and the branch will be write-locked. "
1586
"The set_rh hook can be expensive for bzr to trigger, a better "
1587
"hook to use is Branch.post_change_branch_tip.", (0, 15), None))
1588
self.create_hook(HookPoint('open',
1589
"Called with the Branch object that has been opened after a "
1590
"branch is opened.", (1, 8), None))
1591
self.create_hook(HookPoint('post_push',
1592
"Called after a push operation completes. post_push is called "
1593
"with a bzrlib.branch.BranchPushResult object and only runs in the "
1594
"bzr client.", (0, 15), None))
1595
self.create_hook(HookPoint('post_pull',
1596
"Called after a pull operation completes. post_pull is called "
1597
"with a bzrlib.branch.PullResult object and only runs in the "
1598
"bzr client.", (0, 15), None))
1599
self.create_hook(HookPoint('pre_commit',
1600
"Called after a commit is calculated but before it is is "
1601
"completed. pre_commit is called with (local, master, old_revno, "
1602
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1603
"). old_revid is NULL_REVISION for the first commit to a branch, "
1604
"tree_delta is a TreeDelta object describing changes from the "
1605
"basis revision. hooks MUST NOT modify this delta. "
1606
" future_tree is an in-memory tree obtained from "
1607
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1608
"tree.", (0,91), None))
1609
self.create_hook(HookPoint('post_commit',
1610
"Called in the bzr client after a commit has completed. "
1611
"post_commit is called with (local, master, old_revno, old_revid, "
1612
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1613
"commit to a branch.", (0, 15), None))
1614
self.create_hook(HookPoint('post_uncommit',
1615
"Called in the bzr client after an uncommit completes. "
1616
"post_uncommit is called with (local, master, old_revno, "
1617
"old_revid, new_revno, new_revid) where local is the local branch "
1618
"or None, master is the target branch, and an empty branch "
1619
"receives new_revno of 0, new_revid of None.", (0, 15), None))
1620
self.create_hook(HookPoint('pre_change_branch_tip',
1621
"Called in bzr client and server before a change to the tip of a "
1622
"branch is made. pre_change_branch_tip is called with a "
1623
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1624
"commit, uncommit will all trigger this hook.", (1, 6), None))
1625
self.create_hook(HookPoint('post_change_branch_tip',
1626
"Called in bzr client and server after a change to the tip of a "
1627
"branch is made. post_change_branch_tip is called with a "
1628
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1629
"commit, uncommit will all trigger this hook.", (1, 4), None))
1630
self.create_hook(HookPoint('transform_fallback_location',
1631
"Called when a stacked branch is activating its fallback "
1632
"locations. transform_fallback_location is called with (branch, "
1633
"url), and should return a new url. Returning the same url "
1634
"allows it to be used as-is, returning a different one can be "
1635
"used to cause the branch to stack on a closer copy of that "
1636
"fallback_location. Note that the branch cannot have history "
1637
"accessing methods called on it during this hook because the "
1638
"fallback locations have not been activated. When there are "
1639
"multiple hooks installed for transform_fallback_location, "
1640
"all are called with the url returned from the previous hook."
1641
"The order is however undefined.", (1, 9), None))
1644
# install the default hooks into the Branch class.
1645
Branch.hooks = BranchHooks()
1648
class ChangeBranchTipParams(object):
1649
"""Object holding parameters passed to *_change_branch_tip hooks.
1651
There are 5 fields that hooks may wish to access:
1653
:ivar branch: the branch being changed
1654
:ivar old_revno: revision number before the change
1655
:ivar new_revno: revision number after the change
1656
:ivar old_revid: revision id before the change
1657
:ivar new_revid: revision id after the change
1659
The revid fields are strings. The revno fields are integers.
1662
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1663
"""Create a group of ChangeBranchTip parameters.
1665
:param branch: The branch being changed.
1666
:param old_revno: Revision number before the change.
1667
:param new_revno: Revision number after the change.
1668
:param old_revid: Tip revision id before the change.
1669
:param new_revid: Tip revision id after the change.
1671
self.branch = branch
1672
self.old_revno = old_revno
1673
self.new_revno = new_revno
1674
self.old_revid = old_revid
1675
self.new_revid = new_revid
1677
def __eq__(self, other):
1678
return self.__dict__ == other.__dict__
1681
return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1682
self.__class__.__name__, self.branch,
1683
self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1686
class BzrBranchFormat4(BranchFormat):
1687
"""Bzr branch format 4.
1690
- a revision-history file.
1691
- a branch-lock lock file [ to be shared with the bzrdir ]
1694
def get_format_description(self):
1695
"""See BranchFormat.get_format_description()."""
1696
return "Branch format 4"
1698
def initialize(self, a_bzrdir):
1699
"""Create a branch of this format in a_bzrdir."""
1700
utf8_files = [('revision-history', ''),
1701
('branch-name', ''),
1703
return self._initialize_helper(a_bzrdir, utf8_files,
1704
lock_type='branch4', set_format=False)
1707
super(BzrBranchFormat4, self).__init__()
1708
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1710
def network_name(self):
1711
"""The network name for this format is the control dirs disk label."""
1712
return self._matchingbzrdir.get_format_string()
1714
def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
1715
"""See BranchFormat.open()."""
1717
# we are being called directly and must probe.
1718
raise NotImplementedError
1719
return BzrBranch(_format=self,
1720
_control_files=a_bzrdir._control_files,
1722
_repository=a_bzrdir.open_repository())
1725
return "Bazaar-NG branch format 4"
1728
class BranchFormatMetadir(BranchFormat):
1729
"""Common logic for meta-dir based branch formats."""
1731
def _branch_class(self):
1732
"""What class to instantiate on open calls."""
1733
raise NotImplementedError(self._branch_class)
1735
def network_name(self):
1736
"""A simple byte string uniquely identifying this format for RPC calls.
1738
Metadir branch formats use their format string.
1740
return self.get_format_string()
1742
def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
1743
"""See BranchFormat.open()."""
1745
format = BranchFormat.find_format(a_bzrdir)
1746
if format.__class__ != self.__class__:
1747
raise AssertionError("wrong format %r found for %r" %
1750
transport = a_bzrdir.get_branch_transport(None)
1751
control_files = lockable_files.LockableFiles(transport, 'lock',
1753
return self._branch_class()(_format=self,
1754
_control_files=control_files,
1756
_repository=a_bzrdir.find_repository(),
1757
ignore_fallbacks=ignore_fallbacks)
1758
except errors.NoSuchFile:
1759
raise errors.NotBranchError(path=transport.base)
1762
super(BranchFormatMetadir, self).__init__()
1763
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1764
self._matchingbzrdir.set_branch_format(self)
1766
def supports_tags(self):
1770
class BzrBranchFormat5(BranchFormatMetadir):
1771
"""Bzr branch format 5.
1774
- a revision-history file.
1776
- a lock dir guarding the branch itself
1777
- all of this stored in a branch/ subdirectory
1778
- works with shared repositories.
1780
This format is new in bzr 0.8.
1783
def _branch_class(self):
1786
def get_format_string(self):
1787
"""See BranchFormat.get_format_string()."""
1788
return "Bazaar-NG branch format 5\n"
1790
def get_format_description(self):
1791
"""See BranchFormat.get_format_description()."""
1792
return "Branch format 5"
1794
def initialize(self, a_bzrdir):
1795
"""Create a branch of this format in a_bzrdir."""
1796
utf8_files = [('revision-history', ''),
1797
('branch-name', ''),
1799
return self._initialize_helper(a_bzrdir, utf8_files)
1801
def supports_tags(self):
1805
class BzrBranchFormat6(BranchFormatMetadir):
1806
"""Branch format with last-revision and tags.
1808
Unlike previous formats, this has no explicit revision history. Instead,
1809
this just stores the last-revision, and the left-hand history leading
1810
up to there is the history.
1812
This format was introduced in bzr 0.15
1813
and became the default in 0.91.
1816
def _branch_class(self):
1819
def get_format_string(self):
1820
"""See BranchFormat.get_format_string()."""
1821
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1823
def get_format_description(self):
1824
"""See BranchFormat.get_format_description()."""
1825
return "Branch format 6"
1827
def initialize(self, a_bzrdir):
1828
"""Create a branch of this format in a_bzrdir."""
1829
utf8_files = [('last-revision', '0 null:\n'),
1830
('branch.conf', ''),
1833
return self._initialize_helper(a_bzrdir, utf8_files)
1835
def make_tags(self, branch):
1836
"""See bzrlib.branch.BranchFormat.make_tags()."""
1837
return BasicTags(branch)
1839
def supports_set_append_revisions_only(self):
1843
class BzrBranchFormat8(BranchFormatMetadir):
1844
"""Metadir format supporting storing locations of subtree branches."""
1846
def _branch_class(self):
1849
def get_format_string(self):
1850
"""See BranchFormat.get_format_string()."""
1851
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
1853
def get_format_description(self):
1854
"""See BranchFormat.get_format_description()."""
1855
return "Branch format 8"
1857
def initialize(self, a_bzrdir):
1858
"""Create a branch of this format in a_bzrdir."""
1859
utf8_files = [('last-revision', '0 null:\n'),
1860
('branch.conf', ''),
1864
return self._initialize_helper(a_bzrdir, utf8_files)
1867
super(BzrBranchFormat8, self).__init__()
1868
self._matchingbzrdir.repository_format = \
1869
RepositoryFormatKnitPack5RichRoot()
1871
def make_tags(self, branch):
1872
"""See bzrlib.branch.BranchFormat.make_tags()."""
1873
return BasicTags(branch)
1875
def supports_set_append_revisions_only(self):
1878
def supports_stacking(self):
1881
supports_reference_locations = True
1884
class BzrBranchFormat7(BzrBranchFormat8):
1885
"""Branch format with last-revision, tags, and a stacked location pointer.
1887
The stacked location pointer is passed down to the repository and requires
1888
a repository format with supports_external_lookups = True.
1890
This format was introduced in bzr 1.6.
1893
def initialize(self, a_bzrdir):
1894
"""Create a branch of this format in a_bzrdir."""
1895
utf8_files = [('last-revision', '0 null:\n'),
1896
('branch.conf', ''),
1899
return self._initialize_helper(a_bzrdir, utf8_files)
1901
def _branch_class(self):
1904
def get_format_string(self):
1905
"""See BranchFormat.get_format_string()."""
1906
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
1908
def get_format_description(self):
1909
"""See BranchFormat.get_format_description()."""
1910
return "Branch format 7"
1912
def supports_set_append_revisions_only(self):
1915
supports_reference_locations = False
1918
class BranchReferenceFormat(BranchFormat):
1919
"""Bzr branch reference format.
1921
Branch references are used in implementing checkouts, they
1922
act as an alias to the real branch which is at some other url.
1929
def get_format_string(self):
1930
"""See BranchFormat.get_format_string()."""
1931
return "Bazaar-NG Branch Reference Format 1\n"
1933
def get_format_description(self):
1934
"""See BranchFormat.get_format_description()."""
1935
return "Checkout reference format 1"
1937
def get_reference(self, a_bzrdir):
1938
"""See BranchFormat.get_reference()."""
1939
transport = a_bzrdir.get_branch_transport(None)
1940
return transport.get('location').read()
1942
def set_reference(self, a_bzrdir, to_branch):
1943
"""See BranchFormat.set_reference()."""
1944
transport = a_bzrdir.get_branch_transport(None)
1945
location = transport.put_bytes('location', to_branch.base)
1947
def initialize(self, a_bzrdir, target_branch=None):
1948
"""Create a branch of this format in a_bzrdir."""
1949
if target_branch is None:
1950
# this format does not implement branch itself, thus the implicit
1951
# creation contract must see it as uninitializable
1952
raise errors.UninitializableFormat(self)
1953
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1954
branch_transport = a_bzrdir.get_branch_transport(self)
1955
branch_transport.put_bytes('location',
1956
target_branch.bzrdir.root_transport.base)
1957
branch_transport.put_bytes('format', self.get_format_string())
1959
a_bzrdir, _found=True,
1960
possible_transports=[target_branch.bzrdir.root_transport])
1963
super(BranchReferenceFormat, self).__init__()
1964
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1965
self._matchingbzrdir.set_branch_format(self)
1967
def _make_reference_clone_function(format, a_branch):
1968
"""Create a clone() routine for a branch dynamically."""
1969
def clone(to_bzrdir, revision_id=None,
1970
repository_policy=None):
1971
"""See Branch.clone()."""
1972
return format.initialize(to_bzrdir, a_branch)
1973
# cannot obey revision_id limits when cloning a reference ...
1974
# FIXME RBC 20060210 either nuke revision_id for clone, or
1975
# emit some sort of warning/error to the caller ?!
1978
def open(self, a_bzrdir, _found=False, location=None,
1979
possible_transports=None, ignore_fallbacks=False):
1980
"""Return the branch that the branch reference in a_bzrdir points at.
1982
:param a_bzrdir: A BzrDir that contains a branch.
1983
:param _found: a private parameter, do not use it. It is used to
1984
indicate if format probing has already be done.
1985
:param ignore_fallbacks: when set, no fallback branches will be opened
1986
(if there are any). Default is to open fallbacks.
1987
:param location: The location of the referenced branch. If
1988
unspecified, this will be determined from the branch reference in
1990
:param possible_transports: An optional reusable transports list.
1993
format = BranchFormat.find_format(a_bzrdir)
1994
if format.__class__ != self.__class__:
1995
raise AssertionError("wrong format %r found for %r" %
1997
if location is None:
1998
location = self.get_reference(a_bzrdir)
1999
real_bzrdir = bzrdir.BzrDir.open(
2000
location, possible_transports=possible_transports)
2001
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2002
# this changes the behaviour of result.clone to create a new reference
2003
# rather than a copy of the content of the branch.
2004
# I did not use a proxy object because that needs much more extensive
2005
# testing, and we are only changing one behaviour at the moment.
2006
# If we decide to alter more behaviours - i.e. the implicit nickname
2007
# then this should be refactored to introduce a tested proxy branch
2008
# and a subclass of that for use in overriding clone() and ....
2010
result.clone = self._make_reference_clone_function(result)
2014
network_format_registry = registry.FormatRegistry()
2015
"""Registry of formats indexed by their network name.
2017
The network name for a branch format is an identifier that can be used when
2018
referring to formats with smart server operations. See
2019
BranchFormat.network_name() for more detail.
2023
# formats which have no format string are not discoverable
2024
# and not independently creatable, so are not registered.
2025
__format5 = BzrBranchFormat5()
2026
__format6 = BzrBranchFormat6()
2027
__format7 = BzrBranchFormat7()
2028
__format8 = BzrBranchFormat8()
2029
BranchFormat.register_format(__format5)
2030
BranchFormat.register_format(BranchReferenceFormat())
2031
BranchFormat.register_format(__format6)
2032
BranchFormat.register_format(__format7)
2033
BranchFormat.register_format(__format8)
2034
BranchFormat.set_default_format(__format6)
2035
_legacy_formats = [BzrBranchFormat4(),
2037
network_format_registry.register(
2038
_legacy_formats[0].network_name(), _legacy_formats[0].__class__)
2041
class BzrBranch(Branch):
2042
"""A branch stored in the actual filesystem.
2044
Note that it's "local" in the context of the filesystem; it doesn't
2045
really matter if it's on an nfs/smb/afs/coda/... share, as long as
2046
it's writable, and can be accessed via the normal filesystem API.
2048
:ivar _transport: Transport for file operations on this branch's
2049
control files, typically pointing to the .bzr/branch directory.
2050
:ivar repository: Repository for this branch.
2051
:ivar base: The url of the base directory for this branch; the one
2052
containing the .bzr directory.
2055
def __init__(self, _format=None,
2056
_control_files=None, a_bzrdir=None, _repository=None,
2057
ignore_fallbacks=False):
2058
"""Create new branch object at a particular location."""
2059
if a_bzrdir is None:
2060
raise ValueError('a_bzrdir must be supplied')
2062
self.bzrdir = a_bzrdir
2063
self._base = self.bzrdir.transport.clone('..').base
2064
# XXX: We should be able to just do
2065
# self.base = self.bzrdir.root_transport.base
2066
# but this does not quite work yet -- mbp 20080522
2067
self._format = _format
2068
if _control_files is None:
2069
raise ValueError('BzrBranch _control_files is None')
2070
self.control_files = _control_files
2071
self._transport = _control_files._transport
2072
self.repository = _repository
2073
Branch.__init__(self)
2076
return '%s(%r)' % (self.__class__.__name__, self.base)
2080
def _get_base(self):
2081
"""Returns the directory containing the control directory."""
2084
base = property(_get_base, doc="The URL for the root of this branch.")
2086
def _get_config(self):
2087
return TransportConfig(self._transport, 'branch.conf')
2089
def is_locked(self):
2090
return self.control_files.is_locked()
2092
def lock_write(self, token=None):
2093
# All-in-one needs to always unlock/lock.
2094
repo_control = getattr(self.repository, 'control_files', None)
2095
if self.control_files == repo_control or not self.is_locked():
2096
self.repository.lock_write()
2101
return self.control_files.lock_write(token=token)
2104
self.repository.unlock()
2107
def lock_read(self):
2108
# All-in-one needs to always unlock/lock.
2109
repo_control = getattr(self.repository, 'control_files', None)
2110
if self.control_files == repo_control or not self.is_locked():
2111
self.repository.lock_read()
2116
self.control_files.lock_read()
2119
self.repository.unlock()
2124
self.control_files.unlock()
2126
# All-in-one needs to always unlock/lock.
2127
repo_control = getattr(self.repository, 'control_files', None)
2128
if (self.control_files == repo_control or
2129
not self.control_files.is_locked()):
2130
self.repository.unlock()
2131
if not self.control_files.is_locked():
2132
# we just released the lock
2133
self._clear_cached_state()
2135
def peek_lock_mode(self):
2136
if self.control_files._lock_count == 0:
2139
return self.control_files._lock_mode
2141
def get_physical_lock_status(self):
2142
return self.control_files.get_physical_lock_status()
2145
def print_file(self, file, revision_id):
2146
"""See Branch.print_file."""
2147
return self.repository.print_file(file, revision_id)
2149
def _write_revision_history(self, history):
2150
"""Factored out of set_revision_history.
2152
This performs the actual writing to disk.
2153
It is intended to be called by BzrBranch5.set_revision_history."""
2154
self._transport.put_bytes(
2155
'revision-history', '\n'.join(history),
2156
mode=self.bzrdir._get_file_mode())
2159
def set_revision_history(self, rev_history):
2160
"""See Branch.set_revision_history."""
2161
if 'evil' in debug.debug_flags:
2162
mutter_callsite(3, "set_revision_history scales with history.")
2163
check_not_reserved_id = _mod_revision.check_not_reserved_id
2164
for rev_id in rev_history:
2165
check_not_reserved_id(rev_id)
2166
if Branch.hooks['post_change_branch_tip']:
2167
# Don't calculate the last_revision_info() if there are no hooks
2169
old_revno, old_revid = self.last_revision_info()
2170
if len(rev_history) == 0:
2171
revid = _mod_revision.NULL_REVISION
2173
revid = rev_history[-1]
2174
self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
2175
self._write_revision_history(rev_history)
2176
self._clear_cached_state()
2177
self._cache_revision_history(rev_history)
2178
for hook in Branch.hooks['set_rh']:
2179
hook(self, rev_history)
2180
if Branch.hooks['post_change_branch_tip']:
2181
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2183
def _synchronize_history(self, destination, revision_id):
2184
"""Synchronize last revision and revision history between branches.
2186
This version is most efficient when the destination is also a
2187
BzrBranch5, but works for BzrBranch6 as long as the revision
2188
history is the true lefthand parent history, and all of the revisions
2189
are in the destination's repository. If not, set_revision_history
2192
:param destination: The branch to copy the history into
2193
:param revision_id: The revision-id to truncate history at. May
2194
be None to copy complete history.
2196
if not isinstance(destination._format, BzrBranchFormat5):
2197
super(BzrBranch, self)._synchronize_history(
2198
destination, revision_id)
2200
if revision_id == _mod_revision.NULL_REVISION:
2203
new_history = self.revision_history()
2204
if revision_id is not None and new_history != []:
2206
new_history = new_history[:new_history.index(revision_id) + 1]
2208
rev = self.repository.get_revision(revision_id)
2209
new_history = rev.get_history(self.repository)[1:]
2210
destination.set_revision_history(new_history)
2213
def set_last_revision_info(self, revno, revision_id):
2214
"""Set the last revision of this branch.
2216
The caller is responsible for checking that the revno is correct
2217
for this revision id.
2219
It may be possible to set the branch last revision to an id not
2220
present in the repository. However, branches can also be
2221
configured to check constraints on history, in which case this may not
2224
revision_id = _mod_revision.ensure_null(revision_id)
2225
# this old format stores the full history, but this api doesn't
2226
# provide it, so we must generate, and might as well check it's
2228
history = self._lefthand_history(revision_id)
2229
if len(history) != revno:
2230
raise AssertionError('%d != %d' % (len(history), revno))
2231
self.set_revision_history(history)
2233
def _gen_revision_history(self):
2234
history = self._transport.get_bytes('revision-history').split('\n')
2235
if history[-1:] == ['']:
2236
# There shouldn't be a trailing newline, but just in case.
2241
def generate_revision_history(self, revision_id, last_rev=None,
2243
"""Create a new revision history that will finish with revision_id.
2245
:param revision_id: the new tip to use.
2246
:param last_rev: The previous last_revision. If not None, then this
2247
must be a ancestory of revision_id, or DivergedBranches is raised.
2248
:param other_branch: The other branch that DivergedBranches should
2249
raise with respect to.
2251
self.set_revision_history(self._lefthand_history(revision_id,
2252
last_rev, other_branch))
2254
def basis_tree(self):
2255
"""See Branch.basis_tree."""
2256
return self.repository.revision_tree(self.last_revision())
2258
def _get_parent_location(self):
2259
_locs = ['parent', 'pull', 'x-pull']
2262
return self._transport.get_bytes(l).strip('\n')
2263
except errors.NoSuchFile:
2267
def _basic_push(self, target, overwrite, stop_revision):
2268
"""Basic implementation of push without bound branches or hooks.
2270
Must be called with source read locked and target write locked.
2272
result = BranchPushResult()
2273
result.source_branch = self
2274
result.target_branch = target
2275
result.old_revno, result.old_revid = target.last_revision_info()
2276
self.update_references(target)
2277
if result.old_revid != self.last_revision():
2278
# We assume that during 'push' this repository is closer than
2280
graph = self.repository.get_graph(target.repository)
2281
target.update_revisions(self, stop_revision,
2282
overwrite=overwrite, graph=graph)
2283
if self._push_should_merge_tags():
2284
result.tag_conflicts = self.tags.merge_to(target.tags,
2286
result.new_revno, result.new_revid = target.last_revision_info()
2289
def get_stacked_on_url(self):
2290
raise errors.UnstackableBranchFormat(self._format, self.base)
2292
def set_push_location(self, location):
2293
"""See Branch.set_push_location."""
2294
self.get_config().set_user_option(
2295
'push_location', location,
2296
store=_mod_config.STORE_LOCATION_NORECURSE)
2298
def _set_parent_location(self, url):
2300
self._transport.delete('parent')
2302
self._transport.put_bytes('parent', url + '\n',
2303
mode=self.bzrdir._get_file_mode())
2306
class BzrBranch5(BzrBranch):
2307
"""A format 5 branch. This supports new features over plain branches.
2309
It has support for a master_branch which is the data for bound branches.
2312
def get_bound_location(self):
2314
return self._transport.get_bytes('bound')[:-1]
2315
except errors.NoSuchFile:
2319
def get_master_branch(self, possible_transports=None):
2320
"""Return the branch we are bound to.
2322
:return: Either a Branch, or None
2324
This could memoise the branch, but if thats done
2325
it must be revalidated on each new lock.
2326
So for now we just don't memoise it.
2327
# RBC 20060304 review this decision.
2329
bound_loc = self.get_bound_location()
2333
return Branch.open(bound_loc,
2334
possible_transports=possible_transports)
2335
except (errors.NotBranchError, errors.ConnectionError), e:
2336
raise errors.BoundBranchConnectionFailure(
2340
def set_bound_location(self, location):
2341
"""Set the target where this branch is bound to.
2343
:param location: URL to the target branch
2346
self._transport.put_bytes('bound', location+'\n',
2347
mode=self.bzrdir._get_file_mode())
2350
self._transport.delete('bound')
2351
except errors.NoSuchFile:
2356
def bind(self, other):
2357
"""Bind this branch to the branch other.
2359
This does not push or pull data between the branches, though it does
2360
check for divergence to raise an error when the branches are not
2361
either the same, or one a prefix of the other. That behaviour may not
2362
be useful, so that check may be removed in future.
2364
:param other: The branch to bind to
2367
# TODO: jam 20051230 Consider checking if the target is bound
2368
# It is debatable whether you should be able to bind to
2369
# a branch which is itself bound.
2370
# Committing is obviously forbidden,
2371
# but binding itself may not be.
2372
# Since we *have* to check at commit time, we don't
2373
# *need* to check here
2375
# we want to raise diverged if:
2376
# last_rev is not in the other_last_rev history, AND
2377
# other_last_rev is not in our history, and do it without pulling
2379
self.set_bound_location(other.base)
2383
"""If bound, unbind"""
2384
return self.set_bound_location(None)
2387
def update(self, possible_transports=None):
2388
"""Synchronise this branch with the master branch if any.
2390
:return: None or the last_revision that was pivoted out during the
2393
master = self.get_master_branch(possible_transports)
2394
if master is not None:
2395
old_tip = _mod_revision.ensure_null(self.last_revision())
2396
self.pull(master, overwrite=True)
2397
if self.repository.get_graph().is_ancestor(old_tip,
2398
_mod_revision.ensure_null(self.last_revision())):
2404
class BzrBranch8(BzrBranch5):
2405
"""A branch that stores tree-reference locations."""
2407
def _open_hook(self):
2408
if self._ignore_fallbacks:
2411
url = self.get_stacked_on_url()
2412
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2413
errors.UnstackableBranchFormat):
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())
1056
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:
2416
for hook in Branch.hooks['transform_fallback_location']:
2417
url = hook(self, url)
2419
hook_name = Branch.hooks.get_hook_name(hook)
2420
raise AssertionError(
2421
"'transform_fallback_location' hook %s returned "
2422
"None, not a URL." % hook_name)
2423
self._activate_fallback_location(url)
2425
def __init__(self, *args, **kwargs):
2426
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
2427
super(BzrBranch8, self).__init__(*args, **kwargs)
2428
self._last_revision_info_cache = None
2429
self._reference_info = None
2431
def _clear_cached_state(self):
2432
super(BzrBranch8, self)._clear_cached_state()
2433
self._last_revision_info_cache = None
2434
self._reference_info = None
2436
def _last_revision_info(self):
2437
revision_string = self._transport.get_bytes('last-revision')
2438
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2439
revision_id = cache_utf8.get_cached_utf8(revision_id)
2441
return revno, revision_id
2443
def _write_last_revision_info(self, revno, revision_id):
2444
"""Simply write out the revision id, with no checks.
2446
Use set_last_revision_info to perform this safely.
2448
Does not update the revision_history cache.
2449
Intended to be called by set_last_revision_info and
2450
_write_revision_history.
2452
revision_id = _mod_revision.ensure_null(revision_id)
2453
out_string = '%d %s\n' % (revno, revision_id)
2454
self._transport.put_bytes('last-revision', out_string,
2455
mode=self.bzrdir._get_file_mode())
2458
def set_last_revision_info(self, revno, revision_id):
2459
revision_id = _mod_revision.ensure_null(revision_id)
2460
old_revno, old_revid = self.last_revision_info()
2461
if self._get_append_revisions_only():
2462
self._check_history_violation(revision_id)
2463
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2464
self._write_last_revision_info(revno, revision_id)
2465
self._clear_cached_state()
2466
self._last_revision_info_cache = revno, revision_id
2467
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2469
def _synchronize_history(self, destination, revision_id):
2470
"""Synchronize last revision and revision history between branches.
2472
:see: Branch._synchronize_history
2474
# XXX: The base Branch has a fast implementation of this method based
2475
# on set_last_revision_info, but BzrBranch/BzrBranch5 have a slower one
2476
# that uses set_revision_history. This class inherits from BzrBranch5,
2477
# but wants the fast implementation, so it calls
2478
# Branch._synchronize_history directly.
2479
Branch._synchronize_history(self, destination, revision_id)
2481
def _check_history_violation(self, revision_id):
2482
last_revision = _mod_revision.ensure_null(self.last_revision())
2483
if _mod_revision.is_null(last_revision):
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
2485
if last_revision not in self._lefthand_history(revision_id):
2486
raise errors.AppendRevisionsOnlyViolation(self.base)
2488
def _gen_revision_history(self):
2489
"""Generate the revision history from last revision
2491
last_revno, last_revision = self.last_revision_info()
2492
self._extend_partial_history(stop_index=last_revno-1)
2493
return list(reversed(self._partial_revision_history_cache))
2495
def _write_revision_history(self, history):
2496
"""Factored out of set_revision_history.
2498
This performs the actual writing to disk, with format-specific checks.
2499
It is intended to be called by BzrBranch5.set_revision_history.
2501
if len(history) == 0:
2502
last_revision = 'null:'
2504
if history != self._lefthand_history(history[-1]):
2505
raise errors.NotLefthandHistory(history)
2506
last_revision = history[-1]
2507
if self._get_append_revisions_only():
2508
self._check_history_violation(last_revision)
2509
self._write_last_revision_info(len(history), last_revision)
2512
def _set_parent_location(self, url):
2513
"""Set the parent branch"""
2514
self._set_config_location('parent_location', url, make_relative=True)
2517
def _get_parent_location(self):
2518
"""Set the parent branch"""
2519
return self._get_config_location('parent_location')
2522
def _set_all_reference_info(self, info_dict):
2523
"""Replace all reference info stored in a branch.
2525
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
2528
writer = rio.RioWriter(s)
2529
for key, (tree_path, branch_location) in info_dict.iteritems():
2530
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
2531
branch_location=branch_location)
2532
writer.write_stanza(stanza)
2533
self._transport.put_bytes('references', s.getvalue())
2534
self._reference_info = info_dict
2537
def _get_all_reference_info(self):
2538
"""Return all the reference info stored in a branch.
2540
:return: A dict of {file_id: (tree_path, branch_location)}
2542
if self._reference_info is not None:
2543
return self._reference_info
2544
rio_file = self._transport.get('references')
1259
f = AtomicFile(self.controlfilename('pending-merges'))
2546
stanzas = rio.read_stanzas(rio_file)
2547
info_dict = dict((s['file_id'], (s['tree_path'],
2548
s['branch_location'])) for s in stanzas)
1271
class ScratchBranch(Branch):
1272
"""Special test class: a branch that cleans up after itself.
1274
>>> b = ScratchBranch()
2551
self._reference_info = info_dict
2554
def set_reference_info(self, file_id, tree_path, branch_location):
2555
"""Set the branch location to use for a tree reference.
2557
:param file_id: The file-id of the tree reference.
2558
:param tree_path: The path of the tree reference in the tree.
2559
:param branch_location: The location of the branch to retrieve tree
2562
info_dict = self._get_all_reference_info()
2563
info_dict[file_id] = (tree_path, branch_location)
2564
if None in (tree_path, branch_location):
2565
if tree_path is not None:
2566
raise ValueError('tree_path must be None when branch_location'
2568
if branch_location is not None:
2569
raise ValueError('branch_location must be None when tree_path'
2571
del info_dict[file_id]
2572
self._set_all_reference_info(info_dict)
2574
def get_reference_info(self, file_id):
2575
"""Get the tree_path and branch_location for a tree reference.
2577
:return: a tuple of (tree_path, branch_location)
2579
return self._get_all_reference_info().get(file_id, (None, None))
2581
def reference_parent(self, file_id, path, possible_transports=None):
2582
"""Return the parent branch for a tree-reference file_id.
2584
:param file_id: The file_id of the tree reference
2585
:param path: The path of the file_id in the tree
2586
:return: A branch associated with the file_id
2588
branch_location = self.get_reference_info(file_id)[1]
2589
if branch_location is None:
2590
return Branch.reference_parent(self, file_id, path,
2591
possible_transports)
2592
branch_location = urlutils.join(self.base, branch_location)
2593
return Branch.open(branch_location,
2594
possible_transports=possible_transports)
2596
def set_push_location(self, location):
2597
"""See Branch.set_push_location."""
2598
self._set_config_location('push_location', location)
2600
def set_bound_location(self, location):
2601
"""See Branch.set_push_location."""
2603
config = self.get_config()
2604
if location is None:
2605
if config.get_user_option('bound') != 'True':
2608
config.set_user_option('bound', 'False', warn_masked=True)
2611
self._set_config_location('bound_location', location,
2613
config.set_user_option('bound', 'True', warn_masked=True)
2616
def _get_bound_location(self, bound):
2617
"""Return the bound location in the config file.
2619
Return None if the bound parameter does not match"""
2620
config = self.get_config()
2621
config_bound = (config.get_user_option('bound') == 'True')
2622
if config_bound != bound:
2624
return self._get_config_location('bound_location', config=config)
2626
def get_bound_location(self):
2627
"""See Branch.set_push_location."""
2628
return self._get_bound_location(True)
2630
def get_old_bound_location(self):
2631
"""See Branch.get_old_bound_location"""
2632
return self._get_bound_location(False)
2634
def get_stacked_on_url(self):
2635
# you can always ask for the URL; but you might not be able to use it
2636
# if the repo can't support stacking.
2637
## self._check_stackable_repo()
2638
stacked_url = self._get_config_location('stacked_on_location')
2639
if stacked_url is None:
2640
raise errors.NotStacked(self)
2643
def _get_append_revisions_only(self):
2644
value = self.get_config().get_user_option('append_revisions_only')
2645
return value == 'True'
2648
def generate_revision_history(self, revision_id, last_rev=None,
2650
"""See BzrBranch5.generate_revision_history"""
2651
history = self._lefthand_history(revision_id, last_rev, other_branch)
2652
revno = len(history)
2653
self.set_last_revision_info(revno, revision_id)
2656
def get_rev_id(self, revno, history=None):
2657
"""Find the revision id of the specified revno."""
2659
return _mod_revision.NULL_REVISION
2661
last_revno, last_revision_id = self.last_revision_info()
2662
if revno <= 0 or revno > last_revno:
2663
raise errors.NoSuchRevision(self, revno)
2665
if history is not None:
2666
return history[revno - 1]
2668
index = last_revno - revno
2669
if len(self._partial_revision_history_cache) <= index:
2670
self._extend_partial_history(stop_index=index)
2671
if len(self._partial_revision_history_cache) > index:
2672
return self._partial_revision_history_cache[index]
2674
raise errors.NoSuchRevision(self, revno)
2677
def revision_id_to_revno(self, revision_id):
2678
"""Given a revision id, return its revno"""
2679
if _mod_revision.is_null(revision_id):
2682
index = self._partial_revision_history_cache.index(revision_id)
2684
self._extend_partial_history(stop_revision=revision_id)
2685
index = len(self._partial_revision_history_cache) - 1
2686
if self._partial_revision_history_cache[index] != revision_id:
2687
raise errors.NoSuchRevision(self, revision_id)
2688
return self.revno() - index
2691
class BzrBranch7(BzrBranch8):
2692
"""A branch with support for a fallback repository."""
2694
def set_reference_info(self, file_id, tree_path, branch_location):
2695
Branch.set_reference_info(self, file_id, tree_path, branch_location)
2697
def get_reference_info(self, file_id):
2698
Branch.get_reference_info(self, file_id)
2700
def reference_parent(self, file_id, path, possible_transports=None):
2701
return Branch.reference_parent(self, file_id, path,
2702
possible_transports)
2705
class BzrBranch6(BzrBranch7):
2706
"""See BzrBranchFormat6 for the capabilities of this branch.
2708
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
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)
2712
def get_stacked_on_url(self):
2713
raise errors.UnstackableBranchFormat(self._format, self.base)
1340
2716
######################################################################
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')
2717
# results of operations
2720
class _Result(object):
2722
def _show_tag_conficts(self, to_file):
2723
if not getattr(self, 'tag_conflicts', None):
2725
to_file.write('Conflicting tags:\n')
2726
for name, value1, value2 in self.tag_conflicts:
2727
to_file.write(' %s\n' % (name, ))
2730
class PullResult(_Result):
2731
"""Result of a Branch.pull operation.
2733
:ivar old_revno: Revision number before pull.
2734
:ivar new_revno: Revision number after pull.
2735
:ivar old_revid: Tip revision id before pull.
2736
:ivar new_revid: Tip revision id after pull.
2737
:ivar source_branch: Source (local) branch object. (read locked)
2738
:ivar master_branch: Master branch of the target, or the target if no
2740
:ivar local_branch: target branch if there is a Master, else None
2741
:ivar target_branch: Target/destination branch object. (write locked)
2742
:ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2746
# DEPRECATED: pull used to return the change in revno
2747
return self.new_revno - self.old_revno
2749
def report(self, to_file):
2751
if self.old_revid == self.new_revid:
2752
to_file.write('No revisions to pull.\n')
2754
to_file.write('Now on revision %d.\n' % self.new_revno)
2755
self._show_tag_conficts(to_file)
2758
class BranchPushResult(_Result):
2759
"""Result of a Branch.push operation.
2761
:ivar old_revno: Revision number (eg 10) of the target before push.
2762
:ivar new_revno: Revision number (eg 12) of the target after push.
2763
:ivar old_revid: Tip revision id (eg joe@foo.com-1234234-aoeua34) of target
2765
:ivar new_revid: Tip revision id (eg joe@foo.com-5676566-boa234a) of target
2767
:ivar source_branch: Source branch object that the push was from. This is
2768
read locked, and generally is a local (and thus low latency) branch.
2769
:ivar master_branch: If target is a bound branch, the master branch of
2770
target, or target itself. Always write locked.
2771
:ivar target_branch: The direct Branch where data is being sent (write
2773
:ivar local_branch: If the target is a bound branch this will be the
2774
target, otherwise it will be None.
2778
# DEPRECATED: push used to return the change in revno
2779
return self.new_revno - self.old_revno
2781
def report(self, to_file):
2782
"""Write a human-readable description of the result."""
2783
if self.old_revid == self.new_revid:
2784
note('No new revisions to push.')
2786
note('Pushed up to revision %d.' % self.new_revno)
2787
self._show_tag_conficts(to_file)
2790
class BranchCheckResult(object):
2791
"""Results of checking branch consistency.
2796
def __init__(self, branch):
2797
self.branch = branch
2798
self.ghosts_in_mainline = False
2800
def report_results(self, verbose):
2801
"""Report the check results via trace.note.
2803
:param verbose: Requests more detailed display of what was checked,
2806
note('checked branch %s format %s',
2808
self.branch._format)
2809
if self.ghosts_in_mainline:
2810
note('branch contains ghosts in mainline')
2813
class Converter5to6(object):
2814
"""Perform an in-place upgrade of format 5 to format 6"""
2816
def convert(self, branch):
2817
# Data for 5 and 6 can peacefully coexist.
2818
format = BzrBranchFormat6()
2819
new_branch = format.open(branch.bzrdir, _found=True)
2821
# Copy source data into target
2822
new_branch._write_last_revision_info(*branch.last_revision_info())
2823
new_branch.set_parent(branch.get_parent())
2824
new_branch.set_bound_location(branch.get_bound_location())
2825
new_branch.set_push_location(branch.get_push_location())
2827
# New branch has no tags by default
2828
new_branch.tags._set_tag_dict({})
2830
# Copying done; now update target format
2831
new_branch._transport.put_bytes('format',
2832
format.get_format_string(),
2833
mode=new_branch.bzrdir._get_file_mode())
2835
# Clean up old files
2836
new_branch._transport.delete('revision-history')
2838
branch.set_parent(None)
2839
except errors.NoSuchFile:
2841
branch.set_bound_location(None)
2844
class Converter6to7(object):
2845
"""Perform an in-place upgrade of format 6 to format 7"""
2847
def convert(self, branch):
2848
format = BzrBranchFormat7()
2849
branch._set_config_location('stacked_on_location', '')
2850
# update target format
2851
branch._transport.put_bytes('format', format.get_format_string())
2854
class Converter7to8(object):
2855
"""Perform an in-place upgrade of format 6 to format 7"""
2857
def convert(self, branch):
2858
format = BzrBranchFormat8()
2859
branch._transport.put_bytes('references', '')
2860
# update target format
2861
branch._transport.put_bytes('format', format.get_format_string())
2864
def _run_with_write_locked_target(target, callable, *args, **kwargs):
2865
"""Run ``callable(*args, **kwargs)``, write-locking target for the
2868
_run_with_write_locked_target will attempt to release the lock it acquires.
2870
If an exception is raised by callable, then that exception *will* be
2871
propagated, even if the unlock attempt raises its own error. Thus
2872
_run_with_write_locked_target should be preferred to simply doing::
2876
return callable(*args, **kwargs)
2881
# This is very similar to bzrlib.decorators.needs_write_lock. Perhaps they
2882
# should share code?
2885
result = callable(*args, **kwargs)
2887
exc_info = sys.exc_info()
2891
raise exc_info[0], exc_info[1], exc_info[2]
2897
class InterBranch(InterObject):
2898
"""This class represents operations taking place between two branches.
2900
Its instances have methods like pull() and push() and contain
2901
references to the source and target repositories these operations
2902
can be carried out on.
2906
"""The available optimised InterBranch types."""
2909
def _get_branch_formats_to_test():
2910
"""Return a tuple with the Branch formats to use when testing."""
2911
raise NotImplementedError(InterBranch._get_branch_formats_to_test)
2913
def pull(self, overwrite=False, stop_revision=None,
2914
possible_transports=None, local=False):
2915
"""Mirror source into target branch.
2917
The target branch is considered to be 'local', having low latency.
2919
:returns: PullResult instance
2921
raise NotImplementedError(self.pull)
2923
def update_revisions(self, stop_revision=None, overwrite=False,
2925
"""Pull in new perfect-fit revisions.
2927
:param stop_revision: Updated until the given revision
2928
:param overwrite: Always set the branch pointer, rather than checking
2929
to see if it is a proper descendant.
2930
:param graph: A Graph object that can be used to query history
2931
information. This can be None.
2934
raise NotImplementedError(self.update_revisions)
2936
def push(self, overwrite=False, stop_revision=None,
2937
_override_hook_source_branch=None):
2938
"""Mirror the source branch into the target branch.
2940
The source branch is considered to be 'local', having low latency.
2942
raise NotImplementedError(self.push)
2945
class GenericInterBranch(InterBranch):
2946
"""InterBranch implementation that uses public Branch functions.
2950
def _get_branch_formats_to_test():
2951
return BranchFormat._default_format, BranchFormat._default_format
2953
def update_revisions(self, stop_revision=None, overwrite=False,
2955
"""See InterBranch.update_revisions()."""
2956
self.source.lock_read()
2958
other_revno, other_last_revision = self.source.last_revision_info()
2959
stop_revno = None # unknown
2960
if stop_revision is None:
2961
stop_revision = other_last_revision
2962
if _mod_revision.is_null(stop_revision):
2963
# if there are no commits, we're done.
2965
stop_revno = other_revno
2967
# what's the current last revision, before we fetch [and change it
2969
last_rev = _mod_revision.ensure_null(self.target.last_revision())
2970
# we fetch here so that we don't process data twice in the common
2971
# case of having something to pull, and so that the check for
2972
# already merged can operate on the just fetched graph, which will
2973
# be cached in memory.
2974
self.target.fetch(self.source, stop_revision)
2975
# Check to see if one is an ancestor of the other
2978
graph = self.target.repository.get_graph()
2979
if self.target._check_if_descendant_or_diverged(
2980
stop_revision, last_rev, graph, self.source):
2981
# stop_revision is a descendant of last_rev, but we aren't
2982
# overwriting, so we're done.
2984
if stop_revno is None:
2986
graph = self.target.repository.get_graph()
2987
this_revno, this_last_revision = \
2988
self.target.last_revision_info()
2989
stop_revno = graph.find_distance_to_null(stop_revision,
2990
[(other_last_revision, other_revno),
2991
(this_last_revision, this_revno)])
2992
self.target.set_last_revision_info(stop_revno, stop_revision)
2994
self.source.unlock()
2996
def pull(self, overwrite=False, stop_revision=None,
2997
possible_transports=None, _hook_master=None, run_hooks=True,
2998
_override_hook_target=None, local=False):
3001
:param _hook_master: Private parameter - set the branch to
3002
be supplied as the master to pull hooks.
3003
:param run_hooks: Private parameter - if false, this branch
3004
is being called because it's the master of the primary branch,
3005
so it should not run its hooks.
3006
:param _override_hook_target: Private parameter - set the branch to be
3007
supplied as the target_branch to pull hooks.
3008
:param local: Only update the local branch, and not the bound branch.
3010
# This type of branch can't be bound.
3012
raise errors.LocalRequiresBoundBranch()
3013
result = PullResult()
3014
result.source_branch = self.source
3015
if _override_hook_target is None:
3016
result.target_branch = self.target
3018
result.target_branch = _override_hook_target
3019
self.source.lock_read()
3021
# We assume that during 'pull' the target repository is closer than
3023
self.source.update_references(self.target)
3024
graph = self.target.repository.get_graph(self.source.repository)
3025
# TODO: Branch formats should have a flag that indicates
3026
# that revno's are expensive, and pull() should honor that flag.
3028
result.old_revno, result.old_revid = \
3029
self.target.last_revision_info()
3030
self.target.update_revisions(self.source, stop_revision,
3031
overwrite=overwrite, graph=graph)
3032
# TODO: The old revid should be specified when merging tags,
3033
# so a tags implementation that versions tags can only
3034
# pull in the most recent changes. -- JRV20090506
3035
result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
3037
result.new_revno, result.new_revid = self.target.last_revision_info()
3039
result.master_branch = _hook_master
3040
result.local_branch = result.target_branch
3042
result.master_branch = result.target_branch
3043
result.local_branch = None
3045
for hook in Branch.hooks['post_pull']:
3048
self.source.unlock()
3051
def push(self, overwrite=False, stop_revision=None,
3052
_override_hook_source_branch=None):
3053
"""See InterBranch.push.
3055
This is the basic concrete implementation of push()
3057
:param _override_hook_source_branch: If specified, run
3058
the hooks passing this Branch as the source, rather than self.
3059
This is for use of RemoteBranch, where push is delegated to the
3060
underlying vfs-based Branch.
3062
# TODO: Public option to disable running hooks - should be trivial but
3064
self.source.lock_read()
3066
return _run_with_write_locked_target(
3067
self.target, self._push_with_bound_branches, overwrite,
3069
_override_hook_source_branch=_override_hook_source_branch)
3071
self.source.unlock()
3073
def _push_with_bound_branches(self, overwrite, stop_revision,
3074
_override_hook_source_branch=None):
3075
"""Push from source into target, and into target's master if any.
3078
if _override_hook_source_branch:
3079
result.source_branch = _override_hook_source_branch
3080
for hook in Branch.hooks['post_push']:
3083
bound_location = self.target.get_bound_location()
3084
if bound_location and self.target.base != bound_location:
3085
# there is a master branch.
3087
# XXX: Why the second check? Is it even supported for a branch to
3088
# be bound to itself? -- mbp 20070507
3089
master_branch = self.target.get_master_branch()
3090
master_branch.lock_write()
3092
# push into the master from the source branch.
3093
self.source._basic_push(master_branch, overwrite, stop_revision)
3094
# and push into the target branch from the source. Note that we
3095
# push from the source branch again, because its considered the
3096
# highest bandwidth repository.
3097
result = self.source._basic_push(self.target, overwrite,
3099
result.master_branch = master_branch
3100
result.local_branch = self.target
3104
master_branch.unlock()
3107
result = self.source._basic_push(self.target, overwrite,
3109
# TODO: Why set master_branch and local_branch if there's no
3110
# binding? Maybe cleaner to just leave them unset? -- mbp
3112
result.master_branch = self.target
3113
result.local_branch = None
3118
def is_compatible(self, source, target):
3119
# GenericBranch uses the public API, so always compatible
3123
class InterToBranch5(GenericInterBranch):
3126
def _get_branch_formats_to_test():
3127
return BranchFormat._default_format, BzrBranchFormat5()
3129
def pull(self, overwrite=False, stop_revision=None,
3130
possible_transports=None, run_hooks=True,
3131
_override_hook_target=None, local=False):
3132
"""Pull from source into self, updating my master if any.
3134
:param run_hooks: Private parameter - if false, this branch
3135
is being called because it's the master of the primary branch,
3136
so it should not run its hooks.
3138
bound_location = self.target.get_bound_location()
3139
if local and not bound_location:
3140
raise errors.LocalRequiresBoundBranch()
3141
master_branch = None
3142
if not local and bound_location and self.source.base != bound_location:
3143
# not pulling from master, so we need to update master.
3144
master_branch = self.target.get_master_branch(possible_transports)
3145
master_branch.lock_write()
3148
# pull from source into master.
3149
master_branch.pull(self.source, overwrite, stop_revision,
3151
return super(InterToBranch5, self).pull(overwrite,
3152
stop_revision, _hook_master=master_branch,
3153
run_hooks=run_hooks,
3154
_override_hook_target=_override_hook_target)
3157
master_branch.unlock()
3160
InterBranch.register_optimiser(GenericInterBranch)
3161
InterBranch.register_optimiser(InterToBranch5)