14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
17
19
# Original author: David Allouche
19
from bzrlib import errors, merge, revision
20
from bzrlib.branch import Branch
21
from bzrlib.trace import note
27
from .branch import Branch
28
from .i18n import gettext
29
from .trace import note
24
32
def _run_post_switch_hooks(control_dir, to_branch, force, revision_id):
25
from bzrlib.branch import SwitchHookParams
33
from .branch import SwitchHookParams
26
34
hooks = Branch.hooks['post_switch']
33
def switch(control_dir, to_branch, force=False, quiet=False, revision_id=None):
42
def switch(control_dir, to_branch, force=False, quiet=False, revision_id=None,
43
store_uncommitted=False, possible_transports=None):
34
44
"""Switch the branch associated with a checkout.
36
:param control_dir: BzrDir of the checkout to change
46
:param control_dir: ControlDir of the checkout to change
37
47
:param to_branch: branch that the checkout is to reference
38
48
:param force: skip the check for local commits in a heavy checkout
39
49
:param revision_id: revision ID to switch to.
41
_check_pending_merges(control_dir, force)
43
source_repository = control_dir.open_branch().repository
44
except errors.NotBranchError:
45
source_repository = to_branch.repository
46
_set_branch_location(control_dir, to_branch, force)
47
tree = control_dir.open_workingtree()
48
_update(tree, source_repository, quiet, revision_id)
49
_run_post_switch_hooks(control_dir, to_branch, force, revision_id)
51
def _check_pending_merges(control, force=False):
52
"""Check that there are no outstanding pending merges before switching.
54
:param control: BzrDir of the branch to check
57
tree = control.open_workingtree()
58
except errors.NotBranchError, ex:
50
:param store_uncommitted: If True, store uncommitted changes in the
54
tree = control_dir.open_workingtree()
55
except errors.NotBranchError as ex:
59
56
# Lightweight checkout and branch is no longer there
57
if not force or store_uncommitted:
64
# XXX: Should the tree be locked for get_parent_ids?
65
existing_pending_merges = tree.get_parent_ids()[1:]
66
if len(existing_pending_merges) > 0:
67
raise errors.BzrCommandError('Pending merges must be '
68
'committed or reverted before using switch.')
71
def _set_branch_location(control, to_branch, force=False):
62
if store_uncommitted or tree.branch.get_bound_location():
65
tree.lock_tree_write()
68
parent_ids = tree.get_parent_ids()
69
if len(parent_ids) > 1:
70
raise errors.BzrCommandError(
71
gettext('Pending merges must be '
72
'committed or reverted before using switch.'))
75
tree.store_uncommitted()
77
source_repository = to_branch.repository
78
base_revision_id = None
80
source_repository = tree.branch.repository
81
# Attempt to retrieve the base_revision_id now, since some
82
# working tree formats (i.e. git) don't have their own
83
# last_revision but just use that of the currently active branch.
84
base_revision_id = tree.last_revision()
88
with to_branch.lock_read():
89
_set_branch_location(control_dir, to_branch, tree.branch if tree else None, force)
90
tree = control_dir.open_workingtree()
94
tree.lock_tree_write()
96
if base_revision_id is None:
97
# If we couldn't get to the tree's last_revision earlier, perhaps
99
base_revision_id = tree.last_revision()
100
if revision_id is None:
101
revision_id = to_branch.last_revision()
102
if base_revision_id == revision_id:
104
note(gettext("Tree is up to date at revision %d."),
107
base_tree = source_repository.revision_tree(base_revision_id)
108
target_tree = to_branch.repository.revision_tree(revision_id)
109
merge.Merge3Merger(tree, tree, base_tree, target_tree)
110
tree.set_last_revision(revision_id)
112
note(gettext('Updated to revision %d.') % to_branch.revno())
113
if store_uncommitted:
114
tree.restore_uncommitted()
115
_run_post_switch_hooks(control_dir, to_branch, force, revision_id)
120
def _set_branch_location(control, to_branch, current_branch, force=False):
72
121
"""Set location value of a branch reference.
74
:param control: BzrDir of the checkout to change
123
:param control: ControlDir of the checkout to change
75
124
:param to_branch: branch that the checkout is to reference
76
125
:param force: skip the check for local commits in a heavy checkout
78
127
branch_format = control.find_branch_format()
79
128
if branch_format.get_reference(control) is not None:
80
129
# Lightweight checkout: update the branch reference
81
branch_format.set_reference(control, to_branch)
130
branch_format.set_reference(control, None, to_branch)
83
b = control.open_branch()
84
133
bound_branch = b.get_bound_location()
85
134
if bound_branch is not None:
86
135
# Heavyweight checkout: check all local commits
90
139
possible_transports = []
92
141
if not force and _any_local_commits(b, possible_transports):
93
raise errors.BzrCommandError(
142
raise errors.BzrCommandError(gettext(
94
143
'Cannot switch as local commits found in the checkout. '
95
144
'Commit these to the bound branch or use --force to '
97
except errors.BoundBranchConnectionFailure, e:
98
raise errors.BzrCommandError(
99
'Unable to connect to current master branch %(target)s: '
100
'%(error)s To switch anyway, use --force.' %
102
b.set_bound_location(None)
103
b.pull(to_branch, overwrite=True,
104
possible_transports=possible_transports)
105
b.set_bound_location(to_branch.base)
146
except errors.BoundBranchConnectionFailure as e:
147
raise errors.BzrCommandError(gettext(
148
'Unable to connect to current master branch %(target)s: '
149
'%(error)s To switch anyway, use --force.') %
152
b.set_bound_location(None)
153
b.pull(to_branch, overwrite=True,
154
possible_transports=possible_transports)
155
b.set_bound_location(to_branch.base)
156
b.set_parent(b.get_master_branch().get_parent())
107
raise errors.BzrCommandError('Cannot switch a branch, '
158
# If this is a standalone tree and the new branch
159
# is derived from this one, create a lightweight checkout.
161
graph = b.repository.get_graph(to_branch.repository)
162
if (b.controldir._format.colocated_branches and
163
(force or graph.is_ancestor(
164
b.last_revision(), to_branch.last_revision()))):
165
b.controldir.destroy_branch()
166
b.controldir.set_branch_reference(to_branch, name="")
168
raise errors.BzrCommandError(
169
gettext('Cannot switch a branch, only a checkout.'))
111
172
def _any_local_commits(this_branch, possible_transports):
113
174
last_rev = revision.ensure_null(this_branch.last_revision())
114
175
if last_rev != revision.NULL_REVISION:
115
176
other_branch = this_branch.get_master_branch(possible_transports)
116
this_branch.lock_read()
117
other_branch.lock_read()
177
with this_branch.lock_read(), other_branch.lock_read():
119
178
other_last_rev = other_branch.last_revision()
120
179
graph = this_branch.repository.get_graph(
121
180
other_branch.repository)
122
181
if not graph.is_ancestor(last_rev, other_last_rev):
125
other_branch.unlock()
130
def _update(tree, source_repository, quiet=False, revision_id=None):
131
"""Update a working tree to the latest revision of its branch.
133
:param tree: the working tree
134
:param source_repository: repository holding the revisions
136
tree.lock_tree_write()
138
to_branch = tree.branch
139
if revision_id is None:
140
revision_id = to_branch.last_revision()
141
if tree.last_revision() == revision_id:
143
note("Tree is up to date at revision %d.", to_branch.revno())
145
base_tree = source_repository.revision_tree(tree.last_revision())
146
merge.Merge3Merger(tree, tree, base_tree, to_branch.repository.revision_tree(revision_id))
147
tree.set_last_revision(to_branch.last_revision())
149
note('Updated to revision %d.' % to_branch.revno())