1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
20
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
21
import traceback, socket, fnmatch, difflib, time
22
from binascii import hexlify
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
22
from copy import deepcopy
23
from unittest import TestSuite
24
from warnings import warn
25
from inventory import Inventory
26
from trace import mutter, note
27
from tree import Tree, EmptyTree, RevisionTree
28
from inventory import InventoryEntry, Inventory
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
30
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
31
joinpath, sha_string, file_kind, local_time_offset, appendpath
32
from store import ImmutableStore
33
from revision import Revision
34
from errors import bailout, BzrError
35
from textui import show_status
37
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
38
## TODO: Maybe include checks for common corruption of newlines, etc?
42
def find_branch(f, **args):
43
if f and (f.startswith('http://') or f.startswith('https://')):
45
return remotebranch.RemoteBranch(f, **args)
47
return Branch(f, **args)
50
def find_branch_root(f=None):
51
"""Find the branch root enclosing f, or pwd.
53
f may be a filename or a URL.
55
It is not necessary that f exists.
57
Basically we keep looking up until we find the control directory or
61
elif hasattr(os.path, 'realpath'):
62
f = os.path.realpath(f)
64
f = os.path.abspath(f)
65
if not os.path.exists(f):
66
raise BzrError('%r does not exist' % f)
72
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
74
head, tail = os.path.split(f)
76
# reached the root, whatever that may be
77
raise BzrError('%r is not in a branch' % orig_f)
30
config as _mod_config,
35
revision as _mod_revision,
41
from bzrlib.config import BranchConfig, TreeConfig
42
from bzrlib.lockable_files import LockableFiles, TransportLock
43
from bzrlib.tag import (
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
51
HistoryMissing, InvalidRevisionId,
52
InvalidRevisionNumber, LockError, NoSuchFile,
53
NoSuchRevision, NoWorkingTree, NotVersionedError,
54
NotBranchError, UninitializableFormat,
55
UnlistableStore, UnlistableBranch,
57
from bzrlib.symbol_versioning import (deprecated_function,
61
zero_eight, zero_nine,
63
from bzrlib.trace import mutter, note
66
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
67
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
68
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
71
# TODO: Maybe include checks for common corruption of newlines, etc?
73
# TODO: Some operations like log might retrieve the same revisions
74
# repeatedly to calculate deltas. We could perhaps have a weakref
75
# cache in memory to make this faster. In general anything can be
76
# cached in memory between lock and unlock operations. .. nb thats
77
# what the transaction identity map provides
82
80
######################################################################
86
84
"""Branch holding a history of revisions.
89
Base directory of the branch.
87
Base directory/url of the branch.
89
hooks: An instance of BranchHooks.
91
# this is really an instance variable - FIXME move it there
95
# override this to set the strategy for storing tags
97
return DisabledTags(self)
99
def __init__(self, *ignored, **ignored_too):
100
self.tags = self._make_tags()
102
def break_lock(self):
103
"""Break a lock if one is present from another instance.
105
Uses the ui factory to ask for confirmation if the lock may be from
108
This will probe the repository for its lock as well.
110
self.control_files.break_lock()
111
self.repository.break_lock()
112
master = self.get_master_branch()
113
if master is not None:
117
@deprecated_method(zero_eight)
118
def open_downlevel(base):
119
"""Open a branch which may be of an old format."""
120
return Branch.open(base, _unsupported=True)
123
def open(base, _unsupported=False):
124
"""Open the branch rooted at base.
126
For instance, if the branch is at URL/.bzr/branch,
127
Branch.open(URL) -> a Branch instance.
129
control = bzrdir.BzrDir.open(base, _unsupported)
130
return control.open_branch(_unsupported)
133
def open_containing(url):
134
"""Open an existing branch which contains url.
136
This probes for a branch at url, and searches upwards from there.
138
Basically we keep looking up until we find the control directory or
139
run into the root. If there isn't one, raises NotBranchError.
140
If there is one and it is either an unrecognised format or an unsupported
141
format, UnknownFormatError or UnsupportedFormatError are raised.
142
If there is one, it is returned, along with the unused portion of url.
144
control, relpath = bzrdir.BzrDir.open_containing(url)
145
return control.open_branch(), relpath
148
@deprecated_function(zero_eight)
149
def initialize(base):
150
"""Create a new working tree and branch, rooted at 'base' (url)
152
NOTE: This will soon be deprecated in favour of creation
155
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
157
@deprecated_function(zero_eight)
158
def setup_caching(self, cache_root):
159
"""Subclasses that care about caching should override this, and set
160
up cached stores located under cache_root.
162
NOTE: This is unused.
166
def get_config(self):
167
return BranchConfig(self)
170
return self.get_config().get_nickname()
172
def _set_nick(self, nick):
173
self.get_config().set_user_option('nickname', nick)
175
nick = property(_get_nick, _set_nick)
178
raise NotImplementedError(self.is_locked)
180
def lock_write(self):
181
raise NotImplementedError(self.lock_write)
184
raise NotImplementedError(self.lock_read)
187
raise NotImplementedError(self.unlock)
189
def peek_lock_mode(self):
190
"""Return lock mode for the Branch: 'r', 'w' or None"""
191
raise NotImplementedError(self.peek_lock_mode)
193
def get_physical_lock_status(self):
194
raise NotImplementedError(self.get_physical_lock_status)
196
def abspath(self, name):
197
"""Return absolute filename for something in the branch
199
XXX: Robert Collins 20051017 what is this used for? why is it a branch
200
method and not a tree method.
202
raise NotImplementedError(self.abspath)
204
def bind(self, other):
205
"""Bind the local branch the other branch.
207
:param other: The branch to bind to
210
raise errors.UpgradeRequired(self.base)
213
def fetch(self, from_branch, last_revision=None, pb=None):
214
"""Copy revisions from from_branch into this branch.
216
:param from_branch: Where to copy from.
217
:param last_revision: What revision to stop at (None for at the end
219
:param pb: An optional progress bar to use.
221
Returns the copied revision count and the failed revisions in a tuple:
224
if self.base == from_branch.base:
227
nested_pb = ui.ui_factory.nested_progress_bar()
232
from_branch.lock_read()
234
if last_revision is None:
235
pb.update('get source history')
236
last_revision = from_branch.last_revision()
237
if last_revision is None:
238
last_revision = _mod_revision.NULL_REVISION
239
return self.repository.fetch(from_branch.repository,
240
revision_id=last_revision,
243
if nested_pb is not None:
247
def get_bound_location(self):
248
"""Return the URL of the branch we are bound to.
250
Older format branches cannot bind, please be sure to use a metadir
93
def __init__(self, base, init=False, find_root=True, lock_mode='w'):
94
"""Create new branch object at a particular location.
96
base -- Base directory for the branch.
98
init -- If True, create new control files in a previously
99
unversioned directory. If False, the branch must already
102
find_root -- If true and init is false, find the root of the
103
existing branch containing base.
105
In the test suite, creation of new trees is tested using the
106
`ScratchBranch` class.
109
self.base = os.path.realpath(base)
112
self.base = find_branch_root(base)
114
self.base = os.path.realpath(base)
115
if not isdir(self.controlfilename('.')):
116
bailout("not a bzr branch: %s" % quotefn(base),
117
['use "bzr init" to initialize a new working tree',
118
'current bzr can only operate from top-of-tree'])
122
self.text_store = ImmutableStore(self.controlfilename('text-store'))
123
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
124
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
128
return '%s(%r)' % (self.__class__.__name__, self.base)
135
def lock(self, mode='w'):
136
"""Lock the on-disk branch, excluding other processes."""
142
om = os.O_WRONLY | os.O_CREAT
147
raise BzrError("invalid locking mode %r" % mode)
150
lockfile = os.open(self.controlfilename('branch-lock'), om)
152
if e.errno == errno.ENOENT:
153
# might not exist on branches from <0.0.4
154
self.controlfile('branch-lock', 'w').close()
155
lockfile = os.open(self.controlfilename('branch-lock'), om)
159
fcntl.lockf(lockfile, lm)
161
fcntl.lockf(lockfile, fcntl.LOCK_UN)
163
self._lockmode = None
165
self._lockmode = mode
167
warning("please write a locking method for platform %r" % sys.platform)
169
self._lockmode = None
171
self._lockmode = mode
174
def _need_readlock(self):
175
if self._lockmode not in ['r', 'w']:
176
raise BzrError('need read lock on branch, only have %r' % self._lockmode)
178
def _need_writelock(self):
179
if self._lockmode not in ['w']:
180
raise BzrError('need write lock on branch, only have %r' % self._lockmode)
183
def abspath(self, name):
184
"""Return absolute filename for something in the branch"""
185
return os.path.join(self.base, name)
188
def relpath(self, path):
189
"""Return path relative to this branch of something inside it.
191
Raises an error if path is not in this branch."""
192
rp = os.path.realpath(path)
194
if not rp.startswith(self.base):
195
bailout("path %r is not within branch %r" % (rp, self.base))
196
rp = rp[len(self.base):]
197
rp = rp.lstrip(os.sep)
201
def controlfilename(self, file_or_path):
202
"""Return location relative to branch."""
203
if isinstance(file_or_path, types.StringTypes):
204
file_or_path = [file_or_path]
205
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
208
def controlfile(self, file_or_path, mode='r'):
209
"""Open a control file for this branch.
211
There are two classes of file in the control directory: text
212
and binary. binary files are untranslated byte streams. Text
213
control files are stored with Unix newlines and in UTF-8, even
214
if the platform or locale defaults are different.
216
Controlfiles should almost never be opened in write mode but
217
rather should be atomically copied and replaced using atomicfile.
220
fn = self.controlfilename(file_or_path)
222
if mode == 'rb' or mode == 'wb':
223
return file(fn, mode)
224
elif mode == 'r' or mode == 'w':
225
# open in binary mode anyhow so there's no newline translation;
226
# codecs uses line buffering by default; don't want that.
228
return codecs.open(fn, mode + 'b', 'utf-8',
231
raise BzrError("invalid controlfile mode %r" % mode)
235
def _make_control(self):
236
os.mkdir(self.controlfilename([]))
237
self.controlfile('README', 'w').write(
238
"This is a Bazaar-NG control directory.\n"
239
"Do not change any files in this directory.")
240
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
241
for d in ('text-store', 'inventory-store', 'revision-store'):
242
os.mkdir(self.controlfilename(d))
243
for f in ('revision-history', 'merged-patches',
244
'pending-merged-patches', 'branch-name',
246
self.controlfile(f, 'w').write('')
247
mutter('created control directory in ' + self.base)
248
Inventory().write_xml(self.controlfile('inventory','w'))
251
def _check_format(self):
252
"""Check this branch format is supported.
254
The current tool only supports the current unstable format.
256
In the future, we might need different in-memory Branch
257
classes to support downlevel branches. But not yet.
259
# This ignores newlines so that we can open branches created
260
# on Windows from Linux and so on. I think it might be better
261
# to always make all internal files in unix format.
262
fmt = self.controlfile('branch-format', 'r').read()
263
fmt.replace('\r\n', '')
264
if fmt != BZR_BRANCH_FORMAT:
265
bailout('sorry, branch format %r not supported' % fmt,
266
['use a different bzr version',
267
'or remove the .bzr directory and "bzr init" again'])
270
def read_working_inventory(self):
271
"""Read the working inventory."""
272
self._need_readlock()
274
# ElementTree does its own conversion from UTF-8, so open in
276
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
277
mutter("loaded inventory of %d items in %f"
278
% (len(inv), time.time() - before))
282
def _write_inventory(self, inv):
283
"""Update the working inventory.
285
That is to say, the inventory describing changes underway, that
286
will be committed to the next revision.
288
self._need_writelock()
289
## TODO: factor out to atomicfile? is rename safe on windows?
290
## TODO: Maybe some kind of clean/dirty marker on inventory?
291
tmpfname = self.controlfilename('inventory.tmp')
292
tmpf = file(tmpfname, 'wb')
295
inv_fname = self.controlfilename('inventory')
296
if sys.platform == 'win32':
298
os.rename(tmpfname, inv_fname)
299
mutter('wrote working inventory')
302
inventory = property(read_working_inventory, _write_inventory, None,
303
"""Inventory for the working copy.""")
306
def add(self, files, verbose=False):
307
"""Make files versioned.
309
Note that the command line normally calls smart_add instead.
311
This puts the files in the Added state, so that they will be
312
recorded by the next commit.
314
TODO: Perhaps have an option to add the ids even if the files do
317
TODO: Perhaps return the ids of the files? But then again it
318
is easy to retrieve them if they're needed.
320
TODO: Option to specify file id.
322
TODO: Adding a directory should optionally recurse down and
323
add all non-ignored children. Perhaps do that in a
326
self._need_writelock()
328
# TODO: Re-adding a file that is removed in the working copy
329
# should probably put it back with the previous ID.
330
if isinstance(files, types.StringTypes):
333
inv = self.read_working_inventory()
335
if is_control_file(f):
336
bailout("cannot add control file %s" % quotefn(f))
341
bailout("cannot add top-level %r" % f)
343
fullpath = os.path.normpath(self.abspath(f))
346
kind = file_kind(fullpath)
348
# maybe something better?
349
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
351
if kind != 'file' and kind != 'directory':
352
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
354
file_id = gen_file_id(f)
355
inv.add_path(f, kind=kind, file_id=file_id)
358
show_status('A', kind, quotefn(f))
360
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
362
self._write_inventory(inv)
365
def print_file(self, file, revno):
255
def get_old_bound_location(self):
256
"""Return the URL of the branch we used to be bound to
258
raise errors.UpgradeRequired(self.base)
260
def get_commit_builder(self, parents, config=None, timestamp=None,
261
timezone=None, committer=None, revprops=None,
263
"""Obtain a CommitBuilder for this branch.
265
:param parents: Revision ids of the parents of the new revision.
266
:param config: Optional configuration to use.
267
:param timestamp: Optional timestamp recorded for commit.
268
:param timezone: Optional timezone for timestamp.
269
:param committer: Optional committer to set for commit.
270
:param revprops: Optional dictionary of revision properties.
271
:param revision_id: Optional revision id.
275
config = self.get_config()
277
return self.repository.get_commit_builder(self, parents, config,
278
timestamp, timezone, committer, revprops, revision_id)
280
def get_master_branch(self):
281
"""Return the branch we are bound to.
283
:return: Either a Branch, or None
287
def get_revision_delta(self, revno):
288
"""Return the delta for one revision.
290
The delta is relative to its mainline predecessor, or the
291
empty tree for revision 1.
293
assert isinstance(revno, int)
294
rh = self.revision_history()
295
if not (1 <= revno <= len(rh)):
296
raise InvalidRevisionNumber(revno)
297
return self.repository.get_revision_delta(rh[revno-1])
299
def get_root_id(self):
300
"""Return the id of this branches root"""
301
raise NotImplementedError(self.get_root_id)
303
def print_file(self, file, revision_id):
366
304
"""Print `file` to stdout."""
367
self._need_readlock()
368
tree = self.revision_tree(self.lookup_revision(revno))
369
# use inventory as it was in that revision
370
file_id = tree.inventory.path2id(file)
372
bailout("%r is not present in revision %d" % (file, revno))
373
tree.print_file(file_id)
376
def remove(self, files, verbose=False):
377
"""Mark nominated files for removal from the inventory.
379
This does not remove their text. This does not run on
381
TODO: Refuse to remove modified files unless --force is given?
383
TODO: Do something useful with directories.
385
TODO: Should this remove the text or not? Tough call; not
386
removing may be useful and the user can just use use rm, and
387
is the opposite of add. Removing it is consistent with most
388
other tools. Maybe an option.
390
## TODO: Normalize names
391
## TODO: Remove nested loops; better scalability
392
self._need_writelock()
394
if isinstance(files, types.StringTypes):
397
tree = self.working_tree()
400
# do this before any modifications
404
bailout("cannot remove unversioned file %s" % quotefn(f))
405
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
407
# having remove it, it must be either ignored or unknown
408
if tree.is_ignored(f):
412
show_status(new_status, inv[fid].kind, quotefn(f))
415
self._write_inventory(inv)
419
"""Return all unknown files.
421
These are files in the working directory that are not versioned or
422
control files or ignored.
424
>>> b = ScratchBranch(files=['foo', 'foo~'])
425
>>> list(b.unknowns())
428
>>> list(b.unknowns())
431
>>> list(b.unknowns())
434
return self.working_tree().unknowns()
437
def append_revision(self, revision_id):
438
mutter("add {%s} to revision-history" % revision_id)
439
rev_history = self.revision_history()
441
tmprhname = self.controlfilename('revision-history.tmp')
442
rhname = self.controlfilename('revision-history')
444
f = file(tmprhname, 'wt')
445
rev_history.append(revision_id)
446
f.write('\n'.join(rev_history))
450
if sys.platform == 'win32':
452
os.rename(tmprhname, rhname)
456
def get_revision(self, revision_id):
457
"""Return the Revision object for a named revision"""
458
self._need_readlock()
459
r = Revision.read_xml(self.revision_store[revision_id])
460
assert r.revision_id == revision_id
464
def get_inventory(self, inventory_id):
465
"""Get Inventory object by hash.
467
TODO: Perhaps for this and similar methods, take a revision
468
parameter which can be either an integer revno or a
470
self._need_readlock()
471
i = Inventory.read_xml(self.inventory_store[inventory_id])
475
def get_revision_inventory(self, revision_id):
476
"""Return inventory of a past revision."""
477
self._need_readlock()
478
if revision_id == None:
481
return self.get_inventory(self.get_revision(revision_id).inventory_id)
305
raise NotImplementedError(self.print_file)
307
def append_revision(self, *revision_ids):
308
raise NotImplementedError(self.append_revision)
310
def set_revision_history(self, rev_history):
311
raise NotImplementedError(self.set_revision_history)
484
313
def revision_history(self):
485
"""Return sequence of revision hashes on to this branch.
487
>>> ScratchBranch().revision_history()
490
self._need_readlock()
491
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
494
def enum_history(self, direction):
495
"""Return (revno, revision_id) for history of branch.
498
'forward' is from earliest to latest
499
'reverse' is from latest to earliest
501
rh = self.revision_history()
502
if direction == 'forward':
507
elif direction == 'reverse':
513
raise BzrError('invalid history direction %r' % direction)
314
"""Return sequence of revision hashes on to this branch."""
315
raise NotImplementedError(self.revision_history)
517
318
"""Return current revision number for this branch.
642
441
Note that to_name is only the last component of the new name;
643
442
this doesn't change the directory.
645
self._need_writelock()
646
## TODO: Option to move IDs only
647
assert not isinstance(from_paths, basestring)
648
tree = self.working_tree()
650
to_abs = self.abspath(to_name)
651
if not isdir(to_abs):
652
bailout("destination %r is not a directory" % to_abs)
653
if not tree.has_filename(to_name):
654
bailout("destination %r not in working directory" % to_abs)
655
to_dir_id = inv.path2id(to_name)
656
if to_dir_id == None and to_name != '':
657
bailout("destination %r is not a versioned directory" % to_name)
658
to_dir_ie = inv[to_dir_id]
659
if to_dir_ie.kind not in ('directory', 'root_directory'):
660
bailout("destination %r is not a directory" % to_abs)
662
to_idpath = Set(inv.get_idpath(to_dir_id))
665
if not tree.has_filename(f):
666
bailout("%r does not exist in working tree" % f)
667
f_id = inv.path2id(f)
669
bailout("%r is not versioned" % f)
670
name_tail = splitpath(f)[-1]
671
dest_path = appendpath(to_name, name_tail)
672
if tree.has_filename(dest_path):
673
bailout("destination %r already exists" % dest_path)
674
if f_id in to_idpath:
675
bailout("can't move %r to a subdirectory of itself" % f)
677
# OK, so there's a race here, it's possible that someone will
678
# create a file in this interval and then the rename might be
679
# left half-done. But we should have caught most problems.
682
name_tail = splitpath(f)[-1]
683
dest_path = appendpath(to_name, name_tail)
684
print "%s => %s" % (f, dest_path)
685
inv.rename(inv.path2id(f), to_dir_id, name_tail)
687
os.rename(self.abspath(f), self.abspath(dest_path))
689
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
690
["rename rolled back"])
692
self._write_inventory(inv)
697
class ScratchBranch(Branch):
698
"""Special test class: a branch that cleans up after itself.
700
>>> b = ScratchBranch()
708
def __init__(self, files=[], dirs=[]):
709
"""Make a test branch.
711
This creates a temporary directory and runs init-tree in it.
713
If any files are listed, they are created in the working copy.
715
Branch.__init__(self, tempfile.mkdtemp(), init=True)
717
os.mkdir(self.abspath(d))
444
This returns a list of (from_path, to_path) pairs for each
447
raise NotImplementedError(self.move)
449
def get_parent(self):
450
"""Return the parent location of the branch.
452
This is the default location for push/pull/missing. The usual
453
pattern is that the user can override it by specifying a
456
raise NotImplementedError(self.get_parent)
458
def _set_config_location(self, name, url, config=None,
459
make_relative=False):
461
config = self.get_config()
465
url = urlutils.relative_url(self.base, url)
466
config.set_user_option(name, url)
468
def _get_config_location(self, name, config=None):
470
config = self.get_config()
471
location = config.get_user_option(name)
476
def get_submit_branch(self):
477
"""Return the submit location of the branch.
479
This is the default location for bundle. The usual
480
pattern is that the user can override it by specifying a
483
return self.get_config().get_user_option('submit_branch')
485
def set_submit_branch(self, location):
486
"""Return the submit location of the branch.
488
This is the default location for bundle. The usual
489
pattern is that the user can override it by specifying a
492
self.get_config().set_user_option('submit_branch', location)
494
def get_public_branch(self):
495
"""Return the public location of the branch.
497
This is is used by merge directives.
499
return self._get_config_location('public_branch')
501
def set_public_branch(self, location):
502
"""Return the submit location of the branch.
504
This is the default location for bundle. The usual
505
pattern is that the user can override it by specifying a
508
self._set_config_location('public_branch', location)
510
def get_push_location(self):
511
"""Return the None or the location to push this branch to."""
512
raise NotImplementedError(self.get_push_location)
514
def set_push_location(self, location):
515
"""Set a new push location for this branch."""
516
raise NotImplementedError(self.set_push_location)
518
def set_parent(self, url):
519
raise NotImplementedError(self.set_parent)
523
"""Synchronise this branch with the master branch if any.
525
:return: None or the last_revision pivoted out during the update.
529
def check_revno(self, revno):
531
Check whether a revno corresponds to any revision.
532
Zero (the NULL revision) is considered valid.
535
self.check_real_revno(revno)
720
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
727
"""Destroy the test branch, removing the scratch directory."""
729
mutter("delete ScratchBranch %s" % self.base)
730
shutil.rmtree(self.base)
732
# Work around for shutil.rmtree failing on Windows when
733
# readonly files are encountered
734
mutter("hit exception in destroying ScratchBranch: %s" % e)
735
for root, dirs, files in os.walk(self.base, topdown=False):
737
os.chmod(os.path.join(root, name), 0700)
738
shutil.rmtree(self.base)
743
######################################################################
747
def is_control_file(filename):
748
## FIXME: better check
749
filename = os.path.normpath(filename)
750
while filename != '':
751
head, tail = os.path.split(filename)
752
## mutter('check %r for control file' % ((head, tail), ))
753
if tail == bzrlib.BZRDIR:
537
def check_real_revno(self, revno):
539
Check whether a revno corresponds to a real revision.
540
Zero (the NULL revision) is considered invalid
542
if revno < 1 or revno > self.revno():
543
raise InvalidRevisionNumber(revno)
546
def clone(self, *args, **kwargs):
547
"""Clone this branch into to_bzrdir preserving all semantic values.
549
revision_id: if not None, the revision history in the new branch will
550
be truncated to end with revision_id.
552
# for API compatibility, until 0.8 releases we provide the old api:
553
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
554
# after 0.8 releases, the *args and **kwargs should be changed:
555
# def clone(self, to_bzrdir, revision_id=None):
556
if (kwargs.get('to_location', None) or
557
kwargs.get('revision', None) or
558
kwargs.get('basis_branch', None) or
559
(len(args) and isinstance(args[0], basestring))):
560
# backwards compatibility api:
561
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
562
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
565
basis_branch = args[2]
567
basis_branch = kwargs.get('basis_branch', None)
569
basis = basis_branch.bzrdir
574
revision_id = args[1]
576
revision_id = kwargs.get('revision', None)
581
# no default to raise if not provided.
582
url = kwargs.get('to_location')
583
return self.bzrdir.clone(url,
584
revision_id=revision_id,
585
basis=basis).open_branch()
587
# generate args by hand
589
revision_id = args[1]
591
revision_id = kwargs.get('revision_id', None)
595
# no default to raise if not provided.
596
to_bzrdir = kwargs.get('to_bzrdir')
597
result = self._format.initialize(to_bzrdir)
598
self.copy_content_into(result, revision_id=revision_id)
602
def sprout(self, to_bzrdir, revision_id=None):
603
"""Create a new line of development from the branch, into to_bzrdir.
605
revision_id: if not None, the revision history in the new branch will
606
be truncated to end with revision_id.
608
result = self._format.initialize(to_bzrdir)
609
self.copy_content_into(result, revision_id=revision_id)
610
result.set_parent(self.bzrdir.root_transport.base)
613
def _synchronize_history(self, destination, revision_id):
614
"""Synchronize last revision and revision history between branches.
616
This version is most efficient when the destination is also a
617
BzrBranch5, but works for BzrBranch6 as long as the revision
618
history is the true lefthand parent history, and all of the revisions
619
are in the destination's repository. If not, set_revision_history
622
:param destination: The branch to copy the history into
623
:param revision_id: The revision-id to truncate history at. May
624
be None to copy complete history.
626
new_history = self.revision_history()
627
if revision_id is not None:
628
revision_id = osutils.safe_revision_id(revision_id)
630
new_history = new_history[:new_history.index(revision_id) + 1]
632
rev = self.repository.get_revision(revision_id)
633
new_history = rev.get_history(self.repository)[1:]
634
destination.set_revision_history(new_history)
637
def copy_content_into(self, destination, revision_id=None):
638
"""Copy the content of self into destination.
640
revision_id: if not None, the revision history in the new branch will
641
be truncated to end with revision_id.
643
self._synchronize_history(destination, revision_id)
645
parent = self.get_parent()
646
except errors.InaccessibleParent, e:
647
mutter('parent was not accessible to copy: %s', e)
650
destination.set_parent(parent)
651
self.tags.merge_to(destination.tags)
655
"""Check consistency of the branch.
657
In particular this checks that revisions given in the revision-history
658
do actually match up in the revision graph, and that they're all
659
present in the repository.
661
Callers will typically also want to check the repository.
663
:return: A BranchCheckResult.
665
mainline_parent_id = None
666
for revision_id in self.revision_history():
668
revision = self.repository.get_revision(revision_id)
669
except errors.NoSuchRevision, e:
670
raise errors.BzrCheckError("mainline revision {%s} not in repository"
672
# In general the first entry on the revision history has no parents.
673
# But it's not illegal for it to have parents listed; this can happen
674
# in imports from Arch when the parents weren't reachable.
675
if mainline_parent_id is not None:
676
if mainline_parent_id not in revision.parent_ids:
677
raise errors.BzrCheckError("previous revision {%s} not listed among "
679
% (mainline_parent_id, revision_id))
680
mainline_parent_id = revision_id
681
return BranchCheckResult(self)
683
def _get_checkout_format(self):
684
"""Return the most suitable metadir for a checkout of this branch.
685
Weaves are used if this branch's repostory uses weaves.
687
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
688
from bzrlib.repofmt import weaverepo
689
format = bzrdir.BzrDirMetaFormat1()
690
format.repository_format = weaverepo.RepositoryFormat7()
692
format = self.repository.bzrdir.checkout_metadir()
693
format.set_branch_format(self._format)
696
def create_checkout(self, to_location, revision_id=None,
698
"""Create a checkout of a branch.
700
:param to_location: The url to produce the checkout at
701
:param revision_id: The revision to check out
702
:param lightweight: If True, produce a lightweight checkout, otherwise,
703
produce a bound branch (heavyweight checkout)
704
:return: The tree of the created checkout
706
t = transport.get_transport(to_location)
709
except errors.FileExists:
712
format = self._get_checkout_format()
713
checkout = format.initialize_on_transport(t)
714
BranchReferenceFormat().initialize(checkout, self)
716
format = self._get_checkout_format()
717
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
718
to_location, force_new_tree=False, format=format)
719
checkout = checkout_branch.bzrdir
720
checkout_branch.bind(self)
721
# pull up to the specified revision_id to set the initial
722
# branch tip correctly, and seed it with history.
723
checkout_branch.pull(self, stop_revision=revision_id)
724
tree = checkout.create_workingtree(revision_id)
725
basis_tree = tree.basis_tree()
726
basis_tree.lock_read()
728
for path, file_id in basis_tree.iter_references():
729
reference_parent = self.reference_parent(file_id, path)
730
reference_parent.create_checkout(tree.abspath(path),
731
basis_tree.get_reference_revision(file_id, path),
737
def reference_parent(self, file_id, path):
738
"""Return the parent branch for a tree-reference file_id
739
:param file_id: The file_id of the tree reference
740
:param path: The path of the file_id in the tree
741
:return: A branch associated with the file_id
743
# FIXME should provide multiple branches, based on config
744
return Branch.open(self.bzrdir.root_transport.clone(path).base)
746
def supports_tags(self):
747
return self._format.supports_tags()
750
class BranchFormat(object):
751
"""An encapsulation of the initialization and open routines for a format.
753
Formats provide three things:
754
* An initialization routine,
758
Formats are placed in an dict by their format string for reference
759
during branch opening. Its not required that these be instances, they
760
can be classes themselves with class methods - it simply depends on
761
whether state is needed for a given format or not.
763
Once a format is deprecated, just deprecate the initialize and open
764
methods on the format class. Do not deprecate the object, as the
765
object will be created every time regardless.
768
_default_format = None
769
"""The default format used for new branches."""
772
"""The known formats."""
775
def find_format(klass, a_bzrdir):
776
"""Return the format for the branch object in a_bzrdir."""
778
transport = a_bzrdir.get_branch_transport(None)
779
format_string = transport.get("format").read()
780
return klass._formats[format_string]
782
raise NotBranchError(path=transport.base)
784
raise errors.UnknownFormatError(format=format_string)
787
def get_default_format(klass):
788
"""Return the current default format."""
789
return klass._default_format
791
def get_format_string(self):
792
"""Return the ASCII format string that identifies this format."""
793
raise NotImplementedError(self.get_format_string)
795
def get_format_description(self):
796
"""Return the short format description for this format."""
797
raise NotImplementedError(self.get_format_description)
799
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
801
"""Initialize a branch in a bzrdir, with specified files
803
:param a_bzrdir: The bzrdir to initialize the branch in
804
:param utf8_files: The files to create as a list of
805
(filename, content) tuples
806
:param set_format: If True, set the format with
807
self.get_format_string. (BzrBranch4 has its format set
809
:return: a branch in this format
811
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
812
branch_transport = a_bzrdir.get_branch_transport(self)
814
'metadir': ('lock', lockdir.LockDir),
815
'branch4': ('branch-lock', lockable_files.TransportLock),
817
lock_name, lock_class = lock_map[lock_type]
818
control_files = lockable_files.LockableFiles(branch_transport,
819
lock_name, lock_class)
820
control_files.create_lock()
821
control_files.lock_write()
823
control_files.put_utf8('format', self.get_format_string())
825
for file, content in utf8_files:
826
control_files.put_utf8(file, content)
828
control_files.unlock()
829
return self.open(a_bzrdir, _found=True)
831
def initialize(self, a_bzrdir):
832
"""Create a branch of this format in a_bzrdir."""
833
raise NotImplementedError(self.initialize)
835
def is_supported(self):
836
"""Is this format supported?
838
Supported formats can be initialized and opened.
839
Unsupported formats may not support initialization or committing or
840
some other features depending on the reason for not being supported.
844
def open(self, a_bzrdir, _found=False):
845
"""Return the branch object for a_bzrdir
847
_found is a private parameter, do not use it. It is used to indicate
848
if format probing has already be done.
850
raise NotImplementedError(self.open)
853
def register_format(klass, format):
854
klass._formats[format.get_format_string()] = format
857
def set_default_format(klass, format):
858
klass._default_format = format
861
def unregister_format(klass, format):
862
assert klass._formats[format.get_format_string()] is format
863
del klass._formats[format.get_format_string()]
866
return self.get_format_string().rstrip()
868
def supports_tags(self):
869
"""True if this format supports tags stored in the branch"""
870
return False # by default
872
# XXX: Probably doesn't really belong here -- mbp 20070212
873
def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
875
branch_transport = a_bzrdir.get_branch_transport(self)
876
control_files = lockable_files.LockableFiles(branch_transport,
877
lock_filename, lock_class)
878
control_files.create_lock()
879
control_files.lock_write()
881
for filename, content in utf8_files:
882
control_files.put_utf8(filename, content)
884
control_files.unlock()
887
class BranchHooks(dict):
888
"""A dictionary mapping hook name to a list of callables for branch hooks.
890
e.g. ['set_rh'] Is the list of items to be called when the
891
set_revision_history function is invoked.
895
"""Create the default hooks.
897
These are all empty initially, because by default nothing should get
901
# Introduced in 0.15:
902
# invoked whenever the revision history has been set
903
# with set_revision_history. The api signature is
904
# (branch, revision_history), and the branch will
907
# invoked after a push operation completes.
908
# the api signature is
910
# containing the members
911
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
912
# where local is the local branch or None, master is the target
913
# master branch, and the rest should be self explanatory. The source
914
# is read locked and the target branches write locked. Source will
915
# be the local low-latency branch.
916
self['post_push'] = []
917
# invoked after a pull operation completes.
918
# the api signature is
920
# containing the members
921
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
922
# where local is the local branch or None, master is the target
923
# master branch, and the rest should be self explanatory. The source
924
# is read locked and the target branches write locked. The local
925
# branch is the low-latency branch.
926
self['post_pull'] = []
927
# invoked after a commit operation completes.
928
# the api signature is
929
# (local, master, old_revno, old_revid, new_revno, new_revid)
930
# old_revid is NULL_REVISION for the first commit to a branch.
931
self['post_commit'] = []
932
# invoked after a uncommit operation completes.
933
# the api signature is
934
# (local, master, old_revno, old_revid, new_revno, new_revid) where
935
# local is the local branch or None, master is the target branch,
936
# and an empty branch recieves new_revno of 0, new_revid of None.
937
self['post_uncommit'] = []
939
def install_hook(self, hook_name, a_callable):
940
"""Install a_callable in to the hook hook_name.
942
:param hook_name: A hook name. See the __init__ method of BranchHooks
943
for the complete list of hooks.
944
:param a_callable: The callable to be invoked when the hook triggers.
945
The exact signature will depend on the hook - see the __init__
946
method of BranchHooks for details on each hook.
949
self[hook_name].append(a_callable)
951
raise errors.UnknownHook('branch', hook_name)
954
# install the default hooks into the Branch class.
955
Branch.hooks = BranchHooks()
958
class BzrBranchFormat4(BranchFormat):
959
"""Bzr branch format 4.
962
- a revision-history file.
963
- a branch-lock lock file [ to be shared with the bzrdir ]
966
def get_format_description(self):
967
"""See BranchFormat.get_format_description()."""
968
return "Branch format 4"
970
def initialize(self, a_bzrdir):
971
"""Create a branch of this format in a_bzrdir."""
972
utf8_files = [('revision-history', ''),
975
return self._initialize_helper(a_bzrdir, utf8_files,
976
lock_type='branch4', set_format=False)
979
super(BzrBranchFormat4, self).__init__()
980
self._matchingbzrdir = bzrdir.BzrDirFormat6()
982
def open(self, a_bzrdir, _found=False):
983
"""Return the branch object for a_bzrdir
985
_found is a private parameter, do not use it. It is used to indicate
986
if format probing has already be done.
989
# we are being called directly and must probe.
990
raise NotImplementedError
991
return BzrBranch(_format=self,
992
_control_files=a_bzrdir._control_files,
994
_repository=a_bzrdir.open_repository())
997
return "Bazaar-NG branch format 4"
1000
class BzrBranchFormat5(BranchFormat):
1001
"""Bzr branch format 5.
1004
- a revision-history file.
1006
- a lock dir guarding the branch itself
1007
- all of this stored in a branch/ subdirectory
1008
- works with shared repositories.
1010
This format is new in bzr 0.8.
1013
def get_format_string(self):
1014
"""See BranchFormat.get_format_string()."""
1015
return "Bazaar-NG branch format 5\n"
1017
def get_format_description(self):
1018
"""See BranchFormat.get_format_description()."""
1019
return "Branch format 5"
1021
def initialize(self, a_bzrdir):
1022
"""Create a branch of this format in a_bzrdir."""
1023
utf8_files = [('revision-history', ''),
1024
('branch-name', ''),
1026
return self._initialize_helper(a_bzrdir, utf8_files)
1029
super(BzrBranchFormat5, self).__init__()
1030
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1032
def open(self, a_bzrdir, _found=False):
1033
"""Return the branch object for a_bzrdir
1035
_found is a private parameter, do not use it. It is used to indicate
1036
if format probing has already be done.
1039
format = BranchFormat.find_format(a_bzrdir)
1040
assert format.__class__ == self.__class__
1041
transport = a_bzrdir.get_branch_transport(None)
1042
control_files = lockable_files.LockableFiles(transport, 'lock',
1044
return BzrBranch5(_format=self,
1045
_control_files=control_files,
1047
_repository=a_bzrdir.find_repository())
1050
class BzrBranchFormat6(BzrBranchFormat5):
1051
"""Branch format with last-revision
1053
Unlike previous formats, this has no explicit revision history. Instead,
1054
this just stores the last-revision, and the left-hand history leading
1055
up to there is the history.
1057
This format was introduced in bzr 0.15
1060
def get_format_string(self):
1061
"""See BranchFormat.get_format_string()."""
1062
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1064
def get_format_description(self):
1065
"""See BranchFormat.get_format_description()."""
1066
return "Branch format 6"
1068
def initialize(self, a_bzrdir):
1069
"""Create a branch of this format in a_bzrdir."""
1070
utf8_files = [('last-revision', '0 null:\n'),
1071
('branch-name', ''),
1072
('branch.conf', ''),
1075
return self._initialize_helper(a_bzrdir, utf8_files)
1077
def open(self, a_bzrdir, _found=False):
1078
"""Return the branch object for a_bzrdir
1080
_found is a private parameter, do not use it. It is used to indicate
1081
if format probing has already be done.
1084
format = BranchFormat.find_format(a_bzrdir)
1085
assert format.__class__ == self.__class__
1086
transport = a_bzrdir.get_branch_transport(None)
1087
control_files = lockable_files.LockableFiles(transport, 'lock',
1089
return BzrBranch6(_format=self,
1090
_control_files=control_files,
1092
_repository=a_bzrdir.find_repository())
1094
def supports_tags(self):
1098
class BranchReferenceFormat(BranchFormat):
1099
"""Bzr branch reference format.
1101
Branch references are used in implementing checkouts, they
1102
act as an alias to the real branch which is at some other url.
1109
def get_format_string(self):
1110
"""See BranchFormat.get_format_string()."""
1111
return "Bazaar-NG Branch Reference Format 1\n"
1113
def get_format_description(self):
1114
"""See BranchFormat.get_format_description()."""
1115
return "Checkout reference format 1"
1117
def initialize(self, a_bzrdir, target_branch=None):
1118
"""Create a branch of this format in a_bzrdir."""
1119
if target_branch is None:
1120
# this format does not implement branch itself, thus the implicit
1121
# creation contract must see it as uninitializable
1122
raise errors.UninitializableFormat(self)
1123
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1124
branch_transport = a_bzrdir.get_branch_transport(self)
1125
branch_transport.put_bytes('location',
1126
target_branch.bzrdir.root_transport.base)
1127
branch_transport.put_bytes('format', self.get_format_string())
1128
return self.open(a_bzrdir, _found=True)
1131
super(BranchReferenceFormat, self).__init__()
1132
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1134
def _make_reference_clone_function(format, a_branch):
1135
"""Create a clone() routine for a branch dynamically."""
1136
def clone(to_bzrdir, revision_id=None):
1137
"""See Branch.clone()."""
1138
return format.initialize(to_bzrdir, a_branch)
1139
# cannot obey revision_id limits when cloning a reference ...
1140
# FIXME RBC 20060210 either nuke revision_id for clone, or
1141
# emit some sort of warning/error to the caller ?!
1144
def open(self, a_bzrdir, _found=False):
1145
"""Return the branch that the branch reference in a_bzrdir points at.
1147
_found is a private parameter, do not use it. It is used to indicate
1148
if format probing has already be done.
1151
format = BranchFormat.find_format(a_bzrdir)
1152
assert format.__class__ == self.__class__
1153
transport = a_bzrdir.get_branch_transport(None)
1154
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
1155
result = real_bzrdir.open_branch()
1156
# this changes the behaviour of result.clone to create a new reference
1157
# rather than a copy of the content of the branch.
1158
# I did not use a proxy object because that needs much more extensive
1159
# testing, and we are only changing one behaviour at the moment.
1160
# If we decide to alter more behaviours - i.e. the implicit nickname
1161
# then this should be refactored to introduce a tested proxy branch
1162
# and a subclass of that for use in overriding clone() and ....
1164
result.clone = self._make_reference_clone_function(result)
1168
# formats which have no format string are not discoverable
1169
# and not independently creatable, so are not registered.
1170
__default_format = BzrBranchFormat5()
1171
BranchFormat.register_format(__default_format)
1172
BranchFormat.register_format(BranchReferenceFormat())
1173
BranchFormat.register_format(BzrBranchFormat6())
1174
BranchFormat.set_default_format(__default_format)
1175
_legacy_formats = [BzrBranchFormat4(),
1178
class BzrBranch(Branch):
1179
"""A branch stored in the actual filesystem.
1181
Note that it's "local" in the context of the filesystem; it doesn't
1182
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1183
it's writable, and can be accessed via the normal filesystem API.
1186
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
1187
relax_version_check=DEPRECATED_PARAMETER, _format=None,
1188
_control_files=None, a_bzrdir=None, _repository=None):
1189
"""Create new branch object at a particular location.
1191
transport -- A Transport object, defining how to access files.
1193
init -- If True, create new control files in a previously
1194
unversioned directory. If False, the branch must already
1197
relax_version_check -- If true, the usual check for the branch
1198
version is not applied. This is intended only for
1199
upgrade/recovery type use; it's not guaranteed that
1200
all operations will work on old format branches.
1202
Branch.__init__(self)
1203
if a_bzrdir is None:
1204
self.bzrdir = bzrdir.BzrDir.open(transport.base)
1206
self.bzrdir = a_bzrdir
1207
# self._transport used to point to the directory containing the
1208
# control directory, but was not used - now it's just the transport
1209
# for the branch control files. mbp 20070212
1210
self._base = self.bzrdir.transport.clone('..').base
1211
self._format = _format
1212
if _control_files is None:
1213
raise ValueError('BzrBranch _control_files is None')
1214
self.control_files = _control_files
1215
self._transport = _control_files._transport
1216
if deprecated_passed(init):
1217
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1218
"deprecated as of bzr 0.8. Please use Branch.create().",
1222
# this is slower than before deprecation, oh well never mind.
1223
# -> its deprecated.
1224
self._initialize(transport.base)
1225
self._check_format(_format)
1226
if deprecated_passed(relax_version_check):
1227
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1228
"relax_version_check parameter is deprecated as of bzr 0.8. "
1229
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1233
if (not relax_version_check
1234
and not self._format.is_supported()):
1235
raise errors.UnsupportedFormatError(format=fmt)
1236
if deprecated_passed(transport):
1237
warn("BzrBranch.__init__(transport=XXX...): The transport "
1238
"parameter is deprecated as of bzr 0.8. "
1239
"Please use Branch.open, or bzrdir.open_branch().",
1242
self.repository = _repository
1245
return '%s(%r)' % (self.__class__.__name__, self.base)
1249
def _get_base(self):
1250
"""Returns the directory containing the control directory."""
1253
base = property(_get_base, doc="The URL for the root of this branch.")
1255
def _finish_transaction(self):
1256
"""Exit the current transaction."""
1257
return self.control_files._finish_transaction()
1259
def get_transaction(self):
1260
"""Return the current active transaction.
1262
If no transaction is active, this returns a passthrough object
1263
for which all data is immediately flushed and no caching happens.
1265
# this is an explicit function so that we can do tricky stuff
1266
# when the storage in rev_storage is elsewhere.
1267
# we probably need to hook the two 'lock a location' and
1268
# 'have a transaction' together more delicately, so that
1269
# we can have two locks (branch and storage) and one transaction
1270
# ... and finishing the transaction unlocks both, but unlocking
1271
# does not. - RBC 20051121
1272
return self.control_files.get_transaction()
1274
def _set_transaction(self, transaction):
1275
"""Set a new active transaction."""
1276
return self.control_files._set_transaction(transaction)
1278
def abspath(self, name):
1279
"""See Branch.abspath."""
1280
return self.control_files._transport.abspath(name)
1282
def _check_format(self, format):
1283
"""Identify the branch format if needed.
1285
The format is stored as a reference to the format object in
1286
self._format for code that needs to check it later.
1288
The format parameter is either None or the branch format class
1289
used to open this branch.
1291
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
1294
format = BranchFormat.find_format(self.bzrdir)
1295
self._format = format
1296
mutter("got branch format %s", self._format)
1299
def get_root_id(self):
1300
"""See Branch.get_root_id."""
1301
tree = self.repository.revision_tree(self.last_revision())
1302
return tree.inventory.root.file_id
1304
def is_locked(self):
1305
return self.control_files.is_locked()
1307
def lock_write(self):
1308
self.repository.lock_write()
1310
self.control_files.lock_write()
1312
self.repository.unlock()
1315
def lock_read(self):
1316
self.repository.lock_read()
1318
self.control_files.lock_read()
1320
self.repository.unlock()
1324
# TODO: test for failed two phase locks. This is known broken.
1326
self.control_files.unlock()
1328
self.repository.unlock()
1330
def peek_lock_mode(self):
1331
if self.control_files._lock_count == 0:
1334
return self.control_files._lock_mode
1336
def get_physical_lock_status(self):
1337
return self.control_files.get_physical_lock_status()
1340
def print_file(self, file, revision_id):
1341
"""See Branch.print_file."""
1342
return self.repository.print_file(file, revision_id)
1345
def append_revision(self, *revision_ids):
1346
"""See Branch.append_revision."""
1347
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1348
for revision_id in revision_ids:
1349
_mod_revision.check_not_reserved_id(revision_id)
1350
mutter("add {%s} to revision-history" % revision_id)
1351
rev_history = self.revision_history()
1352
rev_history.extend(revision_ids)
1353
self.set_revision_history(rev_history)
1355
def _write_revision_history(self, history):
1356
"""Factored out of set_revision_history.
1358
This performs the actual writing to disk.
1359
It is intended to be called by BzrBranch5.set_revision_history."""
1360
self.control_files.put_bytes(
1361
'revision-history', '\n'.join(history))
1364
def set_revision_history(self, rev_history):
1365
"""See Branch.set_revision_history."""
1366
rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1367
self._write_revision_history(rev_history)
1368
transaction = self.get_transaction()
1369
history = transaction.map.find_revision_history()
1370
if history is not None:
1371
# update the revision history in the identity map.
1372
history[:] = list(rev_history)
1373
# this call is disabled because revision_history is
1374
# not really an object yet, and the transaction is for objects.
1375
# transaction.register_dirty(history)
1377
transaction.map.add_revision_history(rev_history)
1378
# this call is disabled because revision_history is
1379
# not really an object yet, and the transaction is for objects.
1380
# transaction.register_clean(history)
1381
for hook in Branch.hooks['set_rh']:
1382
hook(self, rev_history)
1385
def set_last_revision_info(self, revno, revision_id):
1386
revision_id = osutils.safe_revision_id(revision_id)
1387
history = self._lefthand_history(revision_id)
1388
assert len(history) == revno, '%d != %d' % (len(history), revno)
1389
self.set_revision_history(history)
1391
def _gen_revision_history(self):
1392
history = self.control_files.get('revision-history').read().split('\n')
1393
if history[-1:] == ['']:
1394
# There shouldn't be a trailing newline, but just in case.
1399
def revision_history(self):
1400
"""See Branch.revision_history."""
1401
transaction = self.get_transaction()
1402
history = transaction.map.find_revision_history()
1403
if history is not None:
1404
# mutter("cache hit for revision-history in %s", self)
1405
return list(history)
1406
history = self._gen_revision_history()
1407
transaction.map.add_revision_history(history)
1408
# this call is disabled because revision_history is
1409
# not really an object yet, and the transaction is for objects.
1410
# transaction.register_clean(history, precious=True)
1411
return list(history)
1413
def _lefthand_history(self, revision_id, last_rev=None,
1415
# stop_revision must be a descendant of last_revision
1416
stop_graph = self.repository.get_revision_graph(revision_id)
1417
if last_rev is not None and last_rev not in stop_graph:
1418
# our previous tip is not merged into stop_revision
1419
raise errors.DivergedBranches(self, other_branch)
1420
# make a new revision history from the graph
1421
current_rev_id = revision_id
1423
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1424
new_history.append(current_rev_id)
1425
current_rev_id_parents = stop_graph[current_rev_id]
1427
current_rev_id = current_rev_id_parents[0]
1429
current_rev_id = None
1430
new_history.reverse()
1434
def generate_revision_history(self, revision_id, last_rev=None,
1436
"""Create a new revision history that will finish with revision_id.
1438
:param revision_id: the new tip to use.
1439
:param last_rev: The previous last_revision. If not None, then this
1440
must be a ancestory of revision_id, or DivergedBranches is raised.
1441
:param other_branch: The other branch that DivergedBranches should
1442
raise with respect to.
1444
revision_id = osutils.safe_revision_id(revision_id)
1445
self.set_revision_history(self._lefthand_history(revision_id,
1446
last_rev, other_branch))
1449
def update_revisions(self, other, stop_revision=None):
1450
"""See Branch.update_revisions."""
1453
if stop_revision is None:
1454
stop_revision = other.last_revision()
1455
if stop_revision is None:
1456
# if there are no commits, we're done.
1459
stop_revision = osutils.safe_revision_id(stop_revision)
1460
# whats the current last revision, before we fetch [and change it
1462
last_rev = self.last_revision()
1463
# we fetch here regardless of whether we need to so that we pickup
1465
self.fetch(other, stop_revision)
1466
my_ancestry = self.repository.get_ancestry(last_rev)
1467
if stop_revision in my_ancestry:
1468
# last_revision is a descendant of stop_revision
1470
self.generate_revision_history(stop_revision, last_rev=last_rev,
1475
def basis_tree(self):
1476
"""See Branch.basis_tree."""
1477
return self.repository.revision_tree(self.last_revision())
1479
@deprecated_method(zero_eight)
1480
def working_tree(self):
1481
"""Create a Working tree object for this branch."""
1483
from bzrlib.transport.local import LocalTransport
1484
if (self.base.find('://') != -1 or
1485
not isinstance(self._transport, LocalTransport)):
1486
raise NoWorkingTree(self.base)
1487
return self.bzrdir.open_workingtree()
1490
def pull(self, source, overwrite=False, stop_revision=None,
1491
_hook_master=None, _run_hooks=True):
1494
:param _hook_master: Private parameter - set the branch to
1495
be supplied as the master to push hooks.
1496
:param _run_hooks: Private parameter - allow disabling of
1497
hooks, used when pushing to a master branch.
1499
result = PullResult()
1500
result.source_branch = source
1501
result.target_branch = self
1504
result.old_revno, result.old_revid = self.last_revision_info()
1506
self.update_revisions(source, stop_revision)
1507
except DivergedBranches:
1511
if stop_revision is None:
1512
stop_revision = source.last_revision()
1513
self.generate_revision_history(stop_revision)
1514
result.tag_conflicts = source.tags.merge_to(self.tags)
1515
result.new_revno, result.new_revid = self.last_revision_info()
1517
result.master_branch = _hook_master
1518
result.local_branch = self
1520
result.master_branch = self
1521
result.local_branch = None
1523
for hook in Branch.hooks['post_pull']:
1529
def _get_parent_location(self):
1530
_locs = ['parent', 'pull', 'x-pull']
1533
return self.control_files.get(l).read().strip('\n')
1539
def push(self, target, overwrite=False, stop_revision=None,
1540
_hook_master=None, _run_hooks=True):
1543
:param _hook_master: Private parameter - set the branch to
1544
be supplied as the master to push hooks.
1545
:param _run_hooks: Private parameter - allow disabling of
1546
hooks, used when pushing to a master branch.
1548
result = PushResult()
1549
result.source_branch = self
1550
result.target_branch = target
1553
result.old_revno, result.old_revid = target.last_revision_info()
1555
target.update_revisions(self, stop_revision)
1556
except DivergedBranches:
1560
target.set_revision_history(self.revision_history())
1561
result.tag_conflicts = self.tags.merge_to(target.tags)
1562
result.new_revno, result.new_revid = target.last_revision_info()
1564
result.master_branch = _hook_master
1565
result.local_branch = target
1567
result.master_branch = target
1568
result.local_branch = None
1570
for hook in Branch.hooks['post_push']:
1576
def get_parent(self):
1577
"""See Branch.get_parent."""
1579
assert self.base[-1] == '/'
1580
parent = self._get_parent_location()
1583
# This is an old-format absolute path to a local branch
1584
# turn it into a url
1585
if parent.startswith('/'):
1586
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1588
return urlutils.join(self.base[:-1], parent)
1589
except errors.InvalidURLJoin, e:
1590
raise errors.InaccessibleParent(parent, self.base)
1592
def get_push_location(self):
1593
"""See Branch.get_push_location."""
1594
push_loc = self.get_config().get_user_option('push_location')
1597
def set_push_location(self, location):
1598
"""See Branch.set_push_location."""
1599
self.get_config().set_user_option(
1600
'push_location', location,
1601
store=_mod_config.STORE_LOCATION_NORECURSE)
1604
def set_parent(self, url):
1605
"""See Branch.set_parent."""
1606
# TODO: Maybe delete old location files?
1607
# URLs should never be unicode, even on the local fs,
1608
# FIXUP this and get_parent in a future branch format bump:
1609
# read and rewrite the file, and have the new format code read
1610
# using .get not .get_utf8. RBC 20060125
1612
if isinstance(url, unicode):
1614
url = url.encode('ascii')
1615
except UnicodeEncodeError:
1616
raise bzrlib.errors.InvalidURL(url,
1617
"Urls must be 7-bit ascii, "
1618
"use bzrlib.urlutils.escape")
1619
url = urlutils.relative_url(self.base, url)
1620
self._set_parent_location(url)
1622
def _set_parent_location(self, url):
1624
self.control_files._transport.delete('parent')
1626
assert isinstance(url, str)
1627
self.control_files.put_bytes('parent', url + '\n')
1629
@deprecated_function(zero_nine)
1630
def tree_config(self):
1631
"""DEPRECATED; call get_config instead.
1632
TreeConfig has become part of BranchConfig."""
1633
return TreeConfig(self)
1636
class BzrBranch5(BzrBranch):
1637
"""A format 5 branch. This supports new features over plan branches.
1639
It has support for a master_branch which is the data for bound branches.
1647
super(BzrBranch5, self).__init__(_format=_format,
1648
_control_files=_control_files,
1650
_repository=_repository)
1653
def pull(self, source, overwrite=False, stop_revision=None,
1655
"""Extends branch.pull to be bound branch aware.
1657
:param _run_hooks: Private parameter used to force hook running
1658
off during bound branch double-pushing.
1660
bound_location = self.get_bound_location()
1661
master_branch = None
1662
if bound_location and source.base != bound_location:
1663
# not pulling from master, so we need to update master.
1664
master_branch = self.get_master_branch()
1665
master_branch.lock_write()
1668
# pull from source into master.
1669
master_branch.pull(source, overwrite, stop_revision,
1671
return super(BzrBranch5, self).pull(source, overwrite,
1672
stop_revision, _hook_master=master_branch,
1673
_run_hooks=_run_hooks)
1676
master_branch.unlock()
1679
def push(self, target, overwrite=False, stop_revision=None):
1680
"""Updates branch.push to be bound branch aware."""
1681
bound_location = target.get_bound_location()
1682
master_branch = None
1683
if bound_location and target.base != bound_location:
1684
# not pushing to master, so we need to update master.
1685
master_branch = target.get_master_branch()
1686
master_branch.lock_write()
1689
# push into the master from this branch.
1690
super(BzrBranch5, self).push(master_branch, overwrite,
1691
stop_revision, _run_hooks=False)
1692
# and push into the target branch from this. Note that we push from
1693
# this branch again, because its considered the highest bandwidth
1695
return super(BzrBranch5, self).push(target, overwrite,
1696
stop_revision, _hook_master=master_branch)
1699
master_branch.unlock()
1701
def get_bound_location(self):
1703
return self.control_files.get_utf8('bound').read()[:-1]
1704
except errors.NoSuchFile:
1708
def get_master_branch(self):
1709
"""Return the branch we are bound to.
1711
:return: Either a Branch, or None
1713
This could memoise the branch, but if thats done
1714
it must be revalidated on each new lock.
1715
So for now we just don't memoise it.
1716
# RBC 20060304 review this decision.
1718
bound_loc = self.get_bound_location()
1722
return Branch.open(bound_loc)
1723
except (errors.NotBranchError, errors.ConnectionError), e:
1724
raise errors.BoundBranchConnectionFailure(
1728
def set_bound_location(self, location):
1729
"""Set the target where this branch is bound to.
1731
:param location: URL to the target branch
1734
self.control_files.put_utf8('bound', location+'\n')
1737
self.control_files._transport.delete('bound')
762
def gen_file_id(name):
763
"""Return new file id.
765
This should probably generate proper UUIDs, but for the moment we
766
cope with just randomness because running uuidgen every time is
768
idx = name.rfind('/')
770
name = name[idx+1 : ]
771
idx = name.rfind('\\')
773
name = name[idx+1 : ]
775
name = name.lstrip('.')
777
s = hexlify(rand_bytes(8))
778
return '-'.join((name, compact_date(time.time()), s))
1743
def bind(self, other):
1744
"""Bind this branch to the branch other.
1746
This does not push or pull data between the branches, though it does
1747
check for divergence to raise an error when the branches are not
1748
either the same, or one a prefix of the other. That behaviour may not
1749
be useful, so that check may be removed in future.
1751
:param other: The branch to bind to
1754
# TODO: jam 20051230 Consider checking if the target is bound
1755
# It is debatable whether you should be able to bind to
1756
# a branch which is itself bound.
1757
# Committing is obviously forbidden,
1758
# but binding itself may not be.
1759
# Since we *have* to check at commit time, we don't
1760
# *need* to check here
1762
# we want to raise diverged if:
1763
# last_rev is not in the other_last_rev history, AND
1764
# other_last_rev is not in our history, and do it without pulling
1766
last_rev = self.last_revision()
1767
if last_rev is not None:
1770
other_last_rev = other.last_revision()
1771
if other_last_rev is not None:
1772
# neither branch is new, we have to do some work to
1773
# ascertain diversion.
1774
remote_graph = other.repository.get_revision_graph(
1776
local_graph = self.repository.get_revision_graph(last_rev)
1777
if (last_rev not in remote_graph and
1778
other_last_rev not in local_graph):
1779
raise errors.DivergedBranches(self, other)
1782
self.set_bound_location(other.base)
1786
"""If bound, unbind"""
1787
return self.set_bound_location(None)
1791
"""Synchronise this branch with the master branch if any.
1793
:return: None or the last_revision that was pivoted out during the
1796
master = self.get_master_branch()
1797
if master is not None:
1798
old_tip = self.last_revision()
1799
self.pull(master, overwrite=True)
1800
if old_tip in self.repository.get_ancestry(self.last_revision()):
1806
class BzrBranchExperimental(BzrBranch5):
1807
"""Bzr experimental branch format
1810
- a revision-history file.
1812
- a lock dir guarding the branch itself
1813
- all of this stored in a branch/ subdirectory
1814
- works with shared repositories.
1815
- a tag dictionary in the branch
1817
This format is new in bzr 0.15, but shouldn't be used for real data,
1820
This class acts as it's own BranchFormat.
1823
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1826
def get_format_string(cls):
1827
"""See BranchFormat.get_format_string()."""
1828
return "Bazaar-NG branch format experimental\n"
1831
def get_format_description(cls):
1832
"""See BranchFormat.get_format_description()."""
1833
return "Experimental branch format"
1836
def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1838
branch_transport = a_bzrdir.get_branch_transport(cls)
1839
control_files = lockable_files.LockableFiles(branch_transport,
1840
lock_filename, lock_class)
1841
control_files.create_lock()
1842
control_files.lock_write()
1844
for filename, content in utf8_files:
1845
control_files.put_utf8(filename, content)
1847
control_files.unlock()
1850
def initialize(cls, a_bzrdir):
1851
"""Create a branch of this format in a_bzrdir."""
1852
utf8_files = [('format', cls.get_format_string()),
1853
('revision-history', ''),
1854
('branch-name', ''),
1857
cls._initialize_control_files(a_bzrdir, utf8_files,
1858
'lock', lockdir.LockDir)
1859
return cls.open(a_bzrdir, _found=True)
1862
def open(cls, a_bzrdir, _found=False):
1863
"""Return the branch object for a_bzrdir
1865
_found is a private parameter, do not use it. It is used to indicate
1866
if format probing has already be done.
1869
format = BranchFormat.find_format(a_bzrdir)
1870
assert format.__class__ == cls
1871
transport = a_bzrdir.get_branch_transport(None)
1872
control_files = lockable_files.LockableFiles(transport, 'lock',
1874
return cls(_format=cls,
1875
_control_files=control_files,
1877
_repository=a_bzrdir.find_repository())
1880
def is_supported(cls):
1883
def _make_tags(self):
1884
return BasicTags(self)
1887
def supports_tags(cls):
1891
BranchFormat.register_format(BzrBranchExperimental)
1894
class BzrBranch6(BzrBranch5):
1897
def last_revision_info(self):
1898
revision_string = self.control_files.get('last-revision').read()
1899
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1900
revision_id = cache_utf8.get_cached_utf8(revision_id)
1902
return revno, revision_id
1904
def last_revision(self):
1905
"""Return last revision id, or None"""
1906
revision_id = self.last_revision_info()[1]
1907
if revision_id == _mod_revision.NULL_REVISION:
1911
def _write_last_revision_info(self, revno, revision_id):
1912
"""Simply write out the revision id, with no checks.
1914
Use set_last_revision_info to perform this safely.
1916
Does not update the revision_history cache.
1917
Intended to be called by set_last_revision_info and
1918
_write_revision_history.
1920
if revision_id is None:
1921
revision_id = 'null:'
1922
out_string = '%d %s\n' % (revno, revision_id)
1923
self.control_files.put_bytes('last-revision', out_string)
1926
def set_last_revision_info(self, revno, revision_id):
1927
revision_id = osutils.safe_revision_id(revision_id)
1928
if self._get_append_revisions_only():
1929
self._check_history_violation(revision_id)
1930
self._write_last_revision_info(revno, revision_id)
1931
transaction = self.get_transaction()
1932
cached_history = transaction.map.find_revision_history()
1933
if cached_history is not None:
1934
transaction.map.remove_object(cached_history)
1936
def _check_history_violation(self, revision_id):
1937
last_revision = self.last_revision()
1938
if last_revision is None:
1940
if last_revision not in self._lefthand_history(revision_id):
1941
raise errors.AppendRevisionsOnlyViolation(self.base)
1943
def _gen_revision_history(self):
1944
"""Generate the revision history from last revision
1946
history = list(self.repository.iter_reverse_revision_history(
1947
self.last_revision()))
1951
def _write_revision_history(self, history):
1952
"""Factored out of set_revision_history.
1954
This performs the actual writing to disk, with format-specific checks.
1955
It is intended to be called by BzrBranch5.set_revision_history.
1957
if len(history) == 0:
1958
last_revision = 'null:'
1960
if history != self._lefthand_history(history[-1]):
1961
raise errors.NotLefthandHistory(history)
1962
last_revision = history[-1]
1963
if self._get_append_revisions_only():
1964
self._check_history_violation(last_revision)
1965
self._write_last_revision_info(len(history), last_revision)
1968
def append_revision(self, *revision_ids):
1969
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1970
if len(revision_ids) == 0:
1972
prev_revno, prev_revision = self.last_revision_info()
1973
for revision in self.repository.get_revisions(revision_ids):
1974
if prev_revision == _mod_revision.NULL_REVISION:
1975
if revision.parent_ids != []:
1976
raise errors.NotLeftParentDescendant(self, prev_revision,
1977
revision.revision_id)
1979
if revision.parent_ids[0] != prev_revision:
1980
raise errors.NotLeftParentDescendant(self, prev_revision,
1981
revision.revision_id)
1982
prev_revision = revision.revision_id
1983
self.set_last_revision_info(prev_revno + len(revision_ids),
1987
def _set_parent_location(self, url):
1988
"""Set the parent branch"""
1989
self._set_config_location('parent_location', url, make_relative=True)
1992
def _get_parent_location(self):
1993
"""Set the parent branch"""
1994
return self._get_config_location('parent_location')
1996
def set_push_location(self, location):
1997
"""See Branch.set_push_location."""
1998
self._set_config_location('push_location', location)
2000
def set_bound_location(self, location):
2001
"""See Branch.set_push_location."""
2003
config = self.get_config()
2004
if location is None:
2005
if config.get_user_option('bound') != 'True':
2008
config.set_user_option('bound', 'False')
2011
self._set_config_location('bound_location', location,
2013
config.set_user_option('bound', 'True')
2016
def _get_bound_location(self, bound):
2017
"""Return the bound location in the config file.
2019
Return None if the bound parameter does not match"""
2020
config = self.get_config()
2021
config_bound = (config.get_user_option('bound') == 'True')
2022
if config_bound != bound:
2024
return self._get_config_location('bound_location', config=config)
2026
def get_bound_location(self):
2027
"""See Branch.set_push_location."""
2028
return self._get_bound_location(True)
2030
def get_old_bound_location(self):
2031
"""See Branch.get_old_bound_location"""
2032
return self._get_bound_location(False)
2034
def set_append_revisions_only(self, enabled):
2039
self.get_config().set_user_option('append_revisions_only', value)
2041
def _get_append_revisions_only(self):
2042
value = self.get_config().get_user_option('append_revisions_only')
2043
return value == 'True'
2045
def _synchronize_history(self, destination, revision_id):
2046
"""Synchronize last revision and revision history between branches.
2048
This version is most efficient when the destination is also a
2049
BzrBranch6, but works for BzrBranch5, as long as the destination's
2050
repository contains all the lefthand ancestors of the intended
2051
last_revision. If not, set_last_revision_info will fail.
2053
:param destination: The branch to copy the history into
2054
:param revision_id: The revision-id to truncate history at. May
2055
be None to copy complete history.
2057
if revision_id is None:
2058
revno, revision_id = self.last_revision_info()
2060
revno = self.revision_id_to_revno(revision_id)
2061
destination.set_last_revision_info(revno, revision_id)
2063
def _make_tags(self):
2064
return BasicTags(self)
2067
class BranchTestProviderAdapter(object):
2068
"""A tool to generate a suite testing multiple branch formats at once.
2070
This is done by copying the test once for each transport and injecting
2071
the transport_server, transport_readonly_server, and branch_format
2072
classes into each copy. Each copy is also given a new id() to make it
2076
def __init__(self, transport_server, transport_readonly_server, formats):
2077
self._transport_server = transport_server
2078
self._transport_readonly_server = transport_readonly_server
2079
self._formats = formats
2081
def adapt(self, test):
2082
result = TestSuite()
2083
for branch_format, bzrdir_format in self._formats:
2084
new_test = deepcopy(test)
2085
new_test.transport_server = self._transport_server
2086
new_test.transport_readonly_server = self._transport_readonly_server
2087
new_test.bzrdir_format = bzrdir_format
2088
new_test.branch_format = branch_format
2089
def make_new_test_id():
2090
# the format can be either a class or an instance
2091
name = getattr(branch_format, '__name__',
2092
branch_format.__class__.__name__)
2093
new_id = "%s(%s)" % (new_test.id(), name)
2094
return lambda: new_id
2095
new_test.id = make_new_test_id()
2096
result.addTest(new_test)
2100
######################################################################
2101
# results of operations
2104
class _Result(object):
2106
def _show_tag_conficts(self, to_file):
2107
if not getattr(self, 'tag_conflicts', None):
2109
to_file.write('Conflicting tags:\n')
2110
for name, value1, value2 in self.tag_conflicts:
2111
to_file.write(' %s\n' % (name, ))
2114
class PullResult(_Result):
2115
"""Result of a Branch.pull operation.
2117
:ivar old_revno: Revision number before pull.
2118
:ivar new_revno: Revision number after pull.
2119
:ivar old_revid: Tip revision id before pull.
2120
:ivar new_revid: Tip revision id after pull.
2121
:ivar source_branch: Source (local) branch object.
2122
:ivar master_branch: Master branch of the target, or None.
2123
:ivar target_branch: Target/destination branch object.
2127
# DEPRECATED: pull used to return the change in revno
2128
return self.new_revno - self.old_revno
2130
def report(self, to_file):
2131
if self.old_revid == self.new_revid:
2132
to_file.write('No revisions to pull.\n')
2134
to_file.write('Now on revision %d.\n' % self.new_revno)
2135
self._show_tag_conficts(to_file)
2138
class PushResult(_Result):
2139
"""Result of a Branch.push operation.
2141
:ivar old_revno: Revision number before push.
2142
:ivar new_revno: Revision number after push.
2143
:ivar old_revid: Tip revision id before push.
2144
:ivar new_revid: Tip revision id after push.
2145
:ivar source_branch: Source branch object.
2146
:ivar master_branch: Master branch of the target, or None.
2147
:ivar target_branch: Target/destination branch object.
2151
# DEPRECATED: push used to return the change in revno
2152
return self.new_revno - self.old_revno
2154
def report(self, to_file):
2155
"""Write a human-readable description of the result."""
2156
if self.old_revid == self.new_revid:
2157
to_file.write('No new revisions to push.\n')
2159
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2160
self._show_tag_conficts(to_file)
2163
class BranchCheckResult(object):
2164
"""Results of checking branch consistency.
2169
def __init__(self, branch):
2170
self.branch = branch
2172
def report_results(self, verbose):
2173
"""Report the check results via trace.note.
2175
:param verbose: Requests more detailed display of what was checked,
2178
note('checked branch %s format %s',
2180
self.branch._format)
2183
class Converter5to6(object):
2184
"""Perform an in-place upgrade of format 5 to format 6"""
2186
def convert(self, branch):
2187
# Data for 5 and 6 can peacefully coexist.
2188
format = BzrBranchFormat6()
2189
new_branch = format.open(branch.bzrdir, _found=True)
2191
# Copy source data into target
2192
new_branch.set_last_revision_info(*branch.last_revision_info())
2193
new_branch.set_parent(branch.get_parent())
2194
new_branch.set_bound_location(branch.get_bound_location())
2195
new_branch.set_push_location(branch.get_push_location())
2197
# New branch has no tags by default
2198
new_branch.tags._set_tag_dict({})
2200
# Copying done; now update target format
2201
new_branch.control_files.put_utf8('format',
2202
format.get_format_string())
2204
# Clean up old files
2205
new_branch.control_files._transport.delete('revision-history')
2207
branch.set_parent(None)
2210
branch.set_bound_location(None)