84
80
"""Branch holding a history of revisions.
87
Base directory of the branch.
83
Base directory/url of the branch.
85
hooks: An instance of BranchHooks.
87
# this is really an instance variable - FIXME move it there
92
def __init__(self, base, init=False, find_root=True, lock_mode='w'):
93
"""Create new branch object at a particular location.
95
base -- Base directory for the branch.
97
init -- If True, create new control files in a previously
98
unversioned directory. If False, the branch must already
101
find_root -- If true and init is false, find the root of the
102
existing branch containing base.
104
In the test suite, creation of new trees is tested using the
105
`ScratchBranch` class.
108
self.base = os.path.realpath(base)
111
self.base = find_branch_root(base)
91
def __init__(self, *ignored, **ignored_too):
92
raise NotImplementedError('The Branch class is abstract')
95
"""Break a lock if one is present from another instance.
97
Uses the ui factory to ask for confirmation if the lock may be from
100
This will probe the repository for its lock as well.
102
self.control_files.break_lock()
103
self.repository.break_lock()
104
master = self.get_master_branch()
105
if master is not None:
109
@deprecated_method(zero_eight)
110
def open_downlevel(base):
111
"""Open a branch which may be of an old format."""
112
return Branch.open(base, _unsupported=True)
115
def open(base, _unsupported=False):
116
"""Open the branch rooted at base.
118
For instance, if the branch is at URL/.bzr/branch,
119
Branch.open(URL) -> a Branch instance.
121
control = bzrdir.BzrDir.open(base, _unsupported)
122
return control.open_branch(_unsupported)
125
def open_containing(url):
126
"""Open an existing branch which contains url.
128
This probes for a branch at url, and searches upwards from there.
130
Basically we keep looking up until we find the control directory or
131
run into the root. If there isn't one, raises NotBranchError.
132
If there is one and it is either an unrecognised format or an unsupported
133
format, UnknownFormatError or UnsupportedFormatError are raised.
134
If there is one, it is returned, along with the unused portion of url.
136
control, relpath = bzrdir.BzrDir.open_containing(url)
137
return control.open_branch(), relpath
140
@deprecated_function(zero_eight)
141
def initialize(base):
142
"""Create a new working tree and branch, rooted at 'base' (url)
144
NOTE: This will soon be deprecated in favour of creation
147
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
149
@deprecated_function(zero_eight)
150
def setup_caching(self, cache_root):
151
"""Subclasses that care about caching should override this, and set
152
up cached stores located under cache_root.
154
NOTE: This is unused.
158
def get_config(self):
159
return BranchConfig(self)
162
return self.get_config().get_nickname()
164
def _set_nick(self, nick):
165
self.get_config().set_user_option('nickname', nick)
167
nick = property(_get_nick, _set_nick)
170
raise NotImplementedError(self.is_locked)
172
def lock_write(self):
173
raise NotImplementedError(self.lock_write)
176
raise NotImplementedError(self.lock_read)
179
raise NotImplementedError(self.unlock)
181
def peek_lock_mode(self):
182
"""Return lock mode for the Branch: 'r', 'w' or None"""
183
raise NotImplementedError(self.peek_lock_mode)
185
def get_physical_lock_status(self):
186
raise NotImplementedError(self.get_physical_lock_status)
188
def abspath(self, name):
189
"""Return absolute filename for something in the branch
191
XXX: Robert Collins 20051017 what is this used for? why is it a branch
192
method and not a tree method.
194
raise NotImplementedError(self.abspath)
196
def bind(self, other):
197
"""Bind the local branch the other branch.
199
:param other: The branch to bind to
202
raise errors.UpgradeRequired(self.base)
205
def fetch(self, from_branch, last_revision=None, pb=None):
206
"""Copy revisions from from_branch into this branch.
208
:param from_branch: Where to copy from.
209
:param last_revision: What revision to stop at (None for at the end
211
:param pb: An optional progress bar to use.
213
Returns the copied revision count and the failed revisions in a tuple:
216
if self.base == from_branch.base:
219
nested_pb = ui.ui_factory.nested_progress_bar()
113
self.base = os.path.realpath(base)
114
if not isdir(self.controlfilename('.')):
115
bailout("not a bzr branch: %s" % quotefn(base),
116
['use "bzr init" to initialize a new working tree',
117
'current bzr can only operate from top-of-tree'])
121
self.text_store = ImmutableStore(self.controlfilename('text-store'))
122
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
123
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
127
return '%s(%r)' % (self.__class__.__name__, self.base)
134
def lock(self, mode='w'):
135
"""Lock the on-disk branch, excluding other processes."""
224
from_branch.lock_read()
141
om = os.O_WRONLY | os.O_CREAT
146
raise BzrError("invalid locking mode %r" % mode)
149
lockfile = os.open(self.controlfilename('branch-lock'), om)
151
if e.errno == errno.ENOENT:
152
# might not exist on branches from <0.0.4
153
self.controlfile('branch-lock', 'w').close()
154
lockfile = os.open(self.controlfilename('branch-lock'), om)
226
if last_revision is None:
227
pb.update('get source history')
228
from_history = from_branch.revision_history()
230
last_revision = from_history[-1]
158
fcntl.lockf(lockfile, lm)
160
fcntl.lockf(lockfile, fcntl.LOCK_UN)
162
self._lockmode = None
164
self._lockmode = mode
166
warning("please write a locking method for platform %r" % sys.platform)
168
self._lockmode = None
170
self._lockmode = mode
173
def _need_readlock(self):
174
if self._lockmode not in ['r', 'w']:
175
raise BzrError('need read lock on branch, only have %r' % self._lockmode)
177
def _need_writelock(self):
178
if self._lockmode not in ['w']:
179
raise BzrError('need write lock on branch, only have %r' % self._lockmode)
182
def abspath(self, name):
183
"""Return absolute filename for something in the branch"""
184
return os.path.join(self.base, name)
187
def relpath(self, path):
188
"""Return path relative to this branch of something inside it.
190
Raises an error if path is not in this branch."""
191
rp = os.path.realpath(path)
193
if not rp.startswith(self.base):
194
bailout("path %r is not within branch %r" % (rp, self.base))
195
rp = rp[len(self.base):]
196
rp = rp.lstrip(os.sep)
200
def controlfilename(self, file_or_path):
201
"""Return location relative to branch."""
202
if isinstance(file_or_path, types.StringTypes):
203
file_or_path = [file_or_path]
204
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
207
def controlfile(self, file_or_path, mode='r'):
208
"""Open a control file for this branch.
210
There are two classes of file in the control directory: text
211
and binary. binary files are untranslated byte streams. Text
212
control files are stored with Unix newlines and in UTF-8, even
213
if the platform or locale defaults are different.
215
Controlfiles should almost never be opened in write mode but
216
rather should be atomically copied and replaced using atomicfile.
219
fn = self.controlfilename(file_or_path)
221
if mode == 'rb' or mode == 'wb':
222
return file(fn, mode)
223
elif mode == 'r' or mode == 'w':
224
# open in binary mode anyhow so there's no newline translation;
225
# codecs uses line buffering by default; don't want that.
227
return codecs.open(fn, mode + 'b', 'utf-8',
230
raise BzrError("invalid controlfile mode %r" % mode)
234
def _make_control(self):
235
os.mkdir(self.controlfilename([]))
236
self.controlfile('README', 'w').write(
237
"This is a Bazaar-NG control directory.\n"
238
"Do not change any files in this directory.")
239
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
240
for d in ('text-store', 'inventory-store', 'revision-store'):
241
os.mkdir(self.controlfilename(d))
242
for f in ('revision-history', 'merged-patches',
243
'pending-merged-patches', 'branch-name',
245
self.controlfile(f, 'w').write('')
246
mutter('created control directory in ' + self.base)
247
Inventory().write_xml(self.controlfile('inventory','w'))
250
def _check_format(self):
251
"""Check this branch format is supported.
253
The current tool only supports the current unstable format.
255
In the future, we might need different in-memory Branch
256
classes to support downlevel branches. But not yet.
258
# This ignores newlines so that we can open branches created
259
# on Windows from Linux and so on. I think it might be better
260
# to always make all internal files in unix format.
261
fmt = self.controlfile('branch-format', 'r').read()
262
fmt.replace('\r\n', '')
263
if fmt != BZR_BRANCH_FORMAT:
264
bailout('sorry, branch format %r not supported' % fmt,
265
['use a different bzr version',
266
'or remove the .bzr directory and "bzr init" again'])
269
def read_working_inventory(self):
270
"""Read the working inventory."""
271
self._need_readlock()
273
# ElementTree does its own conversion from UTF-8, so open in
275
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
276
mutter("loaded inventory of %d items in %f"
277
% (len(inv), time.time() - before))
281
def _write_inventory(self, inv):
282
"""Update the working inventory.
284
That is to say, the inventory describing changes underway, that
285
will be committed to the next revision.
287
self._need_writelock()
288
## TODO: factor out to atomicfile? is rename safe on windows?
289
## TODO: Maybe some kind of clean/dirty marker on inventory?
290
tmpfname = self.controlfilename('inventory.tmp')
291
tmpf = file(tmpfname, 'wb')
294
inv_fname = self.controlfilename('inventory')
295
if sys.platform == 'win32':
297
os.rename(tmpfname, inv_fname)
298
mutter('wrote working inventory')
301
inventory = property(read_working_inventory, _write_inventory, None,
302
"""Inventory for the working copy.""")
305
def add(self, files, verbose=False, ids=None):
306
"""Make files versioned.
308
Note that the command line normally calls smart_add instead.
310
This puts the files in the Added state, so that they will be
311
recorded by the next commit.
313
TODO: Perhaps have an option to add the ids even if the files do
316
TODO: Perhaps return the ids of the files? But then again it
317
is easy to retrieve them if they're needed.
319
TODO: Option to specify file id.
321
TODO: Adding a directory should optionally recurse down and
322
add all non-ignored children. Perhaps do that in a
325
self._need_writelock()
327
# TODO: Re-adding a file that is removed in the working copy
328
# should probably put it back with the previous ID.
329
if isinstance(files, types.StringTypes):
330
assert(ids is None or isinstance(ids, types.StringTypes))
336
ids = [None] * len(files)
338
assert(len(ids) == len(files))
340
inv = self.read_working_inventory()
341
for f,file_id in zip(files, ids):
342
if is_control_file(f):
343
bailout("cannot add control file %s" % quotefn(f))
348
bailout("cannot add top-level %r" % f)
350
fullpath = os.path.normpath(self.abspath(f))
353
kind = file_kind(fullpath)
355
# maybe something better?
356
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
358
if kind != 'file' and kind != 'directory':
359
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
362
file_id = gen_file_id(f)
363
inv.add_path(f, kind=kind, file_id=file_id)
366
show_status('A', kind, quotefn(f))
368
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
370
self._write_inventory(inv)
373
def print_file(self, file, revno):
232
# no history in the source branch
233
last_revision = _mod_revision.NULL_REVISION
234
return self.repository.fetch(from_branch.repository,
235
revision_id=last_revision,
238
if nested_pb is not None:
242
def get_bound_location(self):
243
"""Return the URL of the branch we are bound to.
245
Older format branches cannot bind, please be sure to use a metadir
250
def get_commit_builder(self, parents, config=None, timestamp=None,
251
timezone=None, committer=None, revprops=None,
253
"""Obtain a CommitBuilder for this branch.
255
:param parents: Revision ids of the parents of the new revision.
256
:param config: Optional configuration to use.
257
:param timestamp: Optional timestamp recorded for commit.
258
:param timezone: Optional timezone for timestamp.
259
:param committer: Optional committer to set for commit.
260
:param revprops: Optional dictionary of revision properties.
261
:param revision_id: Optional revision id.
265
config = self.get_config()
267
return self.repository.get_commit_builder(self, parents, config,
268
timestamp, timezone, committer, revprops, revision_id)
270
def get_master_branch(self):
271
"""Return the branch we are bound to.
273
:return: Either a Branch, or None
277
def get_revision_delta(self, revno):
278
"""Return the delta for one revision.
280
The delta is relative to its mainline predecessor, or the
281
empty tree for revision 1.
283
assert isinstance(revno, int)
284
rh = self.revision_history()
285
if not (1 <= revno <= len(rh)):
286
raise InvalidRevisionNumber(revno)
287
return self.repository.get_revision_delta(rh[revno-1])
289
def get_root_id(self):
290
"""Return the id of this branches root"""
291
raise NotImplementedError(self.get_root_id)
293
def print_file(self, file, revision_id):
374
294
"""Print `file` to stdout."""
375
self._need_readlock()
376
tree = self.revision_tree(self.lookup_revision(revno))
377
# use inventory as it was in that revision
378
file_id = tree.inventory.path2id(file)
380
bailout("%r is not present in revision %d" % (file, revno))
381
tree.print_file(file_id)
384
def remove(self, files, verbose=False):
385
"""Mark nominated files for removal from the inventory.
387
This does not remove their text. This does not run on
389
TODO: Refuse to remove modified files unless --force is given?
391
TODO: Do something useful with directories.
393
TODO: Should this remove the text or not? Tough call; not
394
removing may be useful and the user can just use use rm, and
395
is the opposite of add. Removing it is consistent with most
396
other tools. Maybe an option.
398
## TODO: Normalize names
399
## TODO: Remove nested loops; better scalability
400
self._need_writelock()
402
if isinstance(files, types.StringTypes):
405
tree = self.working_tree()
408
# do this before any modifications
412
bailout("cannot remove unversioned file %s" % quotefn(f))
413
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
415
# having remove it, it must be either ignored or unknown
416
if tree.is_ignored(f):
420
show_status(new_status, inv[fid].kind, quotefn(f))
423
self._write_inventory(inv)
425
def set_inventory(self, new_inventory_list):
427
for path, file_id, parent, kind in new_inventory_list:
428
name = os.path.basename(path)
431
inv.add(InventoryEntry(file_id, name, kind, parent))
432
self._write_inventory(inv)
436
"""Return all unknown files.
438
These are files in the working directory that are not versioned or
439
control files or ignored.
441
>>> b = ScratchBranch(files=['foo', 'foo~'])
442
>>> list(b.unknowns())
445
>>> list(b.unknowns())
448
>>> list(b.unknowns())
451
return self.working_tree().unknowns()
454
def append_revision(self, revision_id):
455
mutter("add {%s} to revision-history" % revision_id)
456
rev_history = self.revision_history()
458
tmprhname = self.controlfilename('revision-history.tmp')
459
rhname = self.controlfilename('revision-history')
461
f = file(tmprhname, 'wt')
462
rev_history.append(revision_id)
463
f.write('\n'.join(rev_history))
467
if sys.platform == 'win32':
469
os.rename(tmprhname, rhname)
473
def get_revision(self, revision_id):
474
"""Return the Revision object for a named revision"""
475
self._need_readlock()
476
r = Revision.read_xml(self.revision_store[revision_id])
477
assert r.revision_id == revision_id
481
def get_inventory(self, inventory_id):
482
"""Get Inventory object by hash.
484
TODO: Perhaps for this and similar methods, take a revision
485
parameter which can be either an integer revno or a
487
self._need_readlock()
488
i = Inventory.read_xml(self.inventory_store[inventory_id])
492
def get_revision_inventory(self, revision_id):
493
"""Return inventory of a past revision."""
494
self._need_readlock()
495
if revision_id == None:
498
return self.get_inventory(self.get_revision(revision_id).inventory_id)
295
raise NotImplementedError(self.print_file)
297
def append_revision(self, *revision_ids):
298
raise NotImplementedError(self.append_revision)
300
def set_revision_history(self, rev_history):
301
raise NotImplementedError(self.set_revision_history)
501
303
def revision_history(self):
502
"""Return sequence of revision hashes on to this branch.
504
>>> ScratchBranch().revision_history()
507
self._need_readlock()
508
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
511
def enum_history(self, direction):
512
"""Return (revno, revision_id) for history of branch.
515
'forward' is from earliest to latest
516
'reverse' is from latest to earliest
518
rh = self.revision_history()
519
if direction == 'forward':
524
elif direction == 'reverse':
530
raise ValueError('invalid history direction', direction)
304
"""Return sequence of revision hashes on to this branch."""
305
raise NotImplementedError(self.revision_history)
534
308
"""Return current revision number for this branch.
661
412
Note that to_name is only the last component of the new name;
662
413
this doesn't change the directory.
664
self._need_writelock()
665
## TODO: Option to move IDs only
666
assert not isinstance(from_paths, basestring)
667
tree = self.working_tree()
669
to_abs = self.abspath(to_name)
670
if not isdir(to_abs):
671
bailout("destination %r is not a directory" % to_abs)
672
if not tree.has_filename(to_name):
673
bailout("destination %r not in working directory" % to_abs)
674
to_dir_id = inv.path2id(to_name)
675
if to_dir_id == None and to_name != '':
676
bailout("destination %r is not a versioned directory" % to_name)
677
to_dir_ie = inv[to_dir_id]
678
if to_dir_ie.kind not in ('directory', 'root_directory'):
679
bailout("destination %r is not a directory" % to_abs)
681
to_idpath = inv.get_idpath(to_dir_id)
684
if not tree.has_filename(f):
685
bailout("%r does not exist in working tree" % f)
686
f_id = inv.path2id(f)
688
bailout("%r is not versioned" % f)
689
name_tail = splitpath(f)[-1]
690
dest_path = appendpath(to_name, name_tail)
691
if tree.has_filename(dest_path):
692
bailout("destination %r already exists" % dest_path)
693
if f_id in to_idpath:
694
bailout("can't move %r to a subdirectory of itself" % f)
696
# OK, so there's a race here, it's possible that someone will
697
# create a file in this interval and then the rename might be
698
# left half-done. But we should have caught most problems.
701
name_tail = splitpath(f)[-1]
702
dest_path = appendpath(to_name, name_tail)
703
print "%s => %s" % (f, dest_path)
704
inv.rename(inv.path2id(f), to_dir_id, name_tail)
706
os.rename(self.abspath(f), self.abspath(dest_path))
708
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
709
["rename rolled back"])
711
self._write_inventory(inv)
716
class ScratchBranch(Branch):
717
"""Special test class: a branch that cleans up after itself.
719
>>> b = ScratchBranch()
727
def __init__(self, files=[], dirs=[]):
728
"""Make a test branch.
730
This creates a temporary directory and runs init-tree in it.
732
If any files are listed, they are created in the working copy.
734
Branch.__init__(self, tempfile.mkdtemp(), init=True)
736
os.mkdir(self.abspath(d))
415
This returns a list of (from_path, to_path) pairs for each
418
raise NotImplementedError(self.move)
420
def get_parent(self):
421
"""Return the parent location of the branch.
423
This is the default location for push/pull/missing. The usual
424
pattern is that the user can override it by specifying a
427
raise NotImplementedError(self.get_parent)
429
def get_submit_branch(self):
430
"""Return the submit location of the branch.
432
This is the default location for bundle. The usual
433
pattern is that the user can override it by specifying a
436
return self.get_config().get_user_option('submit_branch')
438
def set_submit_branch(self, location):
439
"""Return the submit location of the branch.
441
This is the default location for bundle. The usual
442
pattern is that the user can override it by specifying a
445
self.get_config().set_user_option('submit_branch', location)
447
def get_push_location(self):
448
"""Return the None or the location to push this branch to."""
449
raise NotImplementedError(self.get_push_location)
451
def set_push_location(self, location):
452
"""Set a new push location for this branch."""
453
raise NotImplementedError(self.set_push_location)
455
def set_parent(self, url):
456
raise NotImplementedError(self.set_parent)
460
"""Synchronise this branch with the master branch if any.
462
:return: None or the last_revision pivoted out during the update.
466
def check_revno(self, revno):
468
Check whether a revno corresponds to any revision.
469
Zero (the NULL revision) is considered valid.
472
self.check_real_revno(revno)
739
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
746
"""Destroy the test branch, removing the scratch directory."""
748
mutter("delete ScratchBranch %s" % self.base)
749
shutil.rmtree(self.base)
751
# Work around for shutil.rmtree failing on Windows when
752
# readonly files are encountered
753
mutter("hit exception in destroying ScratchBranch: %s" % e)
754
for root, dirs, files in os.walk(self.base, topdown=False):
756
os.chmod(os.path.join(root, name), 0700)
757
shutil.rmtree(self.base)
474
def check_real_revno(self, revno):
476
Check whether a revno corresponds to a real revision.
477
Zero (the NULL revision) is considered invalid
479
if revno < 1 or revno > self.revno():
480
raise InvalidRevisionNumber(revno)
483
def clone(self, *args, **kwargs):
484
"""Clone this branch into to_bzrdir preserving all semantic values.
486
revision_id: if not None, the revision history in the new branch will
487
be truncated to end with revision_id.
489
# for API compatibility, until 0.8 releases we provide the old api:
490
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
491
# after 0.8 releases, the *args and **kwargs should be changed:
492
# def clone(self, to_bzrdir, revision_id=None):
493
if (kwargs.get('to_location', None) or
494
kwargs.get('revision', None) or
495
kwargs.get('basis_branch', None) or
496
(len(args) and isinstance(args[0], basestring))):
497
# backwards compatibility api:
498
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
499
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
502
basis_branch = args[2]
504
basis_branch = kwargs.get('basis_branch', None)
506
basis = basis_branch.bzrdir
511
revision_id = args[1]
513
revision_id = kwargs.get('revision', None)
518
# no default to raise if not provided.
519
url = kwargs.get('to_location')
520
return self.bzrdir.clone(url,
521
revision_id=revision_id,
522
basis=basis).open_branch()
524
# generate args by hand
526
revision_id = args[1]
528
revision_id = kwargs.get('revision_id', None)
532
# no default to raise if not provided.
533
to_bzrdir = kwargs.get('to_bzrdir')
534
result = self._format.initialize(to_bzrdir)
535
self.copy_content_into(result, revision_id=revision_id)
539
def sprout(self, to_bzrdir, revision_id=None):
540
"""Create a new line of development from the branch, into to_bzrdir.
542
revision_id: if not None, the revision history in the new branch will
543
be truncated to end with revision_id.
545
result = self._format.initialize(to_bzrdir)
546
self.copy_content_into(result, revision_id=revision_id)
547
result.set_parent(self.bzrdir.root_transport.base)
551
def copy_content_into(self, destination, revision_id=None):
552
"""Copy the content of self into destination.
554
revision_id: if not None, the revision history in the new branch will
555
be truncated to end with revision_id.
557
new_history = self.revision_history()
558
if revision_id is not None:
560
new_history = new_history[:new_history.index(revision_id) + 1]
562
rev = self.repository.get_revision(revision_id)
563
new_history = rev.get_history(self.repository)[1:]
564
destination.set_revision_history(new_history)
566
parent = self.get_parent()
567
except errors.InaccessibleParent, e:
568
mutter('parent was not accessible to copy: %s', e)
571
destination.set_parent(parent)
575
"""Check consistency of the branch.
577
In particular this checks that revisions given in the revision-history
578
do actually match up in the revision graph, and that they're all
579
present in the repository.
581
Callers will typically also want to check the repository.
583
:return: A BranchCheckResult.
585
mainline_parent_id = None
586
for revision_id in self.revision_history():
588
revision = self.repository.get_revision(revision_id)
589
except errors.NoSuchRevision, e:
590
raise errors.BzrCheckError("mainline revision {%s} not in repository"
592
# In general the first entry on the revision history has no parents.
593
# But it's not illegal for it to have parents listed; this can happen
594
# in imports from Arch when the parents weren't reachable.
595
if mainline_parent_id is not None:
596
if mainline_parent_id not in revision.parent_ids:
597
raise errors.BzrCheckError("previous revision {%s} not listed among "
599
% (mainline_parent_id, revision_id))
600
mainline_parent_id = revision_id
601
return BranchCheckResult(self)
603
def _get_checkout_format(self):
604
"""Return the most suitable metadir for a checkout of this branch.
605
Weaves are used if this branch's repostory uses weaves.
607
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
608
from bzrlib import repository
609
format = bzrdir.BzrDirMetaFormat1()
610
format.repository_format = repository.RepositoryFormat7()
612
format = self.repository.bzrdir.cloning_metadir()
615
def create_checkout(self, to_location, revision_id=None,
617
"""Create a checkout of a branch.
619
:param to_location: The url to produce the checkout at
620
:param revision_id: The revision to check out
621
:param lightweight: If True, produce a lightweight checkout, otherwise,
622
produce a bound branch (heavyweight checkout)
623
:return: The tree of the created checkout
625
t = transport.get_transport(to_location)
628
except errors.FileExists:
631
checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
632
BranchReferenceFormat().initialize(checkout, self)
634
format = self._get_checkout_format()
635
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
636
to_location, force_new_tree=False, format=format)
637
checkout = checkout_branch.bzrdir
638
checkout_branch.bind(self)
639
# pull up to the specified revision_id to set the initial
640
# branch tip correctly, and seed it with history.
641
checkout_branch.pull(self, stop_revision=revision_id)
642
return checkout.create_workingtree(revision_id)
645
class BranchFormat(object):
646
"""An encapsulation of the initialization and open routines for a format.
648
Formats provide three things:
649
* An initialization routine,
653
Formats are placed in an dict by their format string for reference
654
during branch opening. Its not required that these be instances, they
655
can be classes themselves with class methods - it simply depends on
656
whether state is needed for a given format or not.
658
Once a format is deprecated, just deprecate the initialize and open
659
methods on the format class. Do not deprecate the object, as the
660
object will be created every time regardless.
663
_default_format = None
664
"""The default format used for new branches."""
667
"""The known formats."""
670
def find_format(klass, a_bzrdir):
671
"""Return the format for the branch object in a_bzrdir."""
673
transport = a_bzrdir.get_branch_transport(None)
674
format_string = transport.get("format").read()
675
return klass._formats[format_string]
677
raise NotBranchError(path=transport.base)
679
raise errors.UnknownFormatError(format=format_string)
682
def get_default_format(klass):
683
"""Return the current default format."""
684
return klass._default_format
686
def get_format_string(self):
687
"""Return the ASCII format string that identifies this format."""
688
raise NotImplementedError(self.get_format_string)
690
def get_format_description(self):
691
"""Return the short format description for this format."""
692
raise NotImplementedError(self.get_format_string)
694
def initialize(self, a_bzrdir):
695
"""Create a branch of this format in a_bzrdir."""
696
raise NotImplementedError(self.initialize)
698
def is_supported(self):
699
"""Is this format supported?
701
Supported formats can be initialized and opened.
702
Unsupported formats may not support initialization or committing or
703
some other features depending on the reason for not being supported.
707
def open(self, a_bzrdir, _found=False):
708
"""Return the branch object for a_bzrdir
710
_found is a private parameter, do not use it. It is used to indicate
711
if format probing has already be done.
713
raise NotImplementedError(self.open)
716
def register_format(klass, format):
717
klass._formats[format.get_format_string()] = format
720
def set_default_format(klass, format):
721
klass._default_format = format
724
def unregister_format(klass, format):
725
assert klass._formats[format.get_format_string()] is format
726
del klass._formats[format.get_format_string()]
729
return self.get_format_string().rstrip()
732
class BranchHooks(dict):
733
"""A dictionary mapping hook name to a list of callables for branch hooks.
735
e.g. ['set_rh'] Is the list of items to be called when the
736
set_revision_history function is invoked.
740
"""Create the default hooks.
742
These are all empty initially, because by default nothing should get
746
# invoked whenever the revision history has been set
747
# with set_revision_history. The api signature is
748
# (branch, revision_history), and the branch will
749
# be write-locked. Introduced in 0.15.
752
def install_hook(self, hook_name, a_callable):
753
"""Install a_callable in to the hook hook_name.
755
:param hook_name: A hook name. See the __init__ method of BranchHooks
756
for the complete list of hooks.
757
:param a_callable: The callable to be invoked when the hook triggers.
758
The exact signature will depend on the hook - see the __init__
759
method of BranchHooks for details on each hook.
762
self[hook_name].append(a_callable)
764
raise errors.UnknownHook('branch', hook_name)
767
# install the default hooks into the Branch class.
768
Branch.hooks = BranchHooks()
771
class BzrBranchFormat4(BranchFormat):
772
"""Bzr branch format 4.
775
- a revision-history file.
776
- a branch-lock lock file [ to be shared with the bzrdir ]
779
def get_format_description(self):
780
"""See BranchFormat.get_format_description()."""
781
return "Branch format 4"
783
def initialize(self, a_bzrdir):
784
"""Create a branch of this format in a_bzrdir."""
785
mutter('creating branch in %s', a_bzrdir.transport.base)
786
branch_transport = a_bzrdir.get_branch_transport(self)
787
utf8_files = [('revision-history', ''),
790
control_files = lockable_files.LockableFiles(branch_transport,
791
'branch-lock', lockable_files.TransportLock)
792
control_files.create_lock()
793
control_files.lock_write()
795
for file, content in utf8_files:
796
control_files.put_utf8(file, content)
798
control_files.unlock()
799
return self.open(a_bzrdir, _found=True)
802
super(BzrBranchFormat4, self).__init__()
803
self._matchingbzrdir = bzrdir.BzrDirFormat6()
805
def open(self, a_bzrdir, _found=False):
806
"""Return the branch object for a_bzrdir
808
_found is a private parameter, do not use it. It is used to indicate
809
if format probing has already be done.
812
# we are being called directly and must probe.
813
raise NotImplementedError
814
return BzrBranch(_format=self,
815
_control_files=a_bzrdir._control_files,
817
_repository=a_bzrdir.open_repository())
820
return "Bazaar-NG branch format 4"
823
class BzrBranchFormat5(BranchFormat):
824
"""Bzr branch format 5.
827
- a revision-history file.
829
- a lock dir guarding the branch itself
830
- all of this stored in a branch/ subdirectory
831
- works with shared repositories.
833
This format is new in bzr 0.8.
836
def get_format_string(self):
837
"""See BranchFormat.get_format_string()."""
838
return "Bazaar-NG branch format 5\n"
840
def get_format_description(self):
841
"""See BranchFormat.get_format_description()."""
842
return "Branch format 5"
844
def initialize(self, a_bzrdir):
845
"""Create a branch of this format in a_bzrdir."""
846
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
847
branch_transport = a_bzrdir.get_branch_transport(self)
848
utf8_files = [('revision-history', ''),
851
control_files = lockable_files.LockableFiles(branch_transport, 'lock',
853
control_files.create_lock()
854
control_files.lock_write()
855
control_files.put_utf8('format', self.get_format_string())
857
for file, content in utf8_files:
858
control_files.put_utf8(file, content)
860
control_files.unlock()
861
return self.open(a_bzrdir, _found=True, )
864
super(BzrBranchFormat5, self).__init__()
865
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
867
def open(self, a_bzrdir, _found=False):
868
"""Return the branch object for a_bzrdir
870
_found is a private parameter, do not use it. It is used to indicate
871
if format probing has already be done.
874
format = BranchFormat.find_format(a_bzrdir)
875
assert format.__class__ == self.__class__
876
transport = a_bzrdir.get_branch_transport(None)
877
control_files = lockable_files.LockableFiles(transport, 'lock',
879
return BzrBranch5(_format=self,
880
_control_files=control_files,
882
_repository=a_bzrdir.find_repository())
885
return "Bazaar-NG Metadir branch format 5"
888
class BranchReferenceFormat(BranchFormat):
889
"""Bzr branch reference format.
891
Branch references are used in implementing checkouts, they
892
act as an alias to the real branch which is at some other url.
899
def get_format_string(self):
900
"""See BranchFormat.get_format_string()."""
901
return "Bazaar-NG Branch Reference Format 1\n"
903
def get_format_description(self):
904
"""See BranchFormat.get_format_description()."""
905
return "Checkout reference format 1"
907
def initialize(self, a_bzrdir, target_branch=None):
908
"""Create a branch of this format in a_bzrdir."""
909
if target_branch is None:
910
# this format does not implement branch itself, thus the implicit
911
# creation contract must see it as uninitializable
912
raise errors.UninitializableFormat(self)
913
mutter('creating branch reference in %s', a_bzrdir.transport.base)
914
branch_transport = a_bzrdir.get_branch_transport(self)
915
branch_transport.put_bytes('location',
916
target_branch.bzrdir.root_transport.base)
917
branch_transport.put_bytes('format', self.get_format_string())
918
return self.open(a_bzrdir, _found=True)
921
super(BranchReferenceFormat, self).__init__()
922
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
924
def _make_reference_clone_function(format, a_branch):
925
"""Create a clone() routine for a branch dynamically."""
926
def clone(to_bzrdir, revision_id=None):
927
"""See Branch.clone()."""
928
return format.initialize(to_bzrdir, a_branch)
929
# cannot obey revision_id limits when cloning a reference ...
930
# FIXME RBC 20060210 either nuke revision_id for clone, or
931
# emit some sort of warning/error to the caller ?!
934
def open(self, a_bzrdir, _found=False):
935
"""Return the branch that the branch reference in a_bzrdir points at.
937
_found is a private parameter, do not use it. It is used to indicate
938
if format probing has already be done.
941
format = BranchFormat.find_format(a_bzrdir)
942
assert format.__class__ == self.__class__
943
transport = a_bzrdir.get_branch_transport(None)
944
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
945
result = real_bzrdir.open_branch()
946
# this changes the behaviour of result.clone to create a new reference
947
# rather than a copy of the content of the branch.
948
# I did not use a proxy object because that needs much more extensive
949
# testing, and we are only changing one behaviour at the moment.
950
# If we decide to alter more behaviours - i.e. the implicit nickname
951
# then this should be refactored to introduce a tested proxy branch
952
# and a subclass of that for use in overriding clone() and ....
954
result.clone = self._make_reference_clone_function(result)
958
# formats which have no format string are not discoverable
959
# and not independently creatable, so are not registered.
960
__default_format = BzrBranchFormat5()
961
BranchFormat.register_format(__default_format)
962
BranchFormat.register_format(BranchReferenceFormat())
963
BranchFormat.set_default_format(__default_format)
964
_legacy_formats = [BzrBranchFormat4(),
967
class BzrBranch(Branch):
968
"""A branch stored in the actual filesystem.
970
Note that it's "local" in the context of the filesystem; it doesn't
971
really matter if it's on an nfs/smb/afs/coda/... share, as long as
972
it's writable, and can be accessed via the normal filesystem API.
975
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
976
relax_version_check=DEPRECATED_PARAMETER, _format=None,
977
_control_files=None, a_bzrdir=None, _repository=None):
978
"""Create new branch object at a particular location.
980
transport -- A Transport object, defining how to access files.
982
init -- If True, create new control files in a previously
983
unversioned directory. If False, the branch must already
986
relax_version_check -- If true, the usual check for the branch
987
version is not applied. This is intended only for
988
upgrade/recovery type use; it's not guaranteed that
989
all operations will work on old format branches.
992
self.bzrdir = bzrdir.BzrDir.open(transport.base)
994
self.bzrdir = a_bzrdir
995
self._transport = self.bzrdir.transport.clone('..')
996
self._base = self._transport.base
997
self._format = _format
998
if _control_files is None:
999
raise ValueError('BzrBranch _control_files is None')
1000
self.control_files = _control_files
1001
if deprecated_passed(init):
1002
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1003
"deprecated as of bzr 0.8. Please use Branch.create().",
1007
# this is slower than before deprecation, oh well never mind.
1008
# -> its deprecated.
1009
self._initialize(transport.base)
1010
self._check_format(_format)
1011
if deprecated_passed(relax_version_check):
1012
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1013
"relax_version_check parameter is deprecated as of bzr 0.8. "
1014
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1018
if (not relax_version_check
1019
and not self._format.is_supported()):
1020
raise errors.UnsupportedFormatError(format=fmt)
1021
if deprecated_passed(transport):
1022
warn("BzrBranch.__init__(transport=XXX...): The transport "
1023
"parameter is deprecated as of bzr 0.8. "
1024
"Please use Branch.open, or bzrdir.open_branch().",
1027
self.repository = _repository
1030
return '%s(%r)' % (self.__class__.__name__, self.base)
1034
def _get_base(self):
1037
base = property(_get_base, doc="The URL for the root of this branch.")
1039
def _finish_transaction(self):
1040
"""Exit the current transaction."""
1041
return self.control_files._finish_transaction()
1043
def get_transaction(self):
1044
"""Return the current active transaction.
1046
If no transaction is active, this returns a passthrough object
1047
for which all data is immediately flushed and no caching happens.
1049
# this is an explicit function so that we can do tricky stuff
1050
# when the storage in rev_storage is elsewhere.
1051
# we probably need to hook the two 'lock a location' and
1052
# 'have a transaction' together more delicately, so that
1053
# we can have two locks (branch and storage) and one transaction
1054
# ... and finishing the transaction unlocks both, but unlocking
1055
# does not. - RBC 20051121
1056
return self.control_files.get_transaction()
1058
def _set_transaction(self, transaction):
1059
"""Set a new active transaction."""
1060
return self.control_files._set_transaction(transaction)
1062
def abspath(self, name):
1063
"""See Branch.abspath."""
1064
return self.control_files._transport.abspath(name)
1066
def _check_format(self, format):
1067
"""Identify the branch format if needed.
1069
The format is stored as a reference to the format object in
1070
self._format for code that needs to check it later.
1072
The format parameter is either None or the branch format class
1073
used to open this branch.
1075
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
1078
format = BranchFormat.find_format(self.bzrdir)
1079
self._format = format
1080
mutter("got branch format %s", self._format)
1083
def get_root_id(self):
1084
"""See Branch.get_root_id."""
1085
tree = self.repository.revision_tree(self.last_revision())
1086
return tree.inventory.root.file_id
1088
def is_locked(self):
1089
return self.control_files.is_locked()
1091
def lock_write(self):
1092
self.repository.lock_write()
1094
self.control_files.lock_write()
1096
self.repository.unlock()
1099
def lock_read(self):
1100
self.repository.lock_read()
1102
self.control_files.lock_read()
1104
self.repository.unlock()
1108
# TODO: test for failed two phase locks. This is known broken.
1110
self.control_files.unlock()
1112
self.repository.unlock()
1114
def peek_lock_mode(self):
1115
if self.control_files._lock_count == 0:
1118
return self.control_files._lock_mode
1120
def get_physical_lock_status(self):
1121
return self.control_files.get_physical_lock_status()
1124
def print_file(self, file, revision_id):
1125
"""See Branch.print_file."""
1126
return self.repository.print_file(file, revision_id)
1129
def append_revision(self, *revision_ids):
1130
"""See Branch.append_revision."""
1131
for revision_id in revision_ids:
1132
_mod_revision.check_not_reserved_id(revision_id)
1133
mutter("add {%s} to revision-history" % revision_id)
1134
rev_history = self.revision_history()
1135
rev_history.extend(revision_ids)
1136
self.set_revision_history(rev_history)
1139
def set_revision_history(self, rev_history):
1140
"""See Branch.set_revision_history."""
1141
self.control_files.put_utf8(
1142
'revision-history', '\n'.join(rev_history))
1143
transaction = self.get_transaction()
1144
history = transaction.map.find_revision_history()
1145
if history is not None:
1146
# update the revision history in the identity map.
1147
history[:] = list(rev_history)
1148
# this call is disabled because revision_history is
1149
# not really an object yet, and the transaction is for objects.
1150
# transaction.register_dirty(history)
1152
transaction.map.add_revision_history(rev_history)
1153
# this call is disabled because revision_history is
1154
# not really an object yet, and the transaction is for objects.
1155
# transaction.register_clean(history)
1156
for hook in Branch.hooks['set_rh']:
1157
hook(self, rev_history)
1160
def revision_history(self):
1161
"""See Branch.revision_history."""
1162
transaction = self.get_transaction()
1163
history = transaction.map.find_revision_history()
1164
if history is not None:
1165
# mutter("cache hit for revision-history in %s", self)
1166
return list(history)
1167
decode_utf8 = cache_utf8.decode
1168
history = [decode_utf8(l.rstrip('\r\n')) for l in
1169
self.control_files.get('revision-history').readlines()]
1170
transaction.map.add_revision_history(history)
1171
# this call is disabled because revision_history is
1172
# not really an object yet, and the transaction is for objects.
1173
# transaction.register_clean(history, precious=True)
1174
return list(history)
1177
def generate_revision_history(self, revision_id, last_rev=None,
1179
"""Create a new revision history that will finish with revision_id.
1181
:param revision_id: the new tip to use.
1182
:param last_rev: The previous last_revision. If not None, then this
1183
must be a ancestory of revision_id, or DivergedBranches is raised.
1184
:param other_branch: The other branch that DivergedBranches should
1185
raise with respect to.
1187
# stop_revision must be a descendant of last_revision
1188
stop_graph = self.repository.get_revision_graph(revision_id)
1189
if last_rev is not None and last_rev not in stop_graph:
1190
# our previous tip is not merged into stop_revision
1191
raise errors.DivergedBranches(self, other_branch)
1192
# make a new revision history from the graph
1193
current_rev_id = revision_id
1195
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1196
new_history.append(current_rev_id)
1197
current_rev_id_parents = stop_graph[current_rev_id]
1199
current_rev_id = current_rev_id_parents[0]
1201
current_rev_id = None
1202
new_history.reverse()
1203
self.set_revision_history(new_history)
1206
def update_revisions(self, other, stop_revision=None):
1207
"""See Branch.update_revisions."""
1210
if stop_revision is None:
1211
stop_revision = other.last_revision()
1212
if stop_revision is None:
1213
# if there are no commits, we're done.
1215
# whats the current last revision, before we fetch [and change it
1217
last_rev = self.last_revision()
1218
# we fetch here regardless of whether we need to so that we pickup
1220
self.fetch(other, stop_revision)
1221
my_ancestry = self.repository.get_ancestry(last_rev)
1222
if stop_revision in my_ancestry:
1223
# last_revision is a descendant of stop_revision
1225
self.generate_revision_history(stop_revision, last_rev=last_rev,
1230
def basis_tree(self):
1231
"""See Branch.basis_tree."""
1232
return self.repository.revision_tree(self.last_revision())
1234
@deprecated_method(zero_eight)
1235
def working_tree(self):
1236
"""Create a Working tree object for this branch."""
1238
from bzrlib.transport.local import LocalTransport
1239
if (self.base.find('://') != -1 or
1240
not isinstance(self._transport, LocalTransport)):
1241
raise NoWorkingTree(self.base)
1242
return self.bzrdir.open_workingtree()
1245
def pull(self, source, overwrite=False, stop_revision=None):
1246
"""See Branch.pull."""
1249
old_count = len(self.revision_history())
1251
self.update_revisions(source, stop_revision)
1252
except DivergedBranches:
1256
self.set_revision_history(source.revision_history())
1257
new_count = len(self.revision_history())
1258
return new_count - old_count
1263
def push(self, target, overwrite=False, stop_revision=None):
1264
"""See Branch.push."""
1267
old_count = len(target.revision_history())
1269
target.update_revisions(self, stop_revision)
1270
except DivergedBranches:
1274
target.set_revision_history(self.revision_history())
1275
new_count = len(target.revision_history())
1276
return new_count - old_count
1280
def get_parent(self):
1281
"""See Branch.get_parent."""
1283
_locs = ['parent', 'pull', 'x-pull']
1284
assert self.base[-1] == '/'
1287
parent = self.control_files.get(l).read().strip('\n')
1290
# This is an old-format absolute path to a local branch
1291
# turn it into a url
1292
if parent.startswith('/'):
1293
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1295
return urlutils.join(self.base[:-1], parent)
1296
except errors.InvalidURLJoin, e:
1297
raise errors.InaccessibleParent(parent, self.base)
1300
def get_push_location(self):
1301
"""See Branch.get_push_location."""
1302
push_loc = self.get_config().get_user_option('push_location')
1305
def set_push_location(self, location):
1306
"""See Branch.set_push_location."""
1307
self.get_config().set_user_option(
1308
'push_location', location,
1309
store=_mod_config.STORE_LOCATION_NORECURSE)
1312
def set_parent(self, url):
1313
"""See Branch.set_parent."""
1314
# TODO: Maybe delete old location files?
1315
# URLs should never be unicode, even on the local fs,
1316
# FIXUP this and get_parent in a future branch format bump:
1317
# read and rewrite the file, and have the new format code read
1318
# using .get not .get_utf8. RBC 20060125
1320
self.control_files._transport.delete('parent')
1322
if isinstance(url, unicode):
1324
url = url.encode('ascii')
1325
except UnicodeEncodeError:
1326
raise bzrlib.errors.InvalidURL(url,
1327
"Urls must be 7-bit ascii, "
1328
"use bzrlib.urlutils.escape")
1330
url = urlutils.relative_url(self.base, url)
1331
self.control_files.put('parent', StringIO(url + '\n'))
1333
@deprecated_function(zero_nine)
1334
def tree_config(self):
1335
"""DEPRECATED; call get_config instead.
1336
TreeConfig has become part of BranchConfig."""
1337
return TreeConfig(self)
1340
class BzrBranch5(BzrBranch):
1341
"""A format 5 branch. This supports new features over plan branches.
1343
It has support for a master_branch which is the data for bound branches.
1351
super(BzrBranch5, self).__init__(_format=_format,
1352
_control_files=_control_files,
1354
_repository=_repository)
1357
def pull(self, source, overwrite=False, stop_revision=None):
1358
"""Extends branch.pull to be bound branch aware."""
1359
bound_location = self.get_bound_location()
1360
if source.base != bound_location:
1361
# not pulling from master, so we need to update master.
1362
master_branch = self.get_master_branch()
1364
master_branch.pull(source)
1365
source = master_branch
1366
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1369
def push(self, target, overwrite=False, stop_revision=None):
1370
"""Updates branch.push to be bound branch aware."""
1371
bound_location = target.get_bound_location()
1372
if target.base != bound_location:
1373
# not pushing to master, so we need to update master.
1374
master_branch = target.get_master_branch()
1376
# push into the master from this branch.
1377
super(BzrBranch5, self).push(master_branch, overwrite,
1379
# and push into the target branch from this. Note that we push from
1380
# this branch again, because its considered the highest bandwidth
1382
return super(BzrBranch5, self).push(target, overwrite, stop_revision)
1384
def get_bound_location(self):
1386
return self.control_files.get_utf8('bound').read()[:-1]
1387
except errors.NoSuchFile:
1391
def get_master_branch(self):
1392
"""Return the branch we are bound to.
1394
:return: Either a Branch, or None
1396
This could memoise the branch, but if thats done
1397
it must be revalidated on each new lock.
1398
So for now we just don't memoise it.
1399
# RBC 20060304 review this decision.
1401
bound_loc = self.get_bound_location()
1405
return Branch.open(bound_loc)
1406
except (errors.NotBranchError, errors.ConnectionError), e:
1407
raise errors.BoundBranchConnectionFailure(
1411
def set_bound_location(self, location):
1412
"""Set the target where this branch is bound to.
1414
:param location: URL to the target branch
1417
self.control_files.put_utf8('bound', location+'\n')
1420
self.control_files._transport.delete('bound')
1426
def bind(self, other):
1427
"""Bind this branch to the branch other.
1429
This does not push or pull data between the branches, though it does
1430
check for divergence to raise an error when the branches are not
1431
either the same, or one a prefix of the other. That behaviour may not
1432
be useful, so that check may be removed in future.
1434
:param other: The branch to bind to
1437
# TODO: jam 20051230 Consider checking if the target is bound
1438
# It is debatable whether you should be able to bind to
1439
# a branch which is itself bound.
1440
# Committing is obviously forbidden,
1441
# but binding itself may not be.
1442
# Since we *have* to check at commit time, we don't
1443
# *need* to check here
1445
# we want to raise diverged if:
1446
# last_rev is not in the other_last_rev history, AND
1447
# other_last_rev is not in our history, and do it without pulling
1449
last_rev = self.last_revision()
1450
if last_rev is not None:
1453
other_last_rev = other.last_revision()
1454
if other_last_rev is not None:
1455
# neither branch is new, we have to do some work to
1456
# ascertain diversion.
1457
remote_graph = other.repository.get_revision_graph(
1459
local_graph = self.repository.get_revision_graph(last_rev)
1460
if (last_rev not in remote_graph and
1461
other_last_rev not in local_graph):
1462
raise errors.DivergedBranches(self, other)
1465
self.set_bound_location(other.base)
1469
"""If bound, unbind"""
1470
return self.set_bound_location(None)
1474
"""Synchronise this branch with the master branch if any.
1476
:return: None or the last_revision that was pivoted out during the
1479
master = self.get_master_branch()
1480
if master is not None:
1481
old_tip = self.last_revision()
1482
self.pull(master, overwrite=True)
1483
if old_tip in self.repository.get_ancestry(self.last_revision()):
1489
class BranchTestProviderAdapter(object):
1490
"""A tool to generate a suite testing multiple branch formats at once.
1492
This is done by copying the test once for each transport and injecting
1493
the transport_server, transport_readonly_server, and branch_format
1494
classes into each copy. Each copy is also given a new id() to make it
1498
def __init__(self, transport_server, transport_readonly_server, formats):
1499
self._transport_server = transport_server
1500
self._transport_readonly_server = transport_readonly_server
1501
self._formats = formats
1503
def adapt(self, test):
1504
result = TestSuite()
1505
for branch_format, bzrdir_format in self._formats:
1506
new_test = deepcopy(test)
1507
new_test.transport_server = self._transport_server
1508
new_test.transport_readonly_server = self._transport_readonly_server
1509
new_test.bzrdir_format = bzrdir_format
1510
new_test.branch_format = branch_format
1511
def make_new_test_id():
1512
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1513
return lambda: new_id
1514
new_test.id = make_new_test_id()
1515
result.addTest(new_test)
1519
class BranchCheckResult(object):
1520
"""Results of checking branch consistency.
1525
def __init__(self, branch):
1526
self.branch = branch
1528
def report_results(self, verbose):
1529
"""Report the check results via trace.note.
1531
:param verbose: Requests more detailed display of what was checked,
1534
note('checked branch %s format %s',
1536
self.branch._format)
762
1539
######################################################################
766
def is_control_file(filename):
767
## FIXME: better check
768
filename = os.path.normpath(filename)
769
while filename != '':
770
head, tail = os.path.split(filename)
771
## mutter('check %r for control file' % ((head, tail), ))
772
if tail == bzrlib.BZRDIR:
781
def gen_file_id(name):
782
"""Return new file id.
784
This should probably generate proper UUIDs, but for the moment we
785
cope with just randomness because running uuidgen every time is
790
idx = name.rfind('/')
792
name = name[idx+1 : ]
793
idx = name.rfind('\\')
795
name = name[idx+1 : ]
797
# make it not a hidden file
798
name = name.lstrip('.')
800
# remove any wierd characters; we don't escape them but rather
802
name = re.sub(r'[^\w.]', '', name)
804
s = hexlify(rand_bytes(8))
805
return '-'.join((name, compact_date(time.time()), s))
1543
@deprecated_function(zero_eight)
1544
def is_control_file(*args, **kwargs):
1545
"""See bzrlib.workingtree.is_control_file."""
1546
from bzrlib import workingtree
1547
return workingtree.is_control_file(*args, **kwargs)